jnc_api 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/examples/feed/models.rb +18 -0
- data/examples/feed/tracker.rb +42 -25
- data/lib/jnc_api/version.rb +1 -1
- data/lib/jnc_api.rb +33 -11
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b5c0c7ff7e9d1c813d480ec4259924e06360da877dc5557b0e03a0a7ba8facd
|
4
|
+
data.tar.gz: d8a450bfb8247ed113e864e3bf485946a377e63113e71f4e600904bcfc9332df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fabdcf372e4dff0005238902b7711e2dd28c4c10473af52761e25b37dab99b4a5bc6e8fbbfd892d35e0aeb2d58081b3887deb572b882a56b390f29567eab4c72
|
7
|
+
data.tar.gz: 9e6226781e0f4f21265b5a4161ba7d01e87b841967bb37a3dfab3188321d36af9573dd02bb2cf897d5e462af3930c05ddc59cd5d8ee3c808e13a62094ee5f2a9
|
data/examples/feed/models.rb
CHANGED
@@ -46,8 +46,26 @@ ActiveRecord::Schema.define do
|
|
46
46
|
t.timestamps
|
47
47
|
end
|
48
48
|
end
|
49
|
+
|
50
|
+
unless table_exists?(:manga_parts)
|
51
|
+
create_table :manga_parts, force: :cascade do |t|
|
52
|
+
t.integer :part_id, null: false
|
53
|
+
t.text :uuid
|
54
|
+
t.text :ngtoken
|
55
|
+
t.text :kgtoken
|
56
|
+
t.text :webpub
|
57
|
+
t.timestamps
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
49
61
|
end
|
50
62
|
|
51
63
|
class Part < ActiveRecord::Base
|
64
|
+
has_many :manga_parts
|
52
65
|
serialize :crypt_data, coder: JSON
|
53
66
|
end
|
67
|
+
|
68
|
+
class MangaPart < ActiveRecord::Base
|
69
|
+
belongs_to :part
|
70
|
+
serialize :webpub, coder: JSON
|
71
|
+
end
|
data/examples/feed/tracker.rb
CHANGED
@@ -23,7 +23,7 @@ gemfile do
|
|
23
23
|
gem "activerecord", "~> 7.1", require: "active_record"
|
24
24
|
gem "sqlite3", "~> 1.4", require: "sqlite3"
|
25
25
|
gem "addressable", "~> 2.8", require: "addressable"
|
26
|
-
gem
|
26
|
+
gem "optparse", require: "optparse"
|
27
27
|
|
28
28
|
gem "jnc_api", path: "../..", require: "jnc_api"
|
29
29
|
end
|
@@ -55,18 +55,22 @@ def assign_part_from_json(part, p)
|
|
55
55
|
part.series_type = p["type"]
|
56
56
|
part.legacy_id = p["partLegacyId"]
|
57
57
|
part.title = p["partTitle"]
|
58
|
-
part.updated = p["updated"]
|
59
58
|
part.clear_data = p["clearData"]
|
60
59
|
end
|
61
60
|
|
62
61
|
options = {}
|
63
|
-
OptionParser
|
64
|
-
|
65
|
-
|
62
|
+
OptionParser
|
63
|
+
.new do |opts|
|
64
|
+
opts.on(
|
65
|
+
"--slug=SLUG",
|
66
|
+
"Only download this part as identified by the slug"
|
67
|
+
) { |s| options[:slug] = s }
|
66
68
|
end
|
67
|
-
|
69
|
+
.parse!
|
68
70
|
|
69
|
-
|
71
|
+
me = api.me
|
72
|
+
puts me
|
73
|
+
user_id = JSON.parse(me)["legacyId"]
|
70
74
|
|
71
75
|
if options[:slug]
|
72
76
|
opt_slug = options[:slug]
|
@@ -74,10 +78,11 @@ if options[:slug]
|
|
74
78
|
part = Part.find_or_create_by(slug: opt_slug)
|
75
79
|
|
76
80
|
assign_part_from_json(part, p)
|
81
|
+
part.updated = p["updated"]
|
77
82
|
|
78
83
|
part.save!
|
79
84
|
|
80
|
-
e = api.embed(p["
|
85
|
+
e = api.embed(p["partId"])
|
81
86
|
|
82
87
|
part.data = e
|
83
88
|
part.crypt_data = p["cryptData"]
|
@@ -98,37 +103,49 @@ else
|
|
98
103
|
if part.date_published &&
|
99
104
|
item["date_published"].to_datetime == part.date_published.to_datetime
|
100
105
|
puts "Skipping part #{part.slug} (feed item already seen)"
|
106
|
+
|
101
107
|
next
|
102
108
|
end
|
103
109
|
|
104
|
-
|
110
|
+
puts "sleeping for 5s"
|
111
|
+
sleep 5
|
105
112
|
|
106
113
|
p = JSON.parse(api.parts_data(slug))
|
107
|
-
|
108
114
|
assign_part_from_json(part, p)
|
109
115
|
|
110
|
-
part.image = item["image"]
|
111
|
-
part.date_published = item["date_published"]
|
112
|
-
|
113
|
-
part.save!
|
114
|
-
|
115
|
-
# skip items that are not novels e.g. manga
|
116
|
-
# we currently have no way to decode the manga parts
|
117
|
-
if p["type"] != "NOVEL"
|
118
|
-
puts "Skipping part #{part.slug} (not NOVEL type unsupported)"
|
119
|
-
next
|
120
|
-
end
|
121
|
-
|
122
116
|
# skip items if the part is already up-to-date
|
123
117
|
if part.data && p["updated"].to_datetime == part.updated.to_datetime
|
124
118
|
puts "Skipping part #{part.slug} (feed item updated)"
|
119
|
+
|
125
120
|
next
|
126
121
|
end
|
127
122
|
|
128
|
-
|
123
|
+
# note: we currently have no way to descramble the manga parts
|
124
|
+
if p["type"] == "MANGA"
|
125
|
+
puts "part #{part.slug} is manga, downloading webpub"
|
126
|
+
tokens = JSON.parse(api.manga_parts(part.legacy_id))
|
127
|
+
manga_part = part.manga_parts.find_or_create_by(uuid: tokens["uuid"])
|
128
|
+
manga_part.uuid = tokens["uuid"]
|
129
|
+
manga_part.ngtoken = tokens["ngtoken"]
|
130
|
+
manga_part.kgtoken = tokens["kgtoken"]
|
131
|
+
|
132
|
+
puts tokens
|
133
|
+
puts "sleeping for 30s"
|
134
|
+
sleep 5
|
135
|
+
|
136
|
+
manga_part.webpub = api.webpub(tokens["uuid"], tokens["ngtoken"])
|
137
|
+
manga_part.save!
|
138
|
+
else
|
139
|
+
e = api.embed(p["partLegacyId"])
|
140
|
+
|
141
|
+
part.data = e
|
142
|
+
part.crypt_data = p["cryptData"]
|
143
|
+
end
|
144
|
+
|
145
|
+
part.image = item["image"]
|
146
|
+
part.date_published = item["date_published"]
|
147
|
+
part.updated = p["updated"]
|
129
148
|
|
130
|
-
part.data = e
|
131
|
-
part.crypt_data = p["cryptData"]
|
132
149
|
part.save!
|
133
150
|
|
134
151
|
puts "Updated part #{part.slug}"
|
data/lib/jnc_api/version.rb
CHANGED
data/lib/jnc_api.rb
CHANGED
@@ -22,6 +22,19 @@ require "httparty"
|
|
22
22
|
module JncApi
|
23
23
|
class Error < StandardError
|
24
24
|
end
|
25
|
+
V2_ROUTES = {
|
26
|
+
login: "https://labs.j-novel.club/app/v2/auth/login",
|
27
|
+
logout: "https://labs.j-novel.club/app/v2/auth/logout",
|
28
|
+
series: "https://labs.j-novel.club/app/v2/series",
|
29
|
+
me: "https://labs.j-novel.club/app/v2/me",
|
30
|
+
feed: "https://labs.j-novel.club/feed/user",
|
31
|
+
parts: "https://labs.j-novel.club/app/v2/parts",
|
32
|
+
embed: "https://labs.j-novel.club/embed/v2",
|
33
|
+
completion: "https://labs.j-novel.club/app/v2/me/completion",
|
34
|
+
manga_parts: "https://api.j-novel.club/api/mangaParts",
|
35
|
+
webpub: "https://m11.j-novel.club/nebel/wp"
|
36
|
+
}
|
37
|
+
|
25
38
|
V1_ROUTES = {
|
26
39
|
login: "https://labs.j-novel.club/app/v1/auth/login",
|
27
40
|
logout: "https://labs.j-novel.club/app/v1/auth/logout",
|
@@ -32,13 +45,13 @@ module JncApi
|
|
32
45
|
embed: "https://labs.j-novel.club/embed/",
|
33
46
|
completion: "https://labs.j-novel.club/app/v1/me/completion",
|
34
47
|
manga_parts: "https://api.j-novel.club/api/mangaParts",
|
35
|
-
webpub: "https://m11.j-novel.club/nebel/wp"
|
48
|
+
webpub: "https://m11.j-novel.club/nebel/wp"
|
36
49
|
}
|
37
50
|
|
38
51
|
class Api
|
39
52
|
attr_reader :token
|
40
53
|
|
41
|
-
def self.login(login, password, routes =
|
54
|
+
def self.login(login, password, routes = V2_ROUTES)
|
42
55
|
response =
|
43
56
|
HTTParty.post(
|
44
57
|
routes[:login] + "?format=json",
|
@@ -61,7 +74,14 @@ module JncApi
|
|
61
74
|
def initialize(routes: V1_ROUTES, token: nil)
|
62
75
|
@routes = routes
|
63
76
|
@token = token
|
64
|
-
@default_headers = {
|
77
|
+
@default_headers = {
|
78
|
+
"User-Agent" =>
|
79
|
+
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0",
|
80
|
+
"Accept" =>
|
81
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
82
|
+
"Accept-Language" => "en-US,en;q=0.5",
|
83
|
+
"Content-Type" => "application/json"
|
84
|
+
}
|
65
85
|
@bearer = {"Authorization" => "Bearer #{@token}"}
|
66
86
|
end
|
67
87
|
|
@@ -163,9 +183,10 @@ module JncApi
|
|
163
183
|
# we can't seem to use bearer tokens for the manga endpoints
|
164
184
|
def manga_parts(part_id)
|
165
185
|
response =
|
166
|
-
|
167
|
-
|
168
|
-
|
186
|
+
HTTParty.get(
|
187
|
+
@routes[:manga_parts] + "/#{part_id}/token?access_token=#{@token}",
|
188
|
+
headers: @default_headers
|
189
|
+
)
|
169
190
|
|
170
191
|
if response.success?
|
171
192
|
response.body
|
@@ -178,10 +199,12 @@ module JncApi
|
|
178
199
|
# likely for cheaper static hosting (as long as the locations are
|
179
200
|
# difficult to guess such as uuid)
|
180
201
|
def webpub(uuid, ngtoken)
|
181
|
-
response =
|
182
|
-
|
183
|
-
|
184
|
-
|
202
|
+
response =
|
203
|
+
HTTParty.post(
|
204
|
+
@routes[:webpub] + "/#{uuid}",
|
205
|
+
body: ngtoken,
|
206
|
+
headers: @default_headers
|
207
|
+
)
|
185
208
|
|
186
209
|
if response.success?
|
187
210
|
response.body
|
@@ -189,6 +212,5 @@ module JncApi
|
|
189
212
|
raise Error.new(response.inspect)
|
190
213
|
end
|
191
214
|
end
|
192
|
-
|
193
215
|
end
|
194
216
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jnc_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- parasquid
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-05-30 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: httparty
|
@@ -56,7 +55,6 @@ metadata:
|
|
56
55
|
homepage_uri: https://codeberg.org/parasquid/jnc_api
|
57
56
|
source_code_uri: https://codeberg.org/parasquid/jnc_api
|
58
57
|
changelog_uri: https://codeberg.org/parasquid/jnc_api/src/branch/main/CHANGELOG.md
|
59
|
-
post_install_message:
|
60
58
|
rdoc_options: []
|
61
59
|
require_paths:
|
62
60
|
- lib
|
@@ -71,8 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
69
|
- !ruby/object:Gem::Version
|
72
70
|
version: '0'
|
73
71
|
requirements: []
|
74
|
-
rubygems_version: 3.
|
75
|
-
signing_key:
|
72
|
+
rubygems_version: 3.6.2
|
76
73
|
specification_version: 4
|
77
74
|
summary: Light wrapper for the J-Novel Club API.
|
78
75
|
test_files: []
|