ani-web 1.5.8
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.
Potentially problematic release.
This version of ani-web might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/client/dist/assets/AnimeInfo-C7DQp7Oo.js +1 -0
- package/client/dist/assets/AnimeInfo-Sb3YiXHJ.css +1 -0
- package/client/dist/assets/AnimeInfoPage-DJA7AJQ8.js +2 -0
- package/client/dist/assets/Button-Fq9KaUOg.css +1 -0
- package/client/dist/assets/Button-o0l9V_NG.js +1 -0
- package/client/dist/assets/ErrorMessage-Ddf2zmRx.js +1 -0
- package/client/dist/assets/ErrorMessage-FOxXyZC9.css +1 -0
- package/client/dist/assets/Home-CKHJA97j.css +1 -0
- package/client/dist/assets/Home-Dey0azy1.js +1 -0
- package/client/dist/assets/Insights-BSRcCkDs.css +1 -0
- package/client/dist/assets/Insights-CogjPOd_.js +1 -0
- package/client/dist/assets/MAL-CYArH4yf.js +1 -0
- package/client/dist/assets/MAL-DeQNXXPx.css +1 -0
- package/client/dist/assets/Player-BWFN9gud.js +9 -0
- package/client/dist/assets/Player-CBCYW7uG.css +1 -0
- package/client/dist/assets/PlayerSettings-BgStUrrP.css +1 -0
- package/client/dist/assets/PlayerSettings-rWZuATQf.js +1 -0
- package/client/dist/assets/RemoveConfirmationModal-BBiogSdf.css +1 -0
- package/client/dist/assets/RemoveConfirmationModal-CLYqyGOv.js +1 -0
- package/client/dist/assets/Search-DZAWgKwq.js +1 -0
- package/client/dist/assets/Search-lWsVQ0Ke.css +1 -0
- package/client/dist/assets/Settings-Bv9fX-x3.css +1 -0
- package/client/dist/assets/Settings-DyisJGeD.js +1 -0
- package/client/dist/assets/ToggleSwitch-CLnWnAuY.js +1 -0
- package/client/dist/assets/ToggleSwitch-DInRb7iM.css +1 -0
- package/client/dist/assets/Watchlist-2dVYksxq.css +1 -0
- package/client/dist/assets/Watchlist-CuqJISI3.js +1 -0
- package/client/dist/assets/hls.light-DcbkToIY.js +27 -0
- package/client/dist/assets/index-BK_Zaqaw.css +1 -0
- package/client/dist/assets/index-CHVF4D4L.js +178 -0
- package/client/dist/assets/useAnimeInfoData-Cr58brCY.js +1 -0
- package/client/dist/assets/useIsMobile-gHo4t6g6.js +1 -0
- package/client/dist/assets/vendor-DdbgYKo4.js +3 -0
- package/client/dist/favicon.ico +0 -0
- package/client/dist/index.html +35 -0
- package/client/dist/logo.png +0 -0
- package/client/dist/placeholder.svg +4 -0
- package/client/dist/robots.txt +3 -0
- package/client/package.json +54 -0
- package/orchestrator.js +302 -0
- package/package.json +69 -0
- package/server/dist/config.js +86 -0
- package/server/dist/constants.json +1359 -0
- package/server/dist/controllers/auth.controller.js +213 -0
- package/server/dist/controllers/data.controller.js +126 -0
- package/server/dist/controllers/insights.controller.js +125 -0
- package/server/dist/controllers/proxy.controller.js +235 -0
- package/server/dist/controllers/settings.controller.js +147 -0
- package/server/dist/controllers/watchlist.controller.js +499 -0
- package/server/dist/db.js +231 -0
- package/server/dist/github-sync.js +279 -0
- package/server/dist/google.js +274 -0
- package/server/dist/logger.js +21 -0
- package/server/dist/providers/123anime.provider.js +229 -0
- package/server/dist/providers/allanime.provider.js +773 -0
- package/server/dist/providers/animepahe.provider.js +313 -0
- package/server/dist/providers/animeya.provider.js +799 -0
- package/server/dist/providers/provider.interface.js +2 -0
- package/server/dist/rclone.js +126 -0
- package/server/dist/repositories/insights.repository.js +30 -0
- package/server/dist/repositories/notifications.repository.js +22 -0
- package/server/dist/repositories/settings.repository.js +13 -0
- package/server/dist/repositories/shows-meta.repository.js +39 -0
- package/server/dist/repositories/watched-episodes.repository.js +60 -0
- package/server/dist/repositories/watchlist.repository.js +49 -0
- package/server/dist/routes/auth.routes.js +23 -0
- package/server/dist/routes/data.routes.js +43 -0
- package/server/dist/routes/insights.routes.js +11 -0
- package/server/dist/routes/proxy.routes.js +13 -0
- package/server/dist/routes/settings.routes.js +26 -0
- package/server/dist/routes/watchlist.routes.js +26 -0
- package/server/dist/server.js +179 -0
- package/server/dist/sync-config.js +28 -0
- package/server/dist/sync.js +383 -0
- package/server/dist/utils/db-utils.js +36 -0
- package/server/dist/utils/env.utils.js +70 -0
- package/server/package.json +54 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DatabaseWrapper = void 0;
|
|
7
|
+
const node_sqlite_1 = require("node:sqlite");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
11
|
+
class DatabaseWrapper {
|
|
12
|
+
db;
|
|
13
|
+
isClosed = false;
|
|
14
|
+
statementCache = new Map();
|
|
15
|
+
constructor(_dbPath, db) {
|
|
16
|
+
this.db = db;
|
|
17
|
+
}
|
|
18
|
+
static async create(dbPath) {
|
|
19
|
+
try {
|
|
20
|
+
const dir = path_1.default.dirname(dbPath);
|
|
21
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
22
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
const db = new node_sqlite_1.DatabaseSync(dbPath);
|
|
25
|
+
return new DatabaseWrapper(dbPath, db);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
logger_1.default.error({ err: e }, `Failed to initialize database at ${dbPath}`);
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
scheduleSave() { }
|
|
33
|
+
async saveNow() { }
|
|
34
|
+
configure(option, value) {
|
|
35
|
+
if (option === 'busyTimeout') {
|
|
36
|
+
this.db.exec(`PRAGMA busy_timeout = ${value}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
serialize(cb) {
|
|
40
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
41
|
+
try {
|
|
42
|
+
cb();
|
|
43
|
+
this.db.exec('COMMIT');
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
this.db.exec('ROLLBACK');
|
|
47
|
+
throw e;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
close(cb) {
|
|
51
|
+
if (this.isClosed) {
|
|
52
|
+
if (cb)
|
|
53
|
+
cb(null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
this.isClosed = true;
|
|
58
|
+
this.statementCache.clear();
|
|
59
|
+
this.db.close();
|
|
60
|
+
if (cb)
|
|
61
|
+
cb(null);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
logger_1.default.error({ err: e }, 'Error during database close');
|
|
65
|
+
if (cb)
|
|
66
|
+
cb(e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
isClosedCheck() {
|
|
70
|
+
return this.isClosed;
|
|
71
|
+
}
|
|
72
|
+
getPreparedStatement(query) {
|
|
73
|
+
let stmt = this.statementCache.get(query);
|
|
74
|
+
if (!stmt) {
|
|
75
|
+
if (this.statementCache.size > 100) {
|
|
76
|
+
this.statementCache.clear();
|
|
77
|
+
}
|
|
78
|
+
stmt = this.db.prepare(query);
|
|
79
|
+
this.statementCache.set(query, stmt);
|
|
80
|
+
}
|
|
81
|
+
return stmt;
|
|
82
|
+
}
|
|
83
|
+
executeAndFinalize(query, params, operation) {
|
|
84
|
+
const stmt = this.getPreparedStatement(query);
|
|
85
|
+
let result;
|
|
86
|
+
if (operation === 'run') {
|
|
87
|
+
if (params && params.length > 0) {
|
|
88
|
+
stmt.run(...params);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
stmt.run();
|
|
92
|
+
}
|
|
93
|
+
result = null;
|
|
94
|
+
}
|
|
95
|
+
else if (operation === 'get') {
|
|
96
|
+
if (params && params.length > 0) {
|
|
97
|
+
result = stmt.get(...params);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
result = stmt.get();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (params && params.length > 0) {
|
|
105
|
+
result = stmt.all(...params);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
result = stmt.all();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
run(query, params, cb, _options) {
|
|
114
|
+
if (this.isClosed) {
|
|
115
|
+
if (cb)
|
|
116
|
+
cb(new Error('Database is closed'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (typeof params === 'function') {
|
|
120
|
+
cb = params;
|
|
121
|
+
params = [];
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const bindableParams = params && Array.isArray(params) && params.length > 0
|
|
125
|
+
? params
|
|
126
|
+
: undefined;
|
|
127
|
+
this.executeAndFinalize(query, bindableParams, 'run');
|
|
128
|
+
if (cb)
|
|
129
|
+
cb(null);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
logger_1.default.error({ err: e, query, params }, 'SQL Execution Error (run)');
|
|
133
|
+
if (cb)
|
|
134
|
+
cb(e);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
get(query, params, cb) {
|
|
138
|
+
if (this.isClosed) {
|
|
139
|
+
if (cb)
|
|
140
|
+
cb(new Error('Database is closed'), null);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (typeof params === 'function') {
|
|
144
|
+
cb = params;
|
|
145
|
+
params = [];
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const bindableParams = params && Array.isArray(params) && params.length > 0
|
|
149
|
+
? params
|
|
150
|
+
: undefined;
|
|
151
|
+
const res = this.executeAndFinalize(query, bindableParams, 'get');
|
|
152
|
+
if (cb)
|
|
153
|
+
cb(null, res);
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
logger_1.default.error({ err: e, query, params }, 'SQL Execution Error (get)');
|
|
157
|
+
if (cb)
|
|
158
|
+
cb(e, null);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
all(query, params, cb) {
|
|
162
|
+
if (this.isClosed) {
|
|
163
|
+
if (cb)
|
|
164
|
+
cb(new Error('Database is closed'), []);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (typeof params === 'function') {
|
|
168
|
+
cb = params;
|
|
169
|
+
params = [];
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const bindableParams = params && Array.isArray(params) && params.length > 0
|
|
173
|
+
? params
|
|
174
|
+
: undefined;
|
|
175
|
+
const res = this.executeAndFinalize(query, bindableParams, 'all');
|
|
176
|
+
if (cb)
|
|
177
|
+
cb(null, res);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
logger_1.default.error({ err: e, query, params }, 'SQL Execution Error (all)');
|
|
181
|
+
if (cb)
|
|
182
|
+
cb(e, []);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
prepare(query) {
|
|
186
|
+
const stmt = this.getPreparedStatement(query);
|
|
187
|
+
return {
|
|
188
|
+
run: (...args) => {
|
|
189
|
+
stmt.run(...args);
|
|
190
|
+
},
|
|
191
|
+
all: () => {
|
|
192
|
+
return stmt.all();
|
|
193
|
+
},
|
|
194
|
+
get: () => {
|
|
195
|
+
return stmt.get();
|
|
196
|
+
},
|
|
197
|
+
finalize: () => { },
|
|
198
|
+
runAsync: (cb) => {
|
|
199
|
+
try {
|
|
200
|
+
stmt.run();
|
|
201
|
+
cb(null);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
cb(e);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
backup(backupPath) {
|
|
210
|
+
try {
|
|
211
|
+
if (fs_1.default.existsSync(backupPath)) {
|
|
212
|
+
fs_1.default.rmSync(backupPath, { force: true });
|
|
213
|
+
}
|
|
214
|
+
this.db.exec(`VACUUM INTO '${backupPath}'`);
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
logger_1.default.error({ err: e, backupPath }, 'Database backup failed via VACUUM INTO');
|
|
218
|
+
throw e;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
checkpoint() {
|
|
222
|
+
try {
|
|
223
|
+
this.db.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
logger_1.default.error({ err: e }, 'Database WAL checkpoint failed');
|
|
227
|
+
throw e;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.DatabaseWrapper = DatabaseWrapper;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.githubSyncService = void 0;
|
|
7
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
8
|
+
const db_utils_1 = require("./utils/db-utils");
|
|
9
|
+
const env_utils_1 = require("./utils/env.utils");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const log = logger_1.default.child({ module: 'GitHubSync' });
|
|
12
|
+
const REPO_NAME = 'aniweb-sync-data';
|
|
13
|
+
const DEFAULT_CLIENT_ID = 'Ov23liT1ZtPk7XtN9PZk';
|
|
14
|
+
const GITHUB_SCOPES = ['repo'];
|
|
15
|
+
const GITHUB_API_HEADERS = {
|
|
16
|
+
accept: 'application/vnd.github+json',
|
|
17
|
+
'x-github-api-version': '2026-03-10',
|
|
18
|
+
};
|
|
19
|
+
const SYNC_TABLES = [
|
|
20
|
+
'watchlist',
|
|
21
|
+
'watched_episodes',
|
|
22
|
+
'settings',
|
|
23
|
+
'shows_meta',
|
|
24
|
+
'sync_metadata',
|
|
25
|
+
'dismissed_notifications',
|
|
26
|
+
'discovered_notifications',
|
|
27
|
+
];
|
|
28
|
+
const nativeImport = new Function('specifier', 'return import(specifier)');
|
|
29
|
+
function getGitHubClientId() {
|
|
30
|
+
return process.env.GITHUB_CLIENT_ID || DEFAULT_CLIENT_ID;
|
|
31
|
+
}
|
|
32
|
+
function getSyncFilename() {
|
|
33
|
+
return config_1.CONFIG.IS_DEV ? 'sync.dev.json' : 'sync.json';
|
|
34
|
+
}
|
|
35
|
+
async function loadOctokit(token) {
|
|
36
|
+
const { Octokit } = await nativeImport('@octokit/rest');
|
|
37
|
+
return new Octokit({
|
|
38
|
+
auth: token,
|
|
39
|
+
request: {
|
|
40
|
+
headers: GITHUB_API_HEADERS,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function getErrorStatus(error) {
|
|
45
|
+
if (typeof error === 'object' && error && 'status' in error) {
|
|
46
|
+
return Number(error.status);
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
function quoteIdentifier(identifier) {
|
|
51
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
52
|
+
}
|
|
53
|
+
function getRowsFromAll(db, sql) {
|
|
54
|
+
return (0, db_utils_1.dbAll)(db, sql);
|
|
55
|
+
}
|
|
56
|
+
function readVersion(payload) {
|
|
57
|
+
const versionRow = payload.tables.sync_metadata.find((row) => row.key === 'db_version');
|
|
58
|
+
const value = versionRow?.value;
|
|
59
|
+
return typeof value === 'number' ? value : Number(value || payload.version || 0);
|
|
60
|
+
}
|
|
61
|
+
function normalizePayload(input) {
|
|
62
|
+
if (!input || typeof input !== 'object' || !('tables' in input)) {
|
|
63
|
+
throw new Error('Invalid GitHub sync payload.');
|
|
64
|
+
}
|
|
65
|
+
const payload = input;
|
|
66
|
+
for (const table of SYNC_TABLES) {
|
|
67
|
+
if (!Array.isArray(payload.tables?.[table])) {
|
|
68
|
+
throw new Error(`Invalid GitHub sync payload: missing ${table}.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
73
|
+
class GitHubSyncService {
|
|
74
|
+
deviceState = { status: 'idle' };
|
|
75
|
+
devicePromise = null;
|
|
76
|
+
isAuthenticated() {
|
|
77
|
+
return !!process.env.GITHUB_TOKEN;
|
|
78
|
+
}
|
|
79
|
+
getDeviceState() {
|
|
80
|
+
return this.deviceState;
|
|
81
|
+
}
|
|
82
|
+
async startDeviceAuth(db, runSyncSequence) {
|
|
83
|
+
if (this.deviceState.status === 'pending') {
|
|
84
|
+
return this.deviceState;
|
|
85
|
+
}
|
|
86
|
+
this.deviceState = { status: 'pending' };
|
|
87
|
+
let resolveVerification;
|
|
88
|
+
const verificationReady = new Promise((resolve) => {
|
|
89
|
+
resolveVerification = resolve;
|
|
90
|
+
});
|
|
91
|
+
this.devicePromise = this.runDeviceAuth(db, runSyncSequence, resolveVerification);
|
|
92
|
+
await verificationReady;
|
|
93
|
+
return this.deviceState;
|
|
94
|
+
}
|
|
95
|
+
async getUserProfile() {
|
|
96
|
+
if (!process.env.GITHUB_TOKEN)
|
|
97
|
+
return null;
|
|
98
|
+
const octokit = await loadOctokit(process.env.GITHUB_TOKEN);
|
|
99
|
+
const { data } = await octokit.rest.users.getAuthenticated({ headers: GITHUB_API_HEADERS });
|
|
100
|
+
return {
|
|
101
|
+
login: data.login,
|
|
102
|
+
name: data.name,
|
|
103
|
+
avatarUrl: data.avatar_url,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async logout() {
|
|
107
|
+
await (0, env_utils_1.updateEnvFile)({ GITHUB_TOKEN: '' });
|
|
108
|
+
delete process.env.GITHUB_TOKEN;
|
|
109
|
+
this.deviceState = { status: 'idle' };
|
|
110
|
+
}
|
|
111
|
+
async getRemoteVersion() {
|
|
112
|
+
const payload = await this.fetchSyncPayload();
|
|
113
|
+
return payload ? readVersion(payload) : 0;
|
|
114
|
+
}
|
|
115
|
+
async syncUp(db) {
|
|
116
|
+
const payload = await this.exportDatabase(db);
|
|
117
|
+
const octokit = await this.getOctokit();
|
|
118
|
+
const owner = await this.ensureRepo(octokit);
|
|
119
|
+
const existing = await this.getSyncFile(octokit, owner);
|
|
120
|
+
const content = Buffer.from(JSON.stringify(payload, null, 2), 'utf8').toString('base64');
|
|
121
|
+
await octokit.rest.repos.createOrUpdateFileContents({
|
|
122
|
+
owner,
|
|
123
|
+
repo: REPO_NAME,
|
|
124
|
+
path: getSyncFilename(),
|
|
125
|
+
message: `Sync ani-web data v${payload.version}`,
|
|
126
|
+
content,
|
|
127
|
+
sha: existing?.sha,
|
|
128
|
+
headers: GITHUB_API_HEADERS,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async syncDown(db) {
|
|
132
|
+
const payload = await this.fetchSyncPayload();
|
|
133
|
+
if (!payload) {
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
this.importDatabase(db, payload);
|
|
137
|
+
return readVersion(payload);
|
|
138
|
+
}
|
|
139
|
+
async runDeviceAuth(db, runSyncSequence, resolveVerification) {
|
|
140
|
+
try {
|
|
141
|
+
const { createOAuthDeviceAuth } = await nativeImport('@octokit/auth-oauth-device');
|
|
142
|
+
const auth = createOAuthDeviceAuth({
|
|
143
|
+
clientId: getGitHubClientId(),
|
|
144
|
+
scopes: GITHUB_SCOPES,
|
|
145
|
+
onVerification: (verification) => {
|
|
146
|
+
this.deviceState = {
|
|
147
|
+
status: 'pending',
|
|
148
|
+
verification: {
|
|
149
|
+
user_code: verification.user_code,
|
|
150
|
+
verification_uri: verification.verification_uri,
|
|
151
|
+
expires_in: verification.expires_in,
|
|
152
|
+
interval: verification.interval,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
resolveVerification();
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const authentication = await auth({ type: 'oauth' });
|
|
159
|
+
await (0, env_utils_1.updateEnvFile)({ GITHUB_TOKEN: authentication.token });
|
|
160
|
+
process.env.GITHUB_TOKEN = authentication.token;
|
|
161
|
+
const user = await this.getUserProfile();
|
|
162
|
+
this.deviceState = {
|
|
163
|
+
status: 'success',
|
|
164
|
+
user: user || undefined,
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
await runSyncSequence(db);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
log.error({ err }, 'Post-GitHub-login sync failed');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
this.deviceState = {
|
|
175
|
+
status: 'error',
|
|
176
|
+
error: err instanceof Error ? err.message : 'GitHub device authentication failed.',
|
|
177
|
+
};
|
|
178
|
+
resolveVerification();
|
|
179
|
+
log.error({ err }, 'GitHub device authentication failed');
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
this.devicePromise = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async getOctokit() {
|
|
186
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
187
|
+
throw new Error('GitHub token is not configured.');
|
|
188
|
+
}
|
|
189
|
+
return loadOctokit(process.env.GITHUB_TOKEN);
|
|
190
|
+
}
|
|
191
|
+
async ensureRepo(octokit) {
|
|
192
|
+
const { data: user } = await octokit.rest.users.getAuthenticated({
|
|
193
|
+
headers: GITHUB_API_HEADERS,
|
|
194
|
+
});
|
|
195
|
+
try {
|
|
196
|
+
await octokit.rest.repos.get({
|
|
197
|
+
owner: user.login,
|
|
198
|
+
repo: REPO_NAME,
|
|
199
|
+
headers: GITHUB_API_HEADERS,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
if (getErrorStatus(err) !== 404) {
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
await octokit.rest.repos.createForAuthenticatedUser({
|
|
207
|
+
name: REPO_NAME,
|
|
208
|
+
private: true,
|
|
209
|
+
auto_init: true,
|
|
210
|
+
description: 'Private ani-web synchronization data.',
|
|
211
|
+
headers: GITHUB_API_HEADERS,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return user.login;
|
|
215
|
+
}
|
|
216
|
+
async getSyncFile(octokit, owner) {
|
|
217
|
+
try {
|
|
218
|
+
const response = await octokit.rest.repos.getContent({
|
|
219
|
+
owner,
|
|
220
|
+
repo: REPO_NAME,
|
|
221
|
+
path: getSyncFilename(),
|
|
222
|
+
headers: GITHUB_API_HEADERS,
|
|
223
|
+
});
|
|
224
|
+
const data = response.data;
|
|
225
|
+
if (data.type !== 'file' || !data.content || !data.sha) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
content: Buffer.from(data.content, 'base64').toString('utf8'),
|
|
230
|
+
sha: data.sha,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
if (getErrorStatus(err) === 404) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async fetchSyncPayload() {
|
|
241
|
+
const octokit = await this.getOctokit();
|
|
242
|
+
const owner = await this.ensureRepo(octokit);
|
|
243
|
+
const file = await this.getSyncFile(octokit, owner);
|
|
244
|
+
if (!file) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return normalizePayload(JSON.parse(file.content));
|
|
248
|
+
}
|
|
249
|
+
async exportDatabase(db) {
|
|
250
|
+
const tables = {};
|
|
251
|
+
for (const table of SYNC_TABLES) {
|
|
252
|
+
tables[table] = await getRowsFromAll(db, `SELECT * FROM ${quoteIdentifier(table)}`);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
version: readVersion({ version: 0, exportedAt: '', tables }),
|
|
256
|
+
exportedAt: new Date().toISOString(),
|
|
257
|
+
tables,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
importDatabase(db, payload) {
|
|
261
|
+
db.serialize(() => {
|
|
262
|
+
for (const table of SYNC_TABLES) {
|
|
263
|
+
db.run(`DELETE FROM ${quoteIdentifier(table)}`);
|
|
264
|
+
}
|
|
265
|
+
for (const table of SYNC_TABLES) {
|
|
266
|
+
for (const row of payload.tables[table]) {
|
|
267
|
+
const columns = Object.keys(row);
|
|
268
|
+
if (columns.length === 0)
|
|
269
|
+
continue;
|
|
270
|
+
const columnSql = columns.map(quoteIdentifier).join(', ');
|
|
271
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
272
|
+
const values = columns.map((column) => row[column]);
|
|
273
|
+
db.run(`INSERT INTO ${quoteIdentifier(table)} (${columnSql}) VALUES (${placeholders})`, values);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
exports.githubSyncService = new GitHubSyncService();
|