tsumanne 0.0.0 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cebc96065c92a00109bc21cce1738bbe6d0d9576abeca0b7ee1915cce48da1c4
4
- data.tar.gz: ceab470a37553e4a9811047a4c29deaa048deffb621ae592c4976199067fb306
3
+ metadata.gz: 752ff6fe9658226339a08a115d6cce82cd8923b31a04908f81828a8f29b7cbd2
4
+ data.tar.gz: 2b5c2b610ff91d998c1dc21f991d8896bdf55d303c006c542a21ef5e0da3f3d2
5
5
  SHA512:
6
- metadata.gz: 288fc7f85c1999d509dea4c74797aaaa968caf340fbc657de50ab69b6708c9d30a7b3bc2f2a6e86ac5a19b40ac5ea4b9f5d4dc2bdcc93849856a0d5e0c827a32
7
- data.tar.gz: aca354d1cef58425efc27c76d0bb839a12441cb8f93b345de0ee5e6c725f6f1a642cf2a16986a6fca18055117d8360ab5112df0538a95088c0745f5091dfbce4
6
+ metadata.gz: 1fccf450ca71ee827218be4b2d4fd3633bd3e7183eb0847dedc015f293d303f2614a5c27bfa8351868b6105fbe381de7dc22f9bad591ad3ff23d1eaa6b89d0c7
7
+ data.tar.gz: 9a12be921f3d1d4acfe4b3253035fed4072d9c3ca9983e70e1399bb6b594dccb592590b36920a1dca900f63a7bb7031b720ec9a01d4fbf973359ad6905f9e059
data/README.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Tsumanne
2
2
 
3
+ [![Rake]](https://github.com/eggplants/tsumanne/actions/workflows/rake.yml)
4
+ [![Release Gem]](https://github.com/eggplants/tsumanne/actions/workflows/release.yml)
5
+ [![Gem Version]](https://badge.fury.io/rb/tsumanne)
6
+ [![Maintainability]](https://codeclimate.com/github/eggplants/tsumanne/maintainability)
7
+ [![Test Coverage]](https://codeclimate.com/github/eggplants/tsumanne/test_coverage)
8
+
9
+ [Rake]: <https://github.com/eggplants/tsumanne/actions/workflows/rake.yml/badge.svg>
10
+ [Release Gem]: <https://github.com/eggplants/tsumanne/actions/workflows/release.yml/badge.svg>
11
+ [Gem Version]: <https://badge.fury.io/rb/tsumanne.svg>
12
+ [Maintainability]: <https://api.codeclimate.com/v1/badges/673df4b2c0e15b80f06c/maintainability>
13
+ [Test Coverage]: <https://api.codeclimate.com/v1/badges/673df4b2c0e15b80f06c/test_coverage>
14
+
3
15
  API Wrapper for tsumanne.net
4
16
 
5
17
  ## Installation
@@ -24,7 +36,7 @@ rake
24
36
 
25
37
  ## Contributing
26
38
 
27
- Bug reports and pull requests are welcome on GitHub at https://github.com/eggplants/tsumanne.
39
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/eggplants/tsumanne>.
28
40
 
29
41
  ## License
30
42
 
data/Rakefile CHANGED
@@ -1,25 +1,37 @@
1
+ # typed: ignore
2
+
1
3
  # frozen_string_literal: true
2
4
 
3
5
  require "bundler/gem_tasks"
4
6
  require "rspec/core/rake_task"
7
+ require "rubocop/rake_task"
8
+ # require "sorbet-runtime"
5
9
 
6
- RSpec::Core::RakeTask.new(:spec)
10
+ # T.bind(self, T.all(Rake::DSL, Object))
7
11
 
8
- require "rubocop/rake_task"
12
+ RSpec::Core::RakeTask.new(:spec)
9
13
 
10
14
  RuboCop::RakeTask.new do |task|
11
15
  task.requires << 'rubocop-sorbet'
12
16
  end
13
17
 
18
+ namespace :mdl do
19
+ desc 'Format markdown files with markdownlint'
20
+ task :format do
21
+ system("bundle exec mdl #{Dir.glob("**/*.md") * ?\s}")
22
+ puts "#{$?.success? ? :no : :some} errors found"
23
+ end
24
+ end
25
+
14
26
  namespace :sorbet do
15
27
  desc 'Type-checking with sorbet'
16
28
  task :check do
17
- system("bundle exec srb tc -v")
29
+ system("bundle exec srb tc")
18
30
  end
19
31
 
20
32
  desc 'Type-checking and auto-fix correctable errors with sorbet'
21
33
  task :autocorrect do
22
- system("bundle exec srb tc --autocorrect -v")
34
+ system("bundle exec srb tc --autocorrect")
23
35
  end
24
36
  end
25
37
 
@@ -36,10 +48,12 @@ namespace :tapioca do
36
48
  end
37
49
 
38
50
  task :default do
39
- %i[spec rubocop:autocorrect_all sorbet:autocorrect tapioca:check_shims].each do
40
- puts ".。*゚+.*.。  #{?\s * _1.size}  ゚+..。*゚+"
41
- puts ".。*゚+.*.。  #{_1}  ゚+..。*゚+"
42
- puts ".。*゚+.*.。  #{?\s * _1.size}  ゚+..。*゚+"
51
+ %i[mdl:format rubocop:autocorrect_all sorbet:autocorrect tapioca:check_shims spec].each do
52
+ puts <<~EOS
53
+ .。*゚+.*.。  #{padding = ?= * _1.size}  ゚+..。*゚+
54
+ .。*゚+.*.。  #{_1}  ゚+..。*゚+
55
+ .。*゚+.*.。  #{padding}  ゚+..。*゚+
56
+ EOS
43
57
  Rake::Task[_1].execute
44
58
  end
45
59
  end
data/exe/tsumanne ADDED
@@ -0,0 +1,8 @@
1
+ #! /usr/bin/env ruby
2
+ # typed: strict
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require_relative "../lib/tsumanne/cli"
7
+
8
+ Tsumanne::CLI.start(ARGV)
@@ -0,0 +1,124 @@
1
+ # typed: strict
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "net/http"
6
+ require "uri"
7
+ require "json"
8
+ require "zlib"
9
+
10
+ require "mhtml"
11
+ require "sorbet-runtime"
12
+
13
+ require_relative "models/get_threads"
14
+ require_relative "models/search_thread_from_uri"
15
+ require_relative "models/search_indexes"
16
+ require_relative "models/register_thread"
17
+
18
+ # API module for tsumanne.net includes knowledge as const.
19
+ module Tsumanne
20
+ class API
21
+ extend T::Sig
22
+
23
+ sig{ returns(String) }
24
+ attr_reader :board_id
25
+
26
+ sig { params(board_id: Symbol).void }
27
+ def initialize(board_id:)
28
+ @board_id = T.let(T.must(BOARD_IDS[board_id]), String)
29
+ end
30
+
31
+ sig { params(index: String, page: Integer).returns(GetThreadsResponse) }
32
+ def get_threads(index: "all", page: 1)
33
+ # https://tsumanne.net/si/all/1
34
+ # https://tsumanne.net/si/hoge/1
35
+ GetThreadsResponse.from_hash(fetch_json(paths: [index, page.to_s]))
36
+ end
37
+
38
+ sig { params(thread_id: String).returns(Mhtml::RootDocument) }
39
+ def get_thread_mht(thread_id)
40
+ # https://tsumanne.net/si/mht.php?id=129691
41
+ res = fetch_json(paths: ["mht.php"], query: { id: thread_id }, method: :get_response)
42
+ mht_gz = fetch(paths: [T.let(res["path"], String)])
43
+ # https://ksef-3go.hatenadiary.org/entry/20070924/1190563143
44
+ # https://docs.ruby-lang.org/ja/latest/method/Zlib=3a=3aInflate/s/new.html
45
+ zstream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
46
+ buf = zstream.inflate(mht_gz)
47
+ zstream.finish
48
+ zstream.close
49
+ Mhtml::RootDocument.new(buf)
50
+ end
51
+
52
+ sig { params(thread_path: String).returns(T.nilable(Mhtml::RootDocument)) }
53
+ def get_thread_from_path(thread_path)
54
+ # https://tsumanne.net/si/data/2023/08/30/8883354/
55
+ match_data = %r{^\d{4}/\d{2}/\d{2}/(?<thread_id>\d+)$}.match(thread_path)
56
+ return if match_data.nil?
57
+ get_thread_mht(T.must(match_data[:thread_id]))
58
+ end
59
+
60
+ sig { params(uri: URI).returns(SearchThreadFromUriResponse) }
61
+ def search_thread_from_uri(uri)
62
+ # https://tsumanne.net/si/indexes.php?format=json&w=...&sbmt=URL
63
+ # https://tsumanne.net/si/indexes.php?format=json&w=https%3A%2F%2Fimg.2chan.net%2Fb%2Fres%2F86279902.htm&sbmt=URL
64
+ SearchThreadFromUriResponse.from_hash(
65
+ fetch_json(paths: ["indexes.php"], query: { w: uri, sbmt: :URL })
66
+ )
67
+ end
68
+
69
+ sig { params(keyword: T.nilable(String), order: Symbol, page: Integer).returns(SearchIndexesResponse) }
70
+ def search_indexes(keyword: nil, order: :newer, page: 1)
71
+ # https://tsumanne.net/si/indexes.php?format=json&w=&sbmt=%E2%86%93%E6%96%B0
72
+ SearchIndexesResponse.from_hash(
73
+ fetch_json(paths: ["indexes.php"], query: { w: keyword, sbmt: INDEXES_ORDERS[order], p: page })
74
+ )
75
+ end
76
+
77
+ sig { params(uri: URI, indexes: T.nilable(T::Array[String])).returns(RegisterThreadResponse) }
78
+ def register_thread(uri, indexes: nil)
79
+ # post, https://tsumanne.net/si/input.php?format=json&url=...&category=...
80
+ RegisterThreadResponse.from_hash(
81
+ fetch_json(paths: ["input.php?format=json"], query: { url: uri, category: (indexes || []).join(",") }, method: :post)
82
+ )
83
+ end
84
+
85
+ private
86
+
87
+ sig do
88
+ params(paths: T.nilable(T::Array[String]),
89
+ query: T.nilable(T::Hash[Symbol, T.any(Integer, String, Symbol)]),
90
+ method: Symbol).returns(T.nilable(String))
91
+ end
92
+ def fetch(paths: nil, query: nil, method: :get)
93
+ uri = join_paths(BASE_URL, [@board_id] + (paths || []))
94
+ query = URI.encode_www_form(query || {})
95
+
96
+ case method
97
+ when :get
98
+ uri.query = query
99
+ Net::HTTP.get(uri)
100
+ when :get_response
101
+ uri.query = query
102
+ Net::HTTP.get_response(uri).body
103
+ when :post
104
+ Net::HTTP.post(uri, query).body
105
+ end
106
+ end
107
+
108
+ sig do
109
+ params(paths: T.nilable(T::Array[String]),
110
+ query: T.nilable(T::Hash[Symbol, T.any(Integer, String, Symbol)]),
111
+ method: Symbol).returns(T::Hash[String, T.untyped])
112
+ end
113
+ def fetch_json(paths: nil, query: nil, method: :get)
114
+ query ||= {}
115
+ json = fetch(paths:, query: query.merge({ format: :json }), method:)
116
+ JSON.parse(T.must(json))
117
+ end
118
+
119
+ sig { params(base: String, paths: T::Array[String]).returns(URI::Generic) }
120
+ def join_paths(base, *paths)
121
+ URI.parse(T.must(([base] + paths).reduce { File.join(_1, _2) }))
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module Tsumanne
6
+ class CLI
7
+ extend T::Sig
8
+ sig { params(args: BasicObject).returns(NilClass) }
9
+ def self.start(args)
10
+ p args
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+
3
+ require "sorbet-runtime"
4
+
5
+ class GetThreadsResponse < T::Struct
6
+ extend T::Sig
7
+
8
+ class Log < T::Struct
9
+ extend T::Sig
10
+
11
+ const :id, Integer
12
+ const :url, String # URI
13
+ const :date, String # Time
14
+ const :close, T::Boolean
15
+ const :res, Integer
16
+ const :files, Integer
17
+ const :access, Integer
18
+ const :public, T::Boolean
19
+ const :text, String
20
+ const :thumb, String
21
+ const :del, Integer
22
+ const :atid, T::Boolean
23
+ const :last, String # Time
24
+ const :path, String
25
+ const :category, T::Array[String]
26
+ end
27
+
28
+ const :success, T::Boolean
29
+ const :messages, T::Array[String]
30
+ const :lastpage, Integer
31
+ const :count, Integer
32
+ const :cid, Integer
33
+ const :logs, T::Array[Log]
34
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ require "sorbet-runtime"
4
+
5
+ class RegisterThreadResponse < T::Struct
6
+ extend T::Sig
7
+
8
+ class Tags < T::Struct
9
+ extend T::Sig
10
+
11
+ const :success, T::Boolean
12
+ const :messages, T::Array[String]
13
+ end
14
+
15
+ const :success, T::Boolean
16
+ const :messages, T::Array[String]
17
+ const :tags, Tags
18
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ require "sorbet-runtime"
4
+
5
+ class SearchIndexesResponse < T::Struct
6
+ extend T::Sig
7
+
8
+ class Tag < T::Struct
9
+ extend T::Sig
10
+
11
+ const :tag, String
12
+ end
13
+
14
+ const :success, T::Boolean
15
+ const :messages, T::Array[String]
16
+ const :count, Integer
17
+ const :tags, T::Array[Tag]
18
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+
3
+ require "sorbet-runtime"
4
+
5
+ class SearchThreadFromUriResponse < T::Struct
6
+ extend T::Sig
7
+
8
+ const :success, T::Boolean
9
+ const :messages, T::Array[String]
10
+ const :path, String
11
+ end
@@ -3,5 +3,5 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  module Tsumanne
6
- VERSION = "0.0.0"
6
+ VERSION = "0.0.2"
7
7
  end
data/lib/tsumanne.rb CHANGED
@@ -2,14 +2,9 @@
2
2
 
3
3
  # frozen_string_literal: true
4
4
 
5
- require "net/http"
6
- require "uri"
7
- require "json"
8
- require "zlib"
9
-
10
- require "mhtml"
11
5
  require "sorbet-runtime"
12
6
 
7
+ require_relative "tsumanne/api"
13
8
  require_relative "tsumanne/version"
14
9
 
15
10
  # API module for tsumanne.net includes knowledge as const.
@@ -25,104 +20,4 @@ module Tsumanne
25
20
  INDEXES_ORDERS = T.let({ hira: "↓あ", newer: "↓新" }.freeze, T::Hash[Symbol, String])
26
21
 
27
22
  class Error < StandardError; end
28
-
29
- # API to get information fron tsumanne.net
30
- class API
31
- extend T::Sig
32
-
33
- sig{ returns(String) }
34
- attr_reader :board_id
35
-
36
- sig { params(board_id: Symbol).void }
37
- def initialize(board_id:)
38
- @board_id = T.let(T.must(BOARD_IDS[board_id]), String)
39
- end
40
-
41
- sig { params(index: String, page: Integer).returns(T::Hash[String, T.untyped]) }
42
- def get_threads(index: "all", page: 1)
43
- # https://tsumanne.net/si/all/1
44
- # https://tsumanne.net/si/hoge/1
45
- fetch_json(paths: [index, page.to_s])
46
- end
47
-
48
- sig { params(thread_id: String).returns(Mhtml::RootDocument) }
49
- def get_thread_mht(thread_id)
50
- # https://tsumanne.net/si/mht.php?id=129691
51
- res = fetch_json(paths: ["mht.php"], query: { id: thread_id }, method: :get_response)
52
- mht_gz = fetch(paths: [T.let(res["path"], String)])
53
- # https://ksef-3go.hatenadiary.org/entry/20070924/1190563143
54
- # https://docs.ruby-lang.org/ja/latest/method/Zlib=3a=3aInflate/s/new.html
55
- zstream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
56
- buf = zstream.inflate(mht_gz)
57
- zstream.finish
58
- zstream.close
59
- Mhtml::RootDocument.new(buf)
60
- end
61
-
62
- sig { params(thread_path: String).returns(T.nilable(Mhtml::RootDocument)) }
63
- def get_thread_from_path(thread_path)
64
- # https://tsumanne.net/si/data/2023/08/30/8883354/
65
- match_data = %r{^\d{4}/\d{2}/\d{2}/(?<thread_id>\d+)$}.match(thread_path)
66
- return if match_data.nil?
67
- get_thread_mht(T.must(match_data[:thread_id]))
68
- end
69
-
70
- sig { params(uri: URI).returns(T::Hash[String, T.untyped]) }
71
- def search_thread_from_uri(uri)
72
- # https://tsumanne.net/si/indexes.php?format=json&w=&sbmt=URL
73
- fetch_json(paths: ["indexes.php"], query: { w: uri, sbmt: :URL })
74
- end
75
-
76
- sig { params(keyword: T.nilable(String), order: Symbol, page: Integer).returns(T::Hash[String, T.untyped]) }
77
- def search_indexes(keyword: nil, order: :newer, page: 1)
78
- # https://tsumanne.net/si/indexes.php?format=json&w=&sbmt=URL
79
- # https://tsumanne.net/si/indexes.php?format=json&w=&sbmt=%E2%86%93%E6%96%B0
80
- fetch_json(paths: ["indexes.php"], query: { w: keyword, sbmt: INDEXES_ORDERS[order], p: page })
81
- end
82
-
83
- sig { params(uri: URI, indexes: T.nilable(T::Array[String])).returns(T::Hash[String, T.untyped]) }
84
- def register_thread(uri, indexes: nil)
85
- # post, https://tsumanne.net/si/input.php?format=json&url=...&category=...
86
- fetch_json(paths: ["input.php?format=json"], query: { url: uri, category: (indexes || []).join(",") }, method: :post)
87
- end
88
-
89
- private
90
-
91
- sig do
92
- params(paths: T.nilable(T::Array[String]),
93
- query: T.nilable(T::Hash[Symbol, T.any(Integer, String, Symbol)]),
94
- method: Symbol).returns(T.nilable(String))
95
- end
96
- def fetch(paths: nil, query: nil, method: :get)
97
- uri = join_paths(BASE_URL, [@board_id] + (paths || []))
98
- query = URI.encode_www_form(query || {})
99
-
100
- case method
101
- when :get
102
- uri.query = query
103
- Net::HTTP.get(uri)
104
- when :get_response
105
- uri.query = query
106
- Net::HTTP.get_response(uri).body
107
- when :post
108
- Net::HTTP.post(uri, query).body
109
- end
110
- end
111
-
112
- sig do
113
- params(paths: T.nilable(T::Array[String]),
114
- query: T.nilable(T::Hash[Symbol, T.any(Integer, String, Symbol)]),
115
- method: Symbol).returns(T::Hash[String, T.untyped])
116
- end
117
- def fetch_json(paths: nil, query: nil, method: :get)
118
- query ||= {}
119
- json = fetch(paths:, query: query.merge({ format: :json }), method:)
120
- JSON.parse(T.must(json))
121
- end
122
-
123
- sig { params(base: String, paths: T::Array[String]).returns(URI::Generic) }
124
- def join_paths(base, *paths)
125
- URI.parse(T.must(([base] + paths).reduce { File.join(_1, _2) }))
126
- end
127
- end
128
23
  end
data/sorbet/config CHANGED
@@ -1,4 +1,19 @@
1
1
  --dir
2
2
  .
3
+
4
+ bin/console
5
+ bin/tapioca
6
+ exe/tsumanne
7
+
8
+ Gemfile
9
+ Rakefile
10
+
3
11
  --ignore=tmp/
4
12
  --ignore=vendor/
13
+
14
+ --allowed-extension=.rb
15
+ --allowed-extension=.rbi
16
+ --allowed-extension=.gemspec
17
+ --allowed-extension=.rake
18
+
19
+ --enable-experimental-requires-ancestor
@@ -0,0 +1,8 @@
1
+ # typed: true
2
+
3
+ # DO NOT EDIT MANUALLY
4
+ # This is an autogenerated file for types exported from the `chef-utils` gem.
5
+ # Please instead update this file by running `bin/tapioca gem chef-utils`.
6
+
7
+ # THIS IS AN EMPTY RBI FILE.
8
+ # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem
@@ -0,0 +1,8 @@
1
+ # typed: true
2
+
3
+ # DO NOT EDIT MANUALLY
4
+ # This is an autogenerated file for types exported from the `concurrent-ruby` gem.
5
+ # Please instead update this file by running `bin/tapioca gem concurrent-ruby`.
6
+
7
+ # THIS IS AN EMPTY RBI FILE.
8
+ # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem