refactor twitter service model
This commit is contained in:
parent
4bc28f2285
commit
eea9467846
3 changed files with 103 additions and 94 deletions
|
|
@ -9,6 +9,7 @@
|
||||||
* Refactor 404.html, fix [#4078](https://github.com/diaspora/diaspora/issues/4078)
|
* Refactor 404.html, fix [#4078](https://github.com/diaspora/diaspora/issues/4078)
|
||||||
* Remove the (now useless) last post link from the user profile. [#4540](https://github.com/diaspora/diaspora/pull/4540)
|
* Remove the (now useless) last post link from the user profile. [#4540](https://github.com/diaspora/diaspora/pull/4540)
|
||||||
* Refactor ConversationsController, move query building to User model. [#4547](https://github.com/diaspora/diaspora/pull/4547)
|
* Refactor ConversationsController, move query building to User model. [#4547](https://github.com/diaspora/diaspora/pull/4547)
|
||||||
|
* Refactor the Twitter service model [#4387](https://github.com/diaspora/diaspora/pull/4387)
|
||||||
|
|
||||||
## Bug fixes
|
## Bug fixes
|
||||||
* Highlight down arrow at the user menu on hover [#4441](https://github.com/diaspora/diaspora/pull/4441)
|
* Highlight down arrow at the user menu on hover [#4441](https://github.com/diaspora/diaspora/pull/4441)
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,111 @@
|
||||||
class Services::Twitter < Service
|
class Services::Twitter < Service
|
||||||
include ActionView::Helpers::TextHelper
|
include ActionView::Helpers::TextHelper
|
||||||
|
include Rails.application.routes.url_helpers
|
||||||
include MarkdownifyHelper
|
include MarkdownifyHelper
|
||||||
|
|
||||||
MAX_CHARACTERS = 140
|
MAX_CHARACTERS = 140
|
||||||
SHORTENED_URL_LENGTH = 21
|
SHORTENED_URL_LENGTH = 21
|
||||||
|
|
||||||
LINK_PATTERN = %r{https?://\S+}
|
LINK_PATTERN = %r{https?://\S+}
|
||||||
|
|
||||||
def provider
|
def provider
|
||||||
"twitter"
|
"twitter"
|
||||||
end
|
end
|
||||||
|
|
||||||
def post(post, url='')
|
def post post, url=''
|
||||||
Rails.logger.debug("event=post_to_service type=twitter sender_id=#{self.user_id}")
|
Rails.logger.debug "event=post_to_service type=twitter sender_id=#{self.user_id}"
|
||||||
(0...20).each do |retry_count|
|
tweet = attempt_post post
|
||||||
begin
|
post.tweet_id = tweet.id
|
||||||
message = build_twitter_post(post, url, retry_count)
|
|
||||||
@tweet = client.update(message)
|
|
||||||
break
|
|
||||||
rescue Twitter::Error::Forbidden => e
|
|
||||||
if e.message != 'Status is over 140 characters' || retry_count == 20
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
post.tweet_id = @tweet.id
|
|
||||||
post.save
|
post.save
|
||||||
end
|
end
|
||||||
|
|
||||||
def adjust_length_for_urls(post_text)
|
|
||||||
real_length = post_text.length
|
|
||||||
URI.extract( post_text, ['http','https'] ) do |a_url|
|
|
||||||
# add or subtract from real length - urls for tweets are always
|
|
||||||
# shortened to SHORTENED_URL_LENGTH
|
|
||||||
if a_url.length >= SHORTENED_URL_LENGTH
|
|
||||||
real_length -= a_url.length - SHORTENED_URL_LENGTH
|
|
||||||
else
|
|
||||||
real_length += SHORTENED_URL_LENGTH - a_url.length
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return real_length
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_post_link(post, post_text, maxchars)
|
|
||||||
post_url = Rails.application.routes.url_helpers.short_post_url(
|
|
||||||
post,
|
|
||||||
:protocol => AppConfig.pod_uri.scheme,
|
|
||||||
:host => AppConfig.pod_uri.authority
|
|
||||||
)
|
|
||||||
truncated_text = truncate post_text, length: maxchars - SHORTENED_URL_LENGTH + 1
|
|
||||||
truncated_text = restore_truncated_url truncated_text, post_text, maxchars
|
|
||||||
|
|
||||||
"#{truncated_text} #{post_url}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_twitter_post(post, url, retry_count=0)
|
|
||||||
maxchars = MAX_CHARACTERS - retry_count*5
|
|
||||||
post_text = strip_markdown(post.text(:plain_text => true))
|
|
||||||
#if photos, always include url, otherwise not for short posts
|
|
||||||
if adjust_length_for_urls(post_text) > maxchars || post.photos.any?
|
|
||||||
post_text = add_post_link(post, post_text, maxchars)
|
|
||||||
end
|
|
||||||
return post_text
|
|
||||||
end
|
|
||||||
|
|
||||||
def profile_photo_url
|
def profile_photo_url
|
||||||
client.user(nickname).profile_image_url_https("original")
|
client.user(nickname).profile_image_url_https "original"
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post(post)
|
def delete_post post
|
||||||
if post.present? && post.tweet_id.present?
|
if post.present? && post.tweet_id.present?
|
||||||
Rails.logger.debug("event=delete_from_service type=twitter sender_id=#{self.user_id}")
|
Rails.logger.debug "event=delete_from_service type=twitter sender_id=#{self.user_id}"
|
||||||
delete_from_twitter(post.tweet_id)
|
delete_from_twitter post.tweet_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_from_twitter(service_post_id)
|
|
||||||
client.status_destroy(service_post_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def client
|
def client
|
||||||
@client ||= Twitter::Client.new(
|
@client ||= Twitter::Client.new(
|
||||||
oauth_token: self.access_token,
|
oauth_token: self.access_token,
|
||||||
oauth_token_secret: self.access_secret
|
oauth_token_secret: self.access_secret
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def restore_truncated_url truncated_text, post_text, maxchars
|
|
||||||
return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/
|
|
||||||
|
|
||||||
url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0]
|
def attempt_post post, retry_count=0
|
||||||
truncated_text = truncate(
|
message = build_twitter_post post, retry_count
|
||||||
post_text,
|
tweet = client.update message
|
||||||
length: maxchars - SHORTENED_URL_LENGTH + 2,
|
rescue Twitter::Error::Forbidden => e
|
||||||
separator: ' ',
|
if e.message != 'Status is over 140 characters' || retry_count == 20
|
||||||
omission: ''
|
raise e
|
||||||
)
|
else
|
||||||
|
attempt_post post, retry_count+1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_twitter_post post, retry_count=0
|
||||||
|
max_characters = MAX_CHARACTERS - retry_count * 5
|
||||||
|
|
||||||
|
post_text = strip_markdown post.text(plain_text: true)
|
||||||
|
truncate_and_add_post_link post, post_text, max_characters
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_and_add_post_link post, post_text, max_characters
|
||||||
|
return post_text unless needs_link? post, post_text, max_characters
|
||||||
|
|
||||||
|
post_url = short_post_url(
|
||||||
|
post,
|
||||||
|
protocol: AppConfig.pod_uri.scheme,
|
||||||
|
host: AppConfig.pod_uri.authority
|
||||||
|
)
|
||||||
|
|
||||||
|
truncated_text = truncate post_text, length: max_characters - SHORTENED_URL_LENGTH + 1
|
||||||
|
truncated_text = restore_truncated_url truncated_text, post_text, max_characters
|
||||||
|
|
||||||
|
"#{truncated_text} #{post_url}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def needs_link? post, post_text, max_characters
|
||||||
|
adjust_length_for_urls(post_text) > max_characters || post.photos.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def adjust_length_for_urls post_text
|
||||||
|
real_length = post_text.length
|
||||||
|
|
||||||
|
URI.extract(post_text, ['http','https']) do |url|
|
||||||
|
# add or subtract from real length - urls for tweets are always
|
||||||
|
# shortened to SHORTENED_URL_LENGTH
|
||||||
|
if url.length >= SHORTENED_URL_LENGTH
|
||||||
|
real_length -= url.length - SHORTENED_URL_LENGTH
|
||||||
|
else
|
||||||
|
real_length += SHORTENED_URL_LENGTH - url.length
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
real_length
|
||||||
|
end
|
||||||
|
|
||||||
|
def restore_truncated_url truncated_text, post_text, max_characters
|
||||||
|
return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/
|
||||||
|
|
||||||
|
url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0]
|
||||||
|
truncated_text = truncate(
|
||||||
|
post_text,
|
||||||
|
length: max_characters - SHORTENED_URL_LENGTH + 2,
|
||||||
|
separator: ' ',
|
||||||
|
omission: ''
|
||||||
|
)
|
||||||
|
|
||||||
"#{truncated_text} #{url} ..."
|
"#{truncated_text} #{url} ..."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delete_from_twitter service_post_id
|
||||||
|
client.status_destroy service_post_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ describe Services::Twitter do
|
||||||
it 'sets the tweet_id on the post' do
|
it 'sets the tweet_id on the post' do
|
||||||
@service.post(@post)
|
@service.post(@post)
|
||||||
@post.tweet_id.should match "1234"
|
@post.tweet_id.should match "1234"
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'swallows exception raised by twitter always being down' do
|
it 'swallows exception raised by twitter always being down' do
|
||||||
pending
|
pending
|
||||||
|
|
@ -33,18 +33,18 @@ describe Services::Twitter do
|
||||||
|
|
||||||
it 'should call build_twitter_post' do
|
it 'should call build_twitter_post' do
|
||||||
url = "foo"
|
url = "foo"
|
||||||
@service.should_receive(:build_twitter_post).with(@post, url, 0)
|
@service.should_receive(:build_twitter_post).with(@post, 0)
|
||||||
@service.post(@post, url)
|
@service.post(@post, url)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes text formatting markdown from post text' do
|
it 'removes text formatting markdown from post text' do
|
||||||
message = "Text with some **bolded** and _italic_ parts."
|
message = "Text with some **bolded** and _italic_ parts."
|
||||||
post = stub(:text => message, :photos => [])
|
post = stub(:text => message, :photos => [])
|
||||||
@service.build_twitter_post(post, '').should match "Text with some bolded and italic parts."
|
@service.send(:build_twitter_post, post).should match "Text with some bolded and italic parts."
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "message size limits" do
|
describe "message size limits" do
|
||||||
before :each do
|
before :each do
|
||||||
@long_message_start = SecureRandom.hex(25)
|
@long_message_start = SecureRandom.hex(25)
|
||||||
|
|
@ -54,38 +54,38 @@ describe Services::Twitter do
|
||||||
it "should not truncate a short message" do
|
it "should not truncate a short message" do
|
||||||
short_message = SecureRandom.hex(20)
|
short_message = SecureRandom.hex(20)
|
||||||
short_post = stub(:text => short_message, :photos => [])
|
short_post = stub(:text => short_message, :photos => [])
|
||||||
@service.build_twitter_post(short_post, '').should match short_message
|
@service.send(:build_twitter_post, short_post).should match short_message
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should truncate a long message" do
|
it "should truncate a long message" do
|
||||||
long_message = SecureRandom.hex(220)
|
long_message = SecureRandom.hex(220)
|
||||||
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
||||||
@service.build_twitter_post(long_post, '').length.should be < long_message.length
|
@service.send(:build_twitter_post, long_post).length.should be < long_message.length
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not truncate a long message with an http url" do
|
it "should not truncate a long message with an http url" do
|
||||||
long_message = " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
|
long_message = " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
|
||||||
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
||||||
@post.text = long_message
|
@post.text = long_message
|
||||||
answer = @service.build_twitter_post(@post, '')
|
answer = @service.send(:build_twitter_post, @post)
|
||||||
|
|
||||||
answer.should_not match /\.\.\./
|
answer.should_not match /\.\.\./
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not cut links when truncating a post" do
|
it "should not cut links when truncating a post" do
|
||||||
long_message = SecureRandom.hex(40) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55)
|
long_message = SecureRandom.hex(40) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55)
|
||||||
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
||||||
answer = @service.build_twitter_post(long_post, '')
|
answer = @service.send(:build_twitter_post, long_post)
|
||||||
|
|
||||||
answer.should match /\.\.\./
|
answer.should match /\.\.\./
|
||||||
answer.should match /shortened\.html/
|
answer.should match /shortened\.html/
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should append the otherwise-cut link when truncating a post" do
|
it "should append the otherwise-cut link when truncating a post" do
|
||||||
long_message = "http://joindiaspora.com/a-very-long-decoy-url.html " + SecureRandom.hex(20) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55) + " http://joindiaspora.com/a-very-long-decoy-url-part-2.html"
|
long_message = "http://joindiaspora.com/a-very-long-decoy-url.html " + SecureRandom.hex(20) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55) + " http://joindiaspora.com/a-very-long-decoy-url-part-2.html"
|
||||||
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
||||||
answer = @service.build_twitter_post(long_post, '')
|
answer = @service.send(:build_twitter_post, long_post)
|
||||||
|
|
||||||
answer.should match /\.\.\./
|
answer.should match /\.\.\./
|
||||||
answer.should match /shortened\.html/
|
answer.should match /shortened\.html/
|
||||||
end
|
end
|
||||||
|
|
@ -93,28 +93,28 @@ describe Services::Twitter do
|
||||||
it "should not truncate a long message with an https url" do
|
it "should not truncate a long message with an https url" do
|
||||||
long_message = " https://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
|
long_message = " https://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
|
||||||
@post.text = long_message
|
@post.text = long_message
|
||||||
answer = @service.build_twitter_post(@post, '')
|
answer = @service.send(:build_twitter_post, @post)
|
||||||
answer.should_not match /\.\.\./
|
answer.should_not match /\.\.\./
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should truncate a long message with an ftp url" do
|
it "should truncate a long message with an ftp url" do
|
||||||
long_message = @long_message_start + " ftp://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
|
long_message = @long_message_start + " ftp://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
|
||||||
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
long_post = stub(:text => long_message, :id => 1, :photos => [])
|
||||||
answer = @service.build_twitter_post(long_post, '')
|
answer = @service.send(:build_twitter_post, long_post)
|
||||||
|
|
||||||
answer.should match /\.\.\./
|
answer.should match /\.\.\./
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not truncate a message of maximum length" do
|
it "should not truncate a message of maximum length" do
|
||||||
exact_size_message = SecureRandom.hex(70)
|
exact_size_message = SecureRandom.hex(70)
|
||||||
exact_size_post = stub(:text => exact_size_message, :id => 1, :photos => [])
|
exact_size_post = stub(:text => exact_size_message, :id => 1, :photos => [])
|
||||||
answer = @service.build_twitter_post(exact_size_post, '')
|
answer = @service.send(:build_twitter_post, exact_size_post)
|
||||||
|
|
||||||
answer.should match exact_size_message
|
answer.should match exact_size_message
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with photo" do
|
describe "with photo" do
|
||||||
before do
|
before do
|
||||||
@photos = [alice.build_post(:photo, :pending => true, :user_file=> File.open(photo_fixture_name)),
|
@photos = [alice.build_post(:photo, :pending => true, :user_file=> File.open(photo_fixture_name)),
|
||||||
|
|
@ -128,14 +128,14 @@ describe Services::Twitter do
|
||||||
@status_message.save!
|
@status_message.save!
|
||||||
alice.add_to_streams(@status_message, alice.aspects)
|
alice.add_to_streams(@status_message, alice.aspects)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should include post url in short message with photos" do
|
it "should include post url in short message with photos" do
|
||||||
answer = @service.build_twitter_post(@status_message, '')
|
answer = @service.send(:build_twitter_post, @status_message)
|
||||||
answer.should include 'http'
|
answer.should include 'http'
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#profile_photo_url" do
|
describe "#profile_photo_url" do
|
||||||
it 'returns the original profile photo url' do
|
it 'returns the original profile photo url' do
|
||||||
user_stub = stub
|
user_stub = stub
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue