Skip to content

Instantly share code, notes, and snippets.

@vanadium23
Last active July 12, 2022 14:48
Show Gist options
  • Save vanadium23/d9ea3b7c2e8f3265ff209cb7358127fb to your computer and use it in GitHub Desktop.
Save vanadium23/d9ea3b7c2e8f3265ff209cb7358127fb to your computer and use it in GitHub Desktop.
Script for tampermonkey to add articles count in selection list (My list, archive, favorites)
// ==UserScript==
// @name Pocket Counters
// @namespace http://vanadium23.me/
// @version 0.5
// @description Add counters to pocket list
// @author vanadium23
// @match https://app.getpocket.com/*
// @ran-at document-idle
// @grant none
// ==/UserScript==
(function (indexedDB) {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (!mutation.addedNodes) return
for (var i = 0; i < mutation.addedNodes.length; i++) {
// do things to your newly added nodes here
var node = mutation.addedNodes[i];
if (document.querySelector('ul')) {
observer.disconnect();
setupCounters();
}
}
})
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: true,
});
const dbPromise = new Promise(function(resolve, reject) {
const idb = indexedDB.open("PocketWebData");
idb.onerror = event => reject(event);
idb.onsuccess = event => {
resolve(event.target.result);
};
});
function fetchAllFromDatabase(db) {
return new Promise(function(resolve, reject) {
const transaction = db.transaction("itemList").objectStore("itemList").getAll();
transaction.onerror = event => reject(event);
transaction.onsuccess = event => {
resolve(event.target.result);
};
});
}
function setupCounters() {
'use strict';
let globalState = {
counters: {
queue: 0,
favorite: 0,
archive: 0,
},
articles: {
queue: {},
favorite: {},
archive: {},
}
};
const ARTICLE_TIME_PROPERTY = {
queue: 'time_added',
favorite: 'time_favorited',
archive: 'time_read',
}
const ARTICLE_STATES = {
queue: 'section-mylist',
favorite: 'section-favorites',
archive: 'section-archive',
};
const ARTICLE_STATES_SELECTOR = {
queue: 'a[href="/"]',
favorite: 'a[href="/favorites"]',
archive: 'a[href="/archive"]',
};
const states = Object.keys(ARTICLE_STATES);
const COUNTER_POSTFIX = '-counter';
const FAKE_COUNT_NUMBER = 1000;
function filterArticles(state) {
switch (state) {
case 'queue':
return x => x.status === '0';
break;
case 'favorite':
return x => x.favorite === '1';
break;
case 'archive':
return x => x.status === '1';
break;
}
}
// fetch all articles
function fetchArticlesV2(state, offset = 0, count = 100) {
const object = {
"images": 1,
"videos": 1,
"tags": 1,
"taglist": 1,
"account": 1,
"rediscovery": 1,
"posts": 1,
"total": 1,
"state": state,
"locale_lang": "en-US",
"passedChunk": 12,
"count": count,
"offset": offset
}
if (state == 'favorite') {
object.state = '';
object.favorite = 1;
}
return fetch("https://getpocket.com/v3/fetch?enable_cors=1&consumer_key=78809-9423d8c743a58f62b23ee85c", {
"credentials": "include",
"headers": {
"accept": "*/*",
"accept-language": "en-GB,en;q=0.9,en-US;q=0.8,ru;q=0.7",
"content-type": "application/json",
"x-accept": "application/json; charset=UTF8"
},
"referrer": "https://app.getpocket.com/",
"referrerPolicy": "no-referrer-when-downgrade",
"body": JSON.stringify(object),
"method": "POST",
"mode": "cors"
})
.then(response => response.json());
}
function countArticles(state, offset = 0) {
dbPromise.then(db => {
return fetchAllFromDatabase(db);
}).then(articles => {
const list = articles.filter(filterArticles(state));
// update storage
globalState.counters[state] += list.length;
Object.assign(globalState.articles[state], list);
// update UI
drawCounter(state);
}).catch(error => {
console.error(error);
return fetchArticlesV2(state, offset).then(data => {
// if meta result == 1000, then it is not a real count
let count = parseInt(data.search_meta.total_result_count);
let metaCount = true;
if (count == FAKE_COUNT_NUMBER) {
count = offset === 0 ? FAKE_COUNT_NUMBER : Object.keys(data.list).length;
metaCount = false;
} else if (isNaN(count)) {
count = parseInt(data.total);
metaCount = false;
}
// update storage
globalState.counters[state] += count;
Object.assign(globalState.articles[state], data.list);
// update UI
drawCounter(state);
});
})
}
function getMonday(date) {
var day = date.getDay() || 7;
if (day !== 1)
date.setHours(-24 * (day - 1));
return date;
}
function drawCounter(state) {
for (let counterType of ['', '-today-', '-week-']) {
const selector = `.${ARTICLE_STATES[state]}${counterType}${COUNTER_POSTFIX}`;
let counter = document.querySelector(selector);
if (counter) {
let unreadCount = '?';
if (counterType === '') {
unreadCount = globalState.counters[state];
} else if (counterType === '-today-') {
const timeProperty = ARTICLE_TIME_PROPERTY[state];
const articles = globalState.articles[state];
const today = new Date();
today.setHours(0, 0, 0, 0);
unreadCount = Object.keys(articles).filter(
itemId => (parseInt(articles[itemId][timeProperty]) * 1000) > today
).length;
} else if (counterType === '-week-') {
const timeProperty = ARTICLE_TIME_PROPERTY[state];
const articles = globalState.articles[state];
const monday = getMonday(new Date());
monday.setHours(0, 0, 0, 0);
unreadCount = Object.keys(articles).filter(
itemId => (parseInt(articles[itemId][timeProperty]) * 1000) > monday
).length;
}
counter.innerHTML = ` (${unreadCount})`;
}
}
}
function setupCounter(state) {
const selector = ARTICLE_STATES_SELECTOR[state];
let listElement = document.querySelector(selector);
if (listElement === null) {
return;
}
const counterSelector = `${ARTICLE_STATES[state]}${COUNTER_POSTFIX}`;
const counterElement = document.querySelector(counterSelector);
if (listElement && !counterElement) {
const detailedStatistics = `
<span class="${ARTICLE_STATES[state]}${COUNTER_POSTFIX}">?</span>
`;
listElement.innerHTML = listElement.innerHTML + detailedStatistics;
}
}
for (let state of states) {
setupCounter(state);
countArticles(state);
}
}
})(window.indexedDB);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment