// ==UserScript==
// @name 字节圈增强脚本
// @namespace http://tampermonkey.net/
// @version 0.4
// @license WTFPL
// @description try to take over the world!
// @author Yxxx
// @match https://ee.bytedance.net/malaita/pc/*
// @icon https://ee.bytedance.net/malaita/static/img/malaita.png
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// ==/UserScript==
(function () {
const MENU_KEY_TIME_ORDER = 'menu_time_order';
const MENU_KEY_AUTO_BACKUP = 'menu_auto_backup';
const MENU_KEY_FILTER_NEW_BYTEDANCER = 'menu_filter_new_bytedancer';
const MENU_KEY_FILTER_ANONYMOUS = 'menu_key_filter_anonymous';
const MENU_KEY_POST_BLACKWORD_LIST = 'menu_key_post_blackword_list';
const MENU_KEY_DEBUG_MENU = 'menu_key_debug_menu';
const MENU_ALL = [
[MENU_KEY_TIME_ORDER, '帖子按时间排序', true, () => { switchMenuCommand(MENU_KEY_TIME_ORDER); alert('请手动刷新页面以生效.'); }],
[MENU_KEY_AUTO_BACKUP, '备份浏览过的帖子', false, () => { switchMenuCommand(MENU_KEY_AUTO_BACKUP); }],
[MENU_KEY_FILTER_NEW_BYTEDANCER, '过滤新人报道 TODO', true, () => { switchMenuCommand(MENU_KEY_FILTER_NEW_BYTEDANCER) }],
[MENU_KEY_FILTER_ANONYMOUS, '过滤匿名 TODO', true, () => { switchMenuCommand(MENU_KEY_FILTER_ANONYMOUS) }],
[MENU_KEY_POST_BLACKWORD_LIST, '过滤词列表 (逗号分隔) TODO', '', () => { }],
[MENU_KEY_DEBUG_MENU, 'DEBUG MENU', 0, () => { console.log(MENU_VALUE, REGISITED_MENU_ID) }]
];
const MENU_VALUE = {};
const REGISITED_MENU_ID = [];
const TTQ_BACKUP_DB_NAME = 'ttq_backup_db';
// region MENU
function registerMenuCommand() {
console.log(1);
if (REGISITED_MENU_ID.length >= MENU_ALL.length) {
REGISITED_MENU_ID.forEach(id => GM_unregisterMenuCommand(id));
REGISITED_MENU_ID.length = 0;
}
MENU_ALL.forEach(([key, name, defaultValue, handler]) => {
let v = MENU_VALUE[key] ?? GM_getValue(key);
if (v == null){
GM_setValue(key, defaultValue);
v = defaultValue;
};
MENU_VALUE[key] = v;
const menuId = GM_registerMenuCommand(`${v === true ? '✅ ' : v === false ? '❌ ': ''}${name}`, handler);
REGISITED_MENU_ID.push(menuId);
});
}
function switchMenuCommand(key) {
const currentValue = MENU_VALUE[key];
GM_setValue(key, !currentValue);
MENU_VALUE[key] = !currentValue;
registerMenuCommand();
}
function getMenuValue(key) {
return MENU_VALUE[key];
}
// endregion
// region indexDB
function TTQDB() {
this.db = null;
this.isReady = false;
this.dbName = TTQ_BACKUP_DB_NAME;
this.dbVersion = 1;
this.dbStoreName = 'ttq_backup_store';
}
TTQDB.prototype.init = function () {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = reject;
request.onsuccess = () => {
this.db = request.result;
this.isReady = true;
resolve(this.db);
};
request.onupgradeneeded = () => {
const db = request.result;
['posts', 'comments', 'item_comments', 'likes', 'item_likes', 'users'].forEach(storeName => {
if (!db.objectStoreNames.contains(storeName)) {
const store = db.createObjectStore(storeName, { keyPath: 'id' });
store.createIndex('id', 'id', { unique: true });
}
});
};
});
}
TTQDB.prototype.getStore = function (storeName, mode = 'readonly') {
if (!this.db) { throw new Error('db not init'); }
return this.db.transaction(storeName, mode).objectStore(storeName);
}
TTQDB.prototype.get = function (storeName, id) {
return new Promise((resolve, reject) => {
const store = this.getStore(storeName);
const request = store.get(id);
request.onerror = reject;
request.onsuccess = () => resolve(request.result);
});
}
// TTQDB.prototype.exists = function (storeName, id) {
// return new Promise((resolve, reject) => {
// this.ttqDB.get(storeName, id).then(data => {
// resolve(!!data);
// }).catch(reject);
// });
// }
TTQDB.prototype.getAll = function (storeName) {
return new Promise((resolve, reject) => {
const store = this.getStore(storeName);
const request = store.getAll();
request.onerror = reject;
request.onsuccess = () => resolve(request.result);
});
}
// TTQDB.prototype.add = function (storeName, data) {
// return new Promise((resolve, reject) => {
// const store = this.getStore(storeName, 'readwrite');
// const request = store.add(data);
// request.onerror = reject;
// request.onsuccess = () => resolve(request.result);
// });
// }
TTQDB.prototype.put = function (storeName, data) {
return new Promise((resolve, reject) => {
const store = this.getStore(storeName, 'readwrite');
this.get
const request = store.put(data);
request.onerror = reject;
request.onsuccess = () => resolve(request.result);
});
}
// TTQDB.prototype.delete = function (storeName, id) {
// return new Promise((resolve, reject) => {
// const store = this.getStore(storeName, 'readwrite');
// const request = store.delete(id);
// request.onerror = reject;
// request.onsuccess = () => resolve(request.result);
// });
// }
// TTQDB.prototype.clear = function (storeName) {
// return new Promise((resolve, reject) => {
// const store = this.getStore(storeName, 'readwrite');
// const request = store.clear();
// request.onerror = reject;
// request.onsuccess = () => resolve(request.result);
// });
// }
// TTQDB.prototype.count = function (storeName) {
// return new Promise((resolve, reject) => {
// const store = this.getStore(storeName);
// const request = store.count();
// request.onerror = reject;
// request.onsuccess = () => resolve(request.result);
// });
// }
// TTQDB.prototype.close = function () {
// this.db && this.db.close();
// }
// TTQDB.prototype.deleteDB = function () {
// return new Promise((resolve, reject) => {
// const request = indexedDB.deleteDatabase(this.dbName);
// request.onerror = reject;
// request.onsuccess = () => resolve(request.result);
// });
// }
const ttqDB = new TTQDB();
window.unsafeWindow.ttqDB = ttqDB;
async function backupRes(res) {
if (!ttqDB.isReady) await ttqDB.init();
['posts', 'comments', 'likes', 'users'].forEach(storeName =>
res.entities[storeName] && Object.values(res.entities[storeName]).forEach(data => ttqDB.put(storeName, data))
);
['item_comments', 'item_likes'].forEach(storeName =>
res.relationships[storeName]
&& Object.entries(res.relationships[storeName])
.forEach(
([id, data]) => ttqDB.put(storeName, { ...data, id: parseInt(id, 10) })
)
);
}
// endregion
// region main
// region main - hack fetch
const originFetch = fetch;
window.unsafeWindow.fetch = (url, options) => {
return originFetch(url, options).then(async (response) => {
console.log('hack: ', url, options)
if (url.includes('/malaita/v2/user/settings/')) {
// 获取用户设置
if (getMenuValue(MENU_KEY_TIME_ORDER)) {
console.log('hit settings api');
const responseClone = response.clone();
let res = await responseClone.json();
res.feed_type = 1;
console.log(res);
return new Response(JSON.stringify(res), response);
}
} else if (
url.includes('/malaita/feeds/enter/') // 首屏
|| url.includes('/malaita/feeds/time_sequential/') // 下拉刷新 feed
|| /\/malaita\/v2\/1\/items\/\d+\/detail\//.exec(url) // 展开评论
|| /\/malaita\/v2\/1\/items\/\d+\/likes\//.exec(url) // 展开点赞
) {
// feed 与 展开点赞 / 评论列表
const responseClone = response.clone();
let res = await responseClone.json();
// backup
if (getMenuValue(MENU_KEY_AUTO_BACKUP)) {
console.log('hit backup posts');
backupRes(res);
}
return response;
} else {
return response;
}
});
};
// endregion
// region main - backup post
// endregion
// region main - register menu
registerMenuCommand();
// endregion
// endregion
})();