aboutsummaryrefslogtreecommitdiffstats
path: root/core/message.rb
blob: 49bec4d70ede4893bcc9dbe8a070b0d578d7143d (plain)
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
# -*- coding:utf-8 -*-

miquire :core, 'user'
miquire :core, 'retriever'

require 'net/http'
require 'delegate'
miquire :lib, 'typed-array', 'timelimitedqueue'

=begin
= Message
投稿1つを表すクラス。
=end
class Message < Retriever::Model
  # screen nameにマッチする正規表現
  MentionMatcher      = /(?:@|@|〄|☯|⑨|♨)([a-zA-Z0-9_]+)/.freeze

  # screen nameのみから構成される文字列から、@などを切り取るための正規表現
  MentionExactMatcher = /\A(?:@|@|〄|☯|⑨|♨)?([a-zA-Z0-9_]+)\Z/.freeze

  PermalinkMatcher = Regexp.union(
    %r[\Ahttps?://twitter.com/(?:#!/)?(?<screen_name>[a-zA-Z0-9_]+)/status(?:es)?/(?<id>\d+)(?:\?.*)?\Z], # Twitter
    %r[\Ahttp://favstar\.fm/users/(?<screen_name>[a-zA-Z0-9_]+)/status/(?<id>\d+)], # Hey, Favstar. Ban stop me premiamu!
    %r[\Ahttp://aclog\.koba789\.com/i/(?<id>\d+)] # Hey, Twitter. Please BAN me rhenium!
  ).freeze

  extend Gem::Deprecate
  include Retriever::Model::Identity

  register :twitter_tweet, name: "Tweet"

  @@appear_queue = TimeLimitedQueue.new(65536, 0.1, Set){ |messages|
    Plugin.call(:appear, messages) }

  # args format
  # key     | value(class)
  #---------+--------------
  # id      | id of status(mixed)
  # entity  | entity(mixed)
  # message | posted text(String)
  # tags    | kind of message(Array)
  # user    | user who post this message(User or Hash or mixed(User IDNumber))
  # reciver | recive user(User)
  # replyto | source message(Message or mixed(Status ID))
  # retweet | retweet to this message(Message or StatusID)
  # post    | post object(Service)
  # image   | image(URL or Image object)

  field.int    :id, required: true
  field.string :message, required: true             # Message description
  field.has    :user, User, required: true          # Send by user
  field.has    :receiver, User                      # Send to user
  field.has    :replyto, Message                    # Reply to this message
  field.has    :retweet, Message                    # ReTweet to this message
  field.string :source                              # using client
  field.string :geo                                 # geotag
  field.bool   :exact                               # true if complete data
  field.time   :created                             # posted time
  field.time   :modified                            # updated time

  entity_class Retriever::Entity::TwitterEntity

  handle PermalinkMatcher do |uri|
    match = PermalinkMatcher.match(uri.to_s)
    notice match.inspect
    if match
      message = findbyid(match[:id].to_i, Retriever::DataSource::USE_LOCAL_ONLY)
      notice message.inspect
      if message
        message
      else
        Thread.new do
          findbyid(match[:id].to_i, Retriever::DataSource::USE_ALL)
        end
      end
    else
      raise Retriever::RetrieverError, "id##{match[:id]} does not exist in #{self}."
    end
  end

  def self.container_class
    Messages end

  # appearイベント
  def self.appear(message) # :nodoc:
    @@appear_queue.push(message)
  end

  def self.memory
    @memory ||= DataSource.new end

  # Message.newで新しいインスタンスを作らないこと。インスタンスはコアが必要に応じて作る。
  # 検索などをしたい場合は、 _Retriever_ のメソッドを使うこと
  def initialize(value)
    type_strict value => Hash
    if not(value[:image].is_a?(Message::Image)) and value[:image]
      value[:image] = Message::Image.new(value[:image]) end
    super(value)
    if self[:replyto].is_a? Message
      self[:replyto].add_child(self) end
    if self[:retweet].is_a? Message
      self[:retweet].add_child(self) end
    Message.appear(self)
  end

  # 投稿主のidnameを返す
  def idname
    user.idname
  end

  # この投稿へのリプライをつぶやく
  def post(other, &proc)
    other[:replyto] = self
    other[:receiver] = self[:user]
    service = Service.primary
    if service.is_a? Service
      service.post(other){|*a| yield(*a) if block_given? } end end

  # リツイートする
  def retweet
    service = Service.primary
    if retweetable? and service
      service.retweet(self){|*a| yield(*a) if block_given? } end end

  # この投稿を削除する
  def destroy
    service = Service.primary
    if deletable? and service
      service.destroy(self){|*a| yield(*a) if block_given? } end end

  # お気に入り状態を変更する。_fav_ がtrueならお気に入りにし、falseならお気に入りから外す。
  def favorite(fav = true)
    service = Service.primary
    if favoritable? and service
      service.favorite(self, fav) end end

  # お気に入りから削除する
  def unfavorite
    favorite(false) end

  # この投稿のお気に入り状態を返す。お気に入り状態だった場合にtrueを返す
  def favorite?
    favorited_by.include?(Service.primary!.user_obj)
  rescue Service::NotExistError
    false end

  # 投稿がシステムメッセージだった場合にtrueを返す
  def system?
    false
  end

  # この投稿にリプライする権限があればtrueを返す
  def repliable?
    !!Service.primary end

  # この投稿をお気に入りに追加する権限があればtrueを返す
  def favoritable?
    Service.primary end
  alias favoriable? favoritable?

  # この投稿をリツイートする権限があればtrueを返す
  def retweetable?
    Service.primary and not protected? end

  # この投稿を削除する権限があればtrueを返す
  def deletable?
    from_me? end

  # この投稿の投稿主のアカウントの全権限を所有していればtrueを返す
  def from_me?(services=Service)
    services.map(&:user_obj).include?(self[:user]) end

  # この投稿が自分宛ならばtrueを返す
  def to_me?(services=Service)
    services.map(&:user_obj).find(&method(:receive_to?)) end

  # この投稿が公開されているものならtrueを返す。少しでも公開範囲を限定しているならfalseを返す。
  def protected?
    if retweet?
      retweet_ancestor.protected?
    else
      user.protected? end end

  # この投稿が承認されているものならtrueを返す。
  def verified?
    user.verified? end

  # この投稿の投稿主を返す。messageについては、userが必ず付与されていることが保証されているので
  # Deferredを返さない
  def user
    self[:user] end

  def service
    warn "Message#service is obsolete method. use `Service.primary'."
    Service.primary end

  # この投稿を宛てられたユーザを返す
  def receiver
    if self[:receiver].is_a? User
      self[:receiver]
    elsif self[:receiver]
      receiver_id = self[:receiver]
      self[:receiver] = parallel{
        self[:receiver] = User.findbyid(receiver_id) }
    else
      match = MentionMatcher.match(self[:message].to_s)
      if match
        result = User.findbyidname(match[1])
        self[:receiver] = result if result end end end

  # ユーザ _other_ に宛てられたメッセージならtrueを返す。
  # _other_ は、 User か_other_[:id]と_other_[:idname]が呼び出し可能なもの。
  def receive_to?(other)
    type_strict other => :[]
    (self[:receiver].is_a?(User) and other[:id] == self[:receiver][:id]) or receive_user_screen_names.include? other[:idname] end

  # このツイートが宛てられたユーザを可能な限り推測して、その idname(screen_name) を配列で返す。
  # 例えばツイート本文内に「@a @b @c」などと書かれていたら、["a", "b", "c"]を返す。
  # ==== Return
  # 宛てられたユーザの idname(screen_name) の配列
  def receive_user_screen_names
    self[:message].to_s.scan(MentionMatcher).map(&:first) end

  # 自分がこのMessageにリプライを返していればtrue
  def mentioned_by_me?
    children.any?{ |m| m.from_me? } end

  # このメッセージが何かしらの別のメッセージに宛てられたものなら真
  def has_receive_message?
    !!self[:replyto] end
  alias reply? has_receive_message?

  # このメッセージが何かに対するリツイートなら真
  def retweet?
    !!self[:retweet] end

  # この投稿が別の投稿に宛てられたものならそれを返す。
  # _force_retrieve_ がtrueなら、呼び出し元のスレッドでサーバに問い合わせるので、
  # 親投稿を受信していなくてもこの時受信できるが、スレッドがブロッキングされる。
  # falseならサーバに問い合わせずに結果を返す。
  # Messageのインスタンスかnilを返す。
  def receive_message(force_retrieve=false)
    replyto_source(force_retrieve) or retweet_source(force_retrieve) end

  def receive_message_d(force_retrieve=false)
    Thread.new{ receive_message(force_retrieve) } end

  # このMessageの宛先になっているMessageを取得して返す。
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Message|nil 宛先のMessage。宛先がなければnil
  def replyto_source(force_retrieve=false)
    if reply?
      result = get(:replyto, (force_retrieve ? -1 : 1))
      if result.is_a?(Message)
        result.add_child(self) unless result.children.include?(self)
        result end end end

  # replyto_source の戻り値をnextに渡すDeferredableを返す
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Deferredable nextの引数に宛先のMessageを渡す。宛先が無い場合は失敗し、trap{}にnilを渡す
  def replyto_source_d(force_retrieve=true)
    Thread.new do
      result = replyto_source(force_retrieve)
      if result.is_a? Message
        result
      else
        Deferred.fail(result) end end end

  # このMessageがリツイートであるなら、リツイート元のツイートを返す。
  # リツイートではないならnilを返す。リツイートであるかどうかを確認するには、
  # このメソッドの代わりに Message#retweet? を使う。
  # ==== Args
  # [force_retrieve]
  # ==== Return
  # Message|nil リツイート元のMessage。リツイートではないならnil
  def retweet_parent(force_retrieve=false)
    if retweet?
      result = get(:retweet, (force_retrieve ? -1 : 1))
      if result.is_a?(Message)
        result.add_child(self) unless result.retweeted_statuses.include?(self)
        result end end end

  # retweet_parent の戻り値をnextに渡すDeferredableを返す
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Deferredable nextの引数にリプライ元のMessageを渡す。リツイートではない場合は失敗し、trap{}にnilを渡す
  def retweet_parent_d(force_retrieve=true)
    Thread.new do
      result = retweet_source(force_retrieve)
      if result.is_a? Message
        result
      else
        Deferred.fail(result) end end end

  # このMessageが引用した投稿を全て返す
  # ==== Return
  # Enumerable このMessageが引用したMessageのid(Fixnum)
  def quoting_ids
    entity.lazy.select{ |entity|
      :urls == entity[:slug]
    }.map{ |entity|
      PermalinkMatcher.match(entity[:expanded_url])
    }.select(&ret_nth).map do |matched|
      matched[:id].to_i end end

  # このMessageが引用した投稿を全て返す。
  # _force_retrieve_ に真が指定されたらこのメソッドはTwitter APIをリクエストする可能性がある。
  # そのため _force_retrieve_ が真なら、Messageを取得してから返し、
  # 偽ならAPIリクエストが必要ないので、Messageオブジェクトの取得を遅延する。
  # Twitter APIリクエストを行ったがツイートが削除されていた、メモリ上に存在しないなどの理由で
  # 取得できなかったツイートに関しては、戻り値に含まれない
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Enumerable このMessageが引用したMessage
  def quoting_messages(force_retrieve=false)
    return @quoting_messages if defined? @quoting_messages
    if force_retrieve
      @quoting_messages ||= quoting_ids.map{|quoted_id|
        Message.findbyid(quoted_id, -1)
      }.to_a.compact.freeze.tap do |qs|
        qs.each do |q|
          q.add_quoted_by(self) end  end
    else
      quoting_ids.map{|quoted_id|
        Message.findbyid(quoted_id, 0) }.select(&ret_nth) end end

  # このMessageが引用した投稿を全て返す。
  # _force_retrieve_ に真が指定されたらこのメソッドはTwitter APIをリクエストする可能性がある。
  # Twitter APIリクエストを行ったがツイートが削除されていた、メモリ上に存在しないなどの理由で
  # 取得できなかったツイートに関しては、結果がnilとなる
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Deferredable
  def quoting_messages_d(force_retrieve=false)
    Thread.new{ quoting_messages(force_retrieve) } end

  # self が、何らかのツイートを引用しているなら真を返す
  # ==== Return
  # TrueClass|FalseClass
  def quoting?
    !!quoting_ids.first end

  # selfを引用している _retriever_ を登録する
  # ==== Args
  # [retriever] Retriever::Model selfを引用しているRetriever
  # ==== Return
  # self
  def add_quoted_by(retriever)
    atomic do
      @quoted_by ||= Retriever::Model.container_class.new
      unless @quoted_by.include? retriever
        if @quoted_by.frozen?
          @quoted_by = Retriever::Model.container_class.new(@quoted_by + [retriever])
        else
          @quoted_by << retriever end end
      self end end

  # selfを引用しているRetrieverを返す
  # ==== Return
  # Retriever::Model.container_class selfを引用しているRetriever::Modelの配列
  def quoted_by
    if defined? @quoted_by
      @quoted_by
    else
      atomic do
        @quoted_by ||= Retriever::Model.container_class.new end end.freeze end

  # self が、何らかのツイートから引用されているなら真を返す
  # ==== Return
  # TrueClass|FalseClass
  def quoted_by?
    !quoted_by.empty? end

  # 投稿の宛先になっている投稿を再帰的にさかのぼるような _Enumerator_ を返す。
  # ==== Return
  # Enumerator
  def ancestors_enumerator(force_retrieve=false)
    Enumerator.new do |yielder|
      message = self
      while message
        yielder << message
        message = message.receive_message(force_retrieve) end end end
  private :ancestors_enumerator

  # 投稿の宛先になっている投稿を再帰的にさかのぼり、それぞれを引数に取って
  # ブロックが呼ばれる。
  # ブロックが渡されていない場合、 _Enumerator_ を返す。
  # _force_retrieve_ は、 Message#receive_message の引数にそのまま渡される
  # ==== Return
  # obj|Enumerator
  def each_ancestor(force_retrieve=false, &proc)
    e = ancestors_enumerator(force_retrieve)
    if block_given?
      e.each(&proc)
    else
      e end end
  alias :each_ancestors :each_ancestor
  deprecate :each_ancestors, "each_ancestor", 2016, 12

  # 投稿の宛先になっている投稿を再帰的にさかのぼり、それらを配列にして返す。
  # 配列インデックスが大きいものほど、早く投稿された投稿になる。
  # ([0]は[1]へのリプライ)
  def ancestors(force_retrieve=false)
    ancestors_enumerator(force_retrieve).to_a end

  # 投稿の宛先になっている投稿を再帰的にさかのぼり、何にも宛てられていない投稿を返す。
  # つまり、一番祖先を返す。
  def ancestor(force_retrieve=false)
    ancestors(force_retrieve).last end

  # retweet元を再帰的にさかのぼるような _Enumerator_ を返す。
  # この _Enumerator_ は最初にこの _Message_ 自身を yield し、以降は直前に yield した
  # 要素のretweet元を yield する。
  # ==== Return
  # Enumerator
  def retweet_ancestors_enumerator(force_retrieve=false)
    Enumerator.new do |yielder|
      message = self
      while message
        yielder << message
        message = message.retweet_parent(force_retrieve)
      end end end
  private :retweet_ancestors_enumerator

  # retweet元を再帰的にさかのぼり、それぞれを引数に取って
  # ブロックが呼ばれる。
  # ブロックが渡されていない場合、 _Enumerator_ を返す。
  # _force_retrieve_ は、 Message#retweet_parent の引数にそのまま渡される
  # ==== Return
  # obj|Enumerator
  def each_retweet_ancestor(force_retrieve=false, &proc)
    e = retweet_ancestors_enumerator(force_retrieve)
    if block_given?
      e.each(&proc)
    else
      e end end
  alias :each_retweet_ancestors :each_retweet_ancestor
  deprecate :each_retweet_ancestors, "each_retweet_ancestor", 2016, 12

  # retweet元を再帰的に遡り、それらを配列にして返す。
  # 配列の最初の要素は必ずselfになり、以降は直前の要素のリツイート元となる。
  # ([0]は[1]へのリツイート)
  # ==== Return
  # Enumerator
  def retweet_ancestors(force_retrieve=false)
    retweet_ancestors_enumerator(force_retrieve).to_a end

  # リツイート元を再帰的に遡り、リツイートではないツイートを返す。
  # selfがリツイートでない場合は、selfを返す。
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Message
  def retweet_ancestor(force_retrieve=false)
    retweet_ancestors(force_retrieve).last end

  # このMessageがリツイートなら、何のリツイートであるかを返す。
  # 返される値の retweet? は常に false になる
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Message|nil リツイートであればリツイート元のMessage、リツイートでなければnil
  def retweet_source(force_retrieve=false)
    if retweet?
      retweet_ancestor(force_retrieve) end end

  # retweet_source の戻り値をnextに渡すDeferredableを返す
  # ==== Args
  # [force_retrieve] 真なら、ツイートがメモリ上に見つからなかった場合Twitter APIリクエストを発行する
  # ==== Return
  # Deferredable nextの引数にリプライ元のMessageを渡す。リツイートではない場合は失敗し、trap{}にnilを渡す
  def retweet_source_d(force_retrieve=true)
    Thread.new do
      result = retweet_source(force_retrieve)
      if result.is_a? Message
        result
      else
        Deferred.fail(result) end end end

  # このMessageが属する親子ツリーに属する全てのMessageを含むSetを返す
  # ==== Args
  # [force_retrieve] 外部サーバに問い合わせる場合真
  # ==== Return
  # 関係する全てのツイート(Set)
  def around(force_retrieve = false)
    ancestor(force_retrieve).children_all end

  # この投稿に宛てられた投稿をSetオブジェクトにまとめて返す。
  def children
    @children ||= Plugin.filtering(:replied_by, self, Set.new(retweeted_statuses))[1] end

  # childrenを再帰的に遡り全てのMessageを返す
  # ==== Return
  # このMessageの子全てをSetにまとめたもの
  def children_all
    children.inject(Retriever::Model.container_class.new([self])){ |result, item| result.concat item.children_all } end

  # この投稿をお気に入りに登録したUserをSetオブジェクトにまとめて返す。
  def favorited_by
    @favorited ||= Plugin.filtering(:favorited_by, self, Set.new())[1] end

  # この投稿を「自分」がふぁぼっていれば真
  def favorited_by_me?(me = Service.services)
    case me
    when Service
      favorited_by.include? me.user_obj
    when Enumerable
      not (Set.new(favorited_by.map(&:idname)) & Set.new(me.map(&:idname))).empty?
    else
      raise ArgumentError, "first argument should be `Service' or `Enumerable'. but given `#{me.class}'" end end

  # この投稿をリツイートしたユーザを返す
  # ==== Return
  # Enumerable リツイートしたユーザを、リツイートした順番に返す
  def retweeted_by
    has_status_user_ids = Set.new(retweeted_statuses.map(&:user).map(&:id))
    retweeted_sources.lazy.reject{|r|
      r.is_a?(User) and has_status_user_ids.include?(r.id)
    }.map(&:user) end
  alias retweeted_users retweeted_by

  # この投稿に対するリツイートを返す
  def retweeted_statuses
    retweeted_sources.lazy.select{|m| m.is_a?(Message) } end

  # この投稿に対するリツイートまたはユーザを返す
  def retweeted_sources
    @retweets ||= Plugin.filtering(:retweeted_by, self, Set.new())[1].to_a.compact end

  # 選択されているユーザがこのツイートをリツイートしているなら真
  def retweeted?
    retweeted_users.include?(Service.primary!.user_obj)
  rescue Service::NotExistError
    false end

  # この投稿を「自分」がリツイートしていれば真
  def retweeted_by_me?(me = Service.services)
    case me
    when Service
      retweeted_users.include? me.user_obj
    when Enumerable
      not (Set.new(retweeted_users.map(&:idname)) & Set.new(me.map(&:idname))).empty?
    else
      raise ArgumentError, "first argument should be `Service' or `Enumerable'. but given `#{me.class}'" end end

  # この投稿をリツイート等して、 _me_ のタイムラインに出現させたリツイートを返す。
  # 特に誰もリツイートしていない場合は _self_ を返す。
  # リツイート、ふぁぼなどを行う時に使用する。
  # ==== Args
  # [me] Service 対象とするService
  # ==== Return
  # Message
  def introducer(me = Service.primary!)
    Plugin.filtering(:message_introducers, me, self, retweeted_statuses.reject{|m|m.user == me.to_user}).last.to_a.last || self
  end

  # 非公式リツイートやハッシュタグを適切に組み合わせて投稿する
  def body
    self[:message].to_s.freeze
  end

  def inspect
    @value.inspect
  end

  # Message#body と同じだが、投稿制限文字数を超えていた場合には、収まるように末尾を捨てる。
  def to_s
    body[0,140].freeze end
  memoize :to_s

  def to_i
    self[:id].to_i end

  # :nodoc:
  def message
    self end

  # :nodoc:
  def to_message
    self end

  deprecate :message, :none, 2017, 05
  deprecate :to_message, :none, 2017, 05

  # 本文を人間に読みやすい文字列に変換する
  def to_show
    @to_show ||= body.gsub(/&(gt|lt|quot|amp);/){|m| {'gt' => '>', 'lt' => '<', 'quot' => '"', 'amp' => '&'}[$1] }.freeze end

  # このMessageのパーマリンクを取得する
  # ==== Return
  # 次のいずれか
  # [URI] パーマリンク
  # [nil] パーマリンクが存在しない
  def perma_link
    URI.parse("https://twitter.com/#{user[:idname]}/status/#{self[:id]}").freeze end
  memoize :perma_link
  alias :parma_link :perma_link
  deprecate :parma_link, "perma_link", 2016, 12

  # :nodoc:
  def marshal_dump
    raise RuntimeError, 'Message cannot marshalize'
  end

  # :nodoc:
  def add_favorited_by(user, time=Time.now)
    type_strict user => User, time => Time
    return retweet_source.add_favorited_by(user, time) if retweet?
    service = Service.primary
    if service
      set_modified(time) if UserConfig[:favorited_by_anyone_age] and (UserConfig[:favorited_by_myself_age] or service.user != user.idname)
      favorited_by.add(user)
      Plugin.call(:favorite, service, user, self) end end

  # :nodoc:
  def remove_favorited_by(user)
    type_strict user => User
    return retweet_source.remove_favorited_by(user) if retweet?
    service = Service.primary
    if service
      favorited_by.delete(user)
      Plugin.call(:unfavorite, service, user, self) end end

  # :nodoc:
  def add_child(child)
    type_strict child => Message
    if child[:retweet]
      if defined? @retweets
        add_retweet_in_this_thread(child)
      else
        SerialThread.new{
          retweeted_sources
          add_retweet_in_this_thread(child) } end
    else
      if defined? @children
        add_child_in_this_thread(child)
      else
        SerialThread.new{
          children
          add_child_in_this_thread(child) } end end end

  # :nodoc:
  def add_retweet_user(retweet_user, created_at)
    type_strict retweet_user => User
    return retweet_source.add_retweet_user(retweet_user, created_at) if retweet?
    if defined? @retweets
      add_retweet_in_this_thread(retweet_user, created_at)
    else
      SerialThread.new{
        retweeted_sources
        add_retweet_in_this_thread(retweet_user, created_at) } end end

  # 最終更新日時を取得する
  def modified
    @value[:modified] ||= [created, *(@retweets || []).map{ |x| x.modified }].compact.max
  end

  def inspect
    "#<#{self.class.name}: #{id} #{user.inspect} #{to_show}>"
  end

  private

  def add_retweet_in_this_thread(child, created_at=child[:created])
    type_strict child => tcor(Message, User)
    unless @retweets.include? child
      case child
      when Message
        @retweets << child
        @retweets.delete(child.user) if @retweets.include?(child.user)
      when User
        @retweets << child if retweeted_users.include?(child) end end
    service = Service.primary
    set_modified(created_at) if service and UserConfig[:retweeted_by_anyone_age] and ((UserConfig[:retweeted_by_myself_age] or service.user != child.user.idname)) end

  def add_child_in_this_thread(child)
    @children << child
  end

  def set_modified(time)
    if modified < time
      self[:modified] = time
      Plugin::call(:message_modified, self) end
    self end

  class DataSource < Retriever::Model::Memory
    def findbyid(id, policy)
      if id.is_a? Enumerable
        super.map do |v|
          case v
          when Message
            v
          else
            findbyid(v) end end
      else
        result = super
        if result
          result
        elsif policy == Retriever::DataSource::USE_ALL
          result = Service.primary.scan(:status_show, id: id)
          result end end
    rescue Exception => err
      error err
      raise err
    end
  end

  #
  # Sub classes
  #

  # このツイートのユーザ情報
  class MessageUser < User
    undef_method *(public_instance_methods - [:object_id, :__send__])

    def initialize(user, raw)
      abort if not user.is_a? User
      @raw = raw.freeze
      @user = user end

    def [](key)
      @raw.has_key?(key.to_sym) ? @raw[key.to_sym] : @user[key] end

    def is_me?
      @user.me? end
    deprecate :is_me?, "me?", 2017, 05

    def method_missing(*args)
      @user.__send__(*args) end end

  # 添付画像
  class Image
    attr_accessor :url
    attr_reader :resource

    IS_URL = /\Ahttps?:\/\//

    def initialize(resource)
      if(not resource.is_a?(IO)) and (FileTest.exist?(resource.to_s)) then
        @resource = open(resource)
      else
        @resource = resource
        if((IS_URL === resource) != nil) then
          @url = resource
        end
      end
    end

    def path
      if(@resource.is_a?(File)) then
        return @resource.path
      end
      return @url
    end
  end

  # 例外を引き起こした原因となるMessageをセットにして例外を発生させることができる
  class MessageError < Retriever::RetrieverError
    # messageは、Exceptionクラスと名前が被る
    attr_reader :message

    def initialize(body, message)
      super("#{body} occured by #{message[:id]}(#{message[:message]})")
      @message = message end

  end

end

class Messages < TypedArray(Message)
end