simple2ch 0.1.8 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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