diff options
author | Toshiaki Asai <toshi.alternative@gmail.com> | 2017-12-17 06:48:45 +0900 |
---|---|---|
committer | Toshiaki Asai <toshi.alternative@gmail.com> | 2017-12-17 06:48:45 +0900 |
commit | 23657815aa64a86b56b6f929e6cc043b3ab24a4f (patch) | |
tree | bbe96e605b518eac86e9b666390d8a24a99067d1 | |
parent | df01b7bf2d4619e7f4630bab564d1322b5b4508f (diff) | |
parent | a33d7d3396350a5b8452324642d42bb07752ab35 (diff) | |
download | mikutter-23657815aa64a86b56b6f929e6cc043b3ab24a4f.tar.gz |
Merge branch 'topic/spell' into topic/981-world
-rw-r--r-- | core/miquire_plugin.rb | 42 | ||||
-rw-r--r-- | core/mui/cairo_miracle_painter.rb | 9 | ||||
-rw-r--r-- | core/mui/gtk_postbox.rb | 17 | ||||
-rw-r--r-- | core/plugin/command/command.rb | 36 | ||||
-rw-r--r-- | core/plugin/command/conditions.rb | 24 | ||||
-rw-r--r-- | core/plugin/core/core.rb | 13 | ||||
-rw-r--r-- | core/plugin/direct_message/direct_message.rb | 4 | ||||
-rw-r--r-- | core/plugin/search/search.rb | 2 | ||||
-rw-r--r-- | core/plugin/spell/.mikutter.yml | 10 | ||||
-rw-r--r-- | core/plugin/spell/error.rb | 8 | ||||
-rw-r--r-- | core/plugin/spell/spell.rb | 47 | ||||
-rw-r--r-- | core/plugin/spell/struct.rb | 57 | ||||
-rw-r--r-- | core/plugin/streaming/filter.rb | 10 | ||||
-rw-r--r-- | core/plugin/twitter/.mikutter.yml | 1 | ||||
-rw-r--r-- | core/plugin/twitter/mikutwitter/api_call_support.rb | 2 | ||||
-rw-r--r-- | core/plugin/twitter/model/message.rb | 4 | ||||
-rw-r--r-- | core/plugin/twitter/model/world.rb | 36 | ||||
-rw-r--r-- | core/plugin/twitter/twitter.rb | 129 | ||||
-rw-r--r-- | core/plugin/world/keep.rb | 59 | ||||
-rw-r--r-- | core/plugin/world/world.rb | 35 |
20 files changed, 420 insertions, 125 deletions
diff --git a/core/miquire_plugin.rb b/core/miquire_plugin.rb index b7d91b17..139d68f0 100644 --- a/core/miquire_plugin.rb +++ b/core/miquire_plugin.rb @@ -143,27 +143,29 @@ module Miquire::Plugin return true if ::Plugin.instance_exist?(spec[:slug]) return false unless satisfy_mikutter_version?(spec) - depended_plugins(spec).each do |depend| - begin - raise Miquire::LoadError, "plugin #{spec[:slug].inspect} was not loaded because dependent plugin #{depend.inspect} was not loaded." unless load(depend) - rescue Miquire::LoadError => err - raise Miquire::LoadError, "plugin #{spec[:slug].inspect} was not loaded because dependent plugin was not loaded. previous error is:\n#{err.to_s}" + atomic do + depended_plugins(spec).each do |depend| + begin + raise Miquire::LoadError, "plugin #{spec[:slug].inspect} was not loaded because dependent plugin #{depend.inspect} was not loaded." unless load(depend) + rescue Miquire::LoadError => err + raise Miquire::LoadError, "plugin #{spec[:slug].inspect} was not loaded because dependent plugin was not loaded. previous error is:\n#{err.to_s}" + end end - end - notice "plugin loaded: " + File.join(spec[:path], "#{spec[:slug]}.rb") - ::Plugin.create(spec[:slug].to_sym) do - self.spec = spec end - Kernel.load File.join(spec[:path], "#{spec[:slug]}.rb") - if spec[:deprecated_spec] - title = "#{spec[:slug]}: specファイルは非推奨になりました。" - Plugin.call(:modify_activity, - { plugin: spec[:slug], - kind: "error", - title: title, - date: Time.now, - spec: spec, - description: "#{title}\n代わりに.mikutter.ymlを使ってください。"}) end - true end + notice "plugin loaded: " + File.join(spec[:path], "#{spec[:slug]}.rb") + ::Plugin.create(spec[:slug].to_sym) do + self.spec = spec end + Kernel.load File.join(spec[:path], "#{spec[:slug]}.rb") + if spec[:deprecated_spec] + title = "#{spec[:slug]}: specファイルは非推奨になりました。" + Plugin.call(:modify_activity, + { plugin: spec[:slug], + kind: "error", + title: title, + date: Time.now, + spec: spec, + description: "#{title}\n代わりに.mikutter.ymlを使ってください。"}) end + true end + end end end diff --git a/core/mui/cairo_miracle_painter.rb b/core/mui/cairo_miracle_painter.rb index 7708b9e4..9afd92fa 100644 --- a/core/mui/cairo_miracle_painter.rb +++ b/core/mui/cairo_miracle_painter.rb @@ -196,11 +196,12 @@ class Gdk::MiraclePainter < Gtk::Object message.favorite? ? "unfav.png".freeze : "fav.png".freeze] ] end def iob_icon_pixbuf_off + world, = Plugin.filtering(:world_current, nil) [ [(UserConfig[:show_replied_icon] and message.mentioned_by_me? and "reply.png".freeze), UserConfig[:show_verified_icon] && message.user.verified? && "verified.png"], [ if UserConfig[:show_protected_icon] and message.user.protected? "protected.png".freeze - elsif message.retweeted? + elsif Plugin[:miracle_painter].retweeted?(message, world) "retweet.png".freeze end, message.favorite? ? "unfav.png".freeze : nil] ] @@ -210,13 +211,13 @@ class Gdk::MiraclePainter < Gtk::Object @tree.imaginary.create_reply_postbox(message) end def iob_retweet_clicked - if message.retweeted? + world, = Plugin.filtering(:world_current, nil) + if Plugin[:miracle_painter].retweeted?(message, world) retweet = message.retweeted_statuses.find(&:from_me?) retweet.destroy if retweet else - message.retweet + Plugin[:miracle_painter].retweet(message, world) end - # @tree.imaginary.create_reply_postbox(message, :retweet => true) end def iob_fav_clicked diff --git a/core/mui/gtk_postbox.rb b/core/mui/gtk_postbox.rb index 25c6cd5f..7cf30640 100644 --- a/core/mui/gtk_postbox.rb +++ b/core/mui/gtk_postbox.rb @@ -30,6 +30,7 @@ module Gtk # [footer] String テキストフィールドのカーソルの後ろに最初から入力されている文字列 # [to_display_only] true|false toに宛てたリプライを送るなら偽。真ならUI上にtoが表示されるだけ # [use_blind_footer] true|false blind footerを追加するか否か + # [visibility] Symbol|nil compose Spellに渡すvisibilityオプションの値 # [kwrest] Hash 以下の値から成る連想配列 # - delegated_by :: Gtk::PostBox 投稿処理をこのPostBoxに移譲したPostBox # - postboxstrage :: Gtk::Container PostBoxの親で、複数のPostBoxを持つことができるコンテナ @@ -42,6 +43,7 @@ module Gtk footer: ''.freeze, to_display_only: false, use_blind_footer: true, + visibility: nil, **kwrest) mainthread_only @posting = nil @@ -63,6 +65,7 @@ module Gtk @footer = (footer || '').freeze @to_display_only = !!to_display_only @use_blind_footer = !!use_blind_footer + @visibility = visibility super() signal_connect('parent-set'){ if parent @@ -152,10 +155,11 @@ module Gtk return unless before_post text = widget_post.buffer.text text += UserConfig[:footer] if use_blind_footer? - @posting = service.post( - to: to_display_only? ? service : @to, - message: text, - attachments: [] + @posting = Plugin[:gtk].compose( + current_world, + to_display_only? ? nil : @to.first, + body: text, + visibility: @visibility ).next{ destroy }.trap{ |err| @@ -196,7 +200,7 @@ module Gtk Gtk::TextView.new end def postable? - not(widget_post.buffer.text.empty?) and (/[^\p{blank}]/ === widget_post.buffer.text) and service | (@to.empty? ? service : @to) =~ :postable? + not(widget_post.buffer.text.empty?) and (/[^\p{blank}]/ === widget_post.buffer.text) and Plugin[:gtk].compose?(current_world, to_display_only? ? nil : @to.first, visibility: @visibility) end # 新しいPostBoxを作り、そちらにフォーカスを回す @@ -245,7 +249,7 @@ module Gtk true end end def service - @from || current_world + current_world end private def current_world @@ -395,6 +399,7 @@ module Gtk to: @to, footer: @footer, to_display_only: to_display_only?, + visibility: @visibility, **@options } end # 真を返すなら、 @to の要素はPostBoxの下に表示するのみで、投稿時にリプライにしない diff --git a/core/plugin/command/command.rb b/core/plugin/command/command.rb index b7831b73..c49788db 100644 --- a/core/plugin/command/command.rb +++ b/core/plugin/command/command.rb @@ -63,12 +63,16 @@ Plugin.create :command do visible: true, icon: Skin['retweet.png'], role: :timeline) do |opt| - target = opt.messages.select(&:retweetable?).reject{ |m| m.retweeted_by_me? Service.primary }.map(&:introducer) - if target.any?{|message| message.from_me?([Service.primary]) } + world, = Plugin.filtering(:world_current, nil) + target = opt.messages.select{|m| retweet?(m, world) }.reject{|m| retweeted?(m, world) }.map(&:introducer) + if target.any?{|message| message.from_me?([world]) } if ::Gtk::Dialog.confirm(_('過去の栄光にすがりますか?')) - target.each(&:retweet) end + target.each{|m| retweet(m, world) } + end else - target.each(&:retweet) end end + target.each{|m| retweet(m, world) } + end + end command(:delete_retweet, name: _('リツイートをキャンセル'), @@ -76,9 +80,11 @@ Plugin.create :command do visible: true, icon: Skin['retweet_cancel.png'], role: :timeline) do |opt| - opt.messages.each { |m| - retweet = m.retweeted_statuses.find(&:from_me?) - retweet.destroy if retweet and ::Gtk::Dialog.confirm("このつぶやきのリツイートをキャンセルしますか?\n\n#{m.to_show}") } end + current_world, = Plugin.filtering(:world_current, nil) + Delayer::Deferred.when( + opt.messages.map{|m| destroy_retweet(current_world, m) } + ).terminate(_('リツイートをキャンセルしている途中でエラーが発生しました')) + end command(:favorite, name: _('ふぁぼふぁぼする'), @@ -86,7 +92,15 @@ Plugin.create :command do visible: true, icon: Skin['unfav.png'], role: :timeline) do |opt| - opt.messages.select(&:favoritable?).reject{ |m| m.favorited_by_me? Service.primary }.map(&:introducer).each(&:favorite) end + world, = Plugin.filtering(:world_current, nil) + Delayer::Deferred.when( + opt.messages.select{|m| + favorite?(world, m) && !favorited?(world, m) + }.map{|m| + favorite(world, m) + } + ).terminate(_('ふぁぼふぁぼしている途中でエラーが発生しました')) + end command(:delete_favorite, name: _('あんふぁぼ'), @@ -94,7 +108,11 @@ Plugin.create :command do visible: true, icon: Skin['fav.png'], role: :timeline) do |opt| - opt.messages.each(&:unfavorite) end + world, = Plugin.filtering(:world_current, nil) + Delayer::Deferred.when( + opt.messages.map{|m| unfavorite(world, m) } + ).terminate(_('あんふぁぼしている途中でエラーが発生しました')) + end command(:delete, name: _('削除'), diff --git a/core/plugin/command/conditions.rb b/core/plugin/command/conditions.rb index 305ae086..7c09da57 100644 --- a/core/plugin/command/conditions.rb +++ b/core/plugin/command/conditions.rb @@ -47,7 +47,9 @@ module ::Plugin::Command # 選択されているツイートのうち、一つでも現在のアカウントでリツイートできるものがあれば真を返す CanReTweetAny = Condition.new { |opt| current_world, = Plugin.filtering(:world_current, nil) - opt.messages.lazy.map(¤t_world.method(:|)).any?{|c| c.retweetable? && !c.retweeted? } + opt.messages.lazy.any?{|m| + Plugin[:command].retweet?(current_world, m) && !Plugin[:command].retweeted?(current_world, m) + } } # 選択されているツイートが全て、現在のアカウントでリツイート可能な時、真を返す。 @@ -55,8 +57,8 @@ module ::Plugin::Command # ツイートが選択されていなければ偽 CanReTweetAll = Condition.new{ |opt| current_world, = Plugin.filtering(:world_current, nil) - not opt.messages.empty? and opt.messages.lazy.map(¤t_world.method(:|)).all?{ |c| - c.retweetable? and !c.retweeted_by_me? + !opt.messages.empty? && opt.messages.lazy.all?{|m| + Plugin[:command].retweet?(current_world, m) && !Plugin[:command].retweeted?(current_world, m) } } @@ -64,16 +66,16 @@ module ::Plugin::Command # ツイートが選択されていなければ偽 IsReTweetedAll = Condition.new{ |opt| current_world, = Plugin.filtering(:world_current, nil) - not opt.messages.empty? and opt.messages.lazy.map(¤t_world.method(:|)).all?{ |c| - c.retweetable? and c.retweeted_by_me? + !opt.messages.empty? && opt.messages.lazy.all?{|m| + Plugin[:command].destroy_retweet?(current_world, m) } } # 選択されているツイートのうち、一つでも現在のアカウントでふぁぼれるものがあれば真を返す CanFavoriteAny = Condition.new { |opt| current_world, = Plugin.filtering(:world_current, nil) - opt.messages.lazy.map(¤t_world.method(:|)).any?{ |c| - c.favoritable? and !c.favorited_by_me? + opt.messages.any?{|m| + Plugin[:command].favorite?(current_world, m) && !Plugin[:command].favorited?(current_world, m) } } @@ -82,8 +84,8 @@ module ::Plugin::Command # ツイートが選択されていなければ偽 CanFavoriteAll = Condition.new{ |opt| current_world, = Plugin.filtering(:world_current, nil) - not opt.messages.empty? and opt.messages.lazy.map(¤t_world.method(:|)).all? { |c| - c.favoritable? and !c.favorited_by_me? + !opt.messages.empty? and opt.messages.all?{|m| + Plugin[:command].favorite?(current_world, m) } } @@ -91,8 +93,8 @@ module ::Plugin::Command # ツイートが選択されていなければ偽 IsFavoritedAll = Condition.new{ |opt| current_world, = Plugin.filtering(:world_current, nil) - not opt.messages.empty? and opt.messages.lazy.map(¤t_world.method(:|)).all? { |c| - c.favoritable? and c.favorited_by_me? + !opt.messages.empty? and opt.messages.all?{|m| + Plugin[:command].unfavorite?(current_world, m) } } diff --git a/core/plugin/core/core.rb b/core/plugin/core/core.rb index 36ab3df8..ddcaabd9 100644 --- a/core/plugin/core/core.rb +++ b/core/plugin/core/core.rb @@ -1,13 +1,8 @@ # -*- coding: utf-8 -*- -Module.new do - - Plugin.create(:core) do - - # イベントフィルタを他のスレッドで並列実行する - Delayer.new do - Event.filter_another_thread = true end - - end +Plugin.create(:core) do + # イベントフィルタを他のスレッドで並列実行する + Delayer.new do + Event.filter_another_thread = true end end diff --git a/core/plugin/direct_message/direct_message.rb b/core/plugin/direct_message/direct_message.rb index 900f6663..864f7c20 100644 --- a/core/plugin/direct_message/direct_message.rb +++ b/core/plugin/direct_message/direct_message.rb @@ -21,7 +21,7 @@ module Plugin::DirectMessage set_icon Skin['directmessage.png'] u = model timeline timeline_name_for(u) do - postbox(from: Sender.new, to: u, delegate_other: true) + postbox(to: u, delegate_other: true, visibility: :direct) end end @@ -45,7 +45,7 @@ module Plugin::DirectMessage on_direct_messages do |_, dms| dm_distribution = Hash.new {|h,k| h[k] = []} dms.each do |dm| - model = Mikutter::Twitter::DirectMessage.new_ifnecessary(dm) + model = Plugin::Twitter::DirectMessage.new_ifnecessary(dm) dm_distribution[model[:user]] << model dm_distribution[model[:recipient]] << model end diff --git a/core/plugin/search/search.rb b/core/plugin/search/search.rb index c05e4dca..15807395 100644 --- a/core/plugin/search/search.rb +++ b/core/plugin/search/search.rb @@ -37,7 +37,7 @@ Plugin.create :search do searchbtn.signal_connect('clicked'){ |elm| elm.sensitive = querybox.sensitive = false timeline(:search).clear - Service.primary.search(q: querybox.text, count: 100).next{ |res| + search(Service.primary, q: querybox.text, count: 100).next{ |res| timeline(:search) << res if res.is_a? Array elm.sensitive = querybox.sensitive = true }.trap{ |e| diff --git a/core/plugin/spell/.mikutter.yml b/core/plugin/spell/.mikutter.yml new file mode 100644 index 00000000..5787a3db --- /dev/null +++ b/core/plugin/spell/.mikutter.yml @@ -0,0 +1,10 @@ +--- +name: Spell +slug: spell +depends: + mikutter: '3.6' + plugin: [] +version: '1.0' +description: >- + Diva::Modelを操作するための手続きを拡張するため手段として、Spellを提供します。 + 定義されたSpellは、他のプラグインから使うことも出来ます。 diff --git a/core/plugin/spell/error.rb b/core/plugin/spell/error.rb new file mode 100644 index 00000000..7ceb5d30 --- /dev/null +++ b/core/plugin/spell/error.rb @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +module Plugin::Spell + Error = Class.new(StandardError) + SpellNotFoundError = Class.new(Error) + ConditionMismatchError = Class.new(Error) + ArgumentError = Class.new(Error) +end diff --git a/core/plugin/spell/spell.rb b/core/plugin/spell/spell.rb new file mode 100644 index 00000000..16f585a1 --- /dev/null +++ b/core/plugin/spell/spell.rb @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +require_relative 'error' +require_relative 'struct' + +Plugin.create(:spell) do + defdsl :defspell do |spell_name, *constraint, condition: nil, &block| + beh = Plugin::Spell::Spell.new(spell_name.to_sym, + Set.new(constraint).freeze, + condition, + block) + if !respond_to?(spell_name) + defdsl spell_name do |*models| + spell(spell_name, *models) + end + defdsl :"#{spell_name}?" do |*models| + spell?(spell_name, *models) + end + end + filter_search_spell do |yielder, name, models, optional| + yielder << beh if beh.name == name.to_sym && beh.match(models, optional) + [yielder, name, models, optional] + end + end + + defdsl :spell do |name, *models| + optional = {}.freeze + models = models.compact + *models, optional = models unless models.last.is_a?(Diva::Model) + Delayer::Deferred.new.next{ + Enumerator.new{|y| + Plugin.filtering(:search_spell, y, name.to_sym, models, optional) + }.first + }.next{|spell| + raise Plugin::Spell::SpellNotFoundError, "Spell `#{name}' (#{models.map(&:class).join(', ')}) does not exists." unless spell + spell.call(models, optional) + } + end + + defdsl :spell? do |name, *models| + optional = {}.freeze + models = models.compact + *models, optional = models unless models.last.is_a?(Diva::Model) + !Enumerator.new{|y| + Plugin.filtering(:search_spell, y, name.to_sym, models, optional) + }.take(1).to_a.empty? + end +end diff --git a/core/plugin/spell/struct.rb b/core/plugin/spell/struct.rb new file mode 100644 index 00000000..9bc85f5c --- /dev/null +++ b/core/plugin/spell/struct.rb @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +module Plugin::Spell + + Spell = Struct.new(:name, :constraint, :condition, :proc) do + def match(models, optional) + Set.new(constraint.map{|a| Diva::Model(a) }) == Set.new(models.map{|a| a.class }) and condition?(models, optional) + end + + def call(models, optional) + call_spell_block(models, optional, &proc) + end + + def condition?(models, optional) + if condition + call_spell_block(models, optional, exception_message: false, &condition) + else + true + end + rescue Plugin::Spell::ArgumentError => err + false + end + + def to_s + "#{name}[#{constraint.to_a.join(',')}]" + end + + private + def call_spell_block(models, optional, exception_message: true, &block) + order = constraint.map{|a| Diva::Model(a) } + models_sorted = models.sort_by{|m| order.index(m.class) } + optional ||= {}.freeze + args = Array.new + kwargs = Hash.new + block.parameters.each do |kind, name| + case kind + when :req, :opt + raise Plugin::Spell::ArgumentError, exception_message && "too few argument (expect: #{block.arity}, given: #{models.size}) for #{self}" if models_sorted.empty? + args << models_sorted.shift + when :keyreq + raise Plugin::Spell::ArgumentError, exception_message && "required option #{name} of #{self} was not set." unless optional.has_key?(name) + kwargs[name] = optional[name] + when :key + kwargs[name] = optional[name] if optional.has_key?(name) + when :keyrest + kwargs = optional + end + end + raise Plugin::Spell::ArgumentError, exception_message && "too many argument (expect: #{block.arity}, given: #{models.size}) for #{self}" unless models_sorted.empty? + if kwargs.empty? + block.call(*args) + else + block.call(*args, **kwargs) + end + end + end +end diff --git a/core/plugin/streaming/filter.rb b/core/plugin/streaming/filter.rb index 4c3696ae..811d0181 100644 --- a/core/plugin/streaming/filter.rb +++ b/core/plugin/streaming/filter.rb @@ -20,8 +20,12 @@ Plugin.create :streaming do Plugin.call(:filter_stream_force_retry) } end end def start - service = Service.primary - return unless service + twitter = Enumerator.new{|y| + Plugin.filtering(:worlds, y) + }.find{|world| + world.class.slug == :twitter + } + return unless twitter @success_flag = false @fail = MikuTwitter::StreamingFailedActions.new("Filter Stream", self) Thread.new{ @@ -35,7 +39,7 @@ Plugin.create :streaming do param = {} param[:follow] = follow.to_a[0, 5000].map(&:id).join(',') if not follow.empty? param[:track] = track if not track.empty? - r = service.streaming(:filter_stream, param){ |json| + r = twitter.streaming(:filter_stream, param){ |json| json.strip! case json when /\A\{.*\}\Z/ diff --git a/core/plugin/twitter/.mikutter.yml b/core/plugin/twitter/.mikutter.yml index 74feec89..b133c97c 100644 --- a/core/plugin/twitter/.mikutter.yml +++ b/core/plugin/twitter/.mikutter.yml @@ -6,5 +6,6 @@ depends: mikutter: '3.6' plugin: - world + - spell version: '1.0' author: toshi_a diff --git a/core/plugin/twitter/mikutwitter/api_call_support.rb b/core/plugin/twitter/mikutwitter/api_call_support.rb index 7dae3e6b..230e8d4d 100644 --- a/core/plugin/twitter/mikutwitter/api_call_support.rb +++ b/core/plugin/twitter/mikutwitter/api_call_support.rb @@ -192,7 +192,7 @@ module MikuTwitter::ApiCallSupport cnv[:recipient] = user(dm[:recipient]) cnv[:exact] = true cnv[:created] = Time.parse(dm[:created_at]).localtime - Mikutter::Twitter::DirectMessage.new_ifnecessary(cnv) end + Plugin::Twitter::DirectMessage.new_ifnecessary(cnv) end def id(id) id end diff --git a/core/plugin/twitter/model/message.rb b/core/plugin/twitter/model/message.rb index 835df09b..ae4e71e2 100644 --- a/core/plugin/twitter/model/message.rb +++ b/core/plugin/twitter/model/message.rb @@ -116,12 +116,14 @@ class Plugin::Twitter::Message < Diva::Model service = Service.primary if retweetable? and service service.retweet(self){|*a| yield(*a) if block_given? } end end + deprecate :retweet, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#retweet-twitter-tweet)", 2018, 11 # この投稿を削除する def destroy service = Service.primary if deletable? and service service.destroy(self){|*a| yield(*a) if block_given? } end end + deprecate :destroy, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#destroy-twitter-tweet)", 2018, 12 # お気に入り状態を変更する。_fav_ がtrueならお気に入りにし、falseならお気に入りから外す。 def favorite(fav = true) @@ -602,6 +604,7 @@ class Plugin::Twitter::Message < Diva::Model end retweeted_users.include?(world.user_obj) if world.class.slug == :twitter end + deprecate :retweeted?, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#retweeted-twitter-tweet)", 2018, 11 # この投稿を「自分」がリツイートしていれば真 def retweeted_by_me?(world = Enumerator.new{|y| Plugin.filtering(:worlds, y) }) @@ -613,6 +616,7 @@ class Plugin::Twitter::Message < Diva::Model retweeted_users.any?(&our.method(:include?)) end end + deprecate :retweeted_by_me?, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#retweeted-twitter-tweet)", 2018, 11 # この投稿をリツイート等して、 _me_ のタイムラインに出現させたリツイートを返す。 # 特に誰もリツイートしていない場合は _self_ を返す。 diff --git a/core/plugin/twitter/model/world.rb b/core/plugin/twitter/model/world.rb index 2704841c..368111ba 100644 --- a/core/plugin/twitter/model/world.rb +++ b/core/plugin/twitter/model/world.rb @@ -154,35 +154,19 @@ module Plugin::Twitter service.unfavorite(message).next{ Plugin.call(:unfavorite, service, service.user_obj, base) base } end } + deprecate :favorite, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#favorite-twitter-twitter_tweet)", 2018, 12 define_postal :unfavorite def postable?(target=nil) - case target.class.slug - when :twitter_tweet, :twitter_user, :twitter - true - end if target.is_a?(Diva::Model) + Plugin[:twitter].compose?(self, target) end + deprecate :postable?, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#compose-twitter)", 2018, 11 def post(to: nil, message:, **kwrest) - first_responder = Array(to).first || self - if defined?(first_responder.class.slug) - case first_responder.class.slug - when nil, :twitter - post_tweet(message: message) - when :twitter_tweet - post_tweet(replyto: first_responder, message: message) - when :twitter_user - post_tweet(receiver: first_responder, message: message) - when :twitter_direct_message - post_dm(user: first_responder.user, text: message) - else - raise "invalid responder slug #{first_responder.class.slug.inspect}" - end - else - raise "invalid responder #{first_responder.inspect}" - end + Plugin[:twitter].compose(self, to, body: message, **kwrest) end + deprecate :post, "spell (see: https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#compose-twitter)", 2018, 11 def inspect "#<#{self.class.to_s}: #{id.inspect} #{slug.inspect}>" @@ -194,8 +178,11 @@ module Plugin::Twitter result end - private - + # :nodoc: + # 内部で利用するために用意されています。 + # ツイートを投稿したい場合は、 + # https://reference.mikutter.hachune.net/reference/2017/11/28/spell.html#compose-twitter + # を参照してください。 def post_tweet(options) twitter.update(options).next{ |message| Plugin.call(:posted, self, [message]) @@ -204,6 +191,7 @@ module Plugin::Twitter } end + # :nodoc: def post_dm(options) twitter.send_direct_message(options).next do |dm| Plugin.call(:direct_messages, self, [dm]) @@ -211,6 +199,8 @@ module Plugin::Twitter end end + private + def user_initialize if self[:user] self[:user] = Plugin::Twitter::User.new_ifnecessary(self[:user]) diff --git a/core/plugin/twitter/twitter.rb b/core/plugin/twitter/twitter.rb index 10649e55..4b8e74a7 100644 --- a/core/plugin/twitter/twitter.rb +++ b/core/plugin/twitter/twitter.rb @@ -88,6 +88,134 @@ Plugin.create(:twitter) do filter_appear(&gen_message_filter) + defspell(:destroy, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ tweet.from_me?(twitter) } + ) do |twitter, tweet| + (twitter/"statuses/destroy".freeze/tweet.id).message.next{ |destroyed_tweet| + destroyed_tweet[:rule] = :destroy + Plugin.call(:destroyed, [destroyed_tweet]) + destroyed_tweet + } + end + + defspell(:destroy_retweet, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ retweeted?(twitter, tweet) } + ) do |twitter, tweet| + retweeted(twitter, tweet).next{ |retweet| + destroy(twitter, retweet) + } + end + + defspell(:favorite, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ + !favorited?(twitter, tweet) + }) do |twitter, tweet| + Plugin.call(:before_favorite, twitter, twitter.user_obj, tweet) + (twitter/'favorites/create'.freeze).message(id: tweet.id).next{ |favorited_tweet| + Plugin.call(:favorite, twitter, twitter.user_obj, favorited_tweet) + favorited_tweet + }.trap{ |e| + Plugin.call(:fail_favorite, twitter, twitter.user_obj, tweet) + Deferred.fail(e) + } + end + + defspell(:favorited, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ favorited?(twitter.user_obj, tweet) } + ) do |twitter, tweet| + Delayer::Deferred.new.next{ + favorited?(twitter.user, tweet) + } + end + + defspell(:favorited, :twitter_user, :twitter_tweet, + condition: ->(user, tweet){ tweet.favorited_by.include?(user) } + ) do |user, tweet| + Delayer::Deferred.new.next{ + favorited?(user, tweet) + } + end + + defspell(:compose, :twitter, :twitter_tweet, + condition: ->(twitter, tweet, visibility: nil){ + !(visibility && visibility != :public) + }) do |twitter, tweet, body:, **options| + twitter.post_tweet(message: body, replyto: tweet, **options) + end + + defspell(:compose, :twitter, :twitter_direct_message, + condition: ->(twitter, direct_message, visibility: nil){ + !(visibility && visibility != :direct) + }) do |twitter, direct_message, body:, **options| + twitter.post_dm(user: direct_message.user, text: body, **options) + end + + defspell(:compose, :twitter, :twitter_user, + condition: ->(twitter, user, visibility: nil){ + !(visibility && ![:public, :direct].include?(visibility)) + }) do |twitter, user, visibility: nil, body:, **options| + case visibility + when :public, nil + twitter.post_tweet(message: body, receiver: user, **options) + when :direct + twitter.post_dm(user: user, text: body, **options) + else + raise "invalid visibility `#{visibility.inspect}'." + end + end + + # 宛先なしのタイムラインへのツイートか、 _to_ オプション引数で複数宛てにする場合。 + # Twitterでは複数宛先は対応していないため、 _to_ オプションの1つめの値に対する投稿とする + defspell(:compose, :twitter, + condition: ->(twitter, to: nil){ + first = Array(to).compact.first + !(first && !compose?(twitter, first)) + }) do |twitter, body:, to: nil, **options| + first = Array(to).compact.first + if first + compose(twitter, first, body: body, **options) + else + twitter.post_tweet(to: to, message: body, **options) + end + end + + defspell(:retweet, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ !tweet.protected? } + ) do |twitter, tweet| + twitter.retweet(id: tweet.id).next{|retweeted| + Plugin.call(:posted, twitter, [retweeted]) + Plugin.call(:update, twitter, [retweeted]) + retweeted + } + end + + defspell(:retweeted, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ tweet.retweeted_users.include?(twitter.user_obj) } + ) do |twitter, tweet| + Delayer::Deferred.new.next{ + retweet = tweet.retweeted_statuses.find{|rt| rt.user == twitter.user_obj } + if retweet + retweet + else + raise "ReTweet not found." + end + } + end + + defspell(:unfavorite, :twitter, :twitter_tweet, + condition: ->(twitter, tweet){ + favorited?(twitter, tweet) + }) do |twitter, tweet| + (twitter/'favorites/destroy'.freeze).message(id: tweet.id).next{ |unfavorited_tweet| + Plugin.call(:unfavorite, twitter, twitter.user_obj, unfavorited_tweet) + unfavorited_tweet + } + end + + defspell(:search, :twitter) do |twitter, **options| + twitter.search(**options) + end + # リツイートを削除した時、ちゃんとリツイートリストからそれを削除する on_destroyed do |messages| messages.each{ |message| @@ -160,5 +288,4 @@ Plugin.create(:twitter) do builder.build(result[:token]) end - end diff --git a/core/plugin/world/keep.rb b/core/plugin/world/keep.rb index 4bfa2c86..3472a076 100644 --- a/core/plugin/world/keep.rb +++ b/core/plugin/world/keep.rb @@ -27,29 +27,20 @@ module Plugin::World # ==== Return # account_id => {token: ,secret:, ...} def accounts - @account_data ||= @@service_lock.synchronize do - if FileTest.exist? ACCOUNT_FILE - File.open(ACCOUNT_FILE, 'rb'.freeze) do |file| - YAML.load(decrypt(file.read)) end - else - # 旧データの引き継ぎ - result = UserConfig[:accounts] - if result.is_a? Hash - # 0.3開発版のデータがある - account_write result.inject({}){ |hash, item| - key, value = item - hash[key] = value.merge(provider: :twitter, slug: key) - hash } - elsif UserConfig[:twitter_token] and UserConfig[:twitter_secret] - # 0.2.x以前のアカウント情報 - account_write({ default: { - provider: :twitter, - slug: :default, - token: UserConfig[:twitter_token], - secret: UserConfig[:twitter_secret], - user: UserConfig[:verify_credentials] } }) + if @account_data + @account_data + else + @@service_lock.synchronize do + @account_data ||= if FileTest.exist? ACCOUNT_FILE + File.open(ACCOUNT_FILE, 'rb'.freeze) do |file| + YAML.load(decrypt(file.read)) + end else - {} end end end end + migrate_older_account_data + end + end + end + end # アカウント情報を返す # ==== Args @@ -132,5 +123,29 @@ module Plugin::World str = cipher.update(binary_data) << cipher.final str.force_encoding(Encoding::UTF_8) str end + + private + + def migrate_older_account_data + # 旧データの引き継ぎ + result = UserConfig[:accounts] + if result.is_a? Hash + # 0.3開発版のデータがある + account_write result.inject({}){ |hash, item| + key, value = item + hash[key] = value.merge(provider: :twitter, slug: key) + hash } + elsif UserConfig[:twitter_token] and UserConfig[:twitter_secret] + # 0.2.x以前のアカウント情報 + account_write({ default: { + provider: :twitter, + slug: :default, + token: UserConfig[:twitter_token], + secret: UserConfig[:twitter_secret], + user: UserConfig[:verify_credentials] } }) + else + {} + end + end end end diff --git a/core/plugin/world/world.rb b/core/plugin/world/world.rb index b03836dd..584f8eaa 100644 --- a/core/plugin/world/world.rb +++ b/core/plugin/world/world.rb @@ -67,20 +67,13 @@ Plugin.create(:world) do # ==== Return # [Array] アカウントModelを格納したArray def worlds - @worlds ||= Plugin::World::Keep.accounts.map { |id, serialized| - provider = Diva::Model(serialized[:provider]) - if provider - provider.new(serialized) - else - Miquire::Plugin.load(serialized[:provider]) - provider = Diva::Model(serialized[:provider]) - if provider - provider.new(serialized) - else - raise "unknown model #{serialized[:provider].inspect}" - end + if @worlds + @worlds + else + atomic do + load_world_ifn end - }.freeze + end end # 現在選択されているアカウントを返す @@ -130,4 +123,20 @@ Plugin.create(:world) do Plugin.call(:service_destroyed, target) # 互換性のため end + def load_world_ifn + @worlds ||= Plugin::World::Keep.accounts.map { |id, serialized| + provider = Diva::Model(serialized[:provider]) + if provider + provider.new(serialized) + else + Miquire::Plugin.load(serialized[:provider]) + provider = Diva::Model(serialized[:provider]) + if provider + provider.new(serialized) + else + raise "unknown model #{serialized[:provider].inspect}" + end + end + }.freeze + end end |