@zonuexe/techbook-mcp 0.1.0 → 0.2.1
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.
- package/.claude/settings.local.json +13 -1
- package/.codex/skills/techbook-mcp-release-prep/SKILL.md +105 -0
- package/.github/workflows/test.yml +36 -0
- package/.oxlintrc.json +12 -0
- package/AGENTS.md +29 -1
- package/CHANGELOG.md +34 -0
- package/deno.json +3 -0
- package/dist/adapters/html/cheerio-parser.d.ts.map +1 -1
- package/dist/adapters/html/cheerio-parser.js.map +1 -1
- package/dist/adapters/publishers/base.d.ts +22 -1
- package/dist/adapters/publishers/base.d.ts.map +1 -1
- package/dist/adapters/publishers/base.js +142 -2
- package/dist/adapters/publishers/base.js.map +1 -1
- package/dist/adapters/publishers/book-tech.d.ts +3 -0
- package/dist/adapters/publishers/book-tech.d.ts.map +1 -0
- package/dist/adapters/publishers/book-tech.js +95 -0
- package/dist/adapters/publishers/book-tech.js.map +1 -0
- package/dist/adapters/publishers/born-digital.d.ts +3 -0
- package/dist/adapters/publishers/born-digital.d.ts.map +1 -0
- package/dist/adapters/publishers/born-digital.js +122 -0
- package/dist/adapters/publishers/born-digital.js.map +1 -0
- package/dist/adapters/publishers/coronasha.d.ts +3 -0
- package/dist/adapters/publishers/coronasha.d.ts.map +1 -0
- package/dist/adapters/publishers/coronasha.js +119 -0
- package/dist/adapters/publishers/coronasha.js.map +1 -0
- package/dist/adapters/publishers/impress.d.ts +3 -0
- package/dist/adapters/publishers/impress.d.ts.map +1 -0
- package/dist/adapters/publishers/impress.js +92 -0
- package/dist/adapters/publishers/impress.js.map +1 -0
- package/dist/adapters/publishers/manatee.d.ts +3 -0
- package/dist/adapters/publishers/manatee.d.ts.map +1 -0
- package/dist/adapters/publishers/manatee.js +93 -0
- package/dist/adapters/publishers/manatee.js.map +1 -0
- package/dist/adapters/publishers/maruzen-publishing.d.ts +3 -0
- package/dist/adapters/publishers/maruzen-publishing.d.ts.map +1 -0
- package/dist/adapters/publishers/maruzen-publishing.js +108 -0
- package/dist/adapters/publishers/maruzen-publishing.js.map +1 -0
- package/dist/adapters/publishers/optronics.d.ts +3 -0
- package/dist/adapters/publishers/optronics.d.ts.map +1 -0
- package/dist/adapters/publishers/optronics.js +92 -0
- package/dist/adapters/publishers/optronics.js.map +1 -0
- package/dist/adapters/publishers/oreilly-japan.d.ts +3 -0
- package/dist/adapters/publishers/oreilly-japan.d.ts.map +1 -0
- package/dist/adapters/publishers/oreilly-japan.js +112 -0
- package/dist/adapters/publishers/oreilly-japan.js.map +1 -0
- package/dist/adapters/publishers/peaks.d.ts +3 -0
- package/dist/adapters/publishers/peaks.d.ts.map +1 -0
- package/dist/adapters/publishers/peaks.js +80 -0
- package/dist/adapters/publishers/peaks.js.map +1 -0
- package/dist/adapters/publishers/personal-media.d.ts +3 -0
- package/dist/adapters/publishers/personal-media.d.ts.map +1 -0
- package/dist/adapters/publishers/personal-media.js +144 -0
- package/dist/adapters/publishers/personal-media.js.map +1 -0
- package/dist/adapters/publishers/registry.d.ts.map +1 -1
- package/dist/adapters/publishers/registry.js +26 -0
- package/dist/adapters/publishers/registry.js.map +1 -1
- package/dist/adapters/publishers/rutles.d.ts +3 -0
- package/dist/adapters/publishers/rutles.d.ts.map +1 -0
- package/dist/adapters/publishers/rutles.js +128 -0
- package/dist/adapters/publishers/rutles.js.map +1 -0
- package/dist/adapters/publishers/saiensu.d.ts +3 -0
- package/dist/adapters/publishers/saiensu.d.ts.map +1 -0
- package/dist/adapters/publishers/saiensu.js +109 -0
- package/dist/adapters/publishers/saiensu.js.map +1 -0
- package/dist/adapters/publishers/seshop.d.ts +3 -0
- package/dist/adapters/publishers/seshop.d.ts.map +1 -0
- package/dist/adapters/publishers/seshop.js +98 -0
- package/dist/adapters/publishers/seshop.js.map +1 -0
- package/dist/application/get-book-detail.d.ts.map +1 -1
- package/dist/application/get-book-detail.js +5 -0
- package/dist/application/get-book-detail.js.map +1 -1
- package/dist/application/search-books.d.ts.map +1 -1
- package/dist/application/search-books.js +7 -1
- package/dist/application/search-books.js.map +1 -1
- package/dist/domain/book.d.ts +5 -4
- package/dist/domain/book.d.ts.map +1 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/server.js.map +1 -1
- package/flake.nix +1 -1
- package/package.json +7 -5
- package/src/adapters/html/cheerio-parser.ts +4 -3
- package/src/adapters/publishers/base.ts +150 -0
- package/src/adapters/publishers/born-digital.ts +2 -17
- package/src/adapters/publishers/impress.ts +103 -0
- package/src/adapters/publishers/manatee.ts +2 -1
- package/src/adapters/publishers/maruzen-publishing.ts +4 -16
- package/src/adapters/publishers/oreilly-japan.ts +5 -10
- package/src/adapters/publishers/registry.ts +2 -0
- package/src/adapters/publishers/rutles.ts +1 -13
- package/src/adapters/publishers/saiensu.ts +5 -18
- package/src/adapters/publishers/seshop.ts +1 -1
- package/src/adapters/publishers/tatsu-zine.ts +61 -36
- package/src/application/get-book-detail.ts +7 -0
- package/src/application/search-books.ts +6 -1
- package/src/main.ts +1 -0
- package/tests/fixtures/impress-detail-epub.html +746 -0
- package/tests/fixtures/impress-detail-social.html +689 -0
- package/tests/fixtures/tatsu-zine-search.html +29 -13
- package/tests/unit/adapters/base.test.ts +441 -0
- package/tests/unit/adapters/publishers/book-tech.test.ts +18 -15
- package/tests/unit/adapters/publishers/born-digital.test.ts +18 -15
- package/tests/unit/adapters/publishers/coronasha.test.ts +26 -20
- package/tests/unit/adapters/publishers/gihyo.test.ts +21 -19
- package/tests/unit/adapters/publishers/impress.test.ts +129 -0
- package/tests/unit/adapters/publishers/lambdanote.test.ts +12 -11
- package/tests/unit/adapters/publishers/manatee.test.ts +14 -12
- package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +19 -17
- package/tests/unit/adapters/publishers/optronics.test.ts +19 -16
- package/tests/unit/adapters/publishers/oreilly-japan.test.ts +19 -16
- package/tests/unit/adapters/publishers/peaks.test.ts +17 -14
- package/tests/unit/adapters/publishers/personal-media.test.ts +18 -15
- package/tests/unit/adapters/publishers/rutles.test.ts +15 -12
- package/tests/unit/adapters/publishers/saiensu.test.ts +14 -12
- package/tests/unit/adapters/publishers/seshop.test.ts +16 -13
- package/tests/unit/adapters/publishers/tatsu-zine.test.ts +56 -14
- package/tests/unit/adapters/publishers/techbookfest.test.ts +12 -11
- package/tests/unit/adapters/registry.test.ts +37 -0
- package/tests/unit/application/get-book-detail.test.ts +102 -0
- package/tests/unit/application/search-books.test.ts +137 -0
- package/vitest.config.ts +0 -8
|
@@ -17,7 +17,19 @@
|
|
|
17
17
|
"Read(//tmp/**)",
|
|
18
18
|
"Bash(npm test:*)",
|
|
19
19
|
"Bash(python3:*)",
|
|
20
|
-
"WebFetch(domain:www.personal-media.co.jp)"
|
|
20
|
+
"WebFetch(domain:www.personal-media.co.jp)",
|
|
21
|
+
"Bash(grep -rn \"export.*Element\" node_modules/cheerio/dist/esm/*.d.ts)",
|
|
22
|
+
"Bash(npx tsc:*)",
|
|
23
|
+
"Bash(npx oxlint:*)",
|
|
24
|
+
"Bash(npm run:*)",
|
|
25
|
+
"Bash(curl -s \"https://book.impress.co.jp/books/1124101031\")",
|
|
26
|
+
"Bash(curl -s \"https://book.impress.co.jp/books/1125101113\")",
|
|
27
|
+
"Bash(cp /tmp/impress-detail-social.html tests/fixtures/impress-detail-social.html)",
|
|
28
|
+
"Bash(cp /tmp/impress-detail-nodrm.html tests/fixtures/impress-detail-epub.html)",
|
|
29
|
+
"Bash(wc -c tests/fixtures/impress-detail-*.html)",
|
|
30
|
+
"Bash(git -C /Users/megurine/repo/js/techbook-mcp status)",
|
|
31
|
+
"Bash(git -C /Users/megurine/repo/js/techbook-mcp diff)",
|
|
32
|
+
"Bash(git:*)"
|
|
21
33
|
]
|
|
22
34
|
}
|
|
23
35
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: techbook-mcp-release-prep
|
|
3
|
+
description: Prepare a techbook-mcp release by bumping the version, updating the changelog, and running verification. Use when the user asks to prepare the next version, cut a release, or make sure versioned files are consistent before tagging.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# techbook-mcp Release Prep
|
|
7
|
+
|
|
8
|
+
Follow this workflow when preparing a new `techbook-mcp` release.
|
|
9
|
+
|
|
10
|
+
## Decide the Next Version
|
|
11
|
+
|
|
12
|
+
Choose the next semantic version before touching any files.
|
|
13
|
+
|
|
14
|
+
- `patch` (0.x.Y) — bug fixes, no new functionality
|
|
15
|
+
- `minor` (0.X.0) — new adapters, new features, backward-compatible changes
|
|
16
|
+
- `major` (X.0.0) — breaking changes to the MCP tool interface or `BookRecord` schema
|
|
17
|
+
|
|
18
|
+
If the user specifies a version, use it. Otherwise infer from the unreleased commits since the last tag.
|
|
19
|
+
|
|
20
|
+
## Update Versioned Files
|
|
21
|
+
|
|
22
|
+
Update these files together in one pass:
|
|
23
|
+
|
|
24
|
+
- `CHANGELOG.md`
|
|
25
|
+
- `package.json` — `"version"` field
|
|
26
|
+
- `package-lock.json` — `"version"` field in the root object (same value as `package.json`)
|
|
27
|
+
|
|
28
|
+
### CHANGELOG.md Rules
|
|
29
|
+
|
|
30
|
+
- If `CHANGELOG.md` does not exist yet, create it with the standard Keep a Changelog header.
|
|
31
|
+
- Add a new `## [x.y.z] - YYYY-MM-DD` section directly below `## [Unreleased]`.
|
|
32
|
+
- Use Keep a Changelog section headings as needed: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
|
|
33
|
+
- Group the same kinds of changes under the same heading.
|
|
34
|
+
- Write entries user-facing: what changed, not how. Skip internal refactors unless they affect users.
|
|
35
|
+
- List new publisher adapters under `Added`.
|
|
36
|
+
- Preserve the release date in every version heading.
|
|
37
|
+
- Keep the `[Unreleased]` section empty after moving its contents to the new release section.
|
|
38
|
+
- Update the `[Unreleased]` compare link and add the new release compare link at the bottom of the file.
|
|
39
|
+
- Keep version headings and bottom-of-file links consistent so releases and compare ranges remain linkable.
|
|
40
|
+
|
|
41
|
+
### CHANGELOG.md Template (first release)
|
|
42
|
+
|
|
43
|
+
```markdown
|
|
44
|
+
# Changelog
|
|
45
|
+
|
|
46
|
+
All notable changes to this project will be documented in this file.
|
|
47
|
+
|
|
48
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
49
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
50
|
+
|
|
51
|
+
## [Unreleased]
|
|
52
|
+
|
|
53
|
+
## [x.y.z] - YYYY-MM-DD
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- ...
|
|
58
|
+
|
|
59
|
+
[Unreleased]: https://github.com/zonuexe/techbook-mcp/compare/vx.y.z...HEAD
|
|
60
|
+
[x.y.z]: https://github.com/zonuexe/techbook-mcp/releases/tag/vx.y.z
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Verify Before Committing
|
|
64
|
+
|
|
65
|
+
Run all checks in order:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm test
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm run lint
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
All three must pass. Do not commit if any check fails.
|
|
80
|
+
|
|
81
|
+
## Commit the Release
|
|
82
|
+
|
|
83
|
+
Prefer a single release-prep commit containing:
|
|
84
|
+
|
|
85
|
+
- version bump in `package.json` and `package-lock.json`
|
|
86
|
+
- `CHANGELOG.md` update
|
|
87
|
+
|
|
88
|
+
Use this commit message format:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
Bump up version to x.y.z
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Do not include other unrelated changes in the release commit.
|
|
95
|
+
|
|
96
|
+
## Quick Checklist
|
|
97
|
+
|
|
98
|
+
- Working tree starts clean or you understand every pending change.
|
|
99
|
+
- `CHANGELOG.md` has a new `## [x.y.z] - YYYY-MM-DD` section with user-facing entries.
|
|
100
|
+
- `package.json` and `package-lock.json` both show the new version.
|
|
101
|
+
- Bottom-of-file links in `CHANGELOG.md` are consistent.
|
|
102
|
+
- `npm test` passed.
|
|
103
|
+
- `npm run lint` passed.
|
|
104
|
+
- `npm run build` passed.
|
|
105
|
+
- Commit message follows `chore: release x.y.z`.
|
|
@@ -26,6 +26,9 @@ jobs:
|
|
|
26
26
|
- name: Install dependencies
|
|
27
27
|
run: npm ci
|
|
28
28
|
|
|
29
|
+
- name: Lint
|
|
30
|
+
run: npm run lint
|
|
31
|
+
|
|
29
32
|
- name: Type check
|
|
30
33
|
run: npx tsc --noEmit
|
|
31
34
|
|
|
@@ -34,3 +37,36 @@ jobs:
|
|
|
34
37
|
|
|
35
38
|
- name: Build
|
|
36
39
|
run: npm run build
|
|
40
|
+
|
|
41
|
+
bun:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
|
|
46
|
+
- uses: oven-sh/setup-bun@v2
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: bun install
|
|
50
|
+
|
|
51
|
+
- name: Run tests
|
|
52
|
+
run: bun test
|
|
53
|
+
|
|
54
|
+
deno:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
|
|
59
|
+
- uses: denoland/setup-deno@v2
|
|
60
|
+
with:
|
|
61
|
+
deno-version: v2.x
|
|
62
|
+
|
|
63
|
+
- name: Install Node deps (for node_modules)
|
|
64
|
+
uses: actions/setup-node@v4
|
|
65
|
+
with:
|
|
66
|
+
node-version: 22.x
|
|
67
|
+
cache: npm
|
|
68
|
+
|
|
69
|
+
- run: npm ci
|
|
70
|
+
|
|
71
|
+
- name: Run tests
|
|
72
|
+
run: deno test --allow-read --no-check --sloppy-imports tests/
|
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "unicorn"],
|
|
4
|
+
"rules": {
|
|
5
|
+
"no-unused-vars": "error",
|
|
6
|
+
"no-console": "warn",
|
|
7
|
+
"unicorn/no-array-for-each": "error",
|
|
8
|
+
"unicorn/prefer-string-slice": "error",
|
|
9
|
+
"typescript/consistent-type-imports": "error"
|
|
10
|
+
},
|
|
11
|
+
"ignorePatterns": ["dist/**", "node_modules/**"]
|
|
12
|
+
}
|
package/AGENTS.md
CHANGED
|
@@ -3,11 +3,20 @@
|
|
|
3
3
|
日本語技術書の書誌情報を出版社公式サイト・APIから取得するMCPサーバー。
|
|
4
4
|
詳細な設計は [docs/design-doc.md](docs/design-doc.md) を参照。
|
|
5
5
|
|
|
6
|
+
## コミットメッセージ規約
|
|
7
|
+
|
|
8
|
+
Conventional Commits は使わない。コミットメッセージは端的な日本語または英語の命令形で書く。
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
robots.txt チェックを追加し結果を6時間キャッシュする
|
|
12
|
+
Add robots.txt check with 6-hour cache
|
|
13
|
+
```
|
|
14
|
+
|
|
6
15
|
## 開発コマンド
|
|
7
16
|
|
|
8
17
|
```bash
|
|
9
18
|
npm install
|
|
10
|
-
npm test # ユニットテスト実行 (
|
|
19
|
+
npm test # ユニットテスト実行 (node:test)
|
|
11
20
|
npm run build # TypeScript コンパイル → dist/
|
|
12
21
|
```
|
|
13
22
|
|
|
@@ -47,7 +56,12 @@ npm run build # TypeScript コンパイル → dist/
|
|
|
47
56
|
|
|
48
57
|
## テスト方針
|
|
49
58
|
|
|
59
|
+
テストフレームワークは `node:test` + `node:assert/strict` を使う(vitest は使わない)。
|
|
60
|
+
|
|
50
61
|
```typescript
|
|
62
|
+
import { describe, it } from "node:test";
|
|
63
|
+
import assert from "node:assert/strict";
|
|
64
|
+
|
|
51
65
|
// 標準的なテストセットアップ
|
|
52
66
|
function makeDeps(http: MockHttpClient) {
|
|
53
67
|
return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
|
|
@@ -60,6 +74,20 @@ const http = new MockHttpClient()
|
|
|
60
74
|
// POST のモック (GraphQL等)
|
|
61
75
|
const http = new MockHttpClient()
|
|
62
76
|
.addPostResponse("https://api.example.com/graphql", { status: 200, body: json });
|
|
77
|
+
|
|
78
|
+
// vitest → node:assert の主な対応
|
|
79
|
+
// expect(x).toBe(y) → assert.strictEqual(x, y)
|
|
80
|
+
// expect(x).toEqual(y) → assert.deepStrictEqual(x, y)
|
|
81
|
+
// expect(x).toMatchObject(y) → assert.partialDeepStrictEqual(x, y)
|
|
82
|
+
// expect(x).toHaveLength(n) → assert.strictEqual(x.length, n)
|
|
83
|
+
// expect(x).toContain(s) → assert.ok(x.includes(s))
|
|
84
|
+
// expect(x).toMatch(/r/) → assert.match(x, /r/)
|
|
85
|
+
// await expect(p).rejects.toThrow("msg") → await assert.rejects(p, /msg/)
|
|
86
|
+
|
|
87
|
+
// モック関数: node:test の mock.fn() は Bun 非対応のため、テストファイル内に
|
|
88
|
+
// ローカルで mockFn() ヘルパーを定義して使う(Node.js・Bun・Deno 共通で動作)
|
|
89
|
+
// vi.fn().mockResolvedValue(v) → mockFn(async () => v)
|
|
90
|
+
// fn.toHaveBeenCalledOnce() → assert.strictEqual(fn.mock.callCount(), 1)
|
|
63
91
|
```
|
|
64
92
|
|
|
65
93
|
## よくある落とし穴
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.1] - 2026-04-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- 達人出版会アダプターの検索が常に無関係な書籍を返す問題を修正(`?search=` パラメータがサーバーで無視されていたため、全書籍一覧からローカルフィルタリングする方式に変更)
|
|
15
|
+
|
|
16
|
+
## [0.2.0] - 2026-04-12
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- インプレスブックス (`impress-books`) アダプターを追加
|
|
21
|
+
- 各出版社サイトの `robots.txt` をチェックし、クロール可否を判定する機能を追加(結果は6時間キャッシュ)
|
|
22
|
+
- GitHub Actions に Bun・Deno のテストジョブを追加
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- `npx` でバイナリ起動する際に shebang がなくエラーになる問題を修正
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- テストを vitest から `node:test` + `node:assert` に移行(Node.js・Bun・Deno で共通実行可能に)
|
|
31
|
+
|
|
32
|
+
[Unreleased]: https://github.com/zonuexe/techbook-mcp/compare/v0.2.1...HEAD
|
|
33
|
+
[0.2.1]: https://github.com/zonuexe/techbook-mcp/compare/v0.2.0...v0.2.1
|
|
34
|
+
[0.2.0]: https://github.com/zonuexe/techbook-mcp/releases/tag/v0.2.0
|
package/deno.json
ADDED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cheerio-parser.d.ts","sourceRoot":"","sources":["../../../src/adapters/html/cheerio-parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cheerio-parser.d.ts","sourceRoot":"","sources":["../../../src/adapters/html/cheerio-parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAe,MAAM,4BAA4B,CAAC;AA0CxF,qBAAa,iBAAkB,YAAW,UAAU;IAClD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;CAIlC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cheerio-parser.js","sourceRoot":"","sources":["../../../src/adapters/html/cheerio-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"cheerio-parser.js","sourceRoot":"","sources":["../../../src/adapters/html/cheerio-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,MAAM,cAAc;IAEC;IACA;IAFnB,YACmB,CAAqB,EACrB,EAAW;QADX,MAAC,GAAD,CAAC,CAAoB;QACrB,OAAE,GAAF,EAAE,CAAS;IAC3B,CAAC;IAEJ,IAAI;QACF,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;IACvC,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,IAAY;QACf,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,QAAgB;QACnB,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;aACnB,IAAI,CAAC,QAAQ,CAAC;aACd,OAAO,EAAE;aACT,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,EAAa,CAAC,CAAC,CAAC;IAC1D,CAAC;CACF;AAED,MAAM,eAAe;IACU;IAA7B,YAA6B,CAAqB;QAArB,MAAC,GAAD,CAAC,CAAoB;IAAG,CAAC;IAEtD,MAAM,CAAC,QAAgB;QACrB,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;aACpB,OAAO,EAAE;aACT,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,EAAa,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,SAAS,CAAC,QAAgB;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,IAAY;QAChB,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,IAAI,eAAe,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -2,9 +2,30 @@ import type { PublisherDeps } from "../../domain/publisher.js";
|
|
|
2
2
|
import type { EbookStore } from "../../domain/book.js";
|
|
3
3
|
import type { HtmlDocument } from "../../ports/html-parser.js";
|
|
4
4
|
export declare const CACHE_TTL_SECONDS = 3600;
|
|
5
|
-
export declare
|
|
5
|
+
export declare const ROBOTS_CACHE_TTL_SECONDS: number;
|
|
6
|
+
/**
|
|
7
|
+
* 指定URLのオリジンの robots.txt を取得してアクセス可否を返す。
|
|
8
|
+
* 取得結果は6時間キャッシュする。エラー時はアクセスを許可する(fail-open)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkRobotsTxt(url: string, deps: PublisherDeps): Promise<boolean>;
|
|
11
|
+
export declare function fetchText(url: string, deps: PublisherDeps, extraHeaders?: Record<string, string>): Promise<string>;
|
|
6
12
|
/** HTMLタグを除去する(gihyo APIのauthorフィールドのruby markup除去に使用) */
|
|
7
13
|
export declare function stripHtmlTags(html: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* キーワードを EUC-JP でパーセントエンコードする。
|
|
16
|
+
* born-digital・rutles など EUC-JP エンコードのみ受け付けるサイト向け。
|
|
17
|
+
*/
|
|
18
|
+
export declare function encodeEucJp(text: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* "2026年3月25日" → "2026-03-25"
|
|
21
|
+
* 1桁の月・日も対応する。
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseJapaneseDateToISO(text: string): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* 著者名末尾の役割語(著・訳・編・監修・監訳など)を除去して名前だけを返す。
|
|
26
|
+
* 例: "Dan Vanderkam 著" → "Dan Vanderkam"
|
|
27
|
+
*/
|
|
28
|
+
export declare function stripAuthorRole(name: string): string;
|
|
8
29
|
/** "¥3,960" や "3,300円(税込)" などから整数値を取り出す */
|
|
9
30
|
export declare function parseJapanesePrice(text: string): number | undefined;
|
|
10
31
|
/** 相対URLを絶対URLに解決する */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/adapters/publishers/base.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/adapters/publishers/base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAQ/D,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,wBAAwB,QAAW,CAAC;AAyFjD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BvF;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa,EACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,2CAA2C;AAC3C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAInE;AAED,uBAAuB;AACvB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG5D;AAwCD,sCAAsC;AACtC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOjE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,EAAE,CAgBzE"}
|
|
@@ -1,14 +1,116 @@
|
|
|
1
|
+
import iconv from "iconv-lite";
|
|
1
2
|
const DEFAULT_HEADERS = {
|
|
2
3
|
"User-Agent": "techbook-mcp/0.1.0 (+https://github.com/zonuexe/techbook-mcp; bibliographic search bot)",
|
|
3
4
|
"Accept": "text/html,application/xhtml+xml,application/json",
|
|
4
5
|
"Accept-Language": "ja,en;q=0.9",
|
|
5
6
|
};
|
|
6
7
|
export const CACHE_TTL_SECONDS = 3600; // 1時間
|
|
7
|
-
export
|
|
8
|
+
export const ROBOTS_CACHE_TTL_SECONDS = 6 * 3600; // 6時間
|
|
9
|
+
/** robots.txt をパースしてセクション一覧を返す */
|
|
10
|
+
function parseRobotsTxt(content) {
|
|
11
|
+
const sections = [];
|
|
12
|
+
let current = null;
|
|
13
|
+
let inAgentBlock = true;
|
|
14
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
15
|
+
const trimmedRaw = rawLine.trim();
|
|
16
|
+
// 空行(コメント行ではない)のみセクションをリセット
|
|
17
|
+
if (!trimmedRaw || trimmedRaw.startsWith("#")) {
|
|
18
|
+
if (!trimmedRaw) {
|
|
19
|
+
current = null;
|
|
20
|
+
inAgentBlock = true;
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const line = trimmedRaw.split("#")[0].trim();
|
|
25
|
+
if (!line)
|
|
26
|
+
continue;
|
|
27
|
+
const colonIdx = line.indexOf(":");
|
|
28
|
+
if (colonIdx === -1)
|
|
29
|
+
continue;
|
|
30
|
+
const key = line.slice(0, colonIdx).trim().toLowerCase();
|
|
31
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
32
|
+
if (key === "user-agent") {
|
|
33
|
+
if (inAgentBlock && current !== null) {
|
|
34
|
+
// 同じセクションに複数のUser-agent行
|
|
35
|
+
current.agents.push(value.toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// 新しいセクション開始
|
|
39
|
+
current = { agents: [value.toLowerCase()], rules: [] };
|
|
40
|
+
sections.push(current);
|
|
41
|
+
inAgentBlock = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (current !== null && (key === "allow" || key === "disallow")) {
|
|
45
|
+
inAgentBlock = false;
|
|
46
|
+
current.rules.push({ type: key, path: value });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return sections;
|
|
50
|
+
}
|
|
51
|
+
/** 指定ユーザーエージェントに適用されるルールを返す(固有エージェント優先、なければ * にフォールバック) */
|
|
52
|
+
function getRulesForAgent(sections, agentToken) {
|
|
53
|
+
const lower = agentToken.toLowerCase();
|
|
54
|
+
for (const section of sections) {
|
|
55
|
+
if (section.agents.includes(lower))
|
|
56
|
+
return section.rules;
|
|
57
|
+
}
|
|
58
|
+
for (const section of sections) {
|
|
59
|
+
if (section.agents.includes("*"))
|
|
60
|
+
return section.rules;
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
/** パスがルール一覧で許可されているか判定する(最長プレフィックス一致) */
|
|
65
|
+
function isPathAllowed(path, rules) {
|
|
66
|
+
let bestMatch = { length: -1, allowed: true };
|
|
67
|
+
for (const rule of rules) {
|
|
68
|
+
if (!rule.path)
|
|
69
|
+
continue; // 空の Disallow は「全許可」を意味するが不一致として扱う
|
|
70
|
+
if (path.startsWith(rule.path) && rule.path.length > bestMatch.length) {
|
|
71
|
+
bestMatch = { length: rule.path.length, allowed: rule.type === "allow" };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return bestMatch.allowed;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 指定URLのオリジンの robots.txt を取得してアクセス可否を返す。
|
|
78
|
+
* 取得結果は6時間キャッシュする。エラー時はアクセスを許可する(fail-open)。
|
|
79
|
+
*/
|
|
80
|
+
export async function checkRobotsTxt(url, deps) {
|
|
81
|
+
const parsed = new URL(url);
|
|
82
|
+
const origin = `${parsed.protocol}//${parsed.host}`;
|
|
83
|
+
const cacheKey = `robots:${origin}`;
|
|
84
|
+
let content;
|
|
85
|
+
const cached = await deps.cache.get(cacheKey);
|
|
86
|
+
if (cached !== null) {
|
|
87
|
+
content = cached;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
try {
|
|
91
|
+
const response = await deps.http.get(`${origin}/robots.txt`, { headers: DEFAULT_HEADERS });
|
|
92
|
+
content = response.status === 200 ? await response.text() : "";
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// robots.txt 取得失敗時はアクセスを許可する
|
|
96
|
+
content = "";
|
|
97
|
+
}
|
|
98
|
+
await deps.cache.set(cacheKey, content, ROBOTS_CACHE_TTL_SECONDS);
|
|
99
|
+
}
|
|
100
|
+
if (!content)
|
|
101
|
+
return true;
|
|
102
|
+
const sections = parseRobotsTxt(content);
|
|
103
|
+
const rules = getRulesForAgent(sections, "techbook-mcp");
|
|
104
|
+
return isPathAllowed(parsed.pathname + parsed.search, rules);
|
|
105
|
+
}
|
|
106
|
+
export async function fetchText(url, deps, extraHeaders) {
|
|
8
107
|
const cached = await deps.cache.get(url);
|
|
9
108
|
if (cached !== null)
|
|
10
109
|
return cached;
|
|
11
|
-
const
|
|
110
|
+
const headers = extraHeaders
|
|
111
|
+
? { ...DEFAULT_HEADERS, ...extraHeaders }
|
|
112
|
+
: DEFAULT_HEADERS;
|
|
113
|
+
const response = await deps.http.get(url, { headers });
|
|
12
114
|
if (response.status !== 200) {
|
|
13
115
|
throw new Error(`HTTP ${response.status}: ${url}`);
|
|
14
116
|
}
|
|
@@ -20,6 +122,33 @@ export async function fetchText(url, deps) {
|
|
|
20
122
|
export function stripHtmlTags(html) {
|
|
21
123
|
return html.replace(/<[^>]+>/g, "");
|
|
22
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* キーワードを EUC-JP でパーセントエンコードする。
|
|
127
|
+
* born-digital・rutles など EUC-JP エンコードのみ受け付けるサイト向け。
|
|
128
|
+
*/
|
|
129
|
+
export function encodeEucJp(text) {
|
|
130
|
+
const bytes = iconv.encode(text, "euc-jp");
|
|
131
|
+
return Array.from(bytes)
|
|
132
|
+
.map(b => "%" + b.toString(16).toUpperCase().padStart(2, "0"))
|
|
133
|
+
.join("");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* "2026年3月25日" → "2026-03-25"
|
|
137
|
+
* 1桁の月・日も対応する。
|
|
138
|
+
*/
|
|
139
|
+
export function parseJapaneseDateToISO(text) {
|
|
140
|
+
const m = text.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
|
|
141
|
+
if (!m)
|
|
142
|
+
return undefined;
|
|
143
|
+
return `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 著者名末尾の役割語(著・訳・編・監修・監訳など)を除去して名前だけを返す。
|
|
147
|
+
* 例: "Dan Vanderkam 著" → "Dan Vanderkam"
|
|
148
|
+
*/
|
|
149
|
+
export function stripAuthorRole(name) {
|
|
150
|
+
return name.replace(/[\u3000\s]*(著|訳|編|監修|監訳|著訳|著・訳|他)[\u3000\s]*$/, "").trim();
|
|
151
|
+
}
|
|
23
152
|
/** "¥3,960" や "3,300円(税込)" などから整数値を取り出す */
|
|
24
153
|
export function parseJapanesePrice(text) {
|
|
25
154
|
const match = text.match(/[\d,]+/);
|
|
@@ -42,14 +171,25 @@ export function extractAsin(html) {
|
|
|
42
171
|
const EBOOK_STORE_PATTERNS = [
|
|
43
172
|
// DRM-free
|
|
44
173
|
{ pattern: /techbookfest\.org\/product\//, name: "技術書典", drm: "free" },
|
|
174
|
+
{ pattern: /oreilly\.co\.jp\/books\//, name: "オライリー・ジャパン", drm: "free" },
|
|
175
|
+
{ pattern: /shop\.rutles\.net\//, name: "ラトルズ", drm: "free" },
|
|
176
|
+
{ pattern: /peaks\.cc\/books\//, name: "PEAKS", drm: "free" },
|
|
177
|
+
{ pattern: /optronics-ebook\.com\/products\//, name: "オプトロニクス社", drm: "free" },
|
|
45
178
|
{ pattern: /gihyo\.jp\/dp\/ebook\//, name: "Gihyo Digital Publishing", drm: "social" },
|
|
179
|
+
{ pattern: /seshop\.com\/product\//, name: "SEshop", drm: "social" },
|
|
180
|
+
{ pattern: /book-tech\.com\/books\//, name: "BOOK TECH", drm: "social" },
|
|
181
|
+
{ pattern: /wgn-obs\.shop-pro\.jp\/\?pid=/, name: "ボーンデジタル", drm: "social" },
|
|
46
182
|
// ソーシャルDRM (購入時生成IDまたは購入者情報を透かし刻印、技術的制限なし)
|
|
183
|
+
{ pattern: /book\.mynavi\.jp\/manatee\//, name: "マナティ", drm: "social" },
|
|
47
184
|
{ pattern: /www\.lambdanote\.com\/products\//, name: "ラムダノート", drm: "social" },
|
|
48
185
|
{ pattern: /tatsu-zine\.com\/books\/(?!pub\/)/, name: "達人出版会", drm: "social" },
|
|
49
186
|
// ソーシャルDRM (購入者情報透かし入りPDF、技術的制限なし)
|
|
50
187
|
{ pattern: /book\.impress\.co\.jp\/books\//, name: "インプレスブックス", drm: "social" },
|
|
51
188
|
// DRM-attached
|
|
189
|
+
{ pattern: /saiensu\.co\.jp/, name: "サイエンス社", drm: "password_pdf" },
|
|
52
190
|
{ pattern: /amazon\.co\.jp/, name: "Kindle", drm: "drm" },
|
|
191
|
+
{ pattern: /kinokuniya\.co\.jp\/(?:kinoppystore|f\/dsg-08)/, name: "Kinoppy", drm: "drm" },
|
|
192
|
+
{ pattern: /coop-ebook\.jp\/mem\//, name: "VarsityWave eBooks", drm: "drm" },
|
|
53
193
|
{ pattern: /books\.rakuten\.co\.jp|rakuten\.kobo\.com|kobo\.com/, name: "楽天Kobo", drm: "drm" },
|
|
54
194
|
{ pattern: /booklive\.jp/, name: "BookLive", drm: "drm" },
|
|
55
195
|
{ pattern: /honto\.jp/, name: "honto", drm: "drm" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/adapters/publishers/base.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/adapters/publishers/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"book-tech.d.ts","sourceRoot":"","sources":["../../../src/adapters/publishers/book-tech.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,2BAA2B,CAAC;AAqBjF,eAAO,MAAM,eAAe,EAAE,gBA+F7B,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fetchText, parseJapanesePrice, resolveUrl } from "./base.js";
|
|
2
|
+
const BASE_URL = "https://book-tech.com";
|
|
3
|
+
const SEARCH_URL = `${BASE_URL}/books`;
|
|
4
|
+
// クエリパラメータキー(URLエンコード済み)
|
|
5
|
+
const SEARCH_PARAM = "q%5Btitle_or_overview_or_identification_number_1_or_product_code_cont%5D";
|
|
6
|
+
/** "2026/2/20" → "2026-02-20" */
|
|
7
|
+
function parseDate(text) {
|
|
8
|
+
const m = text.match(/(\d{4})\/(\d{1,2})\/(\d{1,2})/);
|
|
9
|
+
if (!m)
|
|
10
|
+
return undefined;
|
|
11
|
+
return `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}`;
|
|
12
|
+
}
|
|
13
|
+
/** "(著)" などの役割語を末尾から除去する */
|
|
14
|
+
function stripRole(name) {
|
|
15
|
+
return name.replace(/\s*[((][^))]*[))]\s*$/, "").trim();
|
|
16
|
+
}
|
|
17
|
+
export const bookTechAdapter = {
|
|
18
|
+
id: "book-tech",
|
|
19
|
+
name: "BOOK TECH",
|
|
20
|
+
baseUrl: BASE_URL,
|
|
21
|
+
async search(query, deps) {
|
|
22
|
+
const word = [query.title, query.author].filter(Boolean).join(" ");
|
|
23
|
+
if (!word)
|
|
24
|
+
return [];
|
|
25
|
+
const url = `${SEARCH_URL}?${SEARCH_PARAM}=${encodeURIComponent(word)}`;
|
|
26
|
+
const html = await fetchText(url, deps);
|
|
27
|
+
const doc = deps.parser.parse(html);
|
|
28
|
+
const results = [];
|
|
29
|
+
const limit = query.limit ?? 10;
|
|
30
|
+
for (const item of doc.select("div.contents-index-item")) {
|
|
31
|
+
const linkEl = item.find("a.book-ribbon-link")[0];
|
|
32
|
+
const href = linkEl?.attr("href");
|
|
33
|
+
if (!href)
|
|
34
|
+
continue;
|
|
35
|
+
const bookUrl = resolveUrl(BASE_URL, href);
|
|
36
|
+
const title = item.find(".contents-index-item-detail-title")[0]?.text().trim();
|
|
37
|
+
if (!title)
|
|
38
|
+
continue;
|
|
39
|
+
const publisherEl = item.find("a[href*='publisher_relations']")[0];
|
|
40
|
+
const publisher = publisherEl?.text().trim() ?? "";
|
|
41
|
+
const authors = item.find("a[href*='author_relations']")
|
|
42
|
+
.map(el => stripRole(el.text().trim()))
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
const priceText = item.find(".contents-index-item-detail-price_include_tax")[0]?.text();
|
|
45
|
+
const price = priceText ? parseJapanesePrice(priceText) : undefined;
|
|
46
|
+
const dateText = item.find(".my-1")[0]?.text();
|
|
47
|
+
const publishedAt = dateText ? parseDate(dateText) : undefined;
|
|
48
|
+
const imgEl = item.find("img.thumb")[0];
|
|
49
|
+
const coverImageUrl = imgEl?.attr("src") ?? undefined;
|
|
50
|
+
results.push({
|
|
51
|
+
title,
|
|
52
|
+
authors,
|
|
53
|
+
publisher,
|
|
54
|
+
url: bookUrl,
|
|
55
|
+
price,
|
|
56
|
+
publishedAt,
|
|
57
|
+
coverImageUrl,
|
|
58
|
+
ebookStores: [{ name: "BOOK TECH", url: bookUrl, drm: "social" }],
|
|
59
|
+
});
|
|
60
|
+
if (results.length >= limit)
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
},
|
|
65
|
+
async getDetail(url, deps) {
|
|
66
|
+
const html = await fetchText(url, deps);
|
|
67
|
+
const doc = deps.parser.parse(html);
|
|
68
|
+
const title = doc.selectOne(".contents-book-about-title h1")?.text().trim() ?? "";
|
|
69
|
+
const publisherEl = doc.selectOne("a[href*='publisher_relations']");
|
|
70
|
+
const publisher = publisherEl?.text().trim() ?? "";
|
|
71
|
+
const authors = doc.select("a[href*='author_relations']")
|
|
72
|
+
.map(el => stripRole(el.text().trim()))
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
const priceText = doc.selectOne(".contents-book-item-detail-price_include_tax")?.text();
|
|
75
|
+
const price = priceText ? parseJapanesePrice(priceText) : undefined;
|
|
76
|
+
const dateText = doc.selectOne(".contents-book-about-publicationdate")?.text();
|
|
77
|
+
const publishedAt = dateText ? parseDate(dateText) : undefined;
|
|
78
|
+
const isbnText = doc.selectOne(".contents-book-about-id")?.text();
|
|
79
|
+
const isbn = isbnText?.match(/\d{13}/)?.[0];
|
|
80
|
+
const imgEl = doc.selectOne("img.thumb");
|
|
81
|
+
const coverImageUrl = imgEl?.attr("src") ?? undefined;
|
|
82
|
+
return {
|
|
83
|
+
title,
|
|
84
|
+
authors,
|
|
85
|
+
publisher,
|
|
86
|
+
url,
|
|
87
|
+
isbn,
|
|
88
|
+
price,
|
|
89
|
+
publishedAt,
|
|
90
|
+
coverImageUrl,
|
|
91
|
+
ebookStores: [{ name: "BOOK TECH", url, drm: "social" }],
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=book-tech.js.map
|