1
1
import StatDatabase from "@db/stat-database"
2
+ import { AUTHOR_EMAIL } from "@src/package"
2
3
import { isNotZeroResult } from "@util/stat"
3
4
4
5
const statDatabase : StatDatabase = new StatDatabase ( chrome . storage . local )
5
6
6
- export type OtherExtension = "web_activity_time_tracker"
7
+ export type OtherExtension = "webtime_tracker" | " web_activity_time_tracker"
7
8
8
9
export type ActionType = 'overwrite' | 'accumulate'
9
10
@@ -18,6 +19,8 @@ export type ImportedData = {
18
19
rows : ImportedRow [ ]
19
20
}
20
21
22
+ const throwError = ( ) => { throw new Error ( "Failed to parse, please check your file or contact the author via " + AUTHOR_EMAIL ) }
23
+
21
24
/**
22
25
* Parse the content to rows
23
26
*
@@ -26,20 +29,18 @@ export type ImportedData = {
26
29
* @returns row data
27
30
*/
28
31
export async function parseFile ( ext : OtherExtension , file : File ) : Promise < ImportedData > {
29
- const text = await file . text ( )
32
+ let rows : ImportedRow [ ] = [ ]
33
+ let focus = false
34
+ let time = false
30
35
if ( ext === 'web_activity_time_tracker' ) {
31
- const lines = text . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => ! ! line ) . splice ( 1 )
32
- const rows : ImportedRow [ ] = lines . map ( line => {
33
- const [ host , date , seconds ] = line . split ( ',' ) . map ( cell => cell . trim ( ) )
34
- const [ year , month , day ] = date . split ( '/' )
35
- const realDate = `${ year } ${ month . length == 2 ? month : '0' + month } ${ day . length == 2 ? day : '0' + day } `
36
- return { host, date : realDate , focus : parseInt ( seconds ) * 1000 }
37
- } )
38
- await doIfExist ( rows , ( row , exist ) => row . exist = exist )
39
- return { rows, focus : true }
40
- } else {
41
- return { rows : [ ] }
36
+ rows = await parseWebActivityTimeTracker ( file )
37
+ focus = true
38
+ } else if ( ext === 'webtime_tracker' ) {
39
+ rows = await parseWebtimeTracker ( file )
40
+ focus = true
42
41
}
42
+ await doIfExist ( rows , ( row , exist ) => row . exist = exist )
43
+ return { rows, focus, time }
43
44
}
44
45
45
46
async function doIfExist < T extends timer . stat . RowKey > ( items : T [ ] , processor : ( item : T , existVal : timer . stat . Result ) => any ) : Promise < void > {
@@ -50,6 +51,79 @@ async function doIfExist<T extends timer.stat.RowKey>(items: T[], processor: (it
50
51
} ) )
51
52
}
52
53
54
+ async function parseWebActivityTimeTracker ( file : File ) : Promise < ImportedRow [ ] > {
55
+ const text = await file . text ( )
56
+ const lines = text . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => ! ! line ) . splice ( 1 )
57
+ const rows : ImportedRow [ ] = lines . map ( line => {
58
+ const [ host , date , seconds ] = line . split ( ',' ) . map ( cell => cell . trim ( ) )
59
+ ! host || ! date || ( ! seconds && seconds !== '0' ) && throwError ( )
60
+ const [ year , month , day ] = date . split ( '/' )
61
+ ! year || ! month || ! day && throwError ( )
62
+ const realDate = `${ year } ${ month . length == 2 ? month : '0' + month } ${ day . length == 2 ? day : '0' + day } `
63
+ return { host, date : realDate , focus : parseInt ( seconds ) * 1000 }
64
+ } )
65
+ return rows
66
+ }
67
+
68
+ type WebtimeTrackerBackup = {
69
+ content : {
70
+ domains : {
71
+ [ domain : string ] : {
72
+ name : string
73
+ days : {
74
+ // date format: 2023-07-22
75
+ [ date : string ] : { seconds : number }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ const WEBTIME_TRACKER_DATE_REG = / ( \d { 2 } ) - ( \d { 2 } ) - \d { 2 } /
83
+ const cvtWebtimeTrackerDate = ( date : string ) : string => WEBTIME_TRACKER_DATE_REG . test ( date ) ? date . split ( '-' ) . join ( '' ) : undefined
84
+
85
+ async function parseWebtimeTracker ( file : File ) : Promise < ImportedRow [ ] > {
86
+ const text = await file . text ( )
87
+ if ( isJsonFile ( file ) ) {
88
+ // JSON file by backup
89
+ const data = JSON . parse ( text ) as WebtimeTrackerBackup
90
+ const domains = data ?. content ?. domains || { }
91
+ const rows : ImportedRow [ ] = Object . entries ( domains )
92
+ . flatMap (
93
+ ( [ host , value ] ) => Object . entries ( value ?. days || { } )
94
+ . map ( ( [ date , item ] ) => [ host , cvtWebtimeTrackerDate ( date ) , item ?. seconds ] as [ string , string , number ] )
95
+ )
96
+ . filter ( ( [ host , date , seconds ] ) => host && date && seconds )
97
+ . map ( ( [ host , date , seconds ] ) => ( {
98
+ host,
99
+ date,
100
+ focus : seconds * 1000
101
+ } as ImportedRow ) )
102
+ console . log ( rows )
103
+ return rows
104
+ } else if ( isCsvFile ( file ) ) {
105
+ const lines = text . split ( '\n' ) . map ( line => line . trim ( ) ) . filter ( line => ! ! line )
106
+ const colHeaders = lines [ 0 ] . split ( ',' )
107
+ const rows : ImportedRow [ ] = [ ]
108
+ lines . slice ( 1 ) . forEach ( line => {
109
+ const cells = line . split ( ',' )
110
+ const host = cells [ 0 ]
111
+ if ( ! host ) return
112
+ for ( let i = 1 ; i < colHeaders ?. length ; i ++ ) {
113
+ const seconds = Number . parseInt ( cells [ i ] )
114
+ const date = cvtWebtimeTrackerDate ( colHeaders [ i ] )
115
+ seconds && date && rows . push ( { host, date, focus : seconds * 1000 } )
116
+ }
117
+ } )
118
+ return rows
119
+ }
120
+ throw new Error ( "Invalid file format" )
121
+ }
122
+
123
+ const isJsonFile = ( file : File ) : boolean => file ?. type ?. startsWith ( 'application/json' )
124
+
125
+ const isCsvFile = ( file : File ) : boolean => file ?. type ?. startsWith ( 'text/csv' )
126
+
53
127
/**
54
128
* Import data
55
129
*/
0 commit comments