From b808fe60f5d971678713cdee75f6f6dcbac4effd Mon Sep 17 00:00:00 2001 From: sheepzh Date: Tue, 25 Jul 2023 00:12:31 +0800 Subject: [PATCH] Support Webtime Tracker (#216) --- .../import-other-button/processor.ts | 100 +++++++++++++++--- .../migration/import-other-button/step1.ts | 6 +- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/src/app/components/data-manage/migration/import-other-button/processor.ts b/src/app/components/data-manage/migration/import-other-button/processor.ts index 00e1fd16..65050aa9 100644 --- a/src/app/components/data-manage/migration/import-other-button/processor.ts +++ b/src/app/components/data-manage/migration/import-other-button/processor.ts @@ -1,9 +1,10 @@ import StatDatabase from "@db/stat-database" +import { AUTHOR_EMAIL } from "@src/package" import { isNotZeroResult } from "@util/stat" const statDatabase: StatDatabase = new StatDatabase(chrome.storage.local) -export type OtherExtension = "web_activity_time_tracker" +export type OtherExtension = "webtime_tracker" | "web_activity_time_tracker" export type ActionType = 'overwrite' | 'accumulate' @@ -18,6 +19,8 @@ export type ImportedData = { rows: ImportedRow[] } +const throwError = () => { throw new Error("Failed to parse, please check your file or contact the author via " + AUTHOR_EMAIL) } + /** * Parse the content to rows * @@ -26,20 +29,18 @@ export type ImportedData = { * @returns row data */ export async function parseFile(ext: OtherExtension, file: File): Promise { - const text = await file.text() + let rows: ImportedRow[] = [] + let focus = false + let time = false if (ext === 'web_activity_time_tracker') { - const lines = text.split('\n').map(line => line.trim()).filter(line => !!line).splice(1) - const rows: ImportedRow[] = lines.map(line => { - const [host, date, seconds] = line.split(',').map(cell => cell.trim()) - const [year, month, day] = date.split('/') - const realDate = `${year}${month.length == 2 ? month : '0' + month}${day.length == 2 ? day : '0' + day}` - return { host, date: realDate, focus: parseInt(seconds) * 1000 } - }) - await doIfExist(rows, (row, exist) => row.exist = exist) - return { rows, focus: true } - } else { - return { rows: [] } + rows = await parseWebActivityTimeTracker(file) + focus = true + } else if (ext === 'webtime_tracker') { + rows = await parseWebtimeTracker(file) + focus = true } + await doIfExist(rows, (row, exist) => row.exist = exist) + return { rows, focus, time } } async function doIfExist(items: T[], processor: (item: T, existVal: timer.stat.Result) => any): Promise { @@ -50,6 +51,79 @@ async function doIfExist(items: T[], processor: (it })) } +async function parseWebActivityTimeTracker(file: File): Promise { + const text = await file.text() + const lines = text.split('\n').map(line => line.trim()).filter(line => !!line).splice(1) + const rows: ImportedRow[] = lines.map(line => { + const [host, date, seconds] = line.split(',').map(cell => cell.trim()) + !host || !date || (!seconds && seconds !== '0') && throwError() + const [year, month, day] = date.split('/') + !year || !month || !day && throwError() + const realDate = `${year}${month.length == 2 ? month : '0' + month}${day.length == 2 ? day : '0' + day}` + return { host, date: realDate, focus: parseInt(seconds) * 1000 } + }) + return rows +} + +type WebtimeTrackerBackup = { + content: { + domains: { + [domain: string]: { + name: string + days: { + // date format: 2023-07-22 + [date: string]: { seconds: number } + } + } + } + } +} + +const WEBTIME_TRACKER_DATE_REG = /(\d{2})-(\d{2})-\d{2}/ +const cvtWebtimeTrackerDate = (date: string): string => WEBTIME_TRACKER_DATE_REG.test(date) ? date.split('-').join('') : undefined + +async function parseWebtimeTracker(file: File): Promise { + const text = await file.text() + if (isJsonFile(file)) { + // JSON file by backup + const data = JSON.parse(text) as WebtimeTrackerBackup + const domains = data?.content?.domains || {} + const rows: ImportedRow[] = Object.entries(domains) + .flatMap( + ([host, value]) => Object.entries(value?.days || {}) + .map(([date, item]) => [host, cvtWebtimeTrackerDate(date), item?.seconds] as [string, string, number]) + ) + .filter(([host, date, seconds]) => host && date && seconds) + .map(([host, date, seconds]) => ({ + host, + date, + focus: seconds * 1000 + } as ImportedRow)) + console.log(rows) + return rows + } else if (isCsvFile(file)) { + const lines = text.split('\n').map(line => line.trim()).filter(line => !!line) + const colHeaders = lines[0].split(',') + const rows: ImportedRow[] = [] + lines.slice(1).forEach(line => { + const cells = line.split(',') + const host = cells[0] + if (!host) return + for (let i = 1; i < colHeaders?.length; i++) { + const seconds = Number.parseInt(cells[i]) + const date = cvtWebtimeTrackerDate(colHeaders[i]) + seconds && date && rows.push({ host, date, focus: seconds * 1000 }) + } + }) + return rows + } + throw new Error("Invalid file format") +} + +const isJsonFile = (file: File): boolean => file?.type?.startsWith('application/json') + +const isCsvFile = (file: File): boolean => file?.type?.startsWith('text/csv') + /** * Import data */ diff --git a/src/app/components/data-manage/migration/import-other-button/step1.ts b/src/app/components/data-manage/migration/import-other-button/step1.ts index 10bdad03..fe0a5f51 100644 --- a/src/app/components/data-manage/migration/import-other-button/step1.ts +++ b/src/app/components/data-manage/migration/import-other-button/step1.ts @@ -5,11 +5,13 @@ import { Document, Close, Right } from "@element-plus/icons-vue" import { ImportedData, OtherExtension, parseFile } from "./processor" const OTHER_NAMES: { [ext in OtherExtension]: string } = { + webtime_tracker: "Webtime Tracker", web_activity_time_tracker: "Web Activity Time Tracker" } const OTHER_FILE_FORMAT: { [ext in OtherExtension]: string } = { - web_activity_time_tracker: '.csv' + webtime_tracker: '.csv,.json', + web_activity_time_tracker: '.csv', } const ALL_TYPES: OtherExtension[] = Object.keys(OTHER_NAMES) as OtherExtension[] @@ -20,7 +22,7 @@ const _default = defineComponent({ next: (_rows: ImportedData) => true, }, setup(_, ctx) { - const type: Ref = ref('web_activity_time_tracker') + const type: Ref = ref('webtime_tracker') const selectedFile: Ref = ref() const fileInput: Ref = ref() const fileParsing: Ref = ref(false)