tsumanne 0.0.0 → 0.0.2

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.
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