aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorToshiaki Asai <toshi.alternative@gmail.com>2016-10-02 21:57:07 +0900
committerToshiaki Asai <toshi.alternative@gmail.com>2016-10-02 21:57:07 +0900
commitdfcf19a4b0f9195dda3cb4ab0dbe1ee30f4ecec0 (patch)
tree37be9be2f6aa3a2bc6a455ade2789b68653ade73 /core
parentba3fef7bd872d3b7c5767dd6232103ff197e1f22 (diff)
parent50ea08c85ec614ecba50fa398ff1777bb07d5d21 (diff)
downloadmikutter-dfcf19a4b0f9195dda3cb4ab0dbe1ee30f4ecec0.tar.gz
Merge branch 'hotfix/3.3' into hotfix/3.4
Diffstat (limited to 'core')
-rw-r--r--core/lib/mikutwitter/api_call_support.rb43
-rw-r--r--core/lib/mikutwitter/api_shortcuts.rb29
-rw-r--r--core/lib/mikutwitter/error.rb3
-rw-r--r--core/mui/gtk_postbox.rb40
-rw-r--r--core/plugin/streaming/streamer.rb10
5 files changed, 107 insertions, 18 deletions
diff --git a/core/lib/mikutwitter/api_call_support.rb b/core/lib/mikutwitter/api_call_support.rb
index e2a132f3..99a0d72c 100644
--- a/core/lib/mikutwitter/api_call_support.rb
+++ b/core/lib/mikutwitter/api_call_support.rb
@@ -40,6 +40,7 @@ module MikuTwitter::ApiCallSupport
# [multi] 名前(複数形)
def self.defparser(uni, multi = :"#{uni}s", container = Array, defaults = {})
parser = lazy{ MikuTwitter::ApiCallSupport::Request::Parser.method(uni) }
+ defaults.freeze
define_method(multi){ |options = {}|
type_strict options => Hash
json(defaults.merge(options)).next{ |node|
@@ -76,7 +77,7 @@ module MikuTwitter::ApiCallSupport
Thread.new{ JSON.parse(res.body).symbolize } } end
defparser :user, :users, Users
- defparser :message, :messages, Messages
+ defparser :message, :messages, Messages, tweet_mode: 'extended'.freeze
defparser :list
defparser :id
defparser :direct_message
@@ -92,7 +93,7 @@ module MikuTwitter::ApiCallSupport
def search(options = {})
type_strict options => Hash
- json(options).next{ |res|
+ json({tweet_mode: 'extended'.freeze}.merge(options)).next{ |res|
Thread.new { Parser.messages res[:statuses] } } end
def inspect
@@ -103,9 +104,9 @@ module MikuTwitter::ApiCallSupport
extend Parser
def message(msg)
- cnv = msg.convert_key(:text => :message,
- :in_reply_to_user_id => :receiver,
+ cnv = msg.convert_key(:in_reply_to_user_id => :receiver,
:in_reply_to_status_id => :replyto)
+ cnv[:message] = msg[:full_text] || msg[:text]
cnv[:source] = $1 if cnv[:source].is_a?(String) and cnv[:source].match(/\A<a\s+.*>(.*?)<\/a>\Z/)
cnv[:created] = (Time.parse(msg[:created_at]).localtime rescue Time.now)
cnv[:user] = Message::MessageUser.new(user(msg[:user]), msg[:user])
@@ -117,6 +118,40 @@ module MikuTwitter::ApiCallSupport
message(msg[:quoted_status]).add_quoted_by(message) end
message end
+ # Streaming APIにはtweet_modeスイッチが効かないとかTwitterアホか!?
+ # ↓
+ # Parser#message に、compat modeも受け付けるような改修を入れる
+ # ↓
+ # Twitter「Streaming APIのcompatモードはちょっと中身が違うんじゃ」
+ # see: https://dev.twitter.com/overview/api/upcoming-changes-to-tweets
+ # ↓
+ # 死にたいのか!?
+ # ↓
+ # 恒例の身売り話が出てくる
+ # see: http://www.afpbb.com/articles/-/3101961
+ # ↓
+ # 死ぬのか!?
+ def streaming_message(msg)
+ cnv = msg.convert_key(:in_reply_to_user_id => :receiver,
+ :in_reply_to_status_id => :replyto)
+ if msg[:extended_tweet]
+ cnv.delete(:extended_tweet)
+ cnv.merge!(msg[:extended_tweet])
+ cnv[:message] = msg[:extended_tweet][:full_text]
+ else
+ cnv[:message] = msg[:text]
+ end
+ cnv[:source] = $1 if cnv[:source].is_a?(String) and cnv[:source].match(/\A<a\s+.*>(.*?)<\/a>\Z/)
+ cnv[:created] = (Time.parse(msg[:created_at]).localtime rescue Time.now)
+ cnv[:user] = Message::MessageUser.new(user(msg[:user]), msg[:user])
+ cnv[:retweet] = streaming_message(msg[:retweeted_status]) if msg[:retweeted_status]
+ cnv[:exact] = [:created_at, :source, :user, :retweeted_status].all?{|k|msg.has_key?(k)}
+ message = cnv[:exact] ? Message.rewind(cnv) : Message.new_ifnecessary(cnv)
+ # search/tweets.json の戻り値のquoted_statusのuserがたまにnullだゾ〜
+ if msg[:quoted_status].is_a?(Hash) and msg[:quoted_status][:user]
+ streaming_message(msg[:quoted_status]).add_quoted_by(message) end
+ message end
+
def messages(msgs)
Messages.new msgs.map{ |msg| message(msg) } end
diff --git a/core/lib/mikutwitter/api_shortcuts.rb b/core/lib/mikutwitter/api_shortcuts.rb
index c4259c24..56ddd2ad 100644
--- a/core/lib/mikutwitter/api_shortcuts.rb
+++ b/core/lib/mikutwitter/api_shortcuts.rb
@@ -122,17 +122,38 @@ module MikuTwitter::APIShortcuts
def update(message)
text = message[:message]
- replyto = message[:replyto]
- receiver = message[:receiver]
+ replyto = message[:replyto] && Message.generate(message[:replyto])
+ receiver = message[:receiver] && User.generate(message[:receiver])
iolist = message[:mediaiolist]
+ is_reply = !!(receiver || replyto)
data = {:status => text }
- data[:in_reply_to_user_id] = User.generate(receiver)[:id].to_s if receiver
- data[:in_reply_to_status_id] = Message.generate(replyto)[:id].to_s if replyto
+ data[:in_reply_to_user_id] = receiver.id if receiver
+ data[:in_reply_to_status_id] = replyto.id if replyto
+ if is_reply
+ forecast_receivers = exclude_receivers = Set.new.freeze
+ if replyto
+ forecast_receivers += replyto.each_ancestor.map(&:user)
+ end
+ mentions = text.match(%r[\A((?:@[a-zA-Z0-9_]+\s+)+)])
+ if mentions
+ specific_screen_names = mentions[1].split(/\s+/).map{|s|s[1, s.size]}
+ exclude_receivers += forecast_receivers.reject{|u| specific_screen_names.include?(u.idname) }
+ text = [*(specific_screen_names - forecast_receivers.map(&:idname)).map{|s|"@#{s}"}, text[mentions.end(0),text.size]].join(' '.freeze)
+ data[:status] = text
+ end
+ data[:auto_populate_reply_metadata] = true
+ data[:exclude_reply_user_ids] = exclude_receivers.map(&:id).join(',') unless exclude_receivers.empty?
+ end
if iolist and !iolist.empty?
Deferred.when(*iolist.collect{ |io| upload_media(io) }).next{|media_list|
data[:media_ids] = media_list.map{|media| media['media_id'] }.join(",")
(self/'statuses/update').message(data) }
else
+ attachment_url = text.match(%r[\A(.+?)\s+(https?://twitter.com/(?:#!/)?(?:[a-zA-Z0-9_]+)/status(?:es)?/(?:\d+)(?:\?.*)?)\Z]m)
+ if attachment_url
+ data[:attachment_url] = attachment_url[2]
+ data[:status] = attachment_url[1]
+ end
(self/'statuses/update').message(data) end end
alias post update
diff --git a/core/lib/mikutwitter/error.rb b/core/lib/mikutwitter/error.rb
index d99850f3..c042bb09 100644
--- a/core/lib/mikutwitter/error.rb
+++ b/core/lib/mikutwitter/error.rb
@@ -35,6 +35,7 @@ def MikuTwitter.TwitterError(code=nil)
MikuTwitter::CouldNotAuthenticateError = Class.new(MikuTwitter::TwitterError(32))
MikuTwitter::SpecifiedResourceWasNotFoundEerror = Class.new(MikuTwitter::TwitterError(34))
+MikuTwitter::AttachmentURLParameterIsInvalidEerror = Class.new(MikuTwitter::TwitterError(44))
MikuTwitter::SuspendOrNotPermittedError = Class.new(MikuTwitter::TwitterError(64))
MikuTwitter::APIVersionTooOldError = Class.new(MikuTwitter::TwitterError(68))
MikuTwitter::RateLimitError = Class.new(MikuTwitter::TwitterError(88))
@@ -55,3 +56,5 @@ MikuTwitter::DontHaveWriteAccessError = Class.new(MikuTwitter::TwitterError(261)
MikuTwitter::CantMuteYourselfError = Class.new(MikuTwitter::TwitterError(271))
MikuTwitter::NotMutingError = Class.new(MikuTwitter::TwitterError(272))
MikuTwitter::DirectMessageExceedTheNumberOfCharacterError = Class.new(MikuTwitter::TwitterError(354))
+MikuTwitter::InReplyToStatusIdDoesNotExistError = Class.new(MikuTwitter::TwitterError(385))
+MikuTwitter::TooManyAttachmentResourceError = Class.new(MikuTwitter::TwitterError(386))
diff --git a/core/mui/gtk_postbox.rb b/core/mui/gtk_postbox.rb
index 760067b6..5e1129c1 100644
--- a/core/mui/gtk_postbox.rb
+++ b/core/mui/gtk_postbox.rb
@@ -264,7 +264,7 @@ module Gtk
# _related_widgets_ のうちどれもアクティブではなく、フォーカスが外れたら削除される設定の場合、このウィジェットを削除する
def destroy_if_necessary(*related_widgets)
- if(not(frozen?) and not([widget_post, *related_widgets].compact.any?{ |w| w.focus? }) and destructible?)
+ if(not(frozen? or destroyed?) and not([widget_post, *related_widgets].compact.any?{ |w| w.focus? }) and destructible?)
destroy
true end end
@@ -289,16 +289,46 @@ module Gtk
def remain_charcount
if not widget_post.destroyed?
- footer = if use_blind_footer? then UserConfig[:footer].size else 0 end
- text = widget_post.buffer.text
+ text = trim_hidden_regions(widget_post.buffer.text + UserConfig[:footer])
Twitter::Extractor.extract_urls(text).map{|url|
if url.length < posted_url_length(url)
-(posted_url_length(url) - url.length)
else
url.length - posted_url_length(url) end
- }.inject(140 - text.size - footer, &:+)
+ }.inject(140 - text.size, &:+)
end end
+ def trim_hidden_regions(text)
+ trim_hidden_header(trim_hidden_footer(text))
+ end
+
+ # 文字列からhidden headerを除いた文字列を返す。
+ # hidden headerが含まれていない場合は、 _text_ を返す。
+ def trim_hidden_header(text)
+ mentions = text.match(%r[\A((?:@[a-zA-Z0-9_]+\s+)+)])
+ forecast_receivers = Set.new.freeze
+ if reply?
+ forecast_receivers += @to.first.each_ancestor.map(&:user)
+ end
+ if mentions
+ specific_screen_names = mentions[1].split(/\s+/).map{|s|s[1, s.size]}
+ [*(specific_screen_names - forecast_receivers.map(&:idname)).map{|s|"@#{s}"}, text[mentions.end(0),text.size]].join(' '.freeze)
+ else
+ text
+ end
+ end
+
+ # 文字列からhidden footerを除いた文字列を返す。
+ # hidden footerが含まれていない場合は、 _text_ を返す。
+ def trim_hidden_footer(text)
+ attachment_url = text.match(%r[\A(.*?)\s+(https?://twitter.com/(?:#!/)?(?:[a-zA-Z0-9_]+)/status(?:es)?/(?:\d+)(?:\?.*)?)\Z]m)
+ if attachment_url
+ attachment_url[1]
+ else
+ text
+ end
+ end
+
# URL _url_ がTwitterに投稿された時に何文字としてカウントされるかを返す
# ==== Args
# [url] String URL
@@ -310,7 +340,7 @@ module Gtk
def focus_out_event(widget, event=nil)
options = @options
Delayer.new{
- if(not(frozen?) and not(options.has_key?(:postboxstorage)) and post_is_empty?)
+ if(not(frozen? or destroyed?) and not(options.has_key?(:postboxstorage)) and post_is_empty?)
destroy_if_necessary(widget_send, widget_tool, *@reply_widgets) end }
false end
diff --git a/core/plugin/streaming/streamer.rb b/core/plugin/streaming/streamer.rb
index 9a3b1e5e..bd7a7caf 100644
--- a/core/plugin/streaming/streamer.rb
+++ b/core/plugin/streaming/streamer.rb
@@ -110,7 +110,7 @@ module ::Plugin::Streaming
defevent(:update, true) do |data|
events = {update: Messages.new, mention: Messages.new, mypost: Messages.new}
data.each { |json|
- msg = MikuTwitter::ApiCallSupport::Request::Parser.message(json.symbolize)
+ msg = MikuTwitter::ApiCallSupport::Request::Parser.streaming_message(json.symbolize)
events[:update] << msg
events[:mention] << msg if msg.to_me?
events[:mypost] << msg if msg.from_me? }
@@ -122,13 +122,13 @@ module ::Plugin::Streaming
defevent(:favorite) do |json|
by = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize)
- to = MikuTwitter::ApiCallSupport::Request::Parser.message(json['target_object'].symbolize)
+ to = MikuTwitter::ApiCallSupport::Request::Parser.streaming_message(json['target_object'].symbolize)
if(to.respond_to?(:add_favorited_by))
to.add_favorited_by(by, Time.parse(json['created_at'])) end end
defevent(:unfavorite) do |json|
by = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize)
- to = MikuTwitter::ApiCallSupport::Request::Parser.message(json['target_object'].symbolize)
+ to = MikuTwitter::ApiCallSupport::Request::Parser.streaming_message(json['target_object'].symbolize)
if(to.respond_to?(:remove_favorited_by))
to.remove_favorited_by(by) end end
@@ -143,12 +143,12 @@ module ::Plugin::Streaming
defevent(:retweeted_retweet) do |json|
by = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize)
#to = MikuTwitter::ApiCallSupport::Request::Parser.user(json['target'].symbolize)
- target_object = MikuTwitter::ApiCallSupport::Request::Parser.message(json['target_object'].symbolize)
+ target_object = MikuTwitter::ApiCallSupport::Request::Parser.streaming_message(json['target_object'].symbolize)
source_object = target_object.retweet_source
source_object.add_retweet_user(by, Time.parse(json['created_at'])) end
defevent(:quoted_tweet) do |json|
- MikuTwitter::ApiCallSupport::Request::Parser.message(json['target_object'].symbolize) end
+ MikuTwitter::ApiCallSupport::Request::Parser.streaming_message(json['target_object'].symbolize) end
defevent(:follow) do |json|
source = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize)