mirror of
https://github.com/bellingcat/auto-archiver-extension.git
synced 2026-06-07 19:18:33 +03:00
store submission + minor improvements
This commit is contained in:
4
.github/funding.yml
vendored
4
.github/funding.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
github: bellingcat
|
# github: bellingcat
|
||||||
custom: https://www.patreon.com/bellingcat
|
# custom: https://www.patreon.com/bellingcat
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
distribution
|
distribution
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
|
distribution.zip
|
||||||
BIN
media/screenshot-01.png
Normal file
BIN
media/screenshot-01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 423 KiB |
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
source/img/ben-archiver-128.png
Normal file
BIN
source/img/ben-archiver-128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
source/img/ben-archiver.png
Normal file
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 |
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user