diff --git a/core/mui/cairo_timeline.rb b/core/mui/cairo_timeline.rb index cdaa16c5..55b940fa 100644 --- a/core/mui/cairo_timeline.rb +++ b/core/mui/cairo_timeline.rb @@ -179,6 +179,7 @@ class Gtk::TimeLine # ==== Args # [iter] 削除するレコード(Gtk::TreeIter) def tl_model_remove(iter) + Plugin.call(:gui_timeline_message_removed, @tl.imaginary, iter[Gtk::TimeLine::InnerTL::MESSAGE]) iter[InnerTL::MIRACLE_PAINTER].destroy @tl.model.remove(iter) end end diff --git a/core/plugin/extract_load_more/.mikutter.yml b/core/plugin/extract_load_more/.mikutter.yml new file mode 100644 index 00000000..28df6b73 --- /dev/null +++ b/core/plugin/extract_load_more/.mikutter.yml @@ -0,0 +1,13 @@ +--- +name: extract_load more +slug: extract_load_more +description: 抽出タブの遡り機能を実装します。 +depends: + mikutter: '3.8' + plugin: + - extract + - load_more_timeline + - gui +version: '1.0' +author: cobodo + diff --git a/core/plugin/extract_load_more/extract_load_more.rb b/core/plugin/extract_load_more/extract_load_more.rb new file mode 100644 index 00000000..be8467d6 --- /dev/null +++ b/core/plugin/extract_load_more/extract_load_more.rb @@ -0,0 +1,70 @@ +Plugin.create(:extract_load_more) do + # データソース単位のload moreイベント + # 第1引数: timeline slug + # 第2引数: datasource slug + # 第3引数: 抽出タブ内にある、そのdatasourceの最古のDiva::Model。 + defevent :extract_load_more_datasource, prototype: [Symbol, Symbol, Diva::Model] + + # 特定のデータソースにDiva::Model群を追加し、必要に応じてtimeline_maxの拡大も行なう。 + # 第1引数: timeline slug + # 第2引数: datasource slug + # 第3引数: datasourceに追加するDiva::Modelの配列 + defevent :extract_load_more_messages, prototype: [Symbol, Symbol, [Diva::Model]] + + def tl_uris + @tl_uris ||= Hash.new # timeline_slug -> datasource_slug -> URI -> time + end + tl_uris + + def extract_tabs + Plugin[:extract].extract_tabs + end + + on_extract_receive_message do |source, messages| + tabs = extract_tabs.values.select{ |r| r.sources && r.using?(source) } + next if tabs.empty? + tabs.each do |record| + @tl_uris[record.slug] ||= Hash.new + @tl_uris[record.slug][source] ||= Hash.new + messages.each do |m| + if m && m.uri + unless m.retweet_source && tl_uris[record.slug][source].has_key?(m.retweet_source.uri.to_s) + @tl_uris[record.slug][source][m.uri.to_s] = m.retweet_source ? m.modified : m.created + end + end + end + end + end + + # n=TLあたりのメッセージ保持数、m=タブあたりの平均データソース数として、O(nm) + on_gui_timeline_message_removed do |i_timeline, message| + next unless (message && message.uri) + @tl_uris[i_timeline.slug] ||= Hash.new + @tl_uris[i_timeline.slug].keys.each do |source| + @tl_uris[i_timeline.slug][source].delete(message.uri.to_s) + end + end + + # O(n) + on_load_more_timeline do |tl_slug| + next unless @tl_uris[tl_slug] + @tl_uris[tl_slug].keys.each do |source| + pair = @tl_uris[tl_slug][source].to_a.min{|a, b| a[1] <=> b[1] } + oldest = timeline(tl_slug).find {|m| m && m.uri && m.uri == pair[0] } + Plugin.call(:extract_load_more_datasource, tl_slug, source, oldest) if oldest + end + end + + on_extract_load_more_messages do |tl_slug, source, messages| + pp "on_extract_load_more_messages: #{tl_slug} #{source} #{messages}" + i_tl = timeline(tl_slug) + i_tl.timeline_max = [messages.size + i_tl.size, i_tl.timeline_max].max + @tl_uris[tl_slug] ||= Hash.new + @tl_uris[tl_slug][source] ||= Hash.new + messages.each do |m| + @tl_uris[tl_slug][source][m.uri] = m.retweet_source ? m.modified : m.created + end + i_tl << messages + end +end + diff --git a/core/plugin/gtk/gtk.rb b/core/plugin/gtk/gtk.rb index 8057a553..cba37efe 100644 --- a/core/plugin/gtk/gtk.rb +++ b/core/plugin/gtk/gtk.rb @@ -611,6 +611,26 @@ Plugin.create :gtk do # _widget_ に対応するウィジェットオブジェクトまたは偽 def find_implement_widget_by_gtkwidget(widget) @slug_dictionary.imaginally_by_gtk(widget) end + + # timeline_maxを取得するフィルタ + filter_gui_timeline_get_timeline_max do |i_tl, _| + [i_tl, widgetof(i_tl).timeline_max] + end + + # timeline_maxを設定するフィルタ + filter_gui_timeline_set_timeline_max do |i_tl, n| + widgetof(i_tl).timeline_max = n + [i_tl, n] + end + + # タイムラインのメッセージを順に処理するフィルタ + filter_gui_timeline_each_messages do |i_tl, y| + widgetof(i_tl).each do |m| + y << m + end + [i_tl, y] + end + end module Plugin::Gtk diff --git a/core/plugin/gui/gui.rb b/core/plugin/gui/gui.rb index ef09ed7e..5de1138e 100644 --- a/core/plugin/gui/gui.rb +++ b/core/plugin/gui/gui.rb @@ -152,4 +152,16 @@ Plugin.create :gui do [(set || {}).merge(Plugin::GUI::Tab.cuscaded)] end + # timeline_maxを取得するフィルタ + defevent :gui_timeline_get_timeline_max, prototype: [Plugin::GUI::Timeline, Integer] + + # timeline_maxを設定するフィルタ + defevent :gui_timeline_set_timeline_max, prototype: [Plugin::GUI::Timeline, Integer] + + # タイムラインのメッセージを順に処理するフィルタ + defevent :gui_timeline_each_messages, prototype: [Plugin::GUI::Timeline, :<<] + + # タイムラインからメッセージが除去された際に発生させるイベント + defevent :gui_timeline_message_removed, prototype: [Plugin::GUI::Timeline, Diva::Model] + end diff --git a/core/plugin/gui/timeline.rb b/core/plugin/gui/timeline.rb index 125d62f9..6dd63630 100644 --- a/core/plugin/gui/timeline.rb +++ b/core/plugin/gui/timeline.rb @@ -13,6 +13,8 @@ class Plugin::GUI::Timeline include Plugin::GUI::HierarchyParent include Plugin::GUI::Widget + include Enumerable + role :timeline set_parent_event :gui_timeline_join_tab @@ -132,4 +134,29 @@ class Plugin::GUI::Timeline Plugin.call(:gui_timeline_set_order, self, block) end + # このタイムライン内の _message_ を繰り返し処理する + def each + enum = Enumerator.new do |y| + Plugin.filtering(:gui_timeline_each_messages, self, y) + end.lazy + return enum unless block_given? + + enum.each do |m| + yield m + end + end + + def size + to_a.size + end + + # timeline_maxを取得する + def timeline_max + Plugin.filtering(:gui_timeline_get_timeline_max, self, nil)[1] || UserConfig[:timeline_max] + end + + # timeline_maxを設定する + def timeline_max=(n) + Plugin.filtering(:gui_timeline_set_timeline_max, self, n) + end end diff --git a/core/plugin/load_more_timeline/.mikutter.yml b/core/plugin/load_more_timeline/.mikutter.yml new file mode 100644 index 00000000..706d4ce1 --- /dev/null +++ b/core/plugin/load_more_timeline/.mikutter.yml @@ -0,0 +1,11 @@ +--- +name: load more timeline +slug: load_more_timeline +description: mikutterのタイムラインを遡るためのイベントを定義し、コマンドを提供します。 +depends: + mikutter: '3.8' + plugin: + - gui +version: '1.0' +author: cobodo + diff --git a/core/plugin/load_more_timeline/load_more_timeline.rb b/core/plugin/load_more_timeline/load_more_timeline.rb new file mode 100644 index 00000000..1c42a3b8 --- /dev/null +++ b/core/plugin/load_more_timeline/load_more_timeline.rb @@ -0,0 +1,15 @@ +Plugin.create(:load_more_timeline) do + defevent :load_more_timeline, prototype: [Symbol] + + # 遡るコマンド + # プラグインは、on_load_more_timelineに渡されたタイムラインslugを見て遡ることができる。 + command(:load_more_timeline, + name: _('タイムラインを遡る'), + condition: lambda{ |opt| true }, + visible: true, + role: :timeline) do |opt| + i_tl = opt.widget + Plugin.call(:load_more_timeline, i_tl.slug) + end +end + diff --git a/core/plugin/twitter_load_more/.mikutter.yml b/core/plugin/twitter_load_more/.mikutter.yml new file mode 100644 index 00000000..e9d7c0c8 --- /dev/null +++ b/core/plugin/twitter_load_more/.mikutter.yml @@ -0,0 +1,16 @@ +--- +name: twitter_load more +slug: twitter_load_more +description: twitterのHome Timeline、リプライ、ユーザータイムライン、リストの遡り機能を実装します。 +depends: + mikutter: '3.8' + plugin: + - twitter + - list + - load_more_timeline + - extract_load_more + - gui + - world +version: '1.0' +author: cobodo + diff --git a/core/plugin/twitter_load_more/twitter_load_more.rb b/core/plugin/twitter_load_more/twitter_load_more.rb new file mode 100644 index 00000000..19a7f142 --- /dev/null +++ b/core/plugin/twitter_load_more/twitter_load_more.rb @@ -0,0 +1,68 @@ +Plugin.create(:twitter_load_more) do + def load_more(tl_slug, source = nil, oldest = nil, &adder) + notice "twitter_load_more: tl_slug=#{tl_slug} source=#{source} oldest=#{oldest}" + world = Plugin.filtering(:worlds, []).first.find{|w| w.class.slug == :twitter } + if tl_slug == :home_timeline + oldest = timeline(tl_slug).min {|a, b| a.modified.to_i <=> b.modified.to_i } + params = { + count: 200, + max_id: oldest.id - 1, + tweet_mode: 'extended'.freeze + } + world.home_timeline(params).next(&adder).terminate("twitter_load_more: home_timeline の追加取得に失敗しました") + elsif tl_slug == :mentions + oldest = timeline(tl_slug).min {|a, b| a.modified.to_i <=> b.modified.to_i } + params = { + count: 200, + max_id: oldest.id - 1, + tweet_mode: 'extended'.freeze + } + world.mentions(params).next(&adder).terminate("twitter_load_more: reply の追加取得に失敗しました") + elsif timeline(tl_slug).parent.is_a?(Plugin::GUI::Fragment) + oldest = timeline(tl_slug).min {|a, b| a.modified.to_i <=> b.modified.to_i } + fragment_slug = timeline(tl_slug).parent.slug + %r!\Ausertimeline_(.+)_[0-9]+_[0-9A-Fa-f]{8}_[0-9A-Fa-f]{8}\z!.match(fragment_slug.to_s) do |m| + params = { + # profile-http://twitter.com/username -> username + screen_name: m[1].split('/').last, + max_id: oldest.id - 1, + count: 200, + include_rts: 1, + tweet_mode: 'extended'.freeze + } + world.user_timeline(params).next(&adder).terminate("twitter_load_more: usertimeline の追加取得に失敗しました") + end + elsif source + list = Plugin[:list].using_lists.find{|list| Plugin[:list].datasource_slug(list) == source } + return unless list + + world = Plugin.filtering(:worlds, []).first.find{|w| w.class.slug == :twitter } + params = { + count: 200, + max_id: oldest.id - 1, + list_id: list[:id], + cache: :keep + } + world.list_statuses(params).next(&adder).terminate("twitter_load_more: home_timeline の追加取得に失敗しました") + end + end + + on_load_more_timeline do |tl_slug| + notice "twitter_load_more: on_load_more_timeline: tl_slug=#{tl_slug}" + load_more(tl_slug) do |messages| + notice "on_load_more_timeline: add #{messages} to #{tl_slug}" + i_tl = timeline(tl_slug) + i_tl.timeline_max = [messages.size + i_tl.size, i_tl.timeline_max].max + i_tl << messages + end + end + + on_extract_load_more_datasource do |tl_slug, source, oldest| + notice "twitter_load_more: on_extract_load_more_datasource: tl_slug=#{tl_slug} source=#{source} oldest=#{oldest}" + load_more(tl_slug, source, oldest) do |messages| + notice "on_extract_load_more_datasource: add #{messages} to #{tl_slug}, #{source}" + Plugin.call(:extract_load_more_messages, tl_slug, source, messages) + end + end +end +