├── 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 | ![Visited web sites](screenshots/visited-sites.png) 16 | 17 | ## Domain related urls 18 | 19 | ![Domain related urls](screenshots/domain.png) 20 | 21 | ## Keywords searches 22 | 23 | ![Keywords searches](screenshots/keywords.png) 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 |
27 | 28 | 29 | 30 | 31 | 32 |
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 | --------------------------------------------------------------------------------