├── electron
├── app
│ ├── css
│ │ └── my.css
│ ├── img
│ │ ├── icon_app_256.ico
│ │ ├── icon_app_256.png
│ │ └── icon_app_64.png
│ ├── index.html
│ └── js
│ │ └── index.js
├── .eslintrc.js
├── package.json
└── main.js
├── .gitignore
├── assets
└── icon.icns
├── screenshots
├── domain.png
├── keywords.png
└── visited-sites.png
├── README.md
└── LICENSE
/electron/app/css/my.css:
--------------------------------------------------------------------------------
1 | :focus { outline: 0; }
2 |
--------------------------------------------------------------------------------
/electron/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'standard'
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /electron/node_modules/
2 | /electron/package-lock.json
3 | /dist/
4 |
--------------------------------------------------------------------------------
/assets/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/assets/icon.icns
--------------------------------------------------------------------------------
/screenshots/domain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/screenshots/domain.png
--------------------------------------------------------------------------------
/screenshots/keywords.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/screenshots/keywords.png
--------------------------------------------------------------------------------
/screenshots/visited-sites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/screenshots/visited-sites.png
--------------------------------------------------------------------------------
/electron/app/img/icon_app_256.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/electron/app/img/icon_app_256.ico
--------------------------------------------------------------------------------
/electron/app/img/icon_app_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/electron/app/img/icon_app_256.png
--------------------------------------------------------------------------------
/electron/app/img/icon_app_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/browserhistory/main/electron/app/img/icon_app_64.png
--------------------------------------------------------------------------------
/electron/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Browser History
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BrowserHistory
2 |
3 | Forensics for Chrome/Chromium based browsers.
4 |
5 | Features :
6 |
7 | - visited web sites
8 | - domain related urls
9 | - downloads
10 | - user logins
11 | - bookmarks
12 |
13 | ## Visited web sites
14 |
15 | 
16 |
17 | ## Domain related urls
18 |
19 | 
20 |
21 | ## Keywords searches
22 |
23 | 
24 |
25 | ## Download
26 |
27 | [Get a release](https://github.com/Tazeg/browserhistory/releases)
28 |
29 | ## Develop
30 |
31 | ```bash
32 | git clone https://github.com/Tazeg/browserhistory.git
33 | cd electron
34 | npm install
35 | npm run dev
36 | ```
37 |
38 | ### Linter
39 |
40 | ```bash
41 | cd electron
42 | npm run lint
43 | ```
44 |
45 | ### Build from source
46 |
47 | ```bash
48 | cd electron
49 | npm run buildlin # Linux 64
50 | npm run buildwin # Windows 64
51 | ```
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 JeffProd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/electron/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "browserhistory",
3 | "version": "1.0.0",
4 | "description": "Forensics for Chrome/Chromium based browsers.",
5 | "main": "main.js",
6 | "build": {
7 | "appId": "com.jeffprod.browserhistory",
8 | "directories": {
9 | "output": "../dist",
10 | "buildResources": "../assets"
11 | },
12 | "linux": {
13 | "asar": true,
14 | "category": "Network",
15 | "artifactName": "${name}-v${version}-linux.${ext}",
16 | "target": [
17 | "AppImage"
18 | ]
19 | },
20 | "win": {
21 | "asar": true,
22 | "icon": "app/img/icon_app_256.ico",
23 | "legalTrademarks": "(c) 2020 JeffProd.com",
24 | "publisherName": "JeffProd.com",
25 | "artifactName": "${name}-v${version}-windows.${ext}",
26 | "target": [
27 | "portable"
28 | ]
29 | }
30 | },
31 | "scripts": {
32 | "start": "ELECTRON_MODE=prod electron .",
33 | "serve": "ELECTRON_MODE=dev electron .",
34 | "dev": "ELECTRON_MODE=dev electron .",
35 | "lint": "eslint . --fix",
36 | "buildlin": "electron-builder -l --x64",
37 | "buildwin": "electron-builder -w --x64"
38 | },
39 | "author": "@JeffProd",
40 | "license": "MIT",
41 | "dependencies": {
42 | "sql.js": "^1.3.2",
43 | "view-design": "^4.4.0-rc.1",
44 | "vue": "^2.6.12"
45 | },
46 | "devDependencies": {
47 | "electron": "^10.1.3",
48 | "electron-builder": "^22.8.1",
49 | "eslint": "^7.11.0",
50 | "eslint-config-standard": "^14.1.1",
51 | "eslint-plugin-import": "^2.22.1",
52 | "eslint-plugin-node": "^11.1.0",
53 | "eslint-plugin-promise": "^4.2.1",
54 | "eslint-plugin-standard": "^4.0.1"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/electron/main.js:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Twitter : @JeffProd
3 | // Web : https://jeffprod.com
4 | // ------------------------------------------------------------------------------
5 |
6 | const url = require('url')
7 | const path = require('path')
8 | const { app, BrowserWindow, Menu } = require('electron')
9 |
10 | let mainWindow = null
11 |
12 | const mainUrl = url.format({ // https://electronjs.org/docs/api/browser-window#winloadurlurl-options
13 | protocol: 'file',
14 | slashes: true,
15 | pathname: path.join(__dirname, 'app/index.html')
16 | })
17 |
18 | const gotTheLock = app.requestSingleInstanceLock()
19 | if (!gotTheLock) {
20 | console.log('App already running')
21 | app.quit()
22 | } else {
23 | app.on('second-instance', () => { // event, commandLine, workingDirectory) => {
24 | // Someone tried to run a second instance, we should focus our window.
25 | if (mainWindow) {
26 | if (mainWindow.isMinimized()) { mainWindow.restore() }
27 | mainWindow.focus()
28 | }
29 | })
30 |
31 | app.on('ready', function () {
32 | mainWindow = new BrowserWindow({
33 | center: true,
34 | minWidth: 1024,
35 | minHeight: 768,
36 | show: false,
37 | autoHideMenuBar: true, // hide menu bar
38 | icon: path.join(__dirname, 'app/img/icon_app_64.png'),
39 | webPreferences: {
40 | nodeIntegration: true,
41 | nodeIntegrationInWorker: true,
42 | enableRemoteModule: true // https://www.electronjs.org/docs/breaking-changes#default-changed-enableremotemodule-defaults-to-false
43 | }
44 | })
45 |
46 | // remove menus
47 | Menu.setApplicationMenu(Menu.buildFromTemplate([]))
48 | mainWindow.loadURL(mainUrl)
49 |
50 | // event on main close
51 | mainWindow.on('closed', function () {
52 | mainWindow = null
53 | app.quit()
54 | })
55 |
56 | mainWindow.webContents.on('dom-ready', function () { // on windows10: did-finish-load and ready-to-show are not triggered
57 | // console.log('user-agent:', mainWindow.webContents.getUserAgent());
58 | if (process.env.ELECTRON_MODE === 'dev') {
59 | mainWindow.webContents.openDevTools()
60 | }
61 | mainWindow.maximize()
62 | mainWindow.show()
63 | })
64 | }) // app.on('ready'
65 |
66 | // Quit when all windows are closed.
67 | app.on('window-all-closed', function () {
68 | // On OS X it is common for applications and their menu bar
69 | // to stay active until the user quits explicitly with Cmd + Q
70 | if (process.platform !== 'darwin') { app.exit() }
71 | }) // app.on('window-all-closed'
72 | } // else
73 |
--------------------------------------------------------------------------------
/electron/app/js/index.js:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Twitter : @JeffProd
3 | // Web : https://jeffprod.com
4 | // ------------------------------------------------------------------------------
5 |
6 | const fs = require('fs')
7 | const path = require('path')
8 | const initSqlJs = require('sql.js/dist/sql-wasm')
9 | const electron = require('electron')
10 | const remote = electron.remote
11 | const dialog = remote.dialog
12 | const shell = electron.shell
13 |
14 | /*eslint-disable */
15 | new Vue({ /* eslint-enable */
16 | el: '#app',
17 |
18 | template: `
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 | /Users/<user>/AppData/Local/Google/Chrome/User Data/Default
34 | /home/<user>/.config/chromium/Default
35 |
36 |
37 |
38 |
39 |
{{ formTop.dirPath }}
40 |
41 |
Visited web sites ({{ dataTopSitesFiltered.length }}/{{ dataTopSites.length }})
42 |
43 |
44 |
45 |
46 |
Domain {{ gDomain }} ({{ dataUrlsFiltered.length }}/{{ dataUrls.length }})
47 |
48 |
49 |
50 |
51 |
52 |
Logins ({{ dataLogin.length }})
53 |
54 |
55 |
56 |
Key words ({{ dataKeywordsFiltered.length }}/{{ dataKeywords.length }})
57 |
58 |
59 |
60 |
Downloaded files ({{ dataDownloadFiltered.length }}/{{ dataDownload.length }})
61 |
62 |
63 |
64 |
65 |
Bookmarks
66 |
67 |
68 |
69 |
70 |
71 |
72 |
77 |
78 |
79 |
80 |
81 | `,
82 |
83 | data: {
84 | areDbLoaded: false,
85 | domainloading: false,
86 | filterTxtTopSites: '',
87 | filterTxtUrls: '',
88 | filterTxtKeywords: '',
89 | filterTxtDownloads: '',
90 | formTop: {
91 | dirPath: '',
92 | disabled: false
93 | },
94 | columnsTopSites: [
95 | {
96 | type: 'index',
97 | width: 80,
98 | align: 'center'
99 | },
100 | {
101 | title: 'Domain',
102 | key: 'domain',
103 | sortable: true,
104 | resizable: true,
105 | width: 300
106 | },
107 | {
108 | title: 'Visits',
109 | key: 'count',
110 | sortable: true,
111 | sortType: 'desc'
112 | }
113 | ],
114 | columnsUrls: [
115 | {
116 | type: 'index',
117 | width: 80,
118 | align: 'center'
119 | },
120 | {
121 | title: 'URL',
122 | key: 'url',
123 | sortable: true,
124 | resizable: true,
125 | width: 600
126 | },
127 | {
128 | title: 'Title',
129 | key: 'title',
130 | sortable: true,
131 | resizable: true,
132 | width: 300
133 | },
134 | {
135 | title: 'Last visit',
136 | key: 'last_visit',
137 | sortable: true,
138 | sortType: 'desc'
139 | }
140 | ],
141 | columnsKeywords: [
142 | {
143 | type: 'index',
144 | width: 80,
145 | align: 'center'
146 | },
147 | {
148 | title: 'Keywords',
149 | key: 'keywords',
150 | sortable: true,
151 | resizable: true,
152 | width: 600
153 | },
154 | {
155 | title: 'Site',
156 | key: 'url',
157 | sortable: true,
158 | resizable: true,
159 | width: 300
160 | },
161 | {
162 | title: 'Last visit',
163 | key: 'last_visit',
164 | sortable: true,
165 | sortType: 'desc'
166 | }
167 | ],
168 | columnsDownload: [
169 | {
170 | type: 'index',
171 | width: 80,
172 | align: 'center'
173 | },
174 | {
175 | title: 'File',
176 | key: 'path',
177 | sortable: true,
178 | resizable: true
179 | },
180 | {
181 | title: 'Start',
182 | key: 'starttime',
183 | sortable: true,
184 | resizable: true,
185 | width: 200
186 | },
187 | {
188 | title: 'End',
189 | key: 'endtime',
190 | sortable: true,
191 | resizable: true,
192 | sortType: 'desc',
193 | width: 200
194 | },
195 | {
196 | title: 'Mime',
197 | key: 'mimetype',
198 | sortable: true,
199 | resizable: true,
200 | width: 200
201 | }
202 | ],
203 | columnsLogin: [
204 | {
205 | type: 'index',
206 | width: 80,
207 | align: 'center'
208 | },
209 | {
210 | title: 'URL',
211 | key: 'action_url',
212 | sortable: true,
213 | resizable: true,
214 | width: 600
215 | },
216 | {
217 | title: 'User name',
218 | key: 'username_value',
219 | sortable: true,
220 | resizable: true,
221 | sortType: 'asc',
222 | width: 300
223 | },
224 | {
225 | title: 'Created',
226 | key: 'datecreated',
227 | sortable: true
228 | }
229 | ],
230 | dbHistory: null, // object db sql.js
231 | dbLoginData: null,
232 | dataTopSites: [], // cf topSites
233 | dataTopSitesFiltered: [],
234 | dataUrls: [],
235 | dataUrlsFiltered: [],
236 | dataKeywords: [],
237 | dataKeywordsFiltered: [],
238 | dataDownload: [],
239 | dataDownloadFiltered: [],
240 | dataLogin: [],
241 | dataBookmarks: [],
242 | gDomain: ''
243 | }, // data
244 |
245 | computed: {
246 | appname () {
247 | return remote.app.getName().charAt(0).toUpperCase() + remote.app.getName().slice(1)
248 | },
249 | appversion () {
250 | return remote.app.getVersion()
251 | }
252 | },
253 |
254 | methods: {
255 | loadDir: function () {
256 | const title = 'User browser path'
257 | // @returns String[] | undefined
258 | const outDir = dialog.showOpenDialogSync({ // https://electronjs.org/docs/api/dialog
259 | title: title,
260 | message: title,
261 | defaultPath: remote.app.getPath('home'),
262 | properties: ['openDirectory']
263 | })
264 | if (!Array.isArray(outDir)) { return }
265 | if (outDir.length < 1) { return }
266 | this.formTop.dirPath = outDir[0]
267 | this.go()
268 | },
269 |
270 | goHome () {
271 | this.areDbLoaded = false
272 | this.dataTopSites = []
273 | this.dataTopSitesFiltered = []
274 | this.dataUrls = []
275 | this.dataUrlsFiltered = []
276 | this.formTop.disabled = false
277 | this.dataKeywords = []
278 | this.dataKeywordsFiltered = []
279 | this.dataDownload = []
280 | this.dataDownloadFiltered = []
281 | this.dataLogin = []
282 | this.dataBookmarks = []
283 | this.gDomain = ''
284 | },
285 |
286 | // process path this.formTop.dirPath
287 | go () {
288 | if (!this.formTop.dirPath) {
289 | this.$Message.warning({
290 | content: 'Please select a path',
291 | duration: 5
292 | })
293 | return
294 | }
295 |
296 | // file db History exists ?
297 | const fileHistory = path.join(this.formTop.dirPath, 'History')
298 | if (!fs.existsSync(fileHistory)) {
299 | this.$Message.error({
300 | content: 'File not found: ' + fileHistory,
301 | duration: 5
302 | })
303 | return
304 | }
305 |
306 | // file db 'Login Data' ?
307 | const fileLoginData = path.join(this.formTop.dirPath, 'Login Data')
308 | if (!fs.existsSync(fileLoginData)) {
309 | this.$Message.error({
310 | content: 'File not found: ' + fileLoginData,
311 | duration: 5
312 | })
313 | return
314 | }
315 |
316 | // bookmarks, non blocking if absent
317 | const fileBookmarks = path.join(this.formTop.dirPath, 'Bookmarks')
318 |
319 | this.formTop.disabled = true
320 | this.$Spin.show()
321 | setTimeout(() => { // let time to the spin to display
322 | this.traiteDbs(fileHistory, fileLoginData, fileBookmarks)
323 | }, 200)
324 | }, // go
325 |
326 | /**
327 | * Process SQlite/JSON files
328 | * @param {string} fileHistory "/home/user/.config/chromium/Default/History" (sqlite3)
329 | * @param {string} fileLoginData "/home/user/.config/chromium/Default/Login Data" (sqlite3)
330 | * @param {string} fileBookmarks "/home/user/.config/chromium/Bookmarks" (json)
331 | */
332 | traiteDbs (fileHistory, fileLoginData, fileBookmarks) {
333 | const bufHistory = fs.readFileSync(fileHistory)
334 | const bufLogin = fs.readFileSync(fileLoginData)
335 | initSqlJs().then((SQL) => {
336 | this.dbHistory = new SQL.Database(bufHistory) // Load the db
337 | this.dbLoginData = new SQL.Database(bufLogin) // Load the db
338 | this.dbHistory.create_function('getDomainFromURL', this.getDomainFromURL)
339 | this.dataTopSites = this.topSites()
340 | this.dataTopSitesFiltered = this.dataTopSites.slice(0)
341 | this.dataKeywords = this.motcles()
342 | this.dataKeywordsFiltered = this.dataKeywords.slice(0)
343 | this.dataDownload = this.downloads()
344 | this.dataDownloadFiltered = this.dataDownload.slice(0)
345 | this.dataLogin = this.logins()
346 | if (fs.existsSync(fileBookmarks)) {
347 | this.dataBookmarks = this.bookmarks(fileBookmarks)
348 | } else {
349 | this.$Message.warning({
350 | content: 'Missing bookmarks file : ' + fileBookmarks,
351 | duration: 5
352 | })
353 | }
354 | this.areDbLoaded = true
355 | this.$Spin.hide()
356 | }) // initSqlJs
357 | }, // traiteDbs
358 |
359 | /**
360 | * Returns the domain from a URL
361 | * @param {string} strUrl 'https://www.toto.com/titi.html'
362 | * @returns {string} 'toto.com'
363 | */
364 | getDomainFromURL (strUrl) {
365 | const url = new URL(strUrl)
366 | return url.hostname
367 | }, // getDomainFromURL
368 |
369 | /**
370 | * Returns most visited domains from the urls in the db History
371 | * @returns {Array} [ { domain: 'toto.fr', count: 22 }, ...]
372 | */
373 | topSites () {
374 | const domains = {} // count['google.fr] = 5
375 | const stmt = this.dbHistory.prepare('SELECT url FROM urls') // Prepare an sql statement
376 | stmt.bind({})
377 | while (stmt.step()) { // foreach row
378 | const row = stmt.getAsObject() // {url: "https://test.example.fr/"}
379 | const domain = this.getDomainFromURL(row.url) !== '' ? this.getDomainFromURL(row.url) : row.url
380 | if (!(domain in domains)) {
381 | domains[domain] = 0
382 | }
383 | domains[domain]++
384 | // console.log(this.getDomainFromURL(row.url))
385 | }
386 | // console.log(domains)
387 | stmt.free()
388 |
389 | const result = []
390 | for (const domain in domains) {
391 | result.push({ domain, count: domains[domain] })
392 | }
393 | return result
394 | }, // topSites
395 |
396 | /**
397 | * Filters the TopSites listing with the typed text filterTxtTopSites
398 | */
399 | filtrerTopSites () {
400 | const pattern = this.filterTxtTopSites.toLowerCase()
401 |
402 | // console.log(`Filtre sur "${pattern}"`)
403 | // console.log(this.data)
404 |
405 | this.dataTopSitesFiltered = this.dataTopSites.filter((row) => {
406 | return row.domain.toLowerCase().includes(pattern)
407 | })
408 | }, // filtrerTopSItes
409 |
410 | /**
411 | * Returns the searched keywords
412 | * @returns {Array} [ { term, url, last_visit }, ...]
413 | */
414 | motcles () {
415 | const stmt = this.dbHistory.prepare('SELECT DISTINCT term,getDomainFromURL(url) AS url,datetime((last_visit_time/1000000)-11644473600, "unixepoch", "localtime") AS last_visit from keyword_search_terms,urls WHERE urls.id=keyword_search_terms.url_id ORDER BY last_visit DESC')
416 | stmt.bind({})
417 | const result = []
418 | while (stmt.step()) { // foreach row
419 | const row = stmt.getAsObject() // { term, url, last_visit }
420 | result.push({ keywords: row.term, url: row.url, last_visit: row.last_visit })
421 | }
422 | stmt.free()
423 | return result
424 | }, // motcles
425 |
426 | /**
427 | * Retourne les fichiers téléchargés
428 | * @returns {Array} [ { path, starttime, endtime, mimetype }, ...]
429 | */
430 | downloads () {
431 | const stmt = this.dbHistory.prepare(`
432 | SELECT
433 | target_path as path,
434 | datetime((start_time/1000000)-11644473600, "unixepoch", "localtime") AS starttime,
435 | datetime((end_time/1000000)-11644473600, "unixepoch", "localtime") AS endtime,
436 | original_mime_type AS mimetype
437 | FROM downloads
438 | WHERE received_bytes>0
439 | `)
440 | stmt.bind({})
441 | const result = []
442 | while (stmt.step()) { // foreach row
443 | const row = stmt.getAsObject()
444 | result.push({ path: row.path, starttime: row.starttime, endtime: row.endtime, mimetype: row.mimetype })
445 | }
446 | stmt.free()
447 | return result
448 | }, // downloads
449 |
450 | /**
451 | * Returns logins
452 | * @returns {Array} [ { action_url, username_value, datecreated }, ...]
453 | */
454 | logins () {
455 | const stmt = this.dbLoginData.prepare(`
456 | SELECT
457 | action_url,
458 | username_value,
459 | datetime((date_created/1000000)-11644473600, "unixepoch", "localtime") as datecreated
460 | FROM
461 | logins
462 | WHERE
463 | action_url <> '' AND username_value <> ''
464 | ORDER BY
465 | username_value
466 | `)
467 | stmt.bind({})
468 | const result = []
469 | while (stmt.step()) { // foreach row
470 | const row = stmt.getAsObject()
471 | result.push({ action_url: row.action_url, username_value: row.username_value, datecreated: row.datecreated })
472 | }
473 | stmt.free()
474 | return result
475 | }, // logins
476 |
477 | /**
478 | * On click on a domain name in the iview table, we load related urls
479 | * @param {object} tablerow row iview
480 | * @param {number} tableindex index row table iview
481 | */
482 | showDomainUrls (tablerow) { //, tableindex) {
483 | // console.log(tablerow, tableindex)
484 | this.gDomain = tablerow.domain
485 | this.domainloading = true
486 | setTimeout(() => {
487 | const stmt = this.dbHistory.prepare(`
488 | SELECT
489 | url,
490 | title,
491 | datetime((last_visit_time/1000000)-11644473600, "unixepoch", "localtime") AS last_visit
492 | FROM
493 | urls
494 | WHERE
495 | url LIKE "http://` + tablerow.domain + `%" OR
496 | url LIKE "https://` + tablerow.domain + `%"
497 | ORDER BY
498 | last_visit DESC
499 | `) // Prepare an sql statement
500 | stmt.bind({})
501 | this.dataUrls = []
502 | while (stmt.step()) { // foreach row
503 | const row = stmt.getAsObject()
504 | const last = row.last_visit === '1601-01-01 01:00:00' ? '' : row.last_visit
505 | this.dataUrls.push({ url: row.url, title: row.title, last_visit: last })
506 | }
507 | stmt.free()
508 | this.dataUrlsFiltered = this.dataUrls.slice(0)
509 | this.domainloading = false
510 | }, 200) // let time for the spin to display
511 | },
512 |
513 | /**
514 | * Filter URLS with this.filterTxtUrls
515 | */
516 | filtrerUrls () {
517 | const pattern = this.filterTxtUrls.toLowerCase()
518 | this.dataUrlsFiltered = this.dataUrls.filter((row) => {
519 | return row.url.toLowerCase().includes(pattern) || row.title.toLowerCase().includes(pattern)
520 | })
521 | },
522 |
523 | /**
524 | * Filter keywords with this.filterTxtKeywords
525 | */
526 | filtrerKeywords () {
527 | const pattern = this.filterTxtKeywords.toLowerCase()
528 | this.dataKeywordsFiltered = this.dataKeywords.filter((row) => {
529 | return row.keywords.toLowerCase().includes(pattern)
530 | })
531 | },
532 |
533 | /**
534 | * Filter downloads with this.filterTxtDownloads
535 | */
536 | filtrerDownloads () {
537 | const pattern = this.filterTxtDownloads.toLowerCase()
538 | this.dataDownloadFiltered = this.dataDownload.filter((row) => {
539 | return row.path.toLowerCase().includes(pattern)
540 | })
541 | },
542 |
543 | /**
544 | * Process the JSON bookmarks
545 | * @param {string} fileBookmarks "/home/user/.config/chromium/Bookmarks" (json)
546 | * @returns {Array} https://www.iviewui.com/components/tree-en
547 | */
548 | bookmarks (fileBookmarks) {
549 | const contentStr = fs.readFileSync(fileBookmarks, 'utf8')
550 | const obj = JSON.parse(contentStr).roots
551 |
552 | // Object.keys(obj).forEach((k) => {
553 | // console.log(k + ' - ' + obj[k])
554 | // })
555 | // => bookmark_bar, other, synced
556 |
557 | let result = []
558 | if ('bookmark_bar' in obj) {
559 | result = this.traiteBookmark(obj.bookmark_bar)
560 | }
561 | return result
562 | },
563 |
564 | /**
565 | * @param {Array} bar
566 | */
567 | traiteBookmark (bar) {
568 | const result = []
569 |
570 | if ('children' in bar) {
571 | bar.children.forEach((c) => {
572 | if (c.type === 'folder') {
573 | // console.log(c.name)
574 | result.push({
575 | title: c.name,
576 | // expand: true,
577 | children: this.traiteBookmark(c)
578 | })
579 | return
580 | }
581 | result.push({
582 | title: c.name + ' (' + c.url + ')'
583 | // expand: true
584 | })
585 | })
586 | }
587 |
588 | return result
589 | }, // traiteBookmark
590 |
591 | openBrowser (url) {
592 | shell.openExternal(url)
593 | }
594 |
595 | } // methods
596 | }) // Vue
597 |
--------------------------------------------------------------------------------