From 0cfd9b687978a4afbf0d33a39556332213764e61 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 6 Feb 2025 17:16:59 -0800 Subject: [PATCH 01/26] Add Facebook to the account type selection view --- src/renderer/src/util.ts | 2 ++ src/renderer/src/views/AccountView.vue | 26 ++++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/util.ts b/src/renderer/src/util.ts index 84d14bb5..18ba19c0 100644 --- a/src/renderer/src/util.ts +++ b/src/renderer/src/util.ts @@ -59,6 +59,8 @@ export function getAccountIcon(accountType: string): string { // Not using the real X logo to avoid trademark issues // return "fa-brands fa-x-twitter"; return "fa-solid fa-xmark"; + case "Facebook": + return "fa-brands fa-facebook"; case "Bluesky": return "fa-brands fa-bluesky"; default: diff --git a/src/renderer/src/views/AccountView.vue b/src/renderer/src/views/AccountView.vue index 910f4f41..dd57b5ce 100644 --- a/src/renderer/src/views/AccountView.vue +++ b/src/renderer/src/views/AccountView.vue @@ -66,9 +66,27 @@ onMounted(async () => {
X
-
- Formerly Twitter, owned by Elon Musk + + Formerly Twitter, owned by billionaire Elon Musk + +
+ + + + +
+ @@ -134,8 +152,4 @@ onMounted(async () => { font-size: 1.2rem; font-weight: bold; } - -.select-account .description .info { - font-size: 1rem; -} \ No newline at end of file From c3f35ede903ea193c82d91530b81025c9b324938 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 10:34:27 -0800 Subject: [PATCH 02/26] Add FacebookAccount table --- src/database/account.ts | 76 +++++++++++---------- src/database/facebook_account.ts | 113 +++++++++++++++++++++++++++++++ src/database/migrations.ts | 25 +++++++ src/shared_types/account.ts | 20 ++++++ 4 files changed, 199 insertions(+), 35 deletions(-) create mode 100644 src/database/facebook_account.ts diff --git a/src/database/account.ts b/src/database/account.ts index d5eb5b20..c986629b 100644 --- a/src/database/account.ts +++ b/src/database/account.ts @@ -3,7 +3,8 @@ import { ipcMain, session } from 'electron'; import { exec, getMainDatabase, Sqlite3Info } from './common'; import { createXAccount, getXAccount, saveXAccount } from './x_account'; import { createBlueskyAccount, getBlueskyAccount, saveBlueskyAccount } from './bluesky_account'; -import { Account, XAccount, BlueskyAccount } from '../shared_types' +import { createFacebookAccount, getFacebookAccount, saveFacebookAccount } from './facebook_account'; +import { Account, XAccount, BlueskyAccount, FacebookAccount } from '../shared_types' import { packageExceptionForReport } from "../util" // Types @@ -14,19 +15,14 @@ interface AccountRow { sortOrder: number; xAccountId: number | null; blueskyAccountID: number | null; + facebookAccountID: number | null; uuid: string; } -// Functions - -export const getAccount = (id: number): Account | null => { - const row: AccountRow | undefined = exec(getMainDatabase(), 'SELECT * FROM account WHERE id = ?', [id], 'get') as AccountRow | undefined; - if (!row) { - return null; - } - +function accountFromAccountRow(row: AccountRow): Account { let xAccount: XAccount | null = null; let blueskyAccount: BlueskyAccount | null = null; + let facebookAccount: FacebookAccount | null = null; switch (row.type) { case "X": if (row.xAccountId) { @@ -39,6 +35,12 @@ export const getAccount = (id: number): Account | null => { blueskyAccount = getBlueskyAccount(row.blueskyAccountID); } break; + + case "Facebook": + if (row.facebookAccountID) { + facebookAccount = getFacebookAccount(row.facebookAccountID); + } + break; } return { @@ -47,15 +49,29 @@ export const getAccount = (id: number): Account | null => { sortOrder: row.sortOrder, xAccount: xAccount, blueskyAccount: blueskyAccount, + facebookAccount: facebookAccount, uuid: row.uuid }; } +// Functions + +export const getAccount = (id: number): Account | null => { + const row: AccountRow | undefined = exec(getMainDatabase(), 'SELECT * FROM account WHERE id = ?', [id], 'get') as AccountRow | undefined; + if (!row) { + return null; + } + + return accountFromAccountRow(row); +} + export async function getAccountUsername(account: Account): Promise { if (account.type == "X" && account.xAccount) { return account.xAccount?.username; } else if (account.type == "Bluesky" && account.blueskyAccount) { return account.blueskyAccount?.username; + } else if (account.type == "Facebook" && account.facebookAccount) { + return account.facebookAccount?.name; } return null; @@ -66,29 +82,7 @@ export const getAccounts = (): Account[] => { const accounts: Account[] = []; for (const row of rows) { - let xAccount: XAccount | null = null; - let blueskyAccount: BlueskyAccount | null = null; - switch (row.type) { - case "X": - if (row.xAccountId) { - xAccount = getXAccount(row.xAccountId); - } - break; - case "Bluesky": - if (row.blueskyAccountID) { - blueskyAccount = getBlueskyAccount(row.blueskyAccountID); - } - break - } - - accounts.push({ - id: row.id, - type: row.type, - sortOrder: row.sortOrder, - xAccount: xAccount, - blueskyAccount: blueskyAccount, - uuid: row.uuid - }); + accounts.push(accountFromAccountRow(row)); } return accounts; } @@ -129,12 +123,16 @@ export const selectAccountType = (accountID: number, type: string): Account => { case "Bluesky": account.blueskyAccount = createBlueskyAccount(); break; + case "Facebook": + account.facebookAccount = createFacebookAccount(); + break; default: throw new Error("Unknown account type"); } const xAccountId = account.xAccount ? account.xAccount.id : null; const blueskyAccountID = account.blueskyAccount ? account.blueskyAccount.id : null; + const facebookAccountID = account.facebookAccount ? account.facebookAccount.id : null; // Update the account exec(getMainDatabase(), ` @@ -142,12 +140,14 @@ export const selectAccountType = (accountID: number, type: string): Account => { SET type = ?, xAccountId = ?, - blueskyAccountID = ? + blueskyAccountID = ?, + facebookAccountID = ? WHERE id = ? `, [ type, xAccountId, blueskyAccountID, + facebookAccountID, account.id ]); @@ -159,9 +159,10 @@ export const selectAccountType = (accountID: number, type: string): Account => { export const saveAccount = (account: Account) => { if (account.xAccount) { saveXAccount(account.xAccount); - } - else if (account.blueskyAccount) { + } else if (account.blueskyAccount) { saveBlueskyAccount(account.blueskyAccount); + } else if (account.facebookAccount) { + saveFacebookAccount(account.facebookAccount); } exec(getMainDatabase(), ` @@ -196,6 +197,11 @@ export const deleteAccount = (accountID: number) => { exec(getMainDatabase(), 'DELETE FROM blueskyAccount WHERE id = ?', [account.blueskyAccount.id]); } break; + case "Facebook": + if (account.facebookAccount) { + exec(getMainDatabase(), 'DELETE FROM facebookAccount WHERE id = ?', [account.facebookAccount.id]); + } + break; } // Delete the account diff --git a/src/database/facebook_account.ts b/src/database/facebook_account.ts new file mode 100644 index 00000000..39d07975 --- /dev/null +++ b/src/database/facebook_account.ts @@ -0,0 +1,113 @@ +import { exec, getMainDatabase, Sqlite3Info } from './common'; +import { FacebookAccount } from '../shared_types' + +// Types + +export interface FacebookAccountRow { + id: number; + createdAt: string; + updatedAt: string; + accessedAt: string; + accountID: string; + name: string; + profileImageDataURI: string; + saveMyData: boolean; + deleteMyData: boolean; + savePosts: boolean; + savePostsHTML: boolean; + deletePosts: boolean; + deletePostsDaysOldEnabled: boolean; + deletePostsDaysOld: number; + deletePostsReactsThresholdEnabled: boolean; + deletePostsReactsThreshold: number; +} + +function facebookAccountRowToFacebookAccount(row: FacebookAccountRow): FacebookAccount { + return { + id: row.id, + createdAt: new Date(row.createdAt), + updatedAt: new Date(row.updatedAt), + accessedAt: new Date(row.accessedAt), + accountID: row.accountID, + name: row.name, + profileImageDataURI: row.profileImageDataURI, + saveMyData: !!row.saveMyData, + deleteMyData: !!row.deleteMyData, + savePosts: !!row.savePosts, + savePostsHTML: !!row.savePostsHTML, + deletePosts: !!row.deletePosts, + deletePostsDaysOldEnabled: !!row.deletePostsDaysOldEnabled, + deletePostsDaysOld: row.deletePostsDaysOld, + deletePostsReactsThresholdEnabled: !!row.deletePostsReactsThresholdEnabled, + deletePostsReactsThreshold: row.deletePostsReactsThreshold + }; +} + +// Functions + +// Get a single Facebook account by ID +export const getFacebookAccount = (id: number): FacebookAccount | null => { + const row: FacebookAccountRow | undefined = exec(getMainDatabase(), 'SELECT * FROM facebookAccount WHERE id = ?', [id], 'get') as FacebookAccountRow | undefined; + if (!row) { + return null; + } + return facebookAccountRowToFacebookAccount(row); +} + +// Get all Facebook accounts +export const getFacebookAccounts = (): FacebookAccount[] => { + const rows: FacebookAccountRow[] = exec(getMainDatabase(), 'SELECT * FROM facebookAccount', [], 'all') as FacebookAccountRow[]; + + const accounts: FacebookAccount[] = []; + for (const row of rows) { + accounts.push(facebookAccountRowToFacebookAccount(row)); + } + return accounts; +} + +// Create a new Facebook account +export const createFacebookAccount = (): FacebookAccount => { + const info: Sqlite3Info = exec(getMainDatabase(), 'INSERT INTO facebookAccount DEFAULT VALUES') as Sqlite3Info; + const account = getFacebookAccount(info.lastInsertRowid); + if (!account) { + throw new Error("Failed to create account"); + } + return account; +} + +// Update the Facebook account based on account.id +export const saveFacebookAccount = (account: FacebookAccount) => { + exec(getMainDatabase(), ` + UPDATE facebookAccount + SET + updatedAt = CURRENT_TIMESTAMP, + accessedAt = CURRENT_TIMESTAMP, + accountID = ?, + name = ?, + profileImageDataURI = ?, + saveMyData = ?, + deleteMyData = ?, + savePosts = ?, + savePostsHTML = ?, + deletePosts = ?, + deletePostsDaysOldEnabled = ?, + deletePostsDaysOld = ?, + deletePostsReactsThresholdEnabled = ?, + deletePostsReactsThreshold = ? + WHERE id = ? + `, [ + account.accountID, + account.name, + account.profileImageDataURI, + account.saveMyData ? 1 : 0, + account.deleteMyData ? 1 : 0, + account.savePosts ? 1 : 0, + account.savePostsHTML ? 1 : 0, + account.deletePosts ? 1 : 0, + account.deletePostsDaysOldEnabled ? 1 : 0, + account.deletePostsDaysOld, + account.deletePostsReactsThresholdEnabled ? 1 : 0, + account.deletePostsReactsThreshold, + account.id + ]); +} diff --git a/src/database/migrations.ts b/src/database/migrations.ts index 3abe8afa..19dc92ba 100644 --- a/src/database/migrations.ts +++ b/src/database/migrations.ts @@ -143,5 +143,30 @@ export const runMainMigrations = () => { `ALTER TABLE account ADD COLUMN blueskyAccountID INTEGER DEFAULT NULL;`, ] }, + // Add Facebook table, and facebookAccountID to account + { + name: "add Facebook table, and facebookAccountID to account", + sql: [ + `CREATE TABLE facebookAccount ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, + accessedAt DATETIME DEFAULT CURRENT_TIMESTAMP, + accountID TEXT, + name TEXT, + profileImageDataURI TEXT, + saveMyData BOOLEAN DEFAULT 1, + deleteMyData BOOLEAN DEFAULT 0, + savePosts BOOLEAN DEFAULT 1, + savePostsHTML BOOLEAN DEFAULT 0, + deletePosts BOOLEAN DEFAULT 1, + deletePostsDaysOldEnabled BOOLEAN DEFAULT 0, + deletePostsDaysOld INTEGER DEFAULT 0, + deletePostsReactsThresholdEnabled BOOLEAN DEFAULT 0, + deletePostsReactsThreshold INTEGER DEFAULT 20 +);`, + `ALTER TABLE account ADD COLUMN facebookAccountID INTEGER DEFAULT NULL;`, + ] + }, ]); } \ No newline at end of file diff --git a/src/shared_types/account.ts b/src/shared_types/account.ts index 9d85da22..418ecd0c 100644 --- a/src/shared_types/account.ts +++ b/src/shared_types/account.ts @@ -4,6 +4,7 @@ export type Account = { sortOrder: number; xAccount: XAccount | null; blueskyAccount: BlueskyAccount | null; + facebookAccount: FacebookAccount | null; uuid: string; } @@ -72,4 +73,23 @@ export type BlueskyAccount = { followersCount: number; postsCount: number; likesCount: number; +} + +export type FacebookAccount = { + id: number; + createdAt: Date; + updatedAt: Date; + accessedAt: Date; + accountID: string; + name: string; + profileImageDataURI: string; + saveMyData: boolean; + deleteMyData: boolean; + savePosts: boolean; + savePostsHTML: boolean; + deletePosts: boolean; + deletePostsDaysOldEnabled: boolean; + deletePostsDaysOld: number; + deletePostsReactsThresholdEnabled: boolean; + deletePostsReactsThreshold: number; } \ No newline at end of file From 17838981f6f533b2cc3a687d2505859dd272d36d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 10:49:17 -0800 Subject: [PATCH 03/26] Make X database functions more DRY too --- src/database/x_account.ts | 56 +++++++++------------------------------ 1 file changed, 13 insertions(+), 43 deletions(-) diff --git a/src/database/x_account.ts b/src/database/x_account.ts index 4885bd87..94dc8f43 100644 --- a/src/database/x_account.ts +++ b/src/database/x_account.ts @@ -39,13 +39,7 @@ interface XAccountRow { likesCount: number; } -// Functions - -export const getXAccount = (id: number): XAccount | null => { - const row: XAccountRow | undefined = exec(getMainDatabase(), 'SELECT * FROM xAccount WHERE id = ?', [id], 'get') as XAccountRow | undefined; - if (!row) { - return null; - } +function xAccountRowtoXAccount(row: XAccountRow): XAccount { return { id: row.id, createdAt: new Date(row.createdAt), @@ -80,7 +74,17 @@ export const getXAccount = (id: number): XAccount | null => { followersCount: row.followersCount, tweetsCount: row.tweetsCount, likesCount: row.likesCount - }; + } +} + +// Functions + +export const getXAccount = (id: number): XAccount | null => { + const row: XAccountRow | undefined = exec(getMainDatabase(), 'SELECT * FROM xAccount WHERE id = ?', [id], 'get') as XAccountRow | undefined; + if (!row) { + return null; + } + return xAccountRowtoXAccount(row); } export const getXAccounts = (): XAccount[] => { @@ -88,41 +92,7 @@ export const getXAccounts = (): XAccount[] => { const accounts: XAccount[] = []; for (const row of rows) { - accounts.push({ - id: row.id, - createdAt: new Date(row.createdAt), - updatedAt: new Date(row.updatedAt), - accessedAt: new Date(row.accessedAt), - username: row.username, - profileImageDataURI: row.profileImageDataURI, - importFromArchive: !!row.importFromArchive, - saveMyData: !!row.saveMyData, - deleteMyData: !!row.deleteMyData, - archiveMyData: !!row.archiveMyData, - archiveTweets: !!row.archiveTweets, - archiveTweetsHTML: !!row.archiveTweetsHTML, - archiveLikes: !!row.archiveLikes, - archiveBookmarks: !!row.archiveBookmarks, - archiveDMs: !!row.archiveDMs, - deleteTweets: !!row.deleteTweets, - deleteTweetsDaysOldEnabled: !!row.deleteTweetsDaysOldEnabled, - deleteTweetsDaysOld: row.deleteTweetsDaysOld, - deleteTweetsLikesThresholdEnabled: !!row.deleteTweetsLikesThresholdEnabled, - deleteTweetsLikesThreshold: row.deleteTweetsLikesThreshold, - deleteTweetsRetweetsThresholdEnabled: !!row.deleteTweetsRetweetsThresholdEnabled, - deleteTweetsRetweetsThreshold: row.deleteTweetsRetweetsThreshold, - deleteRetweets: !!row.deleteRetweets, - deleteRetweetsDaysOldEnabled: !!row.deleteRetweetsDaysOldEnabled, - deleteRetweetsDaysOld: row.deleteRetweetsDaysOld, - deleteLikes: !!row.deleteLikes, - deleteBookmarks: !!row.deleteBookmarks, - deleteDMs: !!row.deleteDMs, - unfollowEveryone: !!row.unfollowEveryone, - followingCount: row.followingCount, - followersCount: row.followersCount, - tweetsCount: row.tweetsCount, - likesCount: row.likesCount - }); + accounts.push(xAccountRowtoXAccount(row)); } return accounts; } From f2a54559348ed0f30befd8b6edda5eedff1c5ebe Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 11:09:57 -0800 Subject: [PATCH 04/26] Rename AccountXView to XView, and rename AccountXViewModel to XViewModel --- .../src/view_models/FacebookViewModel.ts | 0 .../{AccountXViewModel.ts => XViewModel.ts} | 2 +- src/renderer/src/views/AccountView.vue | 4 +- .../src/views/facebook/FacebookView.vue | 434 ++++++++++++++++++ .../src/views/x/XFinishedRunningJobsPage.vue | 6 +- src/renderer/src/views/x/XJobDeleteTweets.vue | 4 +- .../views/x/XLastImportOrBuildComponent.vue | 2 +- .../views/x/{AccountXView.vue => XView.vue} | 4 +- .../src/views/x/XWizardArchiveOptionsPage.vue | 4 +- .../src/views/x/XWizardBuildOptionsPage.vue | 6 +- .../src/views/x/XWizardCheckPremium.vue | 6 +- .../src/views/x/XWizardDeleteOptionsPage.vue | 6 +- .../src/views/x/XWizardImportDownloadPage.vue | 2 +- .../src/views/x/XWizardImportOrBuildPage.vue | 6 +- .../src/views/x/XWizardImportPage.vue | 6 +- .../src/views/x/XWizardImportingPage.vue | 6 +- .../src/views/x/XWizardReviewPage.vue | 6 +- src/renderer/src/views/x/XWizardSidebar.vue | 6 +- 18 files changed, 472 insertions(+), 38 deletions(-) create mode 100644 src/renderer/src/view_models/FacebookViewModel.ts rename src/renderer/src/view_models/{AccountXViewModel.ts => XViewModel.ts} (99%) create mode 100644 src/renderer/src/views/facebook/FacebookView.vue rename src/renderer/src/views/x/{AccountXView.vue => XView.vue} (99%) diff --git a/src/renderer/src/view_models/FacebookViewModel.ts b/src/renderer/src/view_models/FacebookViewModel.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/renderer/src/view_models/AccountXViewModel.ts b/src/renderer/src/view_models/XViewModel.ts similarity index 99% rename from src/renderer/src/view_models/AccountXViewModel.ts rename to src/renderer/src/view_models/XViewModel.ts index cad671fc..82bfab83 100644 --- a/src/renderer/src/view_models/AccountXViewModel.ts +++ b/src/renderer/src/view_models/XViewModel.ts @@ -85,7 +85,7 @@ export type XViewModelState = { currentJobIndex: number; } -export class AccountXViewModel extends BaseViewModel { +export class XViewModel extends BaseViewModel { public progress: XProgress = emptyXProgress(); public rateLimitInfo: XRateLimitInfo = emptyXRateLimitInfo(); public progressInfo: XProgressInfo = emptyXProgressInfo(); diff --git a/src/renderer/src/views/AccountView.vue b/src/renderer/src/views/AccountView.vue index dd57b5ce..062c6ec2 100644 --- a/src/renderer/src/views/AccountView.vue +++ b/src/renderer/src/views/AccountView.vue @@ -1,6 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/renderer/src/views/x/XFinishedRunningJobsPage.vue b/src/renderer/src/views/x/XFinishedRunningJobsPage.vue index d12fb4fb..f9f89844 100644 --- a/src/renderer/src/views/x/XFinishedRunningJobsPage.vue +++ b/src/renderer/src/views/x/XFinishedRunningJobsPage.vue @@ -5,10 +5,10 @@ import { getCurrentInstance, } from 'vue'; import { - AccountXViewModel, + XViewModel, State, FailureState -} from '../../view_models/AccountXViewModel' +} from '../../view_models/XViewModel' import { openURL } from '../../util'; // Get the global emitter @@ -17,7 +17,7 @@ const emitter = vueInstance?.appContext.config.globalProperties.emitter; // Props const props = defineProps<{ - model: AccountXViewModel; + model: XViewModel; // eslint-disable-next-line vue/prop-name-casing failureStateIndexTweets_FailedToRetryAfterRateLimit: boolean; // eslint-disable-next-line vue/prop-name-casing diff --git a/src/renderer/src/views/x/XJobDeleteTweets.vue b/src/renderer/src/views/x/XJobDeleteTweets.vue index 0f1bc530..ab4d7661 100644 --- a/src/renderer/src/views/x/XJobDeleteTweets.vue +++ b/src/renderer/src/views/x/XJobDeleteTweets.vue @@ -1,13 +1,13 @@ + + + + \ No newline at end of file diff --git a/src/renderer/src/views/shared_components/U2FNotice.vue b/src/renderer/src/views/shared_components/U2FNotice.vue new file mode 100644 index 00000000..1bf0e930 --- /dev/null +++ b/src/renderer/src/views/shared_components/U2FNotice.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/views/x/XView.vue b/src/renderer/src/views/x/XView.vue index 29e13ccf..0e37743c 100644 --- a/src/renderer/src/views/x/XView.vue +++ b/src/renderer/src/views/x/XView.vue @@ -16,6 +16,8 @@ import { UserPremiumAPIResponse } from "../../../../cyd-api-client"; import AccountHeader from '../shared_components/AccountHeader.vue'; import SpeechBubble from '../shared_components/SpeechBubble.vue'; +import U2FNotice from '../shared_components/U2FNotice.vue'; +import AutomationNotice from '../shared_components/AutomationNotice.vue'; import XProgressComponent from './XProgressComponent.vue'; import XJobStatusComponent from './XJobStatusComponent.vue'; @@ -43,7 +45,7 @@ import type { import type { DeviceInfo } from '../../types'; import { AutomationErrorType } from '../../automation_errors'; import { XViewModel, State, RunJobsState, FailureState, XViewModelState } from '../../view_models/XViewModel' -import { setAccountRunning, openURL } from '../../util'; +import { setAccountRunning, showQuestionOpenModePremiumFeature } from '../../util'; import { xRequiresPremium, xPostProgress } from '../../util_x'; // Get the global emitter @@ -227,13 +229,13 @@ const updateUserPremium = async () => { }; emitter?.on('signed-in', async () => { - console.log('AccountXView: User signed in'); + console.log('XView: User signed in'); await updateUserAuthenticated(); await updateUserPremium(); }); emitter?.on('signed-out', async () => { - console.log('AccountXView: User signed out'); + console.log('XView: User signed out'); userAuthenticated.value = false; userPremium.value = false; }); @@ -247,7 +249,7 @@ const startJobs = async () => { if (model.value.account?.xAccount && await xRequiresPremium(model.value.account?.xAccount)) { // In open mode, allow the user to continue if (await window.electron.getMode() == "open") { - if (!await window.electron.showQuestion("You're about to run a job that normally requires Premium access, but you're running Cyd in open source developer mode, so you don't have to authenticate with the Cyd server to use these features.\n\nIf you're not contributing to Cyd, please support the project by paying for a Premium plan.", "Continue", "Cancel")) { + if (!await showQuestionOpenModePremiumFeature()) { return; } } @@ -426,24 +428,8 @@ onUnmounted(async () => {
- -

- - If you use a U2F security key (like a Yubikey) for 2FA, press it when you see a white - screen. Read more. -

- - -

- I'm following your instructions. Feel free to switch windows and use - your computer for other things. -

- - -

- Ready for input. -

+ + diff --git a/src/shared_types/facebook.ts b/src/shared_types/facebook.ts new file mode 100644 index 00000000..1da88710 --- /dev/null +++ b/src/shared_types/facebook.ts @@ -0,0 +1,24 @@ +// Facebook models + +export type FacebookJob = { + id: number | null; + jobType: string; // "login", "...", + status: string; // "pending", "running", "finished", "failed", "canceled" + scheduledAt: Date; + startedAt: Date | null; + finishedAt: Date | null; + progressJSON: string; + error: string | null; +}; + +// Other Facebook types + +export type FacebookProgress = { + currentJob: string; +} + +export function emptyFacebookProgress(): FacebookProgress { + return { + currentJob: "" + }; +} diff --git a/src/shared_types/index.ts b/src/shared_types/index.ts index d35a1abb..c841f65e 100644 --- a/src/shared_types/index.ts +++ b/src/shared_types/index.ts @@ -1,3 +1,4 @@ export * from './common'; export * from './account'; -export * from './x'; \ No newline at end of file +export * from './x'; +export * from './facebook'; \ No newline at end of file From cbe103f6e69fe1846208803b131052d1b795454d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 13:34:29 -0800 Subject: [PATCH 06/26] Add Facebook automation error types and plausible event types --- src/renderer/src/automation_errors.ts | 10 ++++++++++ src/renderer/src/types.ts | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/src/renderer/src/automation_errors.ts b/src/renderer/src/automation_errors.ts index 46d996f0..d4614f21 100644 --- a/src/renderer/src/automation_errors.ts +++ b/src/renderer/src/automation_errors.ts @@ -1,4 +1,5 @@ export enum AutomationErrorType { + // X X_manualBugReport = "X_manualBugReport", X_login_FailedToGetUsername = "X_login_FailedToGetUsername", X_login_URLChanged = "X_login_URLChanged", @@ -74,9 +75,14 @@ export enum AutomationErrorType { x_unknownError = "x_unknown", x_loadURLError = "x_loadURLError", x_loadURLURLChanged = "x_loadURLURLChanged", + + // Facebook + facebook_manualBugReport = "facebook_manualBugReport", + facebook_unknownError = "facebook_unknown", } export const AutomationErrorTypeToMessage = { + // X [AutomationErrorType.X_manualBugReport]: "You're manually reporting a bug", [AutomationErrorType.X_login_FailedToGetUsername]: "Failed to get username on login", [AutomationErrorType.X_login_URLChanged]: "URL changed on login", @@ -152,4 +158,8 @@ export const AutomationErrorTypeToMessage = { [AutomationErrorType.x_unknownError]: "An unknown error occurred", [AutomationErrorType.x_loadURLError]: "Error while loading URL", [AutomationErrorType.x_loadURLURLChanged]: "URL changed after loading", + + // Facebook + [AutomationErrorType.facebook_manualBugReport]: "You're manually reporting a bug", + [AutomationErrorType.facebook_unknownError]: "An unknown error occurred", } diff --git a/src/renderer/src/types.ts b/src/renderer/src/types.ts index 2eac9478..0e5903cb 100644 --- a/src/renderer/src/types.ts +++ b/src/renderer/src/types.ts @@ -13,6 +13,7 @@ export const PlausibleEvents = Object.freeze({ AUTOMATION_ERROR_REPORT_SUBMITTED: 'Automation Error Report Submitted', AUTOMATION_ERROR_REPORT_NOT_SUBMITTED: 'Automation Error Report Not Submitted', AUTOMATION_ERROR_REPORT_ERROR: 'Automation Error Report Error', + X_USER_SIGNED_IN: 'X User Signed In', X_JOB_STARTED_LOGIN: 'X Job Started: login', X_JOB_STARTED_INDEX_TWEETS: 'X Job Started: indexTweets', @@ -28,4 +29,11 @@ export const PlausibleEvents = Object.freeze({ X_JOB_STARTED_DELETE_DMS: 'X Job Started: deleteDMs', X_JOB_STARTED_ARCHIVE_BUILD: 'X Job Started: archiveBuild', X_JOB_STARTED_UNFOLLOW_EVERYONE: 'X Job Started: unfollowEveryone', + + FACEBOOK_USER_SIGNED_IN: 'Facebook User Signed In', + FACEBOOK_JOB_STARTED_LOGIN: 'Facebook Job Started: login', + FACEBOOK_JOB_STARTED_SAVE_POSTS: 'Facebook Job Started: savePosts', + FACEBOOK_JOB_STARTED_SAVE_POSTS_HTML: 'Facebook Job Started: savePostsHTML', + FACEBOOK_JOB_STARTED_DELETE_POSTS: 'Facebook Job Started: deletePosts', + FACEBOOK_JOB_STARTED_ARCHIVE_BUILD: 'Facebook Job Started: archiveBuild', }); From 9b19b047b4e3a46443ca7ec3f8d855aa74b98e10 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 13:41:23 -0800 Subject: [PATCH 07/26] Comment out stuff that is not implemented yet --- .../src/views/facebook/FacebookView.vue | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/views/facebook/FacebookView.vue b/src/renderer/src/views/facebook/FacebookView.vue index 47088f29..3637f27e 100644 --- a/src/renderer/src/views/facebook/FacebookView.vue +++ b/src/renderer/src/views/facebook/FacebookView.vue @@ -24,10 +24,9 @@ import type { FacebookJob, } from '../../../../shared_types'; import type { DeviceInfo } from '../../types'; -import { AutomationErrorType } from '../../automation_errors'; import { FacebookViewModel, State, FacebookViewModelState } from '../../view_models/FacebookViewModel' -import { setAccountRunning, showQuestionOpenModePremiumFeature } from '../../util'; -import { facebookRequiresPremium, facebookPostProgress } from '../../util_facebook'; +import { setAccountRunning } from '../../util'; +import { facebookPostProgress } from '../../util_facebook'; // Get the global emitter const vueInstance = getCurrentInstance(); @@ -143,20 +142,20 @@ const onCancelAutomation = () => { emit('onRefreshClicked'); }; -const onReportBug = async () => { - console.log('Report bug clicked'); +// const onReportBug = async () => { +// console.log('Report bug clicked'); - // Pause - model.value.pause(); +// // Pause +// model.value.pause(); - // Submit error report - await model.value.error(AutomationErrorType.Facebook_manualBugReport, { - message: 'User is manually reporting a bug', - state: model.value.saveState() - }, { - currentURL: model.value.webview?.getURL() - }); -} +// // Submit error report +// await model.value.error(AutomationErrorType.facebook_manualBugReport, { +// message: 'User is manually reporting a bug', +// state: model.value.saveState() +// }, { +// currentURL: model.value.webview?.getURL() +// }); +// } // User variables const userAuthenticated = ref(false); From c281fb4dd5b5e6d7912889560729a0d3a1b039e0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 14:33:09 -0800 Subject: [PATCH 08/26] Add FacebookAccountController to the main process, and hook up IPC --- .../facebook_account_controller.ts | 196 ++++++++++++++++++ src/account_facebook/index.ts | 3 + src/account_facebook/ipc.ts | 95 +++++++++ src/account_facebook/types.ts | 31 +++ src/main.ts | 2 + src/preload.ts | 34 ++- src/renderer/src/main.ts | 58 ++++-- 7 files changed, 395 insertions(+), 24 deletions(-) create mode 100644 src/account_facebook/facebook_account_controller.ts create mode 100644 src/account_facebook/index.ts create mode 100644 src/account_facebook/ipc.ts create mode 100644 src/account_facebook/types.ts diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts new file mode 100644 index 00000000..0baa3f57 --- /dev/null +++ b/src/account_facebook/facebook_account_controller.ts @@ -0,0 +1,196 @@ +import path from 'path' + +import { session } from 'electron' +import log from 'electron-log/main'; +import Database from 'better-sqlite3' + +import { + getAccountDataPath, +} from '../util' +import { + FacebookAccount, + FacebookJob, + FacebookProgress, + emptyFacebookProgress, +} from '../shared_types' +import { + runMigrations, + getAccount, + exec, + getConfig, + setConfig, +} from '../database' +import { IMITMController } from '../mitm'; +import { + FacebookJobRow, + convertFacebookJobRowToFacebookJob, +} from './types' + +export class FacebookAccountController { + private accountUUID: string = ""; + // Making this public so it can be accessed in tests + public account: FacebookAccount | null = null; + private accountID: number = 0; + private accountDataPath: string = ""; + + // Making this public so it can be accessed in tests + public db: Database.Database | null = null; + + public mitmController: IMITMController; + private progress: FacebookProgress = emptyFacebookProgress(); + + constructor(accountID: number, mitmController: IMITMController) { + this.mitmController = mitmController; + + this.accountID = accountID; + this.refreshAccount(); + + // Monitor web request metadata + const ses = session.fromPartition(`persist:account-${this.accountID}`); + ses.webRequest.onCompleted((_details) => { + // TODO: Monitor for rate limits + }); + } + + cleanup() { + if (this.db) { + this.db.close(); + this.db = null; + } + } + + refreshAccount() { + // Load the account + const account = getAccount(this.accountID); + if (!account) { + log.error(`FacebookAccountController.refreshAccount: account ${this.accountID} not found`); + return; + } + + // Make sure it's a Facebook account + if (account.type != "Facebook") { + log.error(`FacebookAccountController.refreshAccount: account ${this.accountID} is not a Facebook account`); + return; + } + + // Get the account UUID + this.accountUUID = account.uuid; + log.debug(`FacebookAccountController.refreshAccount: accountUUID=${this.accountUUID}`); + + // Load the Facebook account + this.account = account.facebookAccount; + if (!this.account) { + log.error(`FacebookAccountController.refreshAccount: xAccount ${this.accountID} not found`); + return; + } + } + + initDB() { + if (!this.account || !this.account.accountID) { + log.error("FacebookAccountController: cannot initialize the database because the account is not found, or the account Facebook ID is not found", this.account, this.account?.accountID); + return; + } + + // Make sure the account data folder exists + this.accountDataPath = getAccountDataPath('X', this.account.name); + log.info(`FacebookAccountController.initDB: accountDataPath=${this.accountDataPath}`); + + // Open the database + this.db = new Database(path.join(this.accountDataPath, 'data.sqlite3'), {}); + this.db.pragma('journal_mode = WAL'); + runMigrations(this.db, [ + // Create the tables + { + name: "initial", + sql: [ + `CREATE TABLE job ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + jobType TEXT NOT NULL, + status TEXT NOT NULL, + scheduledAt DATETIME NOT NULL, + startedAt DATETIME, + finishedAt DATETIME, + progressJSON TEXT, + error TEXT +);`, + `CREATE TABLE config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL UNIQUE, + value TEXT NOT NULL +);` + ] + }, + ]) + log.info("FacebookAccountController.initDB: database initialized"); + } + + resetProgress(): FacebookProgress { + log.debug("FacebookAccountController.resetProgress"); + this.progress = emptyFacebookProgress(); + return this.progress; + } + + createJobs(jobTypes: string[]): FacebookJob[] { + if (!this.db) { + this.initDB(); + } + + // Cancel pending jobs + exec(this.db, "UPDATE job SET status = ? WHERE status = ?", ["canceled", "pending"]); + + // Create new pending jobs + jobTypes.forEach((jobType) => { + exec(this.db, 'INSERT INTO job (jobType, status, scheduledAt) VALUES (?, ?, ?)', [ + jobType, + 'pending', + new Date(), + ]); + }); + + // Select pending jobs + const jobs: FacebookJobRow[] = exec(this.db, "SELECT * FROM job WHERE status = ? ORDER BY id", ["pending"], "all") as FacebookJobRow[]; + return jobs.map(convertFacebookJobRowToFacebookJob); + } + + updateJob(job: FacebookJob) { + if (!this.db) { + this.initDB(); + } + + exec( + this.db, + 'UPDATE job SET status = ?, startedAt = ?, finishedAt = ?, progressJSON = ?, error = ? WHERE id = ?', + [job.status, job.startedAt ? job.startedAt : null, job.finishedAt ? job.finishedAt : null, job.progressJSON, job.error, job.id] + ); + } + + async archiveBuild() { + if (!this.db) { + this.initDB(); + } + + if (!this.account) { + return false; + } + + log.info("FacebookAccountController.archiveBuild: building archive"); + + // TODO: implement + } + + async syncProgress(progressJSON: string) { + this.progress = JSON.parse(progressJSON); + } + + async getProgress(): Promise { + return this.progress; + } + + async getConfig(key: string): Promise { + return getConfig(key, this.db); + } + + async setConfig(key: string, value: string) { + return setConfig(key, value, this.db); + } +} diff --git a/src/account_facebook/index.ts b/src/account_facebook/index.ts new file mode 100644 index 00000000..6414e0ee --- /dev/null +++ b/src/account_facebook/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export * from './facebook_account_controller'; +export * from './ipc'; diff --git a/src/account_facebook/ipc.ts b/src/account_facebook/ipc.ts new file mode 100644 index 00000000..9ca62723 --- /dev/null +++ b/src/account_facebook/ipc.ts @@ -0,0 +1,95 @@ +import { ipcMain } from 'electron' + +import { FacebookAccountController } from './facebook_account_controller'; + +import { + FacebookJob, + FacebookProgress, +} from '../shared_types' +import { getMITMController } from '../mitm'; +import { packageExceptionForReport } from '../util' + +const controllers: Record = {}; + +const getFacebookAccountController = (accountID: number): FacebookAccountController => { + if (!controllers[accountID]) { + controllers[accountID] = new FacebookAccountController(accountID, getMITMController(accountID)); + } + controllers[accountID].refreshAccount(); + return controllers[accountID]; +} + +export const defineIPCFacebook = () => { + ipcMain.handle('Facebook:resetProgress', async (_, accountID: number): Promise => { + try { + const controller = getFacebookAccountController(accountID); + return controller.resetProgress(); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:createJobs', async (_, accountID: number, jobTypes: string[]): Promise => { + try { + const controller = getFacebookAccountController(accountID); + return controller.createJobs(jobTypes); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:updateJob', async (_, accountID: number, jobJSON: string) => { + try { + const controller = getFacebookAccountController(accountID); + const job = JSON.parse(jobJSON) as FacebookJob; + controller.updateJob(job); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:archiveBuild', async (_, accountID: number): Promise => { + try { + const controller = getFacebookAccountController(accountID); + await controller.archiveBuild(); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:syncProgress', async (_, accountID: number, progressJSON: string) => { + try { + const controller = getFacebookAccountController(accountID); + await controller.syncProgress(progressJSON); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:getProgress', async (_, accountID: number): Promise => { + try { + const controller = getFacebookAccountController(accountID); + return await controller.getProgress(); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:getConfig', async (_, accountID: number, key: string): Promise => { + try { + const controller = getFacebookAccountController(accountID); + return await controller.getConfig(key); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + + ipcMain.handle('Facebook:setConfig', async (_, accountID: number, key: string, value: string): Promise => { + try { + const controller = getFacebookAccountController(accountID); + return await controller.setConfig(key, value); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); +}; diff --git a/src/account_facebook/types.ts b/src/account_facebook/types.ts new file mode 100644 index 00000000..6fba03b9 --- /dev/null +++ b/src/account_facebook/types.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { FacebookJob } from '../shared_types' + +// Models + +export interface FacebookJobRow { + id: number; + jobType: string; + status: string; + scheduledAt: string; + startedAt: string | null; + finishedAt: string | null; + progressJSON: string | null; + error: string | null; +} + +// Converters + +export function convertFacebookJobRowToFacebookJob(row: FacebookJobRow): FacebookJob { + return { + id: row.id, + jobType: row.jobType, + status: row.status, + scheduledAt: new Date(row.scheduledAt), + startedAt: row.startedAt ? new Date(row.startedAt) : null, + finishedAt: row.finishedAt ? new Date(row.finishedAt) : null, + progressJSON: row.progressJSON ? JSON.parse(row.progressJSON) : null, + error: row.error, + }; +} diff --git a/src/main.ts b/src/main.ts index d4d4148f..73c6198f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,7 @@ import { updateElectronApp, UpdateSourceType } from 'update-electron-app'; import * as database from './database'; import { defineIPCX } from './account_x'; +import { defineIPCFacebook } from './account_facebook'; import { defineIPCArchive } from './archive'; import { getUpdatesBaseURL, @@ -541,6 +542,7 @@ async function createWindow() { // Other IPC events database.defineIPCDatabase(); defineIPCX(); + defineIPCFacebook(); defineIPCArchive(); } // @ts-expect-error: typescript doesn't know about this global variable diff --git a/src/preload.ts b/src/preload.ts index 4bd0c308..3d4b2dec 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -2,8 +2,10 @@ import { contextBridge, ipcRenderer, FileFilter } from 'electron' import { ErrorReport, Account, - XProgress, + ResponseData, + // X XJob, + XProgress, XArchiveStartResponse, XIndexMessagesStartResponse, XDeleteTweetsStartResponse, @@ -11,10 +13,12 @@ import { XProgressInfo, XDatabaseStats, XDeleteReviewStats, - ResponseData, XArchiveInfo, XAccount, XImportArchiveResponse, + // Facebook + FacebookJob, + FacebookProgress, } from './shared_types' contextBridge.exposeInMainWorld('electron', { @@ -264,6 +268,32 @@ contextBridge.exposeInMainWorld('electron', { return ipcRenderer.invoke('X:setConfig', accountID, key, value); } }, + Facebook: { + resetProgress: (accountID: number): Promise => { + return ipcRenderer.invoke('Facebook:resetProgress', accountID); + }, + createJobs: (accountID: number, jobTypes: string[]): Promise => { + return ipcRenderer.invoke('Facebook:createJobs', accountID, jobTypes); + }, + updateJob: (accountID: number, jobJSON: string) => { + ipcRenderer.invoke('Facebook:updateJob', accountID, jobJSON); + }, + archiveBuild: (accountID: number): Promise => { + return ipcRenderer.invoke('Facebook:archiveBuild', accountID); + }, + syncProgress: (accountID: number, progressJSON: string) => { + ipcRenderer.invoke('Facebook:syncProgress', accountID, progressJSON); + }, + getProgress: (accountID: number): Promise => { + return ipcRenderer.invoke('Facebook:getProgress', accountID); + }, + getConfig: (accountID: number, key: string): Promise => { + return ipcRenderer.invoke('Facebook:getConfig', accountID, key); + }, + setConfig: (accountID: number, key: string, value: string): Promise => { + return ipcRenderer.invoke('Facebook:setConfig', accountID, key, value); + }, + }, // Handle events from the main process onPowerMonitorSuspend: (callback: () => void) => { ipcRenderer.on('powerMonitor:suspend', callback); diff --git a/src/renderer/src/main.ts b/src/renderer/src/main.ts index 25c17d13..5f44c7b2 100644 --- a/src/renderer/src/main.ts +++ b/src/renderer/src/main.ts @@ -8,19 +8,23 @@ import { createApp } from "vue"; import type { ErrorReport, Account, - XProgress, + ResponseData, + // X XJob, + XProgress, XArchiveStartResponse, XIndexMessagesStartResponse, XDeleteTweetsStartResponse, XRateLimitInfo, XProgressInfo, XDatabaseStats, - ResponseData, XDeleteReviewStats, XArchiveInfo, XAccount, XImportArchiveResponse, + // Facebook + FacebookProgress, + FacebookJob, } from "../../shared_types"; import App from "./App.vue"; @@ -29,7 +33,7 @@ import { FileFilter } from "electron"; declare global { interface Window { electron: { - checkForUpdates: () => void; + checkForUpdates: () => Promise; getVersion: () => Promise; getMode: () => Promise; getPlatform: () => Promise; @@ -37,30 +41,30 @@ declare global { getDashURL: () => Promise; trackEvent: (eventName: string, userAgent: string) => Promise; shouldOpenDevtools: () => Promise; - showMessage: (message: string, detail: string) => void; - showError: (message: string) => void; + showMessage: (message: string, detail: string) => Promise; + showError: (message: string) => Promise; showQuestion: (message: string, trueText: string, falseText: string) => Promise; showOpenDialog: (selectFolders: boolean, selectFiles: boolean, fileFilters: FileFilter[] | undefined) => Promise; - openURL: (url: string) => void; - loadFileInWebview: (webContentsId: number, filename: string) => void; + openURL: (url: string) => Promise; + loadFileInWebview: (webContentsId: number, filename: string) => Promise; getAccountDataPath: (accountID: number, filename: string) => Promise, startPowerSaveBlocker: () => Promise; - stopPowerSaveBlocker: (powerSaveBlockerID: number) => void; - deleteSettingsAndRestart: () => void; + stopPowerSaveBlocker: (powerSaveBlockerID: number) => Promise; + deleteSettingsAndRestart: () => Promise; database: { getConfig: (key: string) => Promise; - setConfig: (key: string, value: string) => void; + setConfig: (key: string, value: string) => Promise; getErrorReport: (id: number) => Promise; getNewErrorReports: (accountID: number) => Promise; createErrorReport: (accountID: number, accountType: string, errorReportType: string, errorReportData: string, accountUsername: string | null, screenshotDataURI: string | null, sensitiveContextData: string | null) => Promise; - updateErrorReportSubmitted: (id: number) => void; - dismissNewErrorReports: (accountID: number) => void; + updateErrorReportSubmitted: (id: number) => Promise; + dismissNewErrorReports: (accountID: number) => Promise; getAccount: (accountID: number) => Promise; getAccounts: () => Promise; createAccount: () => Promise; selectAccountType: (accountID: number, type: string) => Promise; - saveAccount: (accountJSON: string) => void; - deleteAccount: (accountID: number) => void; + saveAccount: (accountJSON: string) => Promise; + deleteAccount: (accountID: number) => Promise; }, archive: { isPageAlreadySaved: (outputPath: string, basename: string) => Promise; @@ -70,9 +74,9 @@ declare global { resetProgress: (accountID: number) => Promise; createJobs: (accountID: number, jobTypes: string[]) => Promise; getLastFinishedJob: (accountID: number, jobType: string) => Promise; - updateJob: (accountID: number, jobJSON: string) => void; - indexStart: (accountID: number) => void; - indexStop: (accountID: number) => void; + updateJob: (accountID: number, jobJSON: string) => Promise; + indexStart: (accountID: number) => Promise; + indexStop: (accountID: number) => Promise; indexParseAllJSON: (accountID: number) => Promise; indexParseTweets: (accountID: number) => Promise; indexParseConversations: (accountID: number) => Promise; @@ -86,8 +90,8 @@ declare global { archiveTweet: (accountID: number, tweetID: string) => Promise; archiveTweetCheckDate: (accountID: number, tweetID: string) => Promise; archiveBuild: (accountID: number) => Promise; - syncProgress: (accountID: number, progressJSON: string) => void; - openFolder: (accountID: number, folderName: string) => void; + syncProgress: (accountID: number, progressJSON: string) => Promise; + openFolder: (accountID: number, folderName: string) => Promise; getArchiveInfo: (accountID: number) => Promise; resetRateLimitInfo: (accountID: number) => Promise; isRateLimited: (accountID: number) => Promise; @@ -111,10 +115,20 @@ declare global { importXArchive: (accountID: number, archivePath: string, dataType: string) => Promise; getCookie: (accountID: number, name: string) => Promise; getConfig: (accountID: number, key: string) => Promise; - setConfig: (accountID: number, key: string, value: string) => void; + setConfig: (accountID: number, key: string, value: string) => Promise; }; - onPowerMonitorSuspend: (callback: () => void) => void; - onPowerMonitorResume: (callback: () => void) => void; + Facebook: { + resetProgress: (accountID: number) => Promise; + createJobs: (accountID: number, jobTypes: string[]) => Promise; + updateJob: (accountID: number, jobJSON: string) => Promise; + archiveBuild: (accountID: number) => Promise; + syncProgress: (accountID: number, progressJSON: string) => Promise; + getProgress: (accountID: number) => Promise; + getConfig: (accountID: number, key: string) => Promise; + setConfig: (accountID: number, key: string, value: string) => Promise; + }, + onPowerMonitorSuspend: (callback: () => void) => Promise; + onPowerMonitorResume: (callback: () => void) => Promise; }; } } From fdc2cfcda67169bc7d10788506a1f9dc6aa45568 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 7 Feb 2025 14:43:21 -0800 Subject: [PATCH 09/26] Make the Facebook state loop start running --- src/renderer/src/views/AccountView.vue | 8 +++++++- src/renderer/src/views/facebook/FacebookView.vue | 2 +- .../src/views/shared_components/AccountButton.vue | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/views/AccountView.vue b/src/renderer/src/views/AccountView.vue index 062c6ec2..3890d356 100644 --- a/src/renderer/src/views/AccountView.vue +++ b/src/renderer/src/views/AccountView.vue @@ -1,6 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/renderer/src/views/shared_components/AccountButton.vue b/src/renderer/src/views/shared_components/AccountButton.vue index 30a7dd3e..a097c2ce 100644 --- a/src/renderer/src/views/shared_components/AccountButton.vue +++ b/src/renderer/src/views/shared_components/AccountButton.vue @@ -51,10 +51,16 @@ onUnmounted(async () => {
diff --git a/src/renderer/src/views/shared_components/SidebarArchive.vue b/src/renderer/src/views/shared_components/SidebarArchive.vue new file mode 100644 index 00000000..d24ecdd7 --- /dev/null +++ b/src/renderer/src/views/shared_components/SidebarArchive.vue @@ -0,0 +1,49 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/views/x/XView.vue b/src/renderer/src/views/x/XView.vue index 0e37743c..106389b9 100644 --- a/src/renderer/src/views/x/XView.vue +++ b/src/renderer/src/views/x/XView.vue @@ -400,13 +400,13 @@ onUnmounted(async () => { -