aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToshiaki Asai <toshi.alternative@gmail.com>2017-12-17 06:48:45 +0900
committerToshiaki Asai <toshi.alternative@gmail.com>2017-12-17 06:48:45 +0900
commit23657815aa64a86b56b6f929e6cc043b3ab24a4f (patch)
treebbe96e605b518eac86e9b666390d8a24a99067d1
parentdf01b7bf2d4619e7f4630bab564d1322b5b4508f (diff)
parenta33d7d3396350a5b8452324642d42bb07752ab35 (diff)
downloadmikutter-23657815aa64a86b56b6f929e6cc043b3ab24a4f.tar.gz
Merge branch 'topic/spell' into topic/981-world
-rw-r--r--core/miquire_plugin.rb42
-rw-r--r--core/mui/cairo_miracle_painter.rb9
-rw-r--r--core/mui/gtk_postbox.rb17
-rw-r--r--core/plugin/command/command.rb36
-rw-r--r--core/plugin/command/conditions.rb24
-rw-r--r--core/plugin/core/core.rb13
-rw-r--r--core/plugin/direct_message/direct_message.rb4
-rw-r--r--core/plugin/search/search.rb2
-rw-r--r--core/plugin/spell/.mikutter.yml10
-rw-r--r--core/plugin/spell/error.rb8
-rw-r--r--core/plugin/spell/spell.rb47
-rw-r--r--core/plugin/spell/struct.rb57
-rw-r--r--core/plugin/streaming/filter.rb10
-rw-r--r--core/plugin/twitter/.mikutter.yml1
-rw-r--r--core/plugin/twitter/mikutwitter/api_call_support.rb2
-rw-r--r--core/plugin/twitter/model/message.rb4
-rw-r--r--core/plugin/twitter/model/world.rb36
-rw-r--r--core/plugin/twitter/twitter.rb129
-rw-r--r--core/plugin/world/keep.rb59
-rw-r--r--core/plugin/world/world.rb35
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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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