@nemu.pm/tachiyomi-runtime 0.1.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.
- package/dist/http.d.ts +49 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +8 -0
- package/dist/http.js.map +1 -0
- package/dist/image-codec.browser.d.ts +12 -0
- package/dist/image-codec.browser.d.ts.map +1 -0
- package/dist/image-codec.browser.js +133 -0
- package/dist/image-codec.browser.js.map +1 -0
- package/dist/image-codec.d.ts +6 -0
- package/dist/image-codec.d.ts.map +1 -0
- package/dist/image-codec.js +6 -0
- package/dist/image-codec.js.map +1 -0
- package/dist/image-codec.node.d.ts +12 -0
- package/dist/image-codec.node.d.ts.map +1 -0
- package/dist/image-codec.node.js +95 -0
- package/dist/image-codec.node.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/index.node.d.ts +12 -0
- package/dist/index.node.d.ts.map +1 -0
- package/dist/index.node.js +14 -0
- package/dist/index.node.js.map +1 -0
- package/dist/runtime.d.ts +87 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +184 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types.d.ts +169 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -0
- package/src/http.ts +53 -0
- package/src/image-codec.browser.ts +152 -0
- package/src/image-codec.node.ts +119 -0
- package/src/image-codec.ts +5 -0
- package/src/index.node.ts +50 -0
- package/src/index.ts +75 -0
- package/src/runtime.ts +314 -0
- package/src/types.ts +211 -0
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tachiyomi Extension Runtime
|
|
3
|
+
*
|
|
4
|
+
* Provides a clean API for loading and executing Tachiyomi extensions.
|
|
5
|
+
*/
|
|
6
|
+
function unwrapResult(json) {
|
|
7
|
+
const result = JSON.parse(json);
|
|
8
|
+
if (!result.ok) {
|
|
9
|
+
const errMsg = typeof result.error === 'string'
|
|
10
|
+
? result.error
|
|
11
|
+
: JSON.stringify(result.error);
|
|
12
|
+
throw new Error(errMsg || 'Unknown extension error');
|
|
13
|
+
}
|
|
14
|
+
return result.data;
|
|
15
|
+
}
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Runtime Implementation
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Tachiyomi extension runtime.
|
|
21
|
+
*
|
|
22
|
+
* Create one runtime per application, then use it to load extensions.
|
|
23
|
+
*/
|
|
24
|
+
export class TachiyomiRuntime {
|
|
25
|
+
constructor(httpBridge) {
|
|
26
|
+
this.httpBridge = httpBridge;
|
|
27
|
+
this.installHttpBridge();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Install the HTTP bridge on globalThis for Kotlin/JS to call.
|
|
31
|
+
*/
|
|
32
|
+
installHttpBridge() {
|
|
33
|
+
const bridge = this.httpBridge;
|
|
34
|
+
globalThis.tachiyomiHttpRequest = (url, method, headersJson, body, wantBytes) => {
|
|
35
|
+
try {
|
|
36
|
+
const headers = JSON.parse(headersJson || '{}');
|
|
37
|
+
const response = bridge.request({ url, method, headers, body }, wantBytes);
|
|
38
|
+
return {
|
|
39
|
+
status: response.status,
|
|
40
|
+
statusText: response.statusText,
|
|
41
|
+
headersJson: JSON.stringify(response.headers),
|
|
42
|
+
body: response.body,
|
|
43
|
+
error: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return {
|
|
48
|
+
status: 0,
|
|
49
|
+
statusText: '',
|
|
50
|
+
headersJson: '{}',
|
|
51
|
+
body: '',
|
|
52
|
+
error: e.message || 'HTTP request failed',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Load an extension from its JavaScript code.
|
|
59
|
+
*
|
|
60
|
+
* @param code - The compiled extension JavaScript
|
|
61
|
+
* @returns An ExtensionInstance for interacting with the extension's sources
|
|
62
|
+
*/
|
|
63
|
+
loadExtension(code) {
|
|
64
|
+
// Execute the extension code
|
|
65
|
+
const fn = new Function(code);
|
|
66
|
+
fn();
|
|
67
|
+
// Find the exports in globalThis
|
|
68
|
+
// Extensions export to globalThis['moduleName'].tachiyomi.generated
|
|
69
|
+
const g = globalThis;
|
|
70
|
+
let exports = null;
|
|
71
|
+
for (const key of Object.keys(g)) {
|
|
72
|
+
if (g[key]?.tachiyomi?.generated) {
|
|
73
|
+
exports = g[key].tachiyomi.generated;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!exports || typeof exports.getManifest !== 'function') {
|
|
78
|
+
throw new Error('Invalid extension: could not find tachiyomi.generated exports');
|
|
79
|
+
}
|
|
80
|
+
return new ExtensionInstanceImpl(exports);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Internal implementation of ExtensionInstance
|
|
85
|
+
*/
|
|
86
|
+
class ExtensionInstanceImpl {
|
|
87
|
+
constructor(exports) {
|
|
88
|
+
this.sourcesCache = null;
|
|
89
|
+
this.exports = exports;
|
|
90
|
+
}
|
|
91
|
+
getSources() {
|
|
92
|
+
if (this.sourcesCache)
|
|
93
|
+
return this.sourcesCache;
|
|
94
|
+
const json = this.exports.getManifest();
|
|
95
|
+
this.sourcesCache = unwrapResult(json);
|
|
96
|
+
return this.sourcesCache;
|
|
97
|
+
}
|
|
98
|
+
getPopularManga(sourceId, page) {
|
|
99
|
+
const json = this.exports.getPopularManga(sourceId, page);
|
|
100
|
+
return unwrapResult(json);
|
|
101
|
+
}
|
|
102
|
+
getLatestUpdates(sourceId, page) {
|
|
103
|
+
const json = this.exports.getLatestUpdates(sourceId, page);
|
|
104
|
+
return unwrapResult(json);
|
|
105
|
+
}
|
|
106
|
+
searchManga(sourceId, page, query) {
|
|
107
|
+
const json = this.exports.searchManga(sourceId, page, query);
|
|
108
|
+
return unwrapResult(json);
|
|
109
|
+
}
|
|
110
|
+
getMangaDetails(sourceId, manga) {
|
|
111
|
+
const json = this.exports.getMangaDetails(sourceId, manga.url);
|
|
112
|
+
return unwrapResult(json);
|
|
113
|
+
}
|
|
114
|
+
getChapterList(sourceId, manga) {
|
|
115
|
+
const json = this.exports.getChapterList(sourceId, manga.url);
|
|
116
|
+
return unwrapResult(json);
|
|
117
|
+
}
|
|
118
|
+
getPageList(sourceId, chapter) {
|
|
119
|
+
const json = this.exports.getPageList(sourceId, chapter.url);
|
|
120
|
+
return unwrapResult(json);
|
|
121
|
+
}
|
|
122
|
+
getFilterList(sourceId) {
|
|
123
|
+
const json = this.exports.getFilterList(sourceId);
|
|
124
|
+
return unwrapResult(json);
|
|
125
|
+
}
|
|
126
|
+
resetFilters(sourceId) {
|
|
127
|
+
const json = this.exports.resetFilters(sourceId);
|
|
128
|
+
unwrapResult(json);
|
|
129
|
+
}
|
|
130
|
+
applyFilterState(sourceId, filterState) {
|
|
131
|
+
const json = this.exports.applyFilterState(sourceId, JSON.stringify(filterState));
|
|
132
|
+
unwrapResult(json);
|
|
133
|
+
}
|
|
134
|
+
getSettingsSchema(sourceId) {
|
|
135
|
+
if (!this.exports.getSettingsSchema)
|
|
136
|
+
return null;
|
|
137
|
+
try {
|
|
138
|
+
const json = this.exports.getSettingsSchema(sourceId);
|
|
139
|
+
return unwrapResult(json);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
setPreference(sourceId, key, value) {
|
|
146
|
+
if (!this.exports.setPreference)
|
|
147
|
+
return;
|
|
148
|
+
const json = this.exports.setPreference(sourceId, key, JSON.stringify(value));
|
|
149
|
+
unwrapResult(json);
|
|
150
|
+
}
|
|
151
|
+
getHeaders(sourceId) {
|
|
152
|
+
const json = this.exports.getHeaders(sourceId);
|
|
153
|
+
return unwrapResult(json);
|
|
154
|
+
}
|
|
155
|
+
fetchImage(sourceId, pageUrl, imageUrl) {
|
|
156
|
+
const json = this.exports.fetchImage(sourceId, pageUrl, imageUrl);
|
|
157
|
+
return unwrapResult(json);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Create a Tachiyomi runtime with the given HTTP bridge.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* import { createRuntime } from '@tachiyomi-js/runtime';
|
|
166
|
+
*
|
|
167
|
+
* const runtime = createRuntime({
|
|
168
|
+
* request(req, wantBytes) {
|
|
169
|
+
* // Your HTTP implementation here
|
|
170
|
+
* const xhr = new XMLHttpRequest();
|
|
171
|
+
* xhr.open(req.method, req.url, false);
|
|
172
|
+
* // ...
|
|
173
|
+
* return { status: 200, body: '...', headers: {}, statusText: 'OK' };
|
|
174
|
+
* }
|
|
175
|
+
* });
|
|
176
|
+
*
|
|
177
|
+
* const ext = runtime.loadExtension(extensionCode);
|
|
178
|
+
* const sources = ext.getSources();
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export function createRuntime(httpBridge) {
|
|
182
|
+
return new TachiyomiRuntime(httpBridge);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for Tachiyomi extensions
|
|
3
|
+
*/
|
|
4
|
+
export declare enum MangaStatus {
|
|
5
|
+
UNKNOWN = 0,
|
|
6
|
+
ONGOING = 1,
|
|
7
|
+
COMPLETED = 2,
|
|
8
|
+
LICENSED = 3,
|
|
9
|
+
PUBLISHING_FINISHED = 4,
|
|
10
|
+
CANCELLED = 5,
|
|
11
|
+
ON_HIATUS = 6
|
|
12
|
+
}
|
|
13
|
+
export interface Manga {
|
|
14
|
+
url: string;
|
|
15
|
+
title: string;
|
|
16
|
+
artist?: string;
|
|
17
|
+
author?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
genre?: string[];
|
|
20
|
+
status: MangaStatus;
|
|
21
|
+
thumbnailUrl?: string;
|
|
22
|
+
initialized: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface Chapter {
|
|
25
|
+
url: string;
|
|
26
|
+
name: string;
|
|
27
|
+
dateUpload: number;
|
|
28
|
+
chapterNumber: number;
|
|
29
|
+
scanlator?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface Page {
|
|
32
|
+
index: number;
|
|
33
|
+
url: string;
|
|
34
|
+
imageUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface MangasPage {
|
|
37
|
+
mangas: Manga[];
|
|
38
|
+
hasNextPage: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface SourceInfo {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
lang: string;
|
|
44
|
+
baseUrl: string;
|
|
45
|
+
supportsLatest: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface ExtensionManifest {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
pkg: string;
|
|
51
|
+
lang: string;
|
|
52
|
+
version: number;
|
|
53
|
+
nsfw: boolean;
|
|
54
|
+
hasWebView?: boolean;
|
|
55
|
+
hasCloudflare?: boolean;
|
|
56
|
+
icon?: string;
|
|
57
|
+
jsPath?: string;
|
|
58
|
+
sources: SourceInfo[];
|
|
59
|
+
authors?: Author[];
|
|
60
|
+
}
|
|
61
|
+
export interface Author {
|
|
62
|
+
name: string;
|
|
63
|
+
github?: string;
|
|
64
|
+
commits: number;
|
|
65
|
+
firstCommit: string;
|
|
66
|
+
}
|
|
67
|
+
/** Header filter - section label, not interactive */
|
|
68
|
+
export interface FilterHeader {
|
|
69
|
+
type: 'Header';
|
|
70
|
+
name: string;
|
|
71
|
+
}
|
|
72
|
+
/** Separator filter - visual divider */
|
|
73
|
+
export interface FilterSeparator {
|
|
74
|
+
type: 'Separator';
|
|
75
|
+
name: string;
|
|
76
|
+
}
|
|
77
|
+
/** Text filter - free-form input */
|
|
78
|
+
export interface FilterText {
|
|
79
|
+
type: 'Text';
|
|
80
|
+
name: string;
|
|
81
|
+
state: string;
|
|
82
|
+
}
|
|
83
|
+
/** Checkbox filter - boolean toggle */
|
|
84
|
+
export interface FilterCheckBox {
|
|
85
|
+
type: 'CheckBox';
|
|
86
|
+
name: string;
|
|
87
|
+
state: boolean;
|
|
88
|
+
}
|
|
89
|
+
/** TriState filter - ignore/include/exclude */
|
|
90
|
+
export interface FilterTriState {
|
|
91
|
+
type: 'TriState';
|
|
92
|
+
name: string;
|
|
93
|
+
state: number;
|
|
94
|
+
}
|
|
95
|
+
/** Sort filter - sorting options */
|
|
96
|
+
export interface FilterSort {
|
|
97
|
+
type: 'Sort';
|
|
98
|
+
name: string;
|
|
99
|
+
state: {
|
|
100
|
+
index: number;
|
|
101
|
+
ascending: boolean;
|
|
102
|
+
} | null;
|
|
103
|
+
values: string[];
|
|
104
|
+
}
|
|
105
|
+
/** Select filter - dropdown/single select */
|
|
106
|
+
export interface FilterSelect {
|
|
107
|
+
type: 'Select';
|
|
108
|
+
name: string;
|
|
109
|
+
state: number;
|
|
110
|
+
values: string[];
|
|
111
|
+
}
|
|
112
|
+
/** Group filter - container for child filters */
|
|
113
|
+
export interface FilterGroup {
|
|
114
|
+
type: 'Group';
|
|
115
|
+
name: string;
|
|
116
|
+
state: FilterState[];
|
|
117
|
+
}
|
|
118
|
+
/** Union of all filter types */
|
|
119
|
+
export type FilterState = FilterHeader | FilterSeparator | FilterText | FilterCheckBox | FilterTriState | FilterSort | FilterSelect | FilterGroup;
|
|
120
|
+
/** Minimal state update format for applyFilterState() */
|
|
121
|
+
export interface FilterStateUpdate {
|
|
122
|
+
index: number;
|
|
123
|
+
state?: boolean | number | string | {
|
|
124
|
+
index: number;
|
|
125
|
+
ascending: boolean;
|
|
126
|
+
};
|
|
127
|
+
filters?: FilterStateUpdate[];
|
|
128
|
+
}
|
|
129
|
+
/** Convert a single filter to its state update format */
|
|
130
|
+
export declare function filterToStateUpdate(filter: FilterState, index: number): FilterStateUpdate | null;
|
|
131
|
+
/** Convert filter list to JSON string for applyFilterState() */
|
|
132
|
+
export declare function buildFilterStateJson(filters: FilterState[]): string;
|
|
133
|
+
export interface SettingsSchema {
|
|
134
|
+
preferences: PreferenceSchema[];
|
|
135
|
+
}
|
|
136
|
+
export type PreferenceSchema = {
|
|
137
|
+
type: 'EditTextPreference';
|
|
138
|
+
key: string;
|
|
139
|
+
title: string;
|
|
140
|
+
summary?: string;
|
|
141
|
+
default?: string;
|
|
142
|
+
} | {
|
|
143
|
+
type: 'CheckBoxPreference';
|
|
144
|
+
key: string;
|
|
145
|
+
title: string;
|
|
146
|
+
summary?: string;
|
|
147
|
+
default?: boolean;
|
|
148
|
+
} | {
|
|
149
|
+
type: 'ListPreference';
|
|
150
|
+
key: string;
|
|
151
|
+
title: string;
|
|
152
|
+
entries: string[];
|
|
153
|
+
entryValues: string[];
|
|
154
|
+
default?: string;
|
|
155
|
+
} | {
|
|
156
|
+
type: 'MultiSelectListPreference';
|
|
157
|
+
key: string;
|
|
158
|
+
title: string;
|
|
159
|
+
entries: string[];
|
|
160
|
+
entryValues: string[];
|
|
161
|
+
default?: string[];
|
|
162
|
+
} | {
|
|
163
|
+
type: 'SwitchPreferenceCompat';
|
|
164
|
+
key: string;
|
|
165
|
+
title: string;
|
|
166
|
+
summary?: string;
|
|
167
|
+
default?: boolean;
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,oBAAY,WAAW;IACrB,OAAO,IAAI;IACX,OAAO,IAAI;IACX,SAAS,IAAI;IACb,QAAQ,IAAI;IACZ,mBAAmB,IAAI;IACvB,SAAS,IAAI;IACb,SAAS,IAAI;CACd;AAED,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAMD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,gCAAgC;AAChC,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,eAAe,GACf,UAAU,GACV,cAAc,GACd,cAAc,GACd,UAAU,GACV,YAAY,GACZ,WAAW,CAAC;AAMhB,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAC1E,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAoBhG;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAKnE;AAMD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9F;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/F;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAClH;IAAE,IAAI,EAAE,2BAA2B,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/H;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for Tachiyomi extensions
|
|
3
|
+
*/
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Manga & Chapter Types
|
|
6
|
+
// ============================================================================
|
|
7
|
+
export var MangaStatus;
|
|
8
|
+
(function (MangaStatus) {
|
|
9
|
+
MangaStatus[MangaStatus["UNKNOWN"] = 0] = "UNKNOWN";
|
|
10
|
+
MangaStatus[MangaStatus["ONGOING"] = 1] = "ONGOING";
|
|
11
|
+
MangaStatus[MangaStatus["COMPLETED"] = 2] = "COMPLETED";
|
|
12
|
+
MangaStatus[MangaStatus["LICENSED"] = 3] = "LICENSED";
|
|
13
|
+
MangaStatus[MangaStatus["PUBLISHING_FINISHED"] = 4] = "PUBLISHING_FINISHED";
|
|
14
|
+
MangaStatus[MangaStatus["CANCELLED"] = 5] = "CANCELLED";
|
|
15
|
+
MangaStatus[MangaStatus["ON_HIATUS"] = 6] = "ON_HIATUS";
|
|
16
|
+
})(MangaStatus || (MangaStatus = {}));
|
|
17
|
+
/** Convert a single filter to its state update format */
|
|
18
|
+
export function filterToStateUpdate(filter, index) {
|
|
19
|
+
switch (filter.type) {
|
|
20
|
+
case 'CheckBox':
|
|
21
|
+
return { index, state: filter.state };
|
|
22
|
+
case 'TriState':
|
|
23
|
+
return { index, state: filter.state };
|
|
24
|
+
case 'Text':
|
|
25
|
+
return filter.state ? { index, state: filter.state } : null;
|
|
26
|
+
case 'Select':
|
|
27
|
+
return { index, state: filter.state };
|
|
28
|
+
case 'Sort':
|
|
29
|
+
return filter.state ? { index, state: filter.state } : null;
|
|
30
|
+
case 'Group':
|
|
31
|
+
const childUpdates = filter.state
|
|
32
|
+
.map((f, i) => filterToStateUpdate(f, i))
|
|
33
|
+
.filter((u) => u !== null);
|
|
34
|
+
return childUpdates.length > 0 ? { index, filters: childUpdates } : null;
|
|
35
|
+
default:
|
|
36
|
+
return null; // Header, Separator have no state
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Convert filter list to JSON string for applyFilterState() */
|
|
40
|
+
export function buildFilterStateJson(filters) {
|
|
41
|
+
const updates = filters
|
|
42
|
+
.map((f, i) => filterToStateUpdate(f, i))
|
|
43
|
+
.filter((u) => u !== null);
|
|
44
|
+
return JSON.stringify(updates);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;AAE/E,MAAM,CAAN,IAAY,WAQX;AARD,WAAY,WAAW;IACrB,mDAAW,CAAA;IACX,mDAAW,CAAA;IACX,uDAAa,CAAA;IACb,qDAAY,CAAA;IACZ,2EAAuB,CAAA;IACvB,uDAAa,CAAA;IACb,uDAAa,CAAA;AACf,CAAC,EARW,WAAW,KAAX,WAAW,QAQtB;AAqJD,yDAAyD;AACzD,MAAM,UAAU,mBAAmB,CAAC,MAAmB,EAAE,KAAa;IACpE,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,UAAU;YACb,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,KAAK,UAAU;YACb,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9D,KAAK,QAAQ;YACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9D,KAAK,OAAO;YACV,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK;iBAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;iBACxC,MAAM,CAAC,CAAC,CAAC,EAA0B,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;YACrD,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3E;YACE,OAAO,IAAI,CAAC,CAAC,kCAAkC;IACnD,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,oBAAoB,CAAC,OAAsB;IACzD,MAAM,OAAO,GAAG,OAAO;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;SACxC,MAAM,CAAC,CAAC,CAAC,EAA0B,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nemu.pm/tachiyomi-runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Runtime for loading and executing Tachiyomi extensions compiled to JavaScript",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/nemu-pm/tachiyomi-js.git",
|
|
8
|
+
"directory": "runtime"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/nemu-pm/tachiyomi-js#readme",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"browser": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"bun": {
|
|
22
|
+
"types": "./dist/index.node.d.ts",
|
|
23
|
+
"default": "./dist/index.node.js"
|
|
24
|
+
},
|
|
25
|
+
"node": {
|
|
26
|
+
"types": "./dist/index.node.d.ts",
|
|
27
|
+
"default": "./dist/index.node.js"
|
|
28
|
+
},
|
|
29
|
+
"default": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"dev": "tsc --watch",
|
|
42
|
+
"prepublishOnly": "npm run build"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"tachiyomi",
|
|
46
|
+
"manga",
|
|
47
|
+
"extension",
|
|
48
|
+
"runtime"
|
|
49
|
+
],
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"typescript": "^5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"optionalDependencies": {
|
|
55
|
+
"canvas": "^3.0.0-rc3"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"canvas": "^3.0.0-rc3"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"canvas": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Bridge interface for Tachiyomi extensions
|
|
3
|
+
*
|
|
4
|
+
* Extensions use synchronous HTTP calls internally (via OkHttp shim).
|
|
5
|
+
* Consumers must provide an implementation that bridges to their environment.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface HttpRequest {
|
|
9
|
+
url: string;
|
|
10
|
+
method: string;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
body?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HttpResponse {
|
|
16
|
+
status: number;
|
|
17
|
+
statusText: string;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
/** Response body as string. For binary responses, this should be base64 encoded. */
|
|
20
|
+
body: string;
|
|
21
|
+
/** If true, body is base64 encoded binary data */
|
|
22
|
+
isBinary?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* HTTP bridge that extensions call for network requests.
|
|
27
|
+
*
|
|
28
|
+
* IMPORTANT: Extensions expect SYNCHRONOUS responses.
|
|
29
|
+
* In browsers, this typically means running in a Web Worker with sync XHR.
|
|
30
|
+
*/
|
|
31
|
+
export interface HttpBridge {
|
|
32
|
+
/**
|
|
33
|
+
* Perform an HTTP request synchronously.
|
|
34
|
+
*
|
|
35
|
+
* @param request - The HTTP request to make
|
|
36
|
+
* @param wantBytes - If true, return body as base64-encoded binary
|
|
37
|
+
* @returns HTTP response (must be synchronous!)
|
|
38
|
+
*/
|
|
39
|
+
request(request: HttpRequest, wantBytes: boolean): HttpResponse;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Internal format used by Kotlin/JS extensions.
|
|
44
|
+
* The runtime converts between this and the public HttpBridge interface.
|
|
45
|
+
*/
|
|
46
|
+
export interface KotlinHttpResult {
|
|
47
|
+
status: number;
|
|
48
|
+
statusText: string;
|
|
49
|
+
headersJson: string;
|
|
50
|
+
body: string;
|
|
51
|
+
error: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image codec for browser environments.
|
|
3
|
+
* Uses native OffscreenCanvas + createImageBitmap.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export async function decodeImageAsync(base64: string): Promise<{ width: number; height: number; pixels: string } | null> {
|
|
7
|
+
try {
|
|
8
|
+
const binary = atob(base64);
|
|
9
|
+
const bytes = new Uint8Array(binary.length);
|
|
10
|
+
for (let i = 0; i < binary.length; i++) {
|
|
11
|
+
bytes[i] = binary.charCodeAt(i);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const blob = new Blob([bytes]);
|
|
15
|
+
const bitmap = await createImageBitmap(blob);
|
|
16
|
+
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
17
|
+
const ctx = canvas.getContext("2d");
|
|
18
|
+
if (!ctx) return null;
|
|
19
|
+
|
|
20
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
21
|
+
const imageData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
|
|
22
|
+
|
|
23
|
+
// Convert RGBA to ARGB (Android Bitmap format)
|
|
24
|
+
const pixels = new Int32Array(bitmap.width * bitmap.height);
|
|
25
|
+
const rgba = imageData.data;
|
|
26
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
27
|
+
const r = rgba[i * 4];
|
|
28
|
+
const g = rgba[i * 4 + 1];
|
|
29
|
+
const b = rgba[i * 4 + 2];
|
|
30
|
+
const a = rgba[i * 4 + 3];
|
|
31
|
+
pixels[i] = ((a << 24) | (r << 16) | (g << 8) | b) | 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Convert to base64
|
|
35
|
+
const pixelBuffer = new Uint8Array(pixels.buffer);
|
|
36
|
+
let pixelBase64 = "";
|
|
37
|
+
const CHUNK_SIZE = 32768;
|
|
38
|
+
for (let i = 0; i < pixelBuffer.length; i += CHUNK_SIZE) {
|
|
39
|
+
const chunk = pixelBuffer.subarray(i, Math.min(i + CHUNK_SIZE, pixelBuffer.length));
|
|
40
|
+
pixelBase64 += String.fromCharCode.apply(null, chunk as unknown as number[]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { width: bitmap.width, height: bitmap.height, pixels: btoa(pixelBase64) };
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("[ImageCodec] decode error:", e);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function encodeImageAsync(
|
|
51
|
+
pixelsBase64: string,
|
|
52
|
+
width: number,
|
|
53
|
+
height: number,
|
|
54
|
+
format: string,
|
|
55
|
+
quality: number
|
|
56
|
+
): Promise<string | null> {
|
|
57
|
+
try {
|
|
58
|
+
const binary = atob(pixelsBase64);
|
|
59
|
+
const pixelBuffer = new Uint8Array(binary.length);
|
|
60
|
+
for (let i = 0; i < binary.length; i++) {
|
|
61
|
+
pixelBuffer[i] = binary.charCodeAt(i);
|
|
62
|
+
}
|
|
63
|
+
const pixels = new Int32Array(pixelBuffer.buffer);
|
|
64
|
+
|
|
65
|
+
// Convert ARGB to RGBA
|
|
66
|
+
const rgba = new Uint8ClampedArray(width * height * 4);
|
|
67
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
68
|
+
const pixel = pixels[i];
|
|
69
|
+
rgba[i * 4] = (pixel >> 16) & 0xff;
|
|
70
|
+
rgba[i * 4 + 1] = (pixel >> 8) & 0xff;
|
|
71
|
+
rgba[i * 4 + 2] = pixel & 0xff;
|
|
72
|
+
rgba[i * 4 + 3] = (pixel >> 24) & 0xff;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
76
|
+
const ctx = canvas.getContext("2d");
|
|
77
|
+
if (!ctx) return null;
|
|
78
|
+
|
|
79
|
+
const imageData = new ImageData(rgba, width, height);
|
|
80
|
+
ctx.putImageData(imageData, 0, 0);
|
|
81
|
+
|
|
82
|
+
const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
83
|
+
const blob = await canvas.convertToBlob({
|
|
84
|
+
type: mimeType,
|
|
85
|
+
quality: format === "jpeg" ? quality / 100 : undefined,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
89
|
+
const outputBytes = new Uint8Array(arrayBuffer);
|
|
90
|
+
let result = "";
|
|
91
|
+
const CHUNK_SIZE = 32768;
|
|
92
|
+
for (let i = 0; i < outputBytes.length; i += CHUNK_SIZE) {
|
|
93
|
+
const chunk = outputBytes.subarray(i, Math.min(i + CHUNK_SIZE, outputBytes.length));
|
|
94
|
+
result += String.fromCharCode.apply(null, chunk as unknown as number[]);
|
|
95
|
+
}
|
|
96
|
+
return btoa(result);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error("[ImageCodec] encode error:", e);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Sync wrappers using XHR blocking trick (works in Web Workers)
|
|
104
|
+
function decodeImageSync(base64: string): { width: number; height: number; pixels: string } | null {
|
|
105
|
+
try {
|
|
106
|
+
let result: { width: number; height: number; pixels: string } | null = null;
|
|
107
|
+
let done = false;
|
|
108
|
+
|
|
109
|
+
decodeImageAsync(base64).then(r => { result = r; done = true; }).catch(() => { done = true; });
|
|
110
|
+
|
|
111
|
+
const start = Date.now();
|
|
112
|
+
while (!done && Date.now() - start < 30000) {
|
|
113
|
+
const xhr = new XMLHttpRequest();
|
|
114
|
+
xhr.open("GET", "data:text/plain,", false);
|
|
115
|
+
try { xhr.send(); } catch {}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function encodeImageSync(
|
|
124
|
+
pixelsBase64: string,
|
|
125
|
+
width: number,
|
|
126
|
+
height: number,
|
|
127
|
+
format: string,
|
|
128
|
+
quality: number
|
|
129
|
+
): string | null {
|
|
130
|
+
try {
|
|
131
|
+
let result: string | null = null;
|
|
132
|
+
let done = false;
|
|
133
|
+
|
|
134
|
+
encodeImageAsync(pixelsBase64, width, height, format, quality).then(r => { result = r; done = true; }).catch(() => { done = true; });
|
|
135
|
+
|
|
136
|
+
const start = Date.now();
|
|
137
|
+
while (!done && Date.now() - start < 30000) {
|
|
138
|
+
const xhr = new XMLHttpRequest();
|
|
139
|
+
xhr.open("GET", "data:text/plain,", false);
|
|
140
|
+
try { xhr.send(); } catch {}
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function registerImageCodec(): void {
|
|
149
|
+
(globalThis as Record<string, unknown>).tachiyomiDecodeImage = decodeImageSync;
|
|
150
|
+
(globalThis as Record<string, unknown>).tachiyomiEncodeImage = encodeImageSync;
|
|
151
|
+
}
|
|
152
|
+
|