mirror of
https://github.com/zadam/trilium.git
synced 2025-10-02 11:35:41 +08:00
110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
/**
|
|
* @module
|
|
*
|
|
* Goes through all discussions in the source repository and transfers them to the target repository.
|
|
*
|
|
* Limitations:
|
|
* - Upon encountering a locked discussion, the script will fail. Make sure to unlock discussions before running the script.
|
|
*/
|
|
|
|
import { type BrowserContext, chromium } from 'playwright';
|
|
import { createWriteStream, existsSync, readFileSync, writeFileSync } from 'fs';
|
|
|
|
const SOURCE_URL = "https://github.com/TriliumNext/Trilium";
|
|
const TARGET_REPOSITORY_ID = 92111509;
|
|
|
|
const fsLog = createWriteStream('port-discussions.log', { flags: 'a' });
|
|
|
|
async function login(context: BrowserContext) {
|
|
const page = await context.newPage();
|
|
await page.goto('https://github.com/login');
|
|
|
|
console.log("👤 Please log in manually in the opened browser...");
|
|
await page.waitForNavigation({ url: 'https://github.com/' }); // Wait for login
|
|
|
|
// Save storage state (cookies, localStorage, etc.)
|
|
const storage = await context.storageState();
|
|
writeFileSync('auth.json', JSON.stringify(storage))
|
|
await page.close();
|
|
}
|
|
|
|
async function portIssue(issue: string, context: BrowserContext) {
|
|
const page = await context.newPage();
|
|
await page.goto(`${SOURCE_URL}/discussions/${issue}`);
|
|
|
|
const button = page.locator("#dialog-show-discussion-transfer-conversation");
|
|
await button.click();
|
|
|
|
const modal = page.locator("#discussion-transfer-conversation");
|
|
const modalContent = page.locator("#transfer-candidate-repos");
|
|
await modalContent.waitFor({ state: 'visible' });
|
|
|
|
modalContent.locator(`#transfer_repository_${TARGET_REPOSITORY_ID}`).click();
|
|
const navigationPromise = page.waitForNavigation({
|
|
waitUntil: "domcontentloaded"
|
|
});
|
|
|
|
const submitButton = modal.locator(`button[type="submit"]`);
|
|
await submitButton.waitFor({ state: 'attached' });
|
|
await submitButton.click();
|
|
|
|
await navigationPromise;
|
|
console.log(`✅ Discussion ${issue} has been transferred to the target repository.`);
|
|
fsLog.write(`Transferred discussion ${issue} to ${page.url()}\n`);
|
|
await page.waitForTimeout(2000); // Wait for a second to ensure the transfer is complete
|
|
await page.close();
|
|
}
|
|
|
|
async function getFirstPageResults(context: BrowserContext) {
|
|
const page = await context.newPage();
|
|
await page.goto(SOURCE_URL + "/discussions");
|
|
|
|
// Wait for the discussions to load
|
|
const allDiscussionLinks = (await (page.locator(`a[data-hovercard-type="discussion"]`).all()));
|
|
let ids: string[] = [];
|
|
for (const link of allDiscussionLinks) {
|
|
const url = await link.getAttribute('href');
|
|
const number = url?.match(/\/discussions\/(\d+)/)?.[1];
|
|
ids.push(number);
|
|
}
|
|
console.log(`Found ${ids.length} discussions.`);
|
|
await page.close();
|
|
return ids;
|
|
}
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({ headless: false }); // show browser
|
|
let storageState = undefined;
|
|
if (existsSync('auth.json')) {
|
|
console.log("🔑 Using existing authentication state...");
|
|
storageState = JSON.parse(readFileSync('auth.json', 'utf-8'));
|
|
}
|
|
|
|
const context = await browser.newContext({ storageState });
|
|
if (!storageState) {
|
|
await login(context);
|
|
}
|
|
|
|
const travelledIds: string[] = [];
|
|
let ids = await getFirstPageResults(context);
|
|
|
|
while (ids.length > 0) {
|
|
for (const id of ids) {
|
|
try {
|
|
if (travelledIds.includes(id)) {
|
|
console.log(`Discussion ${id} has already been transferred.`);
|
|
process.exit(2);
|
|
}
|
|
|
|
await portIssue(id, context);
|
|
travelledIds.push(id);
|
|
} catch (error) {
|
|
console.error(`❌ Error transferring discussion ${id}:`, error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
ids = await getFirstPageResults(context);
|
|
}
|
|
await browser.close();
|
|
})();
|