@serifpersia/dango 3.0.3
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/LICENSE +21 -0
- package/README.md +247 -0
- package/client/dist/assets/AnimeInfo-CMxs0nRH.css +1 -0
- package/client/dist/assets/AnimeInfo-DVdaocCl.js +1 -0
- package/client/dist/assets/AnimeInfoPage-Bx5EvGt-.js +2 -0
- package/client/dist/assets/Asmr-C2i9GKD4.css +1 -0
- package/client/dist/assets/Asmr-CIjmFtRH.js +2 -0
- package/client/dist/assets/Asmr.module-DPG2JhDJ.js +1 -0
- package/client/dist/assets/AvSyncCalibrator-CIYcyon5.css +1 -0
- package/client/dist/assets/AvSyncCalibrator-vqErH7Cm.js +10 -0
- package/client/dist/assets/ErrorMessage-BwSjzblw.js +1 -0
- package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
- package/client/dist/assets/Home-D7YfGBUk.js +1 -0
- package/client/dist/assets/Home-DKCfEiwl.css +1 -0
- package/client/dist/assets/Insights-BljI64Ua.css +1 -0
- package/client/dist/assets/Insights-CLQGrlpX.js +1 -0
- package/client/dist/assets/Manga-DQ9HYnpk.js +1 -0
- package/client/dist/assets/Manga-H8Wb04w3.css +1 -0
- package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
- package/client/dist/assets/Map-L_ryVBTq.js +1 -0
- package/client/dist/assets/Mature-BeWzAJ7B.js +1 -0
- package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
- package/client/dist/assets/Player-CYpd__FP.js +30 -0
- package/client/dist/assets/Player-DreAhybc.css +1 -0
- package/client/dist/assets/PlayerRedirect-fxUAp9JV.js +1 -0
- package/client/dist/assets/PlayerSettings-BcsotxP9.js +1 -0
- package/client/dist/assets/PlayerSettings-Cqua1CDD.css +1 -0
- package/client/dist/assets/QueueRail-B9Pgbqc3.js +1 -0
- package/client/dist/assets/QueueRail-BnaltOP8.css +1 -0
- package/client/dist/assets/Radio-BvdY5twD.js +1 -0
- package/client/dist/assets/Radio-CUuYy3DY.css +1 -0
- package/client/dist/assets/Search-BzO-aRP7.css +1 -0
- package/client/dist/assets/Search-R9YLAXS1.js +1 -0
- package/client/dist/assets/Search.module-D8ZbQAiK.js +1 -0
- package/client/dist/assets/Settings-9kYcXdTR.js +21 -0
- package/client/dist/assets/Settings-DF8JThUL.css +1 -0
- package/client/dist/assets/SkeletonGrid-D5vRvX5q.js +1 -0
- package/client/dist/assets/SkeletonGrid-DEbYtO4H.css +1 -0
- package/client/dist/assets/SynopsisText-BlFw0R-0.js +1 -0
- package/client/dist/assets/SynopsisText-CUgOjOxt.css +1 -0
- package/client/dist/assets/Trackers-DfZlnnVq.js +2 -0
- package/client/dist/assets/Trackers-Dt9lR_uP.css +1 -0
- package/client/dist/assets/Tv-CVKC2YBs.css +1 -0
- package/client/dist/assets/Tv-Db1Wx_b7.js +15 -0
- package/client/dist/assets/Watchlist-B2nkNZqM.css +1 -0
- package/client/dist/assets/Watchlist-a77CYtzu.js +1 -0
- package/client/dist/assets/hls-6Owgw2Aj.js +1 -0
- package/client/dist/assets/hls-BOLXTnjL.js +42 -0
- package/client/dist/assets/index-B6qdXFtl.css +1 -0
- package/client/dist/assets/index-B8TPVaIS.js +30354 -0
- package/client/dist/assets/index-CpjgU8ou.js +178 -0
- package/client/dist/assets/useAnimeInfoData-BOfyi9UJ.js +1 -0
- package/client/dist/assets/useIsMobile-CM15uQn8.css +1 -0
- package/client/dist/assets/useIsMobile-Cb8HcKRj.js +1 -0
- package/client/dist/assets/useMatureConsent-CZMYBVXd.js +1 -0
- package/client/dist/assets/vendor-rLNfZm7Y.js +1 -0
- package/client/dist/av-sync-test.mp4 +0 -0
- package/client/dist/favicon.ico +0 -0
- package/client/dist/index.html +31 -0
- package/client/dist/logo-mochi.png +0 -0
- package/client/dist/logo-sakura.png +0 -0
- package/client/dist/logo.png +0 -0
- package/client/dist/placeholder.svg +4 -0
- package/client/dist/robots.txt +3 -0
- package/orchestrator.js +416 -0
- package/package.json +89 -0
- package/server/dist/app-auth.js +231 -0
- package/server/dist/config.js +133 -0
- package/server/dist/controllers/auth.controller.js +247 -0
- package/server/dist/controllers/data.controller.js +785 -0
- package/server/dist/controllers/insights.controller.js +197 -0
- package/server/dist/controllers/proxy.controller.js +665 -0
- package/server/dist/controllers/settings.controller.js +267 -0
- package/server/dist/controllers/watchlist.controller.js +967 -0
- package/server/dist/db.js +152 -0
- package/server/dist/discord-gateway.js +381 -0
- package/server/dist/discord-rpc.js +424 -0
- package/server/dist/github-sync.js +403 -0
- package/server/dist/google.js +549 -0
- package/server/dist/lib/anilist.js +1519 -0
- package/server/dist/lib/anischedule.js +202 -0
- package/server/dist/lib/ipc.js +32 -0
- package/server/dist/lib/kitsu.js +552 -0
- package/server/dist/lib/mal.js +886 -0
- package/server/dist/lib/migration.js +335 -0
- package/server/dist/lib/temp-ids.js +11 -0
- package/server/dist/lib/tmdb.js +71 -0
- package/server/dist/lib/tracker/anilist-tracker.js +250 -0
- package/server/dist/lib/tracker/sync.service.js +312 -0
- package/server/dist/logger.js +19 -0
- package/server/dist/providers/123anime.provider.js +240 -0
- package/server/dist/providers/anilight.provider.js +366 -0
- package/server/dist/providers/animepahe.provider.js +531 -0
- package/server/dist/providers/animeya.provider.js +746 -0
- package/server/dist/providers/base-provider.js +45 -0
- package/server/dist/providers/hn.provider.js +473 -0
- package/server/dist/providers/ht.provider.js +439 -0
- package/server/dist/providers/jasmr.provider.js +504 -0
- package/server/dist/providers/kaa.provider.js +388 -0
- package/server/dist/providers/manga/manga.types.js +10 -0
- package/server/dist/providers/manga/mangadex.provider.js +227 -0
- package/server/dist/providers/manga/mangapill.provider.js +232 -0
- package/server/dist/providers/megaplay.provider.js +583 -0
- package/server/dist/providers/op.provider.js +440 -0
- package/server/dist/providers/provider.interface.js +2 -0
- package/server/dist/providers/title-matching.js +234 -0
- package/server/dist/providers/wh.provider.js +600 -0
- package/server/dist/rclone.js +126 -0
- package/server/dist/repositories/insights.repository.js +43 -0
- package/server/dist/repositories/mal-cache.repository.js +66 -0
- package/server/dist/repositories/notifications.repository.js +40 -0
- package/server/dist/repositories/queue.repository.js +64 -0
- package/server/dist/repositories/settings.repository.js +15 -0
- package/server/dist/repositories/shows-meta.repository.js +43 -0
- package/server/dist/repositories/temp-show-ids.repository.js +54 -0
- package/server/dist/repositories/watched-episodes.repository.js +54 -0
- package/server/dist/repositories/watchlist.repository.js +74 -0
- package/server/dist/routes/asmr.routes.js +72 -0
- package/server/dist/routes/auth.routes.js +28 -0
- package/server/dist/routes/data.routes.js +50 -0
- package/server/dist/routes/discord-gateway.routes.js +57 -0
- package/server/dist/routes/insights.routes.js +12 -0
- package/server/dist/routes/lan-auth.routes.js +49 -0
- package/server/dist/routes/manga.routes.js +115 -0
- package/server/dist/routes/proxy.routes.js +15 -0
- package/server/dist/routes/radio.routes.js +143 -0
- package/server/dist/routes/settings.routes.js +27 -0
- package/server/dist/routes/tracker.routes.js +143 -0
- package/server/dist/routes/translate.routes.js +90 -0
- package/server/dist/routes/tv.routes.js +843 -0
- package/server/dist/routes/watchlist.routes.js +312 -0
- package/server/dist/server.js +310 -0
- package/server/dist/shipped-defaults.js +8 -0
- package/server/dist/sync-config.js +32 -0
- package/server/dist/sync.js +506 -0
- package/server/dist/utils/cookie.utils.js +17 -0
- package/server/dist/utils/db-utils.js +15 -0
- package/server/dist/utils/env.utils.js +83 -0
- package/server/dist/utils/machine-id.js +46 -0
- package/server/dist/utils/request-context.js +5 -0
- package/server/dist/utils/security.utils.js +99 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.githubSyncService = void 0;
|
|
40
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
41
|
+
const db_utils_1 = require("./utils/db-utils");
|
|
42
|
+
const temp_ids_1 = require("./lib/temp-ids");
|
|
43
|
+
const env_utils_1 = require("./utils/env.utils");
|
|
44
|
+
const config_1 = require("./config");
|
|
45
|
+
const shipped_defaults_1 = require("./shipped-defaults");
|
|
46
|
+
const log = logger_1.default.child({ module: 'GitHubSync' });
|
|
47
|
+
const REPO_NAME = 'dango-sync-data';
|
|
48
|
+
const LEGACY_REPO_NAME = 'aniweb-sync-data';
|
|
49
|
+
const GITHUB_SCOPES = ['repo', 'delete_repo'];
|
|
50
|
+
const GITHUB_API_HEADERS = {
|
|
51
|
+
accept: 'application/vnd.github+json',
|
|
52
|
+
'x-github-api-version': '2026-03-10',
|
|
53
|
+
};
|
|
54
|
+
const SYNC_TABLES = [
|
|
55
|
+
'watchlist',
|
|
56
|
+
'watched_episodes',
|
|
57
|
+
'queue',
|
|
58
|
+
'settings',
|
|
59
|
+
'shows_meta',
|
|
60
|
+
'sync_metadata',
|
|
61
|
+
'dismissed_notifications',
|
|
62
|
+
'discovered_notifications',
|
|
63
|
+
];
|
|
64
|
+
const nativeImport = new Function('specifier', 'return import(specifier)');
|
|
65
|
+
function getGitHubClientId() {
|
|
66
|
+
const id = process.env.GITHUB_CLIENT_ID || shipped_defaults_1.SHIPPED_DEFAULTS.GITHUB_CLIENT_ID || '';
|
|
67
|
+
if (!id) {
|
|
68
|
+
throw new Error('GITHUB_CLIENT_ID is not configured');
|
|
69
|
+
}
|
|
70
|
+
return id;
|
|
71
|
+
}
|
|
72
|
+
function getSyncFilename() {
|
|
73
|
+
return config_1.CONFIG.IS_DEV ? 'sync.dev.json' : 'sync.json';
|
|
74
|
+
}
|
|
75
|
+
async function loadOctokit(token) {
|
|
76
|
+
const { Octokit } = await nativeImport('@octokit/rest');
|
|
77
|
+
return new Octokit({
|
|
78
|
+
auth: token,
|
|
79
|
+
request: {
|
|
80
|
+
headers: GITHUB_API_HEADERS,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function getErrorStatus(error) {
|
|
85
|
+
if (typeof error === 'object' && error && 'status' in error) {
|
|
86
|
+
return Number(error.status);
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function quoteIdentifier(identifier) {
|
|
91
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
92
|
+
}
|
|
93
|
+
function getRowsFromAll(db, sql) {
|
|
94
|
+
return (0, db_utils_1.dbAll)(db, sql);
|
|
95
|
+
}
|
|
96
|
+
function readVersion(payload) {
|
|
97
|
+
const versionRow = payload.tables.sync_metadata.find((row) => row.key === 'db_version');
|
|
98
|
+
const value = versionRow?.value;
|
|
99
|
+
return typeof value === 'number' ? value : Number(value || payload.version || 0);
|
|
100
|
+
}
|
|
101
|
+
function normalizePayload(input) {
|
|
102
|
+
if (!input || typeof input !== 'object' || !('tables' in input)) {
|
|
103
|
+
throw new Error('Invalid GitHub sync payload.');
|
|
104
|
+
}
|
|
105
|
+
const payload = input;
|
|
106
|
+
for (const table of SYNC_TABLES) {
|
|
107
|
+
if (!Array.isArray(payload.tables?.[table])) {
|
|
108
|
+
throw new Error(`Invalid GitHub sync payload: missing ${table}.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
class GitHubSyncService {
|
|
114
|
+
deviceState = { status: 'idle' };
|
|
115
|
+
devicePromise = null;
|
|
116
|
+
isAuthenticated() {
|
|
117
|
+
return !!process.env.GITHUB_TOKEN;
|
|
118
|
+
}
|
|
119
|
+
async migrateFromAniWebSync() {
|
|
120
|
+
if (!this.isAuthenticated())
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
const octokit = await this.getOctokit();
|
|
124
|
+
const { data: user } = await octokit.rest.users.getAuthenticated({
|
|
125
|
+
headers: GITHUB_API_HEADERS,
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
await octokit.rest.repos.get({
|
|
129
|
+
owner: user.login,
|
|
130
|
+
repo: LEGACY_REPO_NAME,
|
|
131
|
+
headers: GITHUB_API_HEADERS,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (getErrorStatus(err) === 404) {
|
|
136
|
+
log.info('No legacy aniweb-sync-data repo found. Skipping migration.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
log.info('Found legacy aniweb-sync-data repo. Renaming to dango-sync-data...');
|
|
142
|
+
const existingNew = await octokit.rest.repos
|
|
143
|
+
.get({
|
|
144
|
+
owner: user.login,
|
|
145
|
+
repo: REPO_NAME,
|
|
146
|
+
headers: GITHUB_API_HEADERS,
|
|
147
|
+
})
|
|
148
|
+
.catch(() => null);
|
|
149
|
+
if (existingNew) {
|
|
150
|
+
log.info('dango-sync-data repo already exists. Deleting legacy repo.');
|
|
151
|
+
await octokit.rest.repos.delete({
|
|
152
|
+
owner: user.login,
|
|
153
|
+
repo: LEGACY_REPO_NAME,
|
|
154
|
+
headers: GITHUB_API_HEADERS,
|
|
155
|
+
});
|
|
156
|
+
log.info('Deleted legacy aniweb-sync-data repo.');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await octokit.rest.repos.update({
|
|
160
|
+
owner: user.login,
|
|
161
|
+
repo: LEGACY_REPO_NAME,
|
|
162
|
+
name: REPO_NAME,
|
|
163
|
+
description: 'Private dango synchronization data.',
|
|
164
|
+
headers: GITHUB_API_HEADERS,
|
|
165
|
+
});
|
|
166
|
+
log.info('Renamed aniweb-sync-data to dango-sync-data.');
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
log.error({ err }, 'GitHub sync migration failed');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
getDeviceState() {
|
|
173
|
+
return this.deviceState;
|
|
174
|
+
}
|
|
175
|
+
async startDeviceAuth(db, runSyncSequence) {
|
|
176
|
+
if (this.isAuthenticated()) {
|
|
177
|
+
const user = await this.getUserProfile();
|
|
178
|
+
if (user) {
|
|
179
|
+
this.deviceState = {
|
|
180
|
+
status: 'success',
|
|
181
|
+
user: user,
|
|
182
|
+
};
|
|
183
|
+
const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('./utils/env.utils')));
|
|
184
|
+
await updateEnvFile({ SYNC_PROVIDER: 'github' });
|
|
185
|
+
await runSyncSequence(db, 'github');
|
|
186
|
+
return this.deviceState;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
log.warn('Saved GitHub token is invalid or expired. Clearing for new auth.');
|
|
190
|
+
await this.logout();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (this.deviceState.status === 'pending') {
|
|
194
|
+
return this.deviceState;
|
|
195
|
+
}
|
|
196
|
+
this.deviceState = { status: 'pending' };
|
|
197
|
+
let resolveVerification;
|
|
198
|
+
const verificationReady = new Promise((resolve) => {
|
|
199
|
+
resolveVerification = resolve;
|
|
200
|
+
});
|
|
201
|
+
this.devicePromise = this.runDeviceAuth(db, runSyncSequence, resolveVerification);
|
|
202
|
+
await verificationReady;
|
|
203
|
+
return this.deviceState;
|
|
204
|
+
}
|
|
205
|
+
async getUserProfile() {
|
|
206
|
+
if (!process.env.GITHUB_TOKEN)
|
|
207
|
+
return null;
|
|
208
|
+
try {
|
|
209
|
+
const octokit = await loadOctokit(process.env.GITHUB_TOKEN);
|
|
210
|
+
const { data } = await octokit.rest.users.getAuthenticated({
|
|
211
|
+
headers: GITHUB_API_HEADERS,
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
login: data.login,
|
|
215
|
+
name: data.name,
|
|
216
|
+
avatarUrl: data.avatar_url,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
if (getErrorStatus(err) === 401) {
|
|
221
|
+
log.warn('GitHub token is invalid or expired. Logging out.');
|
|
222
|
+
await this.logout();
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async logout() {
|
|
228
|
+
await (0, env_utils_1.updateEnvFile)({ GITHUB_TOKEN: '' });
|
|
229
|
+
delete process.env.GITHUB_TOKEN;
|
|
230
|
+
this.deviceState = { status: 'idle' };
|
|
231
|
+
}
|
|
232
|
+
async getRemoteVersion() {
|
|
233
|
+
const payload = await this.fetchSyncPayload();
|
|
234
|
+
return payload ? readVersion(payload) : 0;
|
|
235
|
+
}
|
|
236
|
+
async syncUp(db) {
|
|
237
|
+
const payload = await this.exportDatabase(db);
|
|
238
|
+
const octokit = await this.getOctokit();
|
|
239
|
+
const owner = await this.ensureRepo(octokit);
|
|
240
|
+
const existing = await this.getSyncFile(octokit, owner);
|
|
241
|
+
const content = Buffer.from(JSON.stringify(payload, null, 2), 'utf8').toString('base64');
|
|
242
|
+
await octokit.rest.repos.createOrUpdateFileContents({
|
|
243
|
+
owner,
|
|
244
|
+
repo: REPO_NAME,
|
|
245
|
+
path: getSyncFilename(),
|
|
246
|
+
message: `Sync dango data v${payload.version}`,
|
|
247
|
+
content,
|
|
248
|
+
sha: existing?.sha,
|
|
249
|
+
headers: GITHUB_API_HEADERS,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async syncDown(db) {
|
|
253
|
+
const payload = await this.fetchSyncPayload();
|
|
254
|
+
if (!payload) {
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
this.importDatabase(db, payload);
|
|
258
|
+
return readVersion(payload);
|
|
259
|
+
}
|
|
260
|
+
async runDeviceAuth(db, runSyncSequence, resolveVerification) {
|
|
261
|
+
try {
|
|
262
|
+
const { createOAuthDeviceAuth } = await nativeImport('@octokit/auth-oauth-device');
|
|
263
|
+
const auth = createOAuthDeviceAuth({
|
|
264
|
+
clientId: getGitHubClientId(),
|
|
265
|
+
scopes: GITHUB_SCOPES,
|
|
266
|
+
onVerification: (verification) => {
|
|
267
|
+
this.deviceState = {
|
|
268
|
+
status: 'pending',
|
|
269
|
+
verification: {
|
|
270
|
+
user_code: verification.user_code,
|
|
271
|
+
verification_uri: verification.verification_uri,
|
|
272
|
+
expires_in: verification.expires_in,
|
|
273
|
+
interval: verification.interval,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
resolveVerification();
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const authentication = await auth({ type: 'oauth' });
|
|
280
|
+
await (0, env_utils_1.updateEnvFile)({ GITHUB_TOKEN: authentication.token, SYNC_PROVIDER: 'github' });
|
|
281
|
+
process.env.GITHUB_TOKEN = authentication.token;
|
|
282
|
+
const user = await this.getUserProfile();
|
|
283
|
+
this.deviceState = {
|
|
284
|
+
status: 'success',
|
|
285
|
+
user: user || undefined,
|
|
286
|
+
};
|
|
287
|
+
try {
|
|
288
|
+
await runSyncSequence(db, 'github');
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
log.error({ err }, 'Post-GitHub-login sync failed');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
this.deviceState = {
|
|
296
|
+
status: 'error',
|
|
297
|
+
error: err instanceof Error ? err.message : 'GitHub device authentication failed.',
|
|
298
|
+
};
|
|
299
|
+
resolveVerification();
|
|
300
|
+
log.error({ err }, 'GitHub device authentication failed');
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
this.devicePromise = null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async getOctokit() {
|
|
307
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
308
|
+
throw new Error('GitHub token is not configured.');
|
|
309
|
+
}
|
|
310
|
+
return loadOctokit(process.env.GITHUB_TOKEN);
|
|
311
|
+
}
|
|
312
|
+
async ensureRepo(octokit) {
|
|
313
|
+
const { data: user } = await octokit.rest.users.getAuthenticated({
|
|
314
|
+
headers: GITHUB_API_HEADERS,
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
await octokit.rest.repos.get({
|
|
318
|
+
owner: user.login,
|
|
319
|
+
repo: REPO_NAME,
|
|
320
|
+
headers: GITHUB_API_HEADERS,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
if (getErrorStatus(err) !== 404) {
|
|
325
|
+
throw err;
|
|
326
|
+
}
|
|
327
|
+
await octokit.rest.repos.createForAuthenticatedUser({
|
|
328
|
+
name: REPO_NAME,
|
|
329
|
+
private: true,
|
|
330
|
+
auto_init: true,
|
|
331
|
+
description: 'Private dango synchronization data.',
|
|
332
|
+
headers: GITHUB_API_HEADERS,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return user.login;
|
|
336
|
+
}
|
|
337
|
+
async getSyncFile(octokit, owner) {
|
|
338
|
+
try {
|
|
339
|
+
const response = await octokit.rest.repos.getContent({
|
|
340
|
+
owner,
|
|
341
|
+
repo: REPO_NAME,
|
|
342
|
+
path: getSyncFilename(),
|
|
343
|
+
headers: GITHUB_API_HEADERS,
|
|
344
|
+
});
|
|
345
|
+
const data = response.data;
|
|
346
|
+
if (data.type !== 'file' || !data.content || !data.sha) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
content: Buffer.from(data.content, 'base64').toString('utf8'),
|
|
351
|
+
sha: data.sha,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
if (getErrorStatus(err) === 404) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
throw err;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async fetchSyncPayload() {
|
|
362
|
+
const octokit = await this.getOctokit();
|
|
363
|
+
const owner = await this.ensureRepo(octokit);
|
|
364
|
+
const file = await this.getSyncFile(octokit, owner);
|
|
365
|
+
if (!file) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return normalizePayload(JSON.parse(file.content));
|
|
369
|
+
}
|
|
370
|
+
async exportDatabase(db) {
|
|
371
|
+
const tables = {};
|
|
372
|
+
for (const table of SYNC_TABLES) {
|
|
373
|
+
const rows = await getRowsFromAll(db, `SELECT * FROM ${quoteIdentifier(table)}`);
|
|
374
|
+
tables[table] = rows.filter((row) => !(0, temp_ids_1.isTempSyncRow)(row));
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
version: readVersion({ version: 0, exportedAt: '', tables }),
|
|
378
|
+
exportedAt: new Date().toISOString(),
|
|
379
|
+
tables,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
importDatabase(db, payload) {
|
|
383
|
+
db.serialize(() => {
|
|
384
|
+
for (const table of SYNC_TABLES) {
|
|
385
|
+
db.run(`DELETE FROM ${quoteIdentifier(table)}`);
|
|
386
|
+
}
|
|
387
|
+
for (const table of SYNC_TABLES) {
|
|
388
|
+
for (const row of payload.tables[table]) {
|
|
389
|
+
if ((0, temp_ids_1.isTempSyncRow)(row))
|
|
390
|
+
continue;
|
|
391
|
+
const columns = Object.keys(row);
|
|
392
|
+
if (columns.length === 0)
|
|
393
|
+
continue;
|
|
394
|
+
const columnSql = columns.map(quoteIdentifier).join(', ');
|
|
395
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
396
|
+
const values = columns.map((column) => row[column]);
|
|
397
|
+
db.run(`INSERT INTO ${quoteIdentifier(table)} (${columnSql}) VALUES (${placeholders})`, values);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
exports.githubSyncService = new GitHubSyncService();
|