File: activity.rb

package info (click to toggle)
mikutter 4.1.3%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 9,260 kB
  • sloc: ruby: 20,126; sh: 183; makefile: 19
file content (290 lines) | stat: -rw-r--r-- 11,339 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# -*- coding: utf-8 -*-
# 通知管理プラグイン

require 'mui/gtk_tree_view_pretty_scroll'

require_relative 'model/activity'
require_relative 'model_selector'

require "set"
require 'typed-array'

# アクティビティの設定の並び順
UserConfig[:activity_kind_order] = nil unless UserConfig[:activity_kind_order].is_a? Array
UserConfig[:activity_kind_order] ||= %w(
	retweet
	favorite
	follow
	list_member_added
	list_member_removed
	dm
	system
	ratelimit
	streaming_status
	error)
# アクティビティタブに保持する通知の数
UserConfig[:activity_max] ||= 1000

Plugin.create(:activity) do

  class ActivityView < ::Gtk::CompatListView
    include ::Gtk::TreeViewPrettyScroll

    ICON = 0
    KIND = 1
    TITLE = 2
    DATE = 3
    MODEL = 4
    URI = 5

    def initialize(plugin)
      type_strict plugin => Plugin
      @plugin = plugin
      super()
    end

    def column_schemer
      [{:kind => :pixbuf, :type => GdkPixbuf::Pixbuf, :label => 'icon'}, # ICON
       {:kind => :text, :type => String, :label => _('種類')},      # KIND
       {:kind => :text, :type => String, :label => _('説明')},      # TITLE
       {:kind => :text, :type => String, :label => _('時刻')},      # DATE
       {type: Plugin::Activity::Activity},                         # Activity Model
       {type: String}                                              # URI
      ].freeze
    end

    def method_missing(*args, &block)
      @plugin.__send__(*args, &block)
    end
  end

  BOOT_TIME = Time.new.freeze
  @contains_uris = Set.new

  # そのイベントをミュートするかどうかを返す(trueなら表示しない)
  def mute?(params)
    mute_kind = UserConfig[:activity_mute_kind]
    if mute_kind.is_a? Array
      return true if mute_kind.map(&:to_s).include? params[:kind].to_s end
    mute_kind_related = UserConfig[:activity_mute_kind_related]
    if mute_kind_related
      return true if mute_kind_related.map(&:to_s).include?(params[:kind].to_s) and !params[:related] end
    false end

  # アクティビティの古い通知を一定時間後に消す
  def reset_activity(model)
    Delayer.new(delay: 60) do
      if not model.destroyed?
        iters = model.to_enum(:each).to_a
        remove_count = iters.size - UserConfig[:activity_max]
        if remove_count > 0
          iters[-remove_count, remove_count].each do |_m,_p,iter|
            @contains_uris.delete(iter[ActivityView::URI])
            model.remove(iter)
          end
        end
        reset_activity(model)
      end
    end
  end

  def gen_listener_for_visible_check(uc, kind)
    UserConfig[uc] ||= []
    Plugin::Settings::Listener.new \
      get: ->(){ UserConfig[uc].include?(kind) rescue false },
      set: ->(value) do
        if value
          UserConfig[uc] += [kind]
        else
          UserConfig[uc] -= [kind] end end end

  def gen_listener_for_invisible_check(uc, kind)
    UserConfig[uc] ||= []
    Plugin::Settings::Listener.new \
      get: ->(){ (not UserConfig[uc].include?(kind)) rescue true },
      set: ->(value) do
        unless value
          UserConfig[uc] += [kind]
        else
          UserConfig[uc] -= [kind] end end end

  def gen_icon_modifier(tree_model, activity)
    ->loaded_icon {
      uri_string = activity.uri.to_s.freeze
      if !tree_model.destroyed? and @contains_uris.include?(uri_string)
        selected_iter = tree_model.to_enum(:each).lazy.map{ |_m,_p,iter|
          iter
        }.find{|iter|
          iter[ActivityView::URI] == uri_string
        }
        selected_iter[ActivityView::ICON] = loaded_icon if selected_iter
      end
    }
  end

  # 新しいアクティビティの種類を定義する。設定に表示されるようになる
  # ==== Args
  # [kind] 種類
  # [name] 表示する名前
  defdsl :defactivity do |kind, name|
    kind, name = kind.to_sym, name.to_s
    filter_activity_kind do |data|
      data[kind] = name
      [data] end end

  activity_view = ActivityView.new(self)
  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::VPaned.new
  activity_detail_view = Gtk::VBox.new
  activity_scroll_view = Gtk::ScrolledWindow.new
  activity_model_selector = Plugin::Activity::ModelSelector.new

  reset_activity(activity_view.model)

  activity_scroll_view.
    set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC).
    set_height_request(88)
  activity_detail_view.
    set_height_request(128)

  activity_container.
    pack1(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),
          true, true).
    pack2(activity_detail_view, true, false)
  activity_scroll_view.add_with_viewport(activity_description)
  activity_detail_view.
    add(activity_scroll_view).
    closeup(activity_model_selector).
    closeup(activity_status.right)

  tab(:activity, _("アクティビティ")) do
    set_icon Skin[:activity]
    nativewidget ::Gtk::EventBox.new.add(activity_container)
  end

  activity_view.ssc("cursor-changed") { |this|
    iter = this.selection.selected
    if iter
      activity_description.rewind(iter[ActivityView::MODEL].description)
      activity_status.set_text(iter[ActivityView::DATE])
      activity_model_selector.set(iter[ActivityView::MODEL].children)
    end
    false
  }

  # アクティビティ更新を受け取った時の処理
  # plugin, kind, title, icon, date, service
  on_modify_activity do |params|
    next if activity_view.destroyed?
    if not mute?(params)
      params = params.dup
      case params[:icon]
      when GdkPixbuf::Pixbuf
        # TODO: Pixbufを渡された時の処理
        params[:icon] = nil
      when Diva::Model, nil, false
      # nothing to do
      else
        params[:icon] = Plugin.collect(:photo_filter, params[:icon], Pluggaloid::COLLECT).first
      end
      activity_view.scroll_to_zero_lator! if activity_view.realized? and activity_view.vadjustment.value == 0.0
      model = Plugin::Activity::Activity.new(params)
      next if @contains_uris.include?(model.uri.to_s)
      @contains_uris << model.uri.to_s
      iter = activity_view.model.prepend
      if model.icon
        iter[ActivityView::ICON] = model.icon.load_pixbuf(width: 24, height: 24, &gen_icon_modifier(activity_view.model, model))
      end
      iter[ActivityView::KIND] = model.kind
      iter[ActivityView::TITLE] = model.title
      iter[ActivityView::DATE] = model.created.strftime('%Y/%m/%d %H:%M:%S')
      iter[ActivityView::MODEL] = model
      iter[ActivityView::URI] = model.uri.to_s
      if (UserConfig[:activity_show_timeline] || []).map(&:to_s).include?(model.kind)
        Plugin.call(:update, nil, [Mikutter::System::Message.new(description: model.description, source: model.plugin_slug.to_s, created: model.created)])
      end
      if (UserConfig[:activity_show_statusbar] || []).map(&:to_s).include?(model.kind)
        Plugin.call(:gui_window_rewindstatus, Plugin::GUI::Window.instance(:default), "#{model.kind}: #{model.title}", 10)
      end
    end
  end

  on_favorite do |service, user, message|
    activity(:favorite, "#{message.user[:idname]}: #{message.to_s}",
             description:(_("@%{user} がふぁぼふぁぼしました") % {user: user[:idname]} + "\n" +
                          "@#{message.user[:idname]}: #{message.to_s}"),
             icon: user.icon,
             related: message.user.me? || user.me?,
             service: service,
             children: [user, message, message.user])
  end

  on_unfavorite do |service, user, message|
    activity(:unfavorite, "#{message.user[:idname]}: #{message.to_s}",
             description:(_("@%{user} があんふぁぼしました") % {user: user[:idname]} + "\n" +
                          "@#{message.user[:idname]}: #{message.to_s}"),
             icon: user.icon,
             related: message.user.me? || user.me?,
             service: service,
             children: [user, message, message.user])
  end

  on_retweet do |retweets|
    retweets.each { |retweet|
      retweet.retweet_source_d.next{ |source|
        activity(:retweet, retweet.to_s,
                 description:(_("@%{user} がリツイートしました") % {user: retweet.user[:idname]} + "\n" +
                              "@#{source.user[:idname]}: #{source.to_s}"),
                 icon: retweet.user.icon,
                 date: retweet[:created],
                 related: (retweet.user.me? || source && source.user.me?),
                 service: Service.primary,
                 children: [retweet.user, source, source.user]) }.terminate(_ 'リツイートソースが取得できませんでした') }
  end

  onunload do
    Addon.remove_tab _('アクティビティ')
  end

  settings _("アクティビティ") do
    activity_kind = Plugin.filtering(:activity_kind, {})
    activity_kind_order = TypedArray(String).new
    if activity_kind
      activity_kind = activity_kind.last
      activity_kind.keys.each{ |kind|
        kind = kind.to_s
        i = where_should_insert_it(kind, activity_kind_order, UserConfig[:activity_kind_order])
        activity_kind_order.insert(i, kind) }
    else
      activity_kind_order = []
      activity_kind = {} end

    activity_kind_order.each do |kind|
      name = activity_kind[kind.to_sym]
      ml_param = {name: name}
      settings name do
        boolean(_('%{name}を表示する') % ml_param, gen_listener_for_invisible_check(:activity_mute_kind, kind)).tooltip(_('%{name}を、アクティビティタイムラインに表示します。チェックを外すと、%{name}の他の設定は無効になります。') % ml_param)
        boolean(_('自分に関係ない%{name}も表示する') % ml_param, gen_listener_for_invisible_check(:activity_mute_kind_related, kind)).tooltip(_('自分に関係ない%{name}もアクティビティタイムラインに表示されるようになります。チェックを外すと、自分に関係ない%{name}は表示されません。') % ml_param)
        boolean(_('タイムラインに表示'), gen_listener_for_visible_check(:activity_show_timeline, kind)).tooltip(_('%{name}が通知された時に、システムメッセージで%{name}を通知します') % ml_param)
        boolean(_('ステータスバーに表示'), gen_listener_for_visible_check(:activity_show_statusbar, kind)).tooltip(_('%{name}が通知された時に、ステータスバーにしばらく表示します') % ml_param)
      end
    end
  end

  defactivity :retweet, _("リツイート")
  defactivity :favorite, _("ふぁぼ")
  defactivity :follow, _("フォロー")
  defactivity :list_member_added, _("リストに追加")
  defactivity :list_member_removed, _("リストから削除")
  defactivity :dm, _("ダイレクトメッセージ")
  defactivity :system, _("システムメッセージ")
  defactivity :error, _("エラー")

end