mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	
		
			
	
	
		
			111 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			111 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/Notes"; | ||
|  | 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(); | ||
|  | })(); |