diff options
Diffstat (limited to 'core/addon')
-rw-r--r-- | core/addon/activity/activity.rb | 245 | ||||
-rw-r--r-- | core/addon/bugreport.rb | 7 | ||||
-rw-r--r-- | core/addon/change_account.rb | 10 | ||||
-rw-r--r-- | core/addon/extract.rb | 4 | ||||
-rw-r--r-- | core/addon/image_file_cache/image_file_cache.rb | 2 | ||||
-rw-r--r-- | core/addon/list/liststream.rb | 2 | ||||
-rw-r--r-- | core/addon/profile.rb | 15 | ||||
-rw-r--r-- | core/addon/search.rb | 3 | ||||
-rw-r--r-- | core/addon/settings/builder.rb | 49 | ||||
-rw-r--r-- | core/addon/settings/multiselect.rb | 67 | ||||
-rw-r--r-- | core/addon/streaming.rb | 31 |
11 files changed, 412 insertions, 23 deletions
diff --git a/core/addon/activity/activity.rb b/core/addon/activity/activity.rb new file mode 100644 index 00000000..32e54ab0 --- /dev/null +++ b/core/addon/activity/activity.rb @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +# 通知管理プラグイン + +miquire :mui, 'tree_view_pretty_scroll' +module Plugin::Activity + # アクティビティを更新する。 + # ==== Args + # [kind] Symbol イベントの種類 + # [title] タイトル + # [args] その他オプション。主に以下の値 + # icon :: String|Gdk::Pixbuf アイコン + # date :: Time イベントの発生した時刻 + # service :: Service 関係するServiceオブジェクト + # related :: 自分に関係するかどうかのフラグ + def activity(kind, title, args = {}) + Plugin.call(:modify_activity, + { plugin: self, + kind: kind, + title: title, + date: Time.new, + description: title }.merge(args)) + end +end + +class Plugin + include Plugin::Activity + + def self.activity(kind, title, args = {}) + Plugin.call(:modify_activity, + { plugin: nil, + kind: kind, + title: title, + date: Time.new, + description: title }.merge(args)) + end +end + +Plugin.create(:activity) do + class ActivityView < Gtk::CRUD + include Gtk::TreeViewPrettyScroll + + ICON = 0 + KIND = 1 + TITLE = 2 + DATE = 3 + PLUGIN = 4 + ID = 5 + SERVICE = 6 + EVENT = 7 + + def initialize + super() + @creatable = @updatable = @deletable = false + end + + def column_schemer + [{:kind => :pixbuf, :type => Gdk::Pixbuf, :label => 'icon'}, # ICON + {:kind => :text, :type => String, :label => '種類'}, # KIND + {:kind => :text, :type => String, :label => '説明'}, # TITLE + {:kind => :text, :type => String, :label => '時刻'}, # DATE + {:type => Plugin}, # PLUGIN + {:type => Integer}, # ID + {:type => Service}, # SERVICE + {:type => Hash} ].freeze # EVENT + end + end + + BOOT_TIME = Time.new.freeze + + # そのイベントをミュートするかどうかを返す(trueなら表示しない) + def mute?(params) + mute_kind = UserConfig[:activity_mute_kind] + if mute_kind.is_a? Array + return true if mute_kind.include? params[:kind].to_s end + mute_kind_related = UserConfig[:activity_mute_kind_related] + if mute_kind_related + return true if mute_kind_related.include?(params[:kind].to_s) and !params[:related] end + false end + + activity_view = ActivityView.new + activity_vscrollbar = Gtk::VScrollbar.new(activity_view.vadjustment) + activity_hscrollbar = Gtk::HScrollbar.new(activity_view.hadjustment) + activity_shell = Gtk::Table.new(2, 2) + activity_description = Gtk::IntelligentTextview.new + activity_status = Gtk::Label.new + activity_container = Gtk::VBox.new + + activity_container. + pack_start(activity_shell. + attach(activity_view, 0, 1, 0, 1, Gtk::FILL|Gtk::SHRINK|Gtk::EXPAND, Gtk::FILL|Gtk::SHRINK|Gtk::EXPAND). + attach(activity_vscrollbar, 1, 2, 0, 1, Gtk::FILL, Gtk::SHRINK|Gtk::FILL). + attach(activity_hscrollbar, 0, 1, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::FILL)). + closeup(activity_description). + closeup(activity_status.right) + + Delayer.new do + Plugin.call(:mui_tab_regist, activity_container, 'アクティビティ', MUI::Skin.get("underconstruction.png")) + end + + activity_view.ssc("cursor-changed") { |this| + iter = this.selection.selected + if iter + activity_description.rewind(iter[ActivityView::EVENT][:description]) + activity_status.set_text(iter[ActivityView::DATE]) + end + false + } + + # アクティビティ更新を受け取った時の処理 + # plugin, kind, title, icon, date, service + on_modify_activity do |params| + if not mute?(params) + iter = activity_view.model.prepend + if params[:icon].is_a? String + iter[ActivityView::ICON] = Gdk::WebImageLoader.pixbuf(params[:icon], 24, 24){ |loaded_icon| + iter[ActivityView::ICON] = loaded_icon } + else + iter[ActivityView::ICON] = params[:icon] end + iter[ActivityView::KIND] = params[:kind].to_s + iter[ActivityView::TITLE] = params[:title].tr("\n", "") + iter[ActivityView::DATE] = params[:date].strftime('%Y/%m/%d %H:%M:%S') + iter[ActivityView::PLUGIN] = params[:plugin] + iter[ActivityView::ID] = 0 + iter[ActivityView::SERVICE] = params[:service] + iter[ActivityView::EVENT] = params + if (UserConfig[:activity_show_timeline] || []).include?(params[:kind].to_s) + Plugin.call(:update, nil, [Message.new(message: params[:description], system: true, source: params[:plugin].to_s, created: params[:date])]) + end + end + end + + on_favorite do |service, user, message| + activity(:favorite, "#{message.user[:idname]}: #{message.to_s}", + description:("@#{user[:idname]} がふぁぼふぁぼしました\n"+ + "@#{message.user[:idname]}: #{message.to_s}\n"+ + "https://twitter.com/#!/#{message.user[:idname]}/statuses/#{message[:id]}"), + icon: user[:profile_image_url], + related: message.user.is_me? || user.is_me?, + service: service) + end + + on_unfavorite do |service, user, message| + activity(:unfavorite, "#{message.user[:idname]}: #{message.to_s}", + description:("@#{user[:idname]} があんふぁぼしました\n"+ + "@#{message.user[:idname]}: #{message.to_s}\n"+ + "https://twitter.com/#!/#{message.user[:idname]}/statuses/#{message[:id]}"), + icon: user[:profile_image_url], + related: message.user.is_me? || user.is_me?, + service: service) + end + + on_retweet do |retweets| + retweets.each { |retweet| + retweet.retweet_source_d.next{ |source| + activity(:retweet, retweet.to_s, + description:("@#{retweet.user[:idname]} がリツイートしました\n"+ + "@#{source.user[:idname]}: #{source.to_s}\n"+ + "https://twitter.com/#!/#{source.user[:idname]}/statuses/#{source[:id]}"), + icon: retweet.user[:profile_image_url], + date: retweet[:created], + related: (retweet.user.is_me? || source && source.user.is_me?), + service: Service.primary) }.terminate('リツイートソースが取得できませんでした') } + end + + on_list_member_added do |service, user, list, source_user| + activity(:list_member_added, "@#{user[:idname]}が#{list[:full_name]}に追加されました", + description:("@#{user[:idname]} が #{list[:full_name]} に追加されました\n"+ + "#{list[:description]} (by @#{list.user[:idname]})\n"+ + "https://twitter.com/#!/#{list.user[:idname]}/#{list[:slug]}"), + icon: user[:profile_image_url], + related: user.is_me? || source_user.is_me?, + service: service) + end + + on_list_member_removed do |service, user, list, source_user| + activity(:list_member_removed, "@#{user[:idname]}が#{list[:full_name]}から削除されました", + description:("@#{user[:idname]} が #{list[:full_name]} から削除されました\n"+ + "#{list[:description]} (by @#{list.user[:idname]})\n"+ + "https://twitter.com/#!/#{list.user[:idname]}/#{list[:slug]}"), + icon: user[:profile_image_url], + related: user.is_me? || source_user.is_me?, + service: service) + end + + on_follow do |by, to| + activity(:follow, "@#{by[:idname]}が@#{to[:idname]}をフョローしました", + related: by.is_me? || to.is_me?, + icon: (to.is_me? ? by : to)[:profile_image_url]) + end + + on_direct_messages do |service, dms| + dms.each{ |dm| + date = Time.parse(dm[:created_at]) + if date > BOOT_TIME + first_line = dm[:sender].is_me? ? "ダイレクトメッセージを送信しました" : "ダイレクトメッセージを受信しました" + activity(:dm, "D #{dm[:recipient][:idname]} #{dm[:text]}", + description: ("#{first_line}\n" + + "@#{dm[:sender][:idname]}: D #{dm[:recipient][:idname]} #{dm[:text]}"), + icon: dm[:sender][:profile_image_url], + service: service, + date: date) end } + end + + onunload do + Addon.remove_tab 'アクティビティ' + end + + settings "アクティビティ" do + settings "表示しないイベント" do + multiselect("以下の自分に関係ないイベント", :activity_mute_kind_related) do + option "retweet", "リツイート" + option "favorite", "ふぁぼ" + option "follow", "フォロー" + option "list_member_added", "リストに追加" + option "list_member_removed", "リストから削除" + option "dm", "ダイレクトメッセージ" + option "system", "システムメッセージ" + option "error", "エラー" + end + + multiselect("以下の全てのイベント", :activity_mute_kind) do + option "retweet", "リツイート" + option "favorite", "ふぁぼ" + option "follow", "フォロー" + option "list_member_added", "リストに追加" + option "list_member_removed", "リストから削除" + option "dm", "ダイレクトメッセージ" + option "system", "システムメッセージ" + option "error", "エラー" + end + end + + multiselect("タイムラインに表示", :activity_show_timeline) do + option "retweet", "リツイート" + option "favorite", "ふぁぼ" + option "follow", "フォロー" + option "list_member_added", "リストに追加" + option "list_member_removed", "リストから削除" + option "dm", "ダイレクトメッセージ" + option "system", "システムメッセージ" + option "error", "エラー" + end + + end +end diff --git a/core/addon/bugreport.rb b/core/addon/bugreport.rb index 35807f37..3f879aeb 100644 --- a/core/addon/bugreport.rb +++ b/core/addon/bugreport.rb @@ -91,11 +91,10 @@ Module.new do http.post('/', eparam) } File.delete(File.expand_path(File.join(Environment::TMPDIR, 'mikutter_error'))) rescue nil File.delete(File.expand_path(File.join(Environment::TMPDIR, 'crashed_exception'))) rescue nil - Plugin.call(:update, nil, [Message.new(:message => "エラー報告を送信しました。ありがとう♡", - :system => true)]) + Plugin.activity :system, "エラー報告を送信しました。ありがとう♡" rescue TimeoutError, StandardError => e - Plugin.call(:update, nil, [Message.new(:message => "#{e.to_s}ピャアアアアアアアアアアアアアアアアアアアアアアアwwwwwwwwwwwwwwwwwwwwww", - :system => true)]) + Plugin.activity :system, "ピャアアアアアアアアアアアアアアアアアアアアアアアwwwwwwwwwwwwwwwwwwwwww" + Plugin.activity :error, e.to_s, exception: e end } end def self.revision diff --git a/core/addon/change_account.rb b/core/addon/change_account.rb index 3a477669..414de440 100644 --- a/core/addon/change_account.rb +++ b/core/addon/change_account.rb @@ -34,7 +34,7 @@ Module.new do main_windows = Plugin.filtering(:get_windows, Set.new).first alert_thread = if(Thread.main != Thread.current) then Thread.current end dialog = Gtk::Dialog.new(Environment::NAME + " ログイン") - container, key, request_token = main(watch) + container, key, request_token = main(watch, dialog) dialog.set_size_request(600, 400) dialog.window_position = Gtk::Window::POS_CENTER dialog.vbox.pack_start(container, true, true, 30) @@ -89,7 +89,7 @@ Module.new do Gtk::VBox.new(false, 0).closeup(attention).closeup(decide) end - def self.main(watch) + def self.main(watch, dialog) goaisatsu = Gtk::VBox.new(false, 0) box = Gtk::VBox.new(false, 8) request_token = watch.request_oauth_token @@ -97,16 +97,18 @@ Module.new do # w.add(Gtk::Mumble.new(Message.new(:message => hello(url), :system => true))).show_all # } goaisatsu.add(Gtk::IntelligentTextview.new(hello(request_token.authorize_url))) - user, key_input = gen_input('暗証番号', true) + user, key_input = gen_input('暗証番号', dialog, true) box.closeup(goaisatsu).closeup(user) return box, key_input, request_token end - def self.gen_input(label, visibility=true, default="") + def self.gen_input(label, dialog, visibility=true, default="") container = Gtk::HBox.new(false, 0) input = Gtk::Entry.new input.text = default input.visibility = visibility + input.signal_connect('activate') { |elm| + dialog.response(Gtk::Dialog::RESPONSE_OK) } container.pack_start(Gtk::Label.new(label), false, true, 0) container.pack_start(Gtk::Alignment.new(1.0, 0.5, 0, 0).add(input), true, true, 0) return container, input diff --git a/core/addon/extract.rb b/core/addon/extract.rb index 161df116..72f23633 100644 --- a/core/addon/extract.rb +++ b/core/addon/extract.rb @@ -95,13 +95,13 @@ Module.new do def hook_plugin(event) Plugin.create(:extract).add_event(event){ |service, messages| - tabclass.tabs.each{ |tab| tab.__send__("event_#{event}", messages) } } + tabclass.tabs.deach{ |tab| tab.__send__("event_#{event}", messages) } } end def boot_plugin [:update,:mention,:posted].each{ |event| hook_plugin(event) } Plugin.create(:extract).add_event(:appear){ |messages| - tabclass.tabs.each{ |tab| tab.__send__("event_appear", messages) } } + tabclass.tabs.deach{ |tab| tab.__send__("event_appear", messages) } } end def tabclass diff --git a/core/addon/image_file_cache/image_file_cache.rb b/core/addon/image_file_cache/image_file_cache.rb index 4c307fa6..c2445794 100644 --- a/core/addon/image_file_cache/image_file_cache.rb +++ b/core/addon/image_file_cache/image_file_cache.rb @@ -9,7 +9,7 @@ Plugin.create :image_file_cache do # appear_limit 回TLに出現したユーザはキャッシュに登録する # (30分ツイートしなければカウンタはリセット) onappear do |messages| - messages.each { |message| + messages.deach { |message| image_url = message.user[:profile_image_url] if not j_include?(image_url) appear_counter[image_url] ||= 0 diff --git a/core/addon/list/liststream.rb b/core/addon/list/liststream.rb index 3949213c..01e71f81 100644 --- a/core/addon/list/liststream.rb +++ b/core/addon/list/liststream.rb @@ -33,7 +33,7 @@ Plugin::create(:liststream) do member_anything - Plugin.filtering(:followings, Set.new).first end def start - service = Service.services.first + service = Service.primary Thread.new{ loop{ sleep(3) diff --git a/core/addon/profile.rb b/core/addon/profile.rb index 96c3af4a..629c4662 100644 --- a/core/addon/profile.rb +++ b/core/addon/profile.rb @@ -30,11 +30,18 @@ Module.new do if not locked[iter[1]] locked[iter[1]] = true flag = iter[0] # = !iter[0] + list = iter[2] @service.__send__(flag ? :delete_list_member : :add_list_member, - :list_id => iter[2]['id'], + :list_id => list['id'], :user_id => user[:id]).next{ |result| iter[0] = !flag if not(@list.destroyed?) locked[iter[1]] = false + if flag + list.remove_member(user) + Plugin.call(:list_member_removed, @service, user, list, @service.user_obj) + else + list.add_member(user) + Plugin.call(:list_member_added, @service, user, list, @service.user_obj) end }.terminate{ |e| locked[iter[1]] = false "@#{user[:idname]} をリスト #{iter[2]['name']} に追加できませんでした" } end } @@ -102,7 +109,7 @@ Module.new do @service.method(new ? :follow : :unfollow).call(user){ |event, msg| case event when :exit - Plugin::call(new ? :followings_created : :followings_destroy, @service, [user]) + Plugin.call(new ? :followings_created : :followings_destroy, @service, [user]) following = new Delayer.new{ unless widget.destroyed? @@ -191,8 +198,8 @@ Module.new do makescreen(user, service) } plugin.add_event(:boot){ |service| set_contextmenu(plugin, service) - Message::Entity.addlinkrule(:user_mentions, /(?:@|@|〄)[a-zA-Z0-9_]+/){ |segment| - idname = segment[:url].match(/^(?:@|@|〄)?(.+)$/)[1] + Message::Entity.addlinkrule(:user_mentions, /(?:@|@|〄|☯|⑨|♨|(?:\W|^)D )[a-zA-Z0-9_]+/){ |segment| + idname = segment[:url].match(/^(?:@|@|〄|☯|⑨|♨|(?:\W|^)D )?(.+)$/)[1] user = User.findbyidname(idname) if user makescreen(user, service) diff --git a/core/addon/search.rb b/core/addon/search.rb index 214f647a..1ba723b5 100644 --- a/core/addon/search.rb +++ b/core/addon/search.rb @@ -13,6 +13,9 @@ Module.new do searchbtn = Gtk::Button.new('検索') savebtn = Gtk::Button.new('保存') + querybox.signal_connect('activate'){ |elm| + searchbtn.clicked } + searchbtn.signal_connect('clicked'){ |elm| elm.sensitive = querybox.sensitive = false main.clear diff --git a/core/addon/settings/builder.rb b/core/addon/settings/builder.rb index 99d984c7..14d55cac 100644 --- a/core/addon/settings/builder.rb +++ b/core/addon/settings/builder.rb @@ -133,6 +133,40 @@ class Plugin::Setting < Gtk::VBox container end + # 複数テキストボックス + # 任意個の項目を入力させて、配列で受け取る。 + # ==== Args + # [label] ラベル + # [config] 設定のキー + def multi(label, config) + settings(label) do + container, box = Gtk::HBox.new(false, 0), Gtk::VBox.new(false, 0) + input_ary = [] + btn_add = Gtk::Button.new(Gtk::Stock::ADD) + array_converter = lambda { + c = Listener[config].get || [] + (c.is_a?(Array) ? c : [c]).select(&ret_nth) } + add_button = lambda { |content| + input = Gtk::Entry.new + input.text = content.to_s + input.ssc(:changed) { |w| + Listener[config].set w.parent.children.map(&:text).select(&ret_nth) } + input.ssc('focus_out_event'){ |w| + w.parent.remove(w) if w.text.empty? + false } + box.closeup input + input } + input_ary = array_converter.call.each(&add_button) + btn_add.ssc(:clicked) { |w| + w.get_ancestor(Gtk::Window).set_focus(add_button.call("").show) + false } + container.pack_start(box, true, true, 0) + container.pack_start(Gtk::Alignment.new(1.0, 1.0, 0, 0).add(btn_add), false, true, 0) + closeup container + container + end + end + # 設定のグループ。関連の強い設定をカテゴライズできる。 # ==== Args # [title] ラベル @@ -213,6 +247,20 @@ class Plugin::Setting < Gtk::VBox closeup container = builder.build(label, config) container end + # 要素を複数個選択させる + # ==== Args + # [label] ラベル + # [config] 設定のキー + # [default] + # 連想配列で、 _値_ => _ラベル_ の形式で、デフォルト値を与える。 + # _block_ と同時に与えれられたら、 _default_ の値が先に入って、 _block_ は後に入る。 + # [&block] 内容 + def multiselect(label, config, default = {}) + builder = Plugin::Setting::MultiSelect.new(default) + builder.instance_eval(&Proc.new) if block_given? + closeup container = builder.build(label, config) + container end + private def about_converter Hash.new(ret_nth).merge!( :logo => lambda{ |value| Gtk::WebIcon.new(value).pixbuf rescue nil } ) end @@ -236,4 +284,5 @@ class Plugin::Setting < Gtk::VBox end require File.expand_path File.join(File.dirname(__FILE__), 'select') +require File.expand_path File.join(File.dirname(__FILE__), 'multiselect') require File.expand_path File.join(File.dirname(__FILE__), 'listener') diff --git a/core/addon/settings/multiselect.rb b/core/addon/settings/multiselect.rb new file mode 100644 index 00000000..c17b3355 --- /dev/null +++ b/core/addon/settings/multiselect.rb @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +require File.expand_path File.join(File.dirname(__FILE__), 'builder') +require File.expand_path File.join(File.dirname(__FILE__), 'select') + +class Plugin::Setting::MultiSelect < Plugin::Setting::Select + + # optionメソッドで追加された項目をウィジェットに組み立てる + # ==== Args + # [label] ラベル。文字列。 + # [config] 設定のキー + # ==== Return + # ウィジェット + def build(label, config) + if has_widget? + group = Gtk::Frame.new.set_border_width(8) + group.set_label(label) + group.add(build_box(Plugin::Setting::Listener[config])) + group + else + group = Gtk::Frame.new.set_border_width(8). + set_label(label) + box = Plugin::Setting.new.set_border_width(4). + closeup(build_combobox(Plugin::Setting::Listener[config])) + group.add(box) + end end + + private + + def build_box(listener) + box = Gtk::VBox.new + + options = @options + box.instance_eval{ + options.each{ |value, face| + if face.is_a? String + closeup check = Gtk::CheckButton.new(face) + elsif face.is_a? Plugin::Setting + container = Gtk::HBox.new + check = Gtk::CheckButton.new + closeup container.closeup(check).add(face) + end + check.signal_connect('toggled'){ |widget| + if widget.active? + listener.set((listener.get || []) + [value]) + else + listener.set((listener.get || []) - [value]) end + face.sensitive = widget.active? if face.is_a? Gtk::Widget } + check.active = (listener.get || []).include? value + face.sensitive = check.active? if face.is_a? Gtk::Widget } } + box end + + # すべてテキストなら、コンボボックスで要素を描画する + def build_combobox(listener) + container = Gtk::VBox.new + sorted = @options.map{ |o| o.first }.sort_by(&:to_s).freeze + state = listener.get || [] + sorted.each{ |node| + check = Gtk::CheckButton.new(@options.assoc(node).last) + check.active = state.include?(node) + check.signal_connect('toggled'){ |widget| + if widget.active? + listener.set((listener.get || []) + [node]) + else + listener.set((listener.get || []) - [node]) end } + container.closeup check } + container end +end diff --git a/core/addon/streaming.rb b/core/addon/streaming.rb index f1149a86..18cf2a96 100644 --- a/core/addon/streaming.rb +++ b/core/addon/streaming.rb @@ -59,7 +59,7 @@ Module.new do yield(service, lock.synchronize{ data = events; events = Set.new; data.freeze }) } rescue => e error e - abort end } + Plugin.activity :error, e.to_s, :exception => e end } define_method("event_#{event_name}"){ |json| type_strict json => tcor(Array, Hash) service ||= @service @@ -68,7 +68,7 @@ Module.new do thread.wakeup else error "event_#{event_name}: event processing thread was dead." - abort end } end + thread.join end } end def start unless @thread and @thread.alive? @@ -96,16 +96,16 @@ Module.new do event_direct_message(json['direct_message']) when json['delete'] if Mopt.debug - Plugin.call(:update, nil, [Message.new(:message => YAML.dump(json), - :system => true)]) end + Plugin.activity :system, YAML.dump(json) end + when !json.has_key?('event') # thread_storage(:update).push(json) event_update(json) when Mopt.debug - Plugin.call(:update, nil, [Message.new(:message => YAML.dump(json), - :system => true)]) + Plugin.activity :system, YAML.dump(json) end rescue Exception => e + Plugin.activity :error, e.to_s, exception: e notice e end end @@ -127,7 +127,8 @@ Module.new do Plugin.call(event_name, service, data) } end define_together_event(:direct_message) do |service, data| - trigger_event(service, :direct_messages => data.map{ |datum| datum.symbolize }) end + trigger_event(service, :direct_messages => data.map{ |datum| + MikuTwitter::ApiCallSupport::Request::Parser.direct_message(datum.symbolize) }) end define_event(:favorite) do |service, json| by = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize) @@ -149,6 +150,22 @@ Module.new do elsif(source.is_me?) Plugin.call(:followings_created, service, [target]) end end + define_event(:list_member_added) do |service, json| + target_user = MikuTwitter::ApiCallSupport::Request::Parser.user(json['target'].symbolize) # リストに追加されたユーザ + list = MikuTwitter::ApiCallSupport::Request::Parser.list(json['target_object'].symbolize) # リスト + source_user = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize) # 追加したユーザ + list.add_member(target_user) + Plugin.call(:list_member_added, service, target_user, list, source_user) + end + + define_event(:list_member_removed) do |service, json| + target_user = MikuTwitter::ApiCallSupport::Request::Parser.user(json['target'].symbolize) # リストに追加されたユーザ + list = MikuTwitter::ApiCallSupport::Request::Parser.list(json['target_object'].symbolize) # リスト + source_user = MikuTwitter::ApiCallSupport::Request::Parser.user(json['source'].symbolize) # 追加したユーザ + list.remove_member(target_user) + Plugin.call(:list_member_removed, service, target_user, list, source_user) + end + def start_streaming(&proc) begin Plugin.call(:rewindstatus, 'UserStream: start') |