trilium/scripts/port-discussions.ts

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();
})();