store submission + minor improvements

This commit is contained in:
msramalho
2023-02-25 14:36:41 +01:00
parent ea33455964
commit 8ed485ed61
14 changed files with 113 additions and 102 deletions

4
.github/funding.yml vendored
View File

@@ -1,2 +1,2 @@
github: bellingcat # github: bellingcat
custom: https://www.patreon.com/bellingcat # custom: https://www.patreon.com/bellingcat

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules node_modules
distribution distribution
.parcel-cache .parcel-cache
distribution.zip

BIN
media/screenshot-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

View File

@@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"build": "rm -rf distribution && parcel build source/manifest.json --no-content-hash --no-source-maps --dist-dir distribution --no-cache --detailed-report 0", "build": "rm -rf distribution && rm distribution.zip && parcel build source/manifest.json --no-content-hash --no-source-maps --dist-dir distribution --no-cache --detailed-report 0 && zip -r distribution.zip distribution",
"lint": "run-p lint:*", "lint": "run-p lint:*",
"lint-fix": "run-p 'lint:* -- --fix'", "lint-fix": "run-p 'lint:* -- --fix'",
"lint:css": "stylelint source/**/*.css", "lint:css": "stylelint source/**/*.css",

View File

@@ -1,33 +1,33 @@
body { body {
font-size: 100%; font-size: 100%;
} }
#app { #app {
min-width: 40em; min-width: 40em;
margin: 15px; margin: 15px;
} }
#icon { #icon {
max-height: 26px; max-height: 26px;
vertical-align: middle; vertical-align: middle;
} }
#archiveResults .row{ #archive-results .row {
/* table-layout: fixed; */ /* table-layout: fixed; */
width:90%; width: 90%;
max-width:100px; max-width: 100px;
} }
/* #archiveResults td { /* #archive-results td {
width: auto; width: auto;
} }
#archiveResults td:nth-child(2) { #archive-results td:nth-child(2) {
width: 150px; width: 150px;
} */ } */
table td { table td {
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
padding: 5px; padding: 5px;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
source/img/ben-archiver.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,76 +1,86 @@
// eslint-disable-next-line import/no-unassigned-import
// import './options-storage.js'; // Import './options-storage.js';
import optionsStorage from './options-storage.js'; import optionsStorage from './options-storage.js';
// TODO: stable ID https://developer.chrome.com/docs/extensions/mv3/tut_oauth/ // TODO: stable ID https://developer.chrome.com/docs/extensions/mv3/tut_oauth/
// TODO: API_ENDPOINT depending on deployment const API_ENDPOINT = 'http://localhost:8004/tasks'
const API_ENDPOINT = 'http://localhost:8000/tasks' // const API_ENDPOINT = 'http://134.122.58.133:8004/tasks';
chrome.runtime.onMessage.addListener(((r, s, sR) => { chrome.runtime.onMessage.addListener(((r, s, sR) => {
processMessages(r, s, sR) processMessages(r, s, sR);
return true; // needed for sendResponse to be async return true; // Needed for sendResponse to be async
})); }));
async function processMessages(request, sender, sendResponse) { async function processMessages(request, sender, sendResponse) {
console.info(`action {${request.action}} from ${sender.tab ? 'content-script (' + sender.tab.url + ')' : 'the extension'}`) console.info(`action {${request.action}} from ${sender.tab ? 'content-script (' + sender.tab.url + ')' : 'the extension'}`);
chrome.identity.getAuthToken({ interactive: true }, async function (access_token) { chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
console.log(access_token); switch (request.action) {
if (request.action === "archive") { case 'archive': {
archiveUrl(sendResponse, access_token); archiveUrl(sendResponse, accessToken);
} else if (request.action === "search") { break;
const tasks = await search(request.query, access_token);
sendResponse(tasks);
} else if (request.action === "status") {
const task_db = await getTaskById(request.task.task_id);
if (task_db?.status == "SUCCESS" || task_db?.status == 'FAILURE') {
console.log("ALREADY FINSIHED, NO REQS")
sendResponse(task_db)
} }
const task_fresh = await checkTaskStatus(request.task, access_token) case 'search': {
sendResponse(task_fresh) const tasks = await search(request.query, accessToken);
} else if (request.action === "getTasks") { sendResponse(tasks);
sendResponse(await getAllTasks()); break;
}
case 'status': {
const taskDb = await getTaskById(request.task.task_id);
if (taskDb?.status === 'SUCCESS' || taskDb?.status === 'FAILURE'|| taskDb?.status === 'REVOKED') {
console.log('ALREADY FINSIHED, NO REQS');
sendResponse(taskDb);
} else {
const taskFresh = await checkTaskStatus(request.task, accessToken);
sendResponse(taskFresh);
}
break;
}
case 'getTasks': {
sendResponse(await getAllTasks());
break;
}
// No default
} }
}); });
} }
function archiveUrl(sendResponse, access_token) { function archiveUrl(sendResponse, accessToken) {
chrome.tabs.query({ chrome.tabs.query({
active: true, active: true,
lastFocusedWindow: true lastFocusedWindow: true,
}, async (tabs) => { }, async tabs => {
let url = tabs[0].url; const url = tabs[0].url;
console.log(`url=${url}`); console.log(`url=${url}`);
const response = await searchTask(url, access_token); const response = await searchTask(url, accessToken);
const new_archive = { url, task_id: response.task_id, status: 'PENDING', result: {} }; const newArchive = { url, task_id: response.task_id, status: 'PENDING', result: {} };
await upsertTask(new_archive); await upsertTask(newArchive);
sendResponse(new_archive); sendResponse(newArchive);
}); });
} }
function searchTask(url, access_token) { function searchTask(url, accessToken) {
console.log(`API: SUBMIT`) console.log('API: SUBMIT');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(API_ENDPOINT, { fetch(API_ENDPOINT, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ url, access_token }), body: JSON.stringify({ url, access_token: accessToken }),
}).then( }).then(
response => response.json(), response => response.json(),
).then(response => resolve(response) ).then(response => resolve(response),
).catch(err => { ).catch(error => {
console.log(`There was an error: ${err}`) console.log(`There was an error: ${error}`);
reject(err) reject(error);
}); });
}) });
} }
function checkTaskStatus(task, access_token) { function checkTaskStatus(task, accessToken) {
console.log(`API: STATUS`) console.log('API: STATUS');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(`${API_ENDPOINT}/${task.task_id}?` + new URLSearchParams({ access_token }), { fetch(`${API_ENDPOINT}/${task.task_id}?` + new URLSearchParams({ access_token: accessToken }), {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -82,47 +92,47 @@ function checkTaskStatus(task, access_token) {
url: task.url, url: task.url,
task_id: response.task_id, task_id: response.task_id,
status: response.task_status, status: response.task_status,
result: JSON.parse(response.task_result), result: typeof response.task_result == "object" ? response.task_result : JSON.parse(response.task_result),
} };
console.log(new_task); console.log(`status ${new_task.url}: ${new_task.task_id}`);
upsertTask(new_task); upsertTask(new_task);
resolve(new_task) resolve(new_task);
} },
).catch(err => reject(err)); ).catch(error => reject(error));
}) });
} }
function search(query, access_token) { function search(query, accessToken) {
console.log(`API: SEARCH`) console.log('API: SEARCH');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(`${API_ENDPOINT}/search?` + new URLSearchParams({ access_token, query }), { fetch(`${API_ENDPOINT}/search?` + new URLSearchParams({ access_token: accessToken, query }), {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}).then( }).then(
response => response.json(), response => response.json(),
).then(response => resolve(response) ).then(response => resolve(response),
).catch(err => { ).catch(error => {
console.log(`There was an error: ${err}`) console.log(`There was an error: ${error}`);
reject(err) reject(error);
}); });
}) });
} }
async function getAllTasks() { async function getAllTasks() {
const storage = await optionsStorage.getAll(); const storage = await optionsStorage.getAll();
return storage.archived_urls; return storage.archivedUrls;
} }
//TODO: improve with less reads from storage // TODO: improve with less reads from storage
async function upsertTask(task) { async function upsertTask(task) {
const storage = await optionsStorage.getAll(); const storage = await optionsStorage.getAll();
storage.archived_urls[task.task_id] = task; storage.archivedUrls[task.task_id] = task;
await optionsStorage.set(storage); await optionsStorage.set(storage);
} }
async function getTaskById(task) { async function getTaskById(task) {
const storage = await optionsStorage.getAll(); const storage = await optionsStorage.getAll();
return storage.archived_urls[task.task_id]; return storage.archivedUrls[task.task_id];
} }

View File

@@ -2,7 +2,7 @@ import OptionsSync from 'webext-options-sync';
export default new OptionsSync({ export default new OptionsSync({
defaults: { defaults: {
archived_urls: {}, archivedUrls: {},
}, },
migrations: [ migrations: [
OptionsSync.migrations.removeUnused, OptionsSync.migrations.removeUnused,

View File

@@ -1,11 +1,10 @@
import { createApp } from "vue"; import {createApp} from 'vue';
import Popup from "../vue/Popup.vue"; import Popup from '../vue/Popup.vue';
import 'materialize-css/dist/css/materialize.min.css' import 'materialize-css/dist/css/materialize.min.css';
import 'material-design-icons/iconfont/material-icons.css' import 'material-design-icons/iconfont/material-icons.css';
const app = createApp(Popup); const app = createApp(Popup);
app.mount("#app"); app.mount('#app');
// Import browser from 'webextension-polyfill'; // Import browser from 'webextension-polyfill';
// import optionsStorage from './options-storage.js'; // import optionsStorage from './options-storage.js';
@@ -19,4 +18,4 @@ document.addEventListener('DOMContentLoaded', async () => {
// document.querySelector('#optionsBtn').addEventListener('click', () => { // document.querySelector('#optionsBtn').addEventListener('click', () => {
// browser.runtime.openOptionsPage(); // browser.runtime.openOptionsPage();
// }); // });
// } // }

View File

@@ -12,13 +12,12 @@
} }
}, },
"icons": { "icons": {
"128": "img/icon.png" "128": "img/ben-archiver.png"
}, },
"permissions": [ "permissions": [
"storage", "tabs", "identity" "storage", "tabs", "identity"
], ],
"host_permissions": [ "host_permissions": [
"*://*/*"
], ],
"background": { "background": {
"service_worker": "js/background.js", "service_worker": "js/background.js",
@@ -35,5 +34,6 @@
"oauth2": { "oauth2": {
"client_id": "572076445849-4cb2a8be1nfi46l80jm741k56s7cjkd0.apps.googleusercontent.com", "client_id": "572076445849-4cb2a8be1nfi46l80jm741k56s7cjkd0.apps.googleusercontent.com",
"scopes": ["https://www.googleapis.com/auth/userinfo.email"] "scopes": ["https://www.googleapis.com/auth/userinfo.email"]
} },
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlKYc35vN+NAqABtFbBU60XoTzAQynotX2H/kF14m/hfUdBXUtndQN0HYdqyseTojHkcmQul8esbo1hztgNnuNW12wJ5wwJqjzYcXHjeRCwmWA0Ohld1Xh+HAwAWDpRfcWCB8S06G79SvVvk4C8WvpVYvO5MX6twBGZy/FpsFI+RodgHPK4doefo9iYRoeR47Zw20EYG+W4z8ehWR2xlpf56yq4IlsVVL78RSYqwwG4vLggXWb1MaSvnwjyVpGlcLZMoRfBgmzQrltbNCPZWS/QQ8SHk90s1kkuoiCHUgMlQIDAQAB"
} }

View File

@@ -1,16 +1,16 @@
<template> <template>
<h5> <h5>
<img src="../img/icon.png" alt="icon" id="icon"> <img src="../img/ben-archiver.png" alt="icon" id="icon">
Auto Archiver extension auto-archiver extension
<button v-on:click="archive" class="waves-effect waves-light btn-small right">Archive!</button> <button v-on:click="archive" class="waves-effect waves-light btn-small right">Archive!</button>
<button v-on:click="searchF" class="waves-effect waves-light btn-small right">SEARCH</button> <!-- <button v-on:click="searchF" class="waves-effect waves-light btn-small right">SEARCH</button> -->
</h5> </h5>
<div class="input-field col s6"> <div class="input-field col s6">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
<input id="icon_prefix" type="text" v-model="search"> <input id="icon_prefix" type="text" v-model="search">
<label for="icon_prefix">Search for URLs</label> <label for="icon_prefix">Search for URLs</label>
</div> </div>
<table id="archiveResults"> <table id="archive-results">
<thead> <thead>
<tr class="row"> <tr class="row">
<th class="col s1"></th> <th class="col s1"></th>

View File

@@ -17,12 +17,13 @@
<div v-if="task.status == 'SUCCESS'"> <div v-if="task.status == 'SUCCESS'">
<i class="material-icons small green-text darken-4">done</i> <i class="material-icons small green-text darken-4">done</i>
</div> </div>
<div v-if="task.status == 'FAILURE'"> <div v-if="task.status == 'FAILURE' || task.status == 'REVOKED'">
<i class="material-icons small red-text darken-4">clear</i> <i class="material-icons small red-text darken-4">clear</i>
</div> </div>
</td> </td>
<td class="col s5"><a :href="task?.url">{{ task.url }}</a></td> <td class="col s5"><a :href="task?.url" target="_blank">{{ task.url }}</a></td>
<td class="col s2"><a v-if="archiveUrl.length" :href="archiveUrl" target="_blank">{{ task?.result?.status || "open" }}</a> </td> <td class="col s2"><a v-if="archiveUrl.length" :href="archiveUrl" target="_blank">{{ task?.result?.status || "open"
}}</a> </td>
<td class="col s3">{{ readbleDate }}</td> <td class="col s3">{{ readbleDate }}</td>
</tr> </tr>
</template> </template>
@@ -54,16 +55,16 @@ export default {
}.bind(this), 2500); }.bind(this), 2500);
}, },
taskFinished: function (task) { taskFinished: function (task) {
return task.status == 'SUCCESS' || task.status == 'FAILURE'; return task.status == 'SUCCESS' || task.status == 'FAILURE' || task.status == 'REVOKED';
} }
}, },
computed: { computed: {
archiveUrl() { archiveUrl() {
// return this.task?.result?.media?.urls.at(0) || ''; // return this.task?.result?.media?.urls.at(0) || '';
console.log(this.task?.result?.media); console.log(this.task?.result?.media);
console.log(this.task?.result?.media?.filter(m=>m?.properties?.id=="_final_media")); console.log(this.task?.result?.media?.filter(m => m?.properties?.id == "_final_media"));
console.log(this.task?.result?.media?.filter(m=>m?.properties?.id=="_final_media")?.urls?.at(0)); console.log(this.task?.result?.media?.filter(m => m?.properties?.id == "_final_media")?.urls?.at(0));
return this.task?.result?.media?.filter(m=>m?.properties?.id=="_final_media")?.at(0)?.urls?.at(0) || ''; return this.task?.result?.media?.filter(m => m?.properties?.id == "_final_media")?.at(0)?.urls?.at(0) || '';
}, },
readbleDate() { readbleDate() {
if (this.task?.result?._processed_at) { if (this.task?.result?._processed_at) {