simple2ch 0.1.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +25 -0
  4. data/.travis.yml +8 -0
  5. data/README.md +24 -31
  6. data/UPDATES.md +46 -9
  7. data/lib/simple2ch.rb +67 -83
  8. data/lib/simple2ch/bbs.rb +54 -0
  9. data/lib/simple2ch/board.rb +26 -50
  10. data/lib/simple2ch/dat.rb +22 -20
  11. data/lib/simple2ch/regex.rb +16 -0
  12. data/lib/simple2ch/{res.rb → response.rb} +58 -57
  13. data/lib/simple2ch/simple2ch_error.rb +9 -0
  14. data/lib/simple2ch/thread.rb +113 -0
  15. data/lib/simple2ch/version.rb +1 -1
  16. data/simple2ch.gemspec +8 -0
  17. data/spec/bbs_spec.rb +30 -0
  18. data/spec/board_spec.rb +89 -54
  19. data/spec/dat_spec.rb +20 -19
  20. data/spec/response_spec.rb +131 -0
  21. data/spec/simple2ch_spec.rb +19 -50
  22. data/spec/spec_helper.rb +65 -37
  23. data/spec/thread_spec.rb +76 -0
  24. data/spec/vcr/Simple2ch_BBS/_boards/use_cache/behaves_like_boards/from_2ch_sc/1_1_1_1_1_2.yml +1117 -0
  25. data/spec/vcr/Simple2ch_BBS/_boards/use_cache/behaves_like_boards/from_open2ch_net/1_1_1_1_2_2.yml +1136 -0
  26. data/spec/vcr/Simple2ch_BBS/_boards/use_force_reload/behaves_like_boards/from_2ch_sc/1_1_2_1_1_2.yml +1117 -0
  27. data/spec/vcr/Simple2ch_BBS/_boards/use_force_reload/behaves_like_boards/from_2ch_sc/1_1_2_1_1_3.yml +1117 -0
  28. data/spec/vcr/Simple2ch_BBS/_boards/use_force_reload/behaves_like_boards/from_open2ch_net/1_1_2_1_2_2.yml +1136 -0
  29. data/spec/vcr/Simple2ch_BBS/_boards/use_force_reload/behaves_like_boards/from_open2ch_net/1_1_2_1_2_3.yml +1136 -0
  30. data/spec/vcr/Simple2ch_Board/_threads/1_4_1.yml +1238 -0
  31. data/spec/vcr/Simple2ch_Board/_threads/1_4_2.yml +1238 -0
  32. data/spec/vcr/Simple2ch_Board/_threads/1_4_3.yml +1238 -0
  33. data/spec/vcr/Simple2ch_Board/_threads/1_4_7.yml +1238 -0
  34. data/spec/vcr/Simple2ch_Board/_threads/1_4_8.yml +1238 -0
  35. data/spec/vcr/Simple2ch_Board/_threads/1_4_9.yml +1238 -0
  36. data/spec/vcr/Simple2ch_Dat/should_have_responses/1_2_1.yml +953 -0
  37. data/spec/vcr/Simple2ch_Dat/should_have_responses/1_2_3.yml +953 -0
  38. data/spec/vcr/Simple2ch_Dat/should_have_responses/1_2_4.yml +953 -0
  39. data/spec/vcr/Simple2ch_Dat/should_have_responses/size/1_2_2_1.yml +953 -0
  40. data/spec/vcr/Simple2ch_Response/_anchors/when_a_thre_have_both_id_and_non-id_responses/1_2_7_1.yml +5321 -0
  41. data/spec/vcr/Simple2ch_Response/_parse/when_real_data/when_2ch_sc/1_1_2_1_1.yml +1599 -0
  42. data/spec/vcr/Simple2ch_Response/_parse/when_real_data/when_open2ch_net/1_1_2_2_1.yml +1465 -0
  43. data/spec/vcr/Simple2ch_Thread/_new/2ch_sc/1_1_1_1.yml +1410 -0
  44. data/spec/vcr/Simple2ch_Thread/_new/2ch_sc/1_1_1_2.yml +1692 -0
  45. data/spec/vcr/Simple2ch_Thread/_new/open2ch_net/1_1_2_1.yml +1410 -0
  46. data/spec/vcr/Simple2ch_Thread/_new/open2ch_net/1_1_2_2.yml +1999 -0
  47. data/spec/vcr/Simple2ch_Thread/_responses/1_2_1.yml +1599 -0
  48. data/spec/vcr/Simple2ch_Thread/_responses/first/1_2_2_1.yml +1599 -0
  49. data/spec/vcr/Simple2ch_Thread/_responses/first/1_2_3_1.yml +1599 -0
  50. data/spec/vcr/Simple2ch_Thread/_title/1_3_1.yml +1599 -0
  51. data/spec/vcr/Simple2ch_Thread/_title/size/1_3_2_1.yml +1599 -0
  52. metadata +185 -11
  53. data/lib/simple2ch/simple2ch_exception.rb +0 -7
  54. data/lib/simple2ch/thre.rb +0 -140
  55. data/spec/res_spec.rb +0 -123
  56. data/spec/thre_spec.rb +0 -122
@@ -1,80 +1,56 @@
1
1
  module Simple2ch
2
2
  class Board
3
- # @return [URI] 板のURL
3
+ # @return [Bbs2chUrlValidator::URL] 板のURL
4
4
  attr_reader :url
5
5
  # @return [String] 板のタイトル
6
6
  attr_reader :title
7
- # @return [String] サーバ名
8
- attr_reader :server_name
9
- # @return [String] 板の名前(コンピュータ名)
10
- attr_reader :board_name
7
+ # @return [Time] 板オブジェクト更新日時
8
+ attr_reader :updated_at
11
9
 
12
10
 
13
11
  # @param [String] title 板の名前
14
12
  # @param [String] url 板のURL
15
13
  # @option [Boolean] fetch_title 板の名前を自動取得するか
16
- def initialize(title, url, fetch_title:nil)
17
- @server_name = @board_name = nil
18
- @url = validate_url url
19
- @title = if fetch_title
20
- (b=Simple2ch.boards(url).find{|bb| bb.url.to_s == @url.to_s}) && b.class!=Array ? b.title : nil
21
- else
22
- title
23
- end
24
- @thres = []
14
+ def initialize(url, title: nil)
15
+ @url = validate_url(url)
16
+ @title = title
17
+ @updated_at = Time.now
25
18
  end
26
19
 
27
20
  # 板に属する全てのスレッドを返す
28
21
  # @return [Array<Thre>] 板に属する全てのスレッド
29
- def thres
30
- if @thres.size > 0
31
- @thres
32
- else
33
- fetch_all_thres
34
- end
35
- end
36
-
37
- # おーぷん2chか否かを返す
38
- # @return [Boolean] おーぷん2chか否か
39
- def open2ch?
40
- @f_open2ch && true
22
+ def threads
23
+ @threads ||= fetch_all_threads
41
24
  end
42
25
 
43
- # 2chタイプ名の取得
44
- # @return [Symbol] 2chタイプ名(:net, :sc, :open)
45
- def type_of_2ch
46
- Simple2ch.type_of_2ch(@url.to_s)
26
+ def ==(other)
27
+ @url.to_s == other.url.to_s
47
28
  end
48
29
 
49
30
  private
50
- # URLが正しいかバリデーションする
51
- # @param [URI] url
52
- # @raise [Simple2ch::NotA2chUrlException] 2chのフォーマットで無いURLを渡したときに発生
53
- # @raise [URI::InvalidURIError] そもそもURLのフォーマットで無いときに発生
31
+
54
32
  def validate_url(url)
55
- sp_uri = URI.parse url
56
- if sp_uri.host.index '2ch'
57
- parsed_url = Simple2ch.parse_url(url.to_s)
58
- @server_name = parsed_url[:server_name]
59
- @board_name = parsed_url[:board_name]
60
- @f_open2ch = !(parsed_url[:openflag].to_s.empty?)
61
- @tld = parsed_url[:tld]
62
- URI.parse("http://#{server_name}.#{parsed_url[:openflag]}2ch.#{@tld}/#{board_name}/")
63
- else
64
- raise NotA2chUrlException, "Given URL :#{url}"
65
- end
33
+ parsed = if url.instance_of?(Bbs2chUrlValidator::UrlInfo)
34
+ url
35
+ else
36
+ Bbs2chUrlValidator::URL.parse(url)
37
+ end
38
+ return parsed if parsed && !parsed.board_name.empty?
39
+ raise NotA2chBoardUrlError
66
40
  end
67
41
 
68
42
  # 板に属する全てのスレッドをsubject.txtから取得する
69
43
  # @return [Array<Thre>] 板に属する全てのスレッド
70
- def fetch_all_thres
71
- subject_url = @url+'subject.txt'
44
+ def fetch_all_threads
45
+ subject_url = @url.subject
72
46
 
73
47
  subject_txt = Simple2ch.fetch(subject_url)
48
+ @threads = []
74
49
  subject_txt.each_line do |line|
75
- @thres << Thre.parse(self, line)
50
+ @threads << Simple2ch::Thread.parse(@url, line)
76
51
  end
77
- @thres
52
+ @updated_at = Time.now
53
+ @threads
78
54
  end
79
55
  end
80
- end
56
+ end
data/lib/simple2ch/dat.rb CHANGED
@@ -4,60 +4,62 @@ module Simple2ch
4
4
  attr_reader :thread_key
5
5
  # @return [String] タイトル
6
6
  attr_reader :title
7
+ # @return [Bbs2chUrlValidator::UrlInfo] URL
8
+ attr_reader :url
7
9
 
8
10
  # @param [Thre] thre スレッド
9
11
  def initialize(thre)
10
12
  @thre = thre
11
13
  @thread_key = thre.thread_key
12
14
  @data = nil
13
- @reses = nil
14
- @f_kako_log = nil
15
+ @responses = nil
16
+ @is_kako_log = nil
15
17
  end
16
18
 
17
19
  # Datを解析して、レスを返す
18
20
  # @return [Array<Res>] レス
19
- def reses
20
- parse_dat unless @reses
21
- @reses
21
+ def responses
22
+ parse_dat unless @responses
23
+ @responses
22
24
  end
23
25
 
24
26
  # Datを解析して過去ログかどうかを返す
25
27
  # @return [Boolean] 過去ログか否か
26
28
  def kako_log?
27
- parse_dat if @f_kako_log.nil?
28
- @f_kako_log
29
+ parse_dat if @is_kako_log.nil?
30
+ @is_kako_log
29
31
  end
30
32
 
31
- private
32
- # datのURLを返す
33
- # @return [URI] datのURL
34
- def dat_url
35
- @thre.board.url+'dat/'+(@thread_key+'.dat')
33
+ # @return [Bbs2chUrlValidator::UrlInfo] dat URL
34
+ def url
35
+ Bbs2chUrlValidator::URL.parse(@thre.url.dat)
36
36
  end
37
37
 
38
+ private
39
+
38
40
  # datファイルを取得する
39
41
  # @return [String] 取得したdatファイルの中身
40
42
  def fetch_dat
41
- @data ||= Simple2ch.fetch(dat_url)
43
+ @data ||= Simple2ch.fetch(url)
42
44
  end
43
45
 
44
46
  # datファイルを解析してResを作成する
45
47
  def parse_dat
46
48
  res_num = 0
47
- @reses = []
48
- @f_kako_log = false
49
+ @responses = []
50
+ @is_kako_log = false
49
51
  fetch_dat.each_line do |l|
50
52
  res_num += 1
51
53
  begin
52
54
  if res_num==1
53
- title = l.split('<>').pop
55
+ title = l.split('<>').pop.chomp
54
56
  @title = title unless @title
55
57
  end
56
- @reses << Res.parse(res_num, l, @thre)
57
- rescue KakoLogException
58
- @f_kako_log = true
58
+ @responses << Response.parse(res_num, l)
59
+ rescue KakoLogError
60
+ @is_kako_log = true
59
61
  end
60
62
  end
61
63
  end
62
64
  end
63
- end
65
+ end
@@ -0,0 +1,16 @@
1
+ module Simple2ch
2
+ class Regex
3
+ # http://www.rubular.com/r/u1TJbQAULD
4
+ BOARD_EXTRACT_REGEX = /<A HREF=http:\/\/(?<subdomain>\w+).(?<openflag>open|)2ch.(?<tld>sc|net)\/(?<board_name>\w+)\/>(?<board_name_ja>.+)<\/A>/
5
+ # http://www.rubular.com/r/a43KJpItsL
6
+ OPEN2CH_THREAD_DATA_EXAMPLE_REGEX = /^<a href="(\/test\/read.cgi\/\w+\/\d{10}\/)l50">1: (.+) \(\d+\)<\/a>$/
7
+ # http://www.rubular.com/r/tjZefqnhP2
8
+ SC2CH_FIRST_RES_DATA_EXAMPLE_REGEX = /<dt>1 :<font color=green><b>(.+)<\/b><\/font>:/
9
+ # http://www.rubular.com/r/Mn3dPVrtXc
10
+ OPEN2CH_FIRST_RES_DATA_EXAMPLE_REGEX = /^<dl><dt res=1><a class=num val=1 href=.\/1>1<\/a> :<font color=#1c740d><b>(.+)<\/b>.+font color=red>主<\/font>/
11
+
12
+ constants(true).each do |c|
13
+ eval "#{c}.freeze"
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  module Simple2ch
2
- class Res
2
+ class Response
3
3
  # @return [Fixnum] レス番号
4
4
  attr_reader :res_num
5
5
  # @return [String] 投稿者名
@@ -12,86 +12,81 @@ module Simple2ch
12
12
  attr_reader :mail
13
13
  # @return [String] 内容
14
14
  attr_reader :contents
15
- # @attr_writer [Thre] thre スレッド
16
- attr_writer :thre
17
15
 
18
- KAKO_LOG_INFO = '過去ログ ★<><>[過去ログ]<><em>■ このスレッドは過去ログ倉庫に格納されています</em><>'
16
+ KAKO_LOG_INFO = '過去ログ ★<><>[過去ログ]<><em>■ このスレッドは過去ログ倉庫に格納されています</em><>'.freeze
19
17
 
20
- #
21
18
  # @param [Fixnum] res_num レス番号
22
19
  # @param [String] author 投稿者名
23
20
  # @param [String] author_id ID
24
21
  # @param [Time] date 書き込み日時
25
22
  # @param [String] mail メール欄
26
23
  # @param [String] contents 内容
27
- def initialize(res_num, author: '', author_id: '', date: nil, mail: '', contents: '', thre: nil)
24
+ def initialize(res_num, author: '', author_id: '', date: nil, mail: '', contents: '')
28
25
  @res_num = res_num
29
26
  @author = author
30
27
  @author_id = author_id
31
28
  @date = date
32
29
  @mail = mail
33
30
  @contents = contents
34
- @thre = thre
35
31
  end
36
32
 
37
33
  # Datの1行から各項目を分離して、Resオブジェクトを返す
38
34
  # @param [Fixnum] res_num レス番号
39
35
  # @param [String] contents datのデータ1行
40
36
  # @return [Res] 新規Resオブジェクト
41
- # @raise [KakoLogException] 過去ログ情報をパースしようとした際に発生
42
- def self.parse(res_num, contents, thre=nil)
43
- unless contents.strip == KAKO_LOG_INFO
44
- hash = parse_dat(contents)
45
- hash[:thre] = thre if thre
46
- return self.new(res_num, hash)
47
- else
48
- raise KakoLogException
49
- end
37
+ # @raise [KakoLogError] 過去ログ情報をパースしようとした際に発生
38
+ def self.parse(res_num, contents)
39
+ raise KakoLogError if contents.strip == KAKO_LOG_INFO
40
+ hash = parse_dat(contents)
41
+ self.new(res_num, hash)
50
42
  end
51
43
 
52
44
  # アンカーを抽出する 荒らしの場合は空配列を返す
53
45
  # @return [Array<Fixnum>] 昇順ソート済みアンカー、荒らしの場合は空配列
54
46
  def anchors
55
47
  arashi_removal_regex = /(?:\d{1,4}(?:\]*>)?(?:>|\[>,+-\]){1,2}){9}/
56
- unless self.contents =~ arashi_removal_regex
57
- splitter_regex = '[,、,  ]'
58
- digit_regex = '(?:\d|[0-9])+'
59
- hyphen_regex = '[−ーー\-〜~〜]'
60
- extracted = self.contents.scan /&gt;((?:#{digit_regex}(?:#{splitter_regex}|#{hyphen_regex})*)+)/
61
- anchors = extracted.flatten.to_s.gsub(/[\"\[\]]/,'').split(/#{splitter_regex}/)
62
- anchors.delete('')
63
- anchors.map! do |a|
64
- if a =~ /(#{digit_regex})#{hyphen_regex}(#{digit_regex})/
65
- (Range.new parseInt($1), parseInt($2)).to_a
66
- else
67
- parseInt(a)
68
- end
48
+ return [] if self.contents =~ arashi_removal_regex
49
+ splitter_regex = '[,、,  ]'
50
+ digit_regex = '(?:\d|[0-9])+'
51
+ hyphen_regex = '[−ーー\-〜~〜]'
52
+ extracted = self.contents.scan /&gt;((?:#{digit_regex}(?:#{splitter_regex}|#{hyphen_regex})*)+)/
53
+ anchors = extracted.flatten.to_s.gsub(/[\"\[\]]/, '').split(/#{splitter_regex}/)
54
+ anchors.delete('')
55
+ anchors.map! do |a|
56
+ if a =~ /(#{digit_regex})#{hyphen_regex}(#{digit_regex})/
57
+ (Range.new parseInt($1), parseInt($2)).to_a
58
+ else
59
+ parseInt(a)
69
60
  end
70
- anchors.flatten.uniq.sort
71
- else
72
- []
73
61
  end
62
+ anchors.flatten.uniq.sort
74
63
  end
75
64
 
76
- # 自レスへのアンカーが書き込まれているレス番号を返す
77
- # @return [Array<Fixnum>] レス番号
78
- def received_anchors
79
- thre = get_thre
80
- received_anchors = thre.received_anchors
81
- received_anchors.fetch(@res_num, [])
65
+ # あぼーんレスか否か
66
+ # @return [Boolean] あぼーんならtrue
67
+ def abone?
68
+ @date == 'あぼーん'
82
69
  end
83
70
 
84
- private
85
- # スレッドを取得する
86
- # @return [Thre] スレッド
87
- def get_thre
88
- if @thre
89
- @thre
90
- else
91
- raise NoThreGivenException
92
- end
71
+ # レスの内容をテキスト情報で得る。&nbsp, &lt, &gt, <br>はそれぞれ「 」、「<」、「>」、「(改行)」に置換される。
72
+ # @return [String] テキスト情報でのレスの内容
73
+ def contents_text
74
+ require 'htmlentities'
75
+ anchor_regex = /<a href="\.\.\/test\/read.cgi\/.+\/\d{10}\/\d{1,4}" target="_blank">(>>\d{1,4})<\/a>/
76
+ @htmlentities ||= HTMLEntities.new
77
+ @htmlentities.decode(@contents).gsub('<br>', "\n").gsub(/<\/?b>/, '').gsub(anchor_regex, '\1')
93
78
  end
94
79
 
80
+ # HTMLタグを取り除いた投稿者名
81
+ # @return <String> HTMLタグを取り除いた投稿者名
82
+ def res_author_text
83
+ require 'htmlentities'
84
+ @htmlentities ||= HTMLEntities.new
85
+ @htmlentities.decode(@author).gsub('<br>', "\n").gsub(/<\/?b>/, '')
86
+ end
87
+
88
+ private
89
+
95
90
  # 全角数字をFixnumへ変換する
96
91
  # @param [String] strnum 全角数字
97
92
  # @return [Fixnum] 数字
@@ -101,9 +96,9 @@ module Simple2ch
101
96
 
102
97
  # Datの1行から各項目を分離して、Resオブジェクトを返すメソッドの実体
103
98
  # @param [String] dat datのデータ1行
104
- # @raise [DatParseException] Datのパースに失敗したときに発生
99
+ # @raise [DatParseError] Datのパースに失敗したときに発生
105
100
  def self.parse_dat(dat)
106
- split_date_and_id_regex = /(?<time>^\d{4}\/\d{2}\/\d{2}\(.\) ?\d{2}:\d{2}:\d{2}(\.\d{2})?)(?: ID:(?<author_id>\S+)$){0,1}/
101
+ split_date_and_id_regex = /(?<time>^\d{4}\/\d{2}\/\d{2}\(.\) ?\d{2}:\d{2}:\d{2}(\.\d{2,3})?)(?: ID:(?<author_id>(\S|.)+)$)?/
107
102
  ret = {}
108
103
  split = dat.split('<>')
109
104
  ret[:author] = split[0]
@@ -116,19 +111,25 @@ module Simple2ch
116
111
  ret[:author_id] = $~[:author_id]
117
112
  else
118
113
  if dat.index 'あぼーん'
119
- {
120
- author: 'あぼーん',
121
- mail: 'あぼーん',
122
- contents: 'あぼーん',
123
- date: 'あぼーん',
124
- author_id: 'あぼーん',
125
- }
114
+ return a_bone_data
115
+ elsif dat.index 'Over 1000 Thread'
116
+ # do nothing
126
117
  else
127
- raise DatParseException, "Data didn't match regex. Data:#{date_and_author_id}"
118
+ raise DatParseError, "Data didn't match regex. Data:#{date_and_author_id}"
128
119
  end
129
120
  end
130
121
 
131
122
  ret
132
123
  end
133
124
  end
125
+
126
+ def a_bone_data
127
+ {
128
+ author: 'あぼーん',
129
+ mail: 'あぼーん',
130
+ contents: 'あぼーん',
131
+ date: 'あぼーん',
132
+ author_id: 'あぼーん',
133
+ }
134
+ end
134
135
  end
@@ -0,0 +1,9 @@
1
+ module Simple2ch
2
+ class Simple2chError < StandardError; end
3
+ class NotA2chUrlError < Simple2chError; end
4
+ class NotA2chBoardUrlError < NotA2chUrlError; end
5
+ class NotA2chThreadUrlError < NotA2chUrlError; end
6
+ class KakoLogError < Simple2chError; end
7
+ class DatParseError < Simple2chError; end
8
+ class NoThreGivenError < Simple2chError; end
9
+ end
@@ -0,0 +1,113 @@
1
+ module Simple2ch
2
+ class Thread
3
+ # @return [String] スレッドキー(Unix time)
4
+ attr_reader :thread_key
5
+ # @return [Bbs2chUrlValidator::UrlInfo] URL
6
+ attr_reader :url
7
+
8
+ # @param [Bbs2chUrlValidator::UrlInfo] URL
9
+ # @param [String] title スレッド名
10
+ def initialize(url, title: nil)
11
+ @title = title
12
+ @url = if url.instance_of?(Bbs2chUrlValidator::UrlInfo)
13
+ url
14
+ else
15
+ Bbs2chUrlValidator::URL.parse(url)
16
+ end
17
+ raise NotA2chThreadUrlError.new "url: #{url}, title: #{title}" if !@url || @url.thread_key.nil?
18
+ end
19
+
20
+ # 板オブジェクトとsubject.txtの1行データを渡すとスレオブジェクトを返す
21
+ # @param [Bbs2chUrlValidator::UrlInfo] board_url スレッドが属する板情報
22
+ # @param [String] thread_data 0000000000.dat<>スレッドタイトル (レス数)
23
+ # @return [Simple2ch::Thread] スレ
24
+ def self.parse(board_url, thread_data)
25
+ thread_data.match(/(\d{10})\.dat<>(.+) \((\d+)\)/) do |m|
26
+ thread_key = m[1]
27
+ title = m[2].force_encoding('utf-8').chomp
28
+ thread_url = Simple2ch.generate_url(:thread, board_url, thread_key: thread_key)
29
+ self.new(thread_url, title: title)
30
+ end
31
+ end
32
+
33
+ # Datを解析して、レスを返す
34
+ # @param [Array<Fixnum>,Fixnum] num_of_responses 取得したいレス番号
35
+ def responses(num_of_responses = nil)
36
+ fetch_dat if @responses.nil?
37
+ case num_of_responses
38
+ when Array
39
+ raise 'Blank array was given.' if num_of_responses.empty?
40
+ @responses.find_all { |r| num_of_responses.index(r.res_num) }
41
+ when Fixnum
42
+ @responses.find { |r| r.res_num == num_of_responses }
43
+ when NilClass
44
+ @responses
45
+ end
46
+ end
47
+ alias response responses
48
+
49
+ # 過去ログかどうかを返す
50
+ # @return [Boolean] 過去ログか否か
51
+ def kako_log?
52
+ fetch_dat if @is_kako_log.nil?
53
+ @is_kako_log
54
+ end
55
+
56
+ # 全てのレスに対し、あるレスへのアンカーが書き込まれているレス番号のハッシュを返す
57
+ # @return [Hash]{ res_num<Fixnum> => res_nums<Array<Fixnum>> } レス番号のハッシュ
58
+ def received_anchors
59
+ @received_anchors ||= calc_received_anchors
60
+ end
61
+
62
+ # 2chタイプ名の取得
63
+ # @return [Symbol] 2chタイプ名(:sc, :open)
64
+ def type_of_2ch
65
+ Simple2ch.type_of_2ch(@url.to_s)
66
+ end
67
+
68
+ ## スレのURLを返す
69
+ ## @return [String] スレのURL
70
+ #def url(board: nil, thread_key: nil, force_refresh: false)
71
+ # if @url && !force_refresh
72
+ # @url
73
+ # else
74
+ # if board
75
+ # parsed_url = Bbs2chUrlValidator::URL.parse(board.url.built_url)
76
+ # Simple2ch.generate_url(:thread, parsed_url, thread_key: thread_key)
77
+ # else
78
+ # fail "Not implemented."
79
+ # end
80
+ # end
81
+ #end
82
+
83
+ # タイトルを返す
84
+ # @return [String] スレッドの名前
85
+ def title
86
+ fetch_dat unless @title
87
+ @title
88
+ end
89
+
90
+ private
91
+
92
+ # 全てのレスに対し、あるレスへのアンカーが書き込まれているレス番号のハッシュを返す
93
+ # @return [Hash]{ res_num<Fixnum> => res_nums<Array<Fixnum>> } レス番号のハッシュ
94
+ def calc_received_anchors
95
+ ret = {}
96
+ responses.each do |res|
97
+ res.anchors.each do |anchor|
98
+ ret.store(anchor, ret.fetch(anchor, []).push(res.res_num))
99
+ end
100
+ end
101
+ ret
102
+ end
103
+
104
+ # Datを取ってきてレスと過去ログかどうかを返す
105
+ # @return [Boolean] is_kako_log 過去ログか否か
106
+ def fetch_dat
107
+ dat = Dat.new(self)
108
+ @responses = dat.responses
109
+ @title = dat.title
110
+ @is_kako_log = dat.kako_log?
111
+ end
112
+ end
113
+ end