mirror of
https://github.com/xh321/LiteLoaderQQNT-Anti-Recall.git
synced 2026-10-16 21:01:19 +08:00
加入配置界面,可动态调整撤回样式以及是否启用数据库
This commit is contained in:
@@ -9,7 +9,25 @@ LiteLoaderQQNT插件,用于比较完善的防撤回。
|
||||
|
||||
直接克隆源码的话,需要手动进行`npm install`。
|
||||
|
||||
所有被撤回的消息会被带上红框,文字加上删除线。**重新进消息界面仍然可以看到。**当前版本重启QQ后**只能恢复文字和表情消息**,图片消息暂不可用,请等待版本更新。
|
||||
## 太长不看版
|
||||
|
||||
在NTQQ打开期间无论怎样操作,**反撤回均能生效**。
|
||||
|
||||
若开启数据库储存(**默认关闭**,请前往配置界面手动开启),NTQQ重启后可恢复已撤回的消息,但仅文本和表情可恢复,图片会恢复失败(请等待版本更新)。
|
||||
|
||||
若不满意撤回的颜色、阴影和提示,也请前往配置界面手动调整。
|
||||
|
||||
若反撤回消息太多,累积在数据库里可能造成加载卡顿,建议隔一段时间通过插件配置界面清理一次已储存的消息。
|
||||
|
||||
## 详细说明
|
||||
|
||||
所有被撤回的消息会被带上红框,下方会出现“已撤回”的小提示(均可通过配置关闭)。**重新进消息界面已撤回的消息仍然可以正常显示。**
|
||||
|
||||
**使用LevelDB进行数据持久化储存**,默认**不**启用,需要手动去设置里开启。如果不开启,重启QQ消息会丢失。若开启,当前版本重启QQ后**只能恢复文字和表情消息**,图片消息暂不可恢复,请等待版本更新。
|
||||
|
||||
如果你需要调整撤回样式,请前往插件的设置界面,手动修改调整。
|
||||
|
||||
不建议手动修改配置文件,修改后不会实时生效(需要重启QQ),建议用配置界面修改。
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +37,6 @@ LiteLoaderQQNT插件,用于比较完善的防撤回。
|
||||
|
||||
图片如果撤回太快,反撤回后有可能会一直加载不出来,这个可能是图片已经从服务器上删除了,这种情况下,**无法恢复**。
|
||||
|
||||
|
||||
|
||||
## 原理介绍
|
||||
|
||||
**之前版本(过时):** 为什么在简介里说是简易的呢?因为这个防撤回原理是通过拦截撤回消息的IPC,这样消息就不会被删除。可是,若重新打开消息界面,NTQQ似乎会重新向逻辑层拉取一次消息(旧版QQ的防撤回直接从通信上就拦截了撤回包,所以没有这个问题;而本插件仅拦截到了渲染层的IPC,逻辑层还是一个黑盒),所以导致消息仍然被撤回了。
|
||||
@@ -29,20 +45,10 @@ LiteLoaderQQNT插件,用于比较完善的防撤回。
|
||||
|
||||
- 为避免太占内存,保存消息的上限默认为1000条。如有需要,可自行修改`main.js`中的`MAX_MSG_SAVED_LIMIT`常量。这个值的意义是,假设默认最多存1000条,那么总接到消息的1000条之前的消息若被撤回则无法恢复。**不过,若一条消息在撤回后你回去看了,并且反撤回生效(也就是只要你看到了这条消息出现红框和删除线),则这条消息会被额外储存到专门的“已撤回消息数组”中,这个数组没有容量上限,不会被本限制所约束。**
|
||||
- 为避免可能存在的并发问题,消息超过上述限制后,每次接到消息后默认会删除保存的消息列表中的前50条,直到低于上述上限。这个50条你也可以通过修改`main.js`中的`DELETE_MSG_COUNT_PER_TIME`常量来变更。建议修改为你平均每秒接到的消息数量。
|
||||
- 任何撤回的消息都会存入本地的数据库(位于LLQQNT的插件数据目录(`plugin_data`里),以便于重启后读取已撤回的消息记录。
|
||||
|
||||
所以,目前反撤回**重启QQ仍然生效**,但图片消息会恢复失败,请等待版本更新。
|
||||
- 若开启数据库储存功能,则任何撤回的消息都会存入本地的数据库(位于LLQQNT的插件数据目录(`plugin_data`里),以便于重启后读取已撤回的消息记录。
|
||||
|
||||
相关详细的原理和说明请参阅源码。
|
||||
|
||||
**使用LevelDB进行数据储存**,还没加入启用和关闭储存的开关,请等待后续版本更新(**如果你不想让插件储存撤回后的消息,你可以暂且不更新版本**)
|
||||
|
||||
## 总结
|
||||
|
||||
在NTQQ打开期间无论怎样操作,**反撤回均能生效**,消息会暂时保存在内存里,不会落盘,所以没有消息泄露风险。(如果你能读插件内存里的消息,你为什么不直接去读QQ的消息列表呢🤣)
|
||||
|
||||
NTQQ重启后,仅文本和表情可恢复,图片会恢复失败。
|
||||
|
||||
## 协议及免责
|
||||
|
||||
MIT | 禁止用于任何非法用途,插件开发属学习与研究目的,仅自用,未提供给任何第三方使用。任何不当使用导致的任何侵权问题责任自负。
|
||||
|
||||
@@ -2,42 +2,144 @@ const http = require("http");
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { ipcMain, dialog } = require("electron");
|
||||
|
||||
var configFilePath = "";
|
||||
|
||||
const { Level } = require("level");
|
||||
var db = null;
|
||||
|
||||
var sampleConfig = {
|
||||
mainColor: "#ff6d6d",
|
||||
saveDb: false,
|
||||
enableShadow: true,
|
||||
enableTip: true
|
||||
};
|
||||
|
||||
var nowConfig = {};
|
||||
|
||||
function initConfig() {
|
||||
fs.writeFileSync(
|
||||
configFilePath,
|
||||
JSON.stringify(sampleConfig, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
if (!fs.existsSync(configFilePath)) {
|
||||
initConfig();
|
||||
return sampleConfig;
|
||||
} else {
|
||||
return JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
|
||||
}
|
||||
}
|
||||
|
||||
async function onLoad(plugin) {
|
||||
configFilePath = path.join(plugin.path.data, "config.json");
|
||||
nowConfig = loadConfig();
|
||||
|
||||
if (nowConfig.mainColor == null) {
|
||||
nowConfig.mainColor = "#ff6d6d";
|
||||
}
|
||||
if (nowConfig.enableShadow == null) {
|
||||
nowConfig.enableShadow = true;
|
||||
}
|
||||
if (nowConfig.enableTip == null) {
|
||||
nowConfig.enableTip = true;
|
||||
}
|
||||
fs.writeFileSync(
|
||||
configFilePath,
|
||||
JSON.stringify(nowConfig, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"LiteLoader.anti_recall.getNowConfig",
|
||||
async (event, message) => {
|
||||
return nowConfig;
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"LiteLoader.anti_recall.saveConfig",
|
||||
async (event, config) => {
|
||||
nowConfig = config;
|
||||
sendChatWindowsMessage(
|
||||
"LiteLoader.anti_recall.mainWindow.repatchCss"
|
||||
);
|
||||
fs.writeFileSync(
|
||||
configFilePath,
|
||||
JSON.stringify(config, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
db = new Level(path.join(plugin.path.data, "qq-recalled-db"), {
|
||||
valueEncoding: "json"
|
||||
});
|
||||
|
||||
output("Loading recalled msgs from db...");
|
||||
var counter = 0;
|
||||
for await (const value of db.values()) {
|
||||
counter++;
|
||||
recalledMsg.push(value);
|
||||
if (value.msg == null) continue;
|
||||
for (item of value.msg.elements) {
|
||||
if (item.picElement != null) {
|
||||
item.picElement = null;
|
||||
item.elementType = 1;
|
||||
item.textElement = {
|
||||
content: "[暂不支持图片消息恢复,请等待反撤回版本更新]",
|
||||
atType: 0,
|
||||
atUid: "0",
|
||||
atTinyId: "0",
|
||||
atNtUid: "",
|
||||
subElementType: 0,
|
||||
atChannelId: "0",
|
||||
atRoleId: "0",
|
||||
atRoleColor: 0,
|
||||
atRoleName: "",
|
||||
needNotify: 0
|
||||
};
|
||||
ipcMain.handle("LiteLoader.anti_recall.clearDb", async (event, message) => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: "warning",
|
||||
title: "警告",
|
||||
message: "清空所有已储存的撤回消息后不可恢复,是否确认清空?",
|
||||
buttons: ["确定", "取消"],
|
||||
cancelId: 1
|
||||
})
|
||||
.then(async (idx) => {
|
||||
//第一个按钮
|
||||
if (idx.response == 0) {
|
||||
await db.clear();
|
||||
dialog.showMessageBox({
|
||||
type: "info",
|
||||
title: "提示",
|
||||
message:
|
||||
"清空完毕,之前保存的所有已撤回消息均被删除,重启QQ后就能看见效果。",
|
||||
buttons: ["确定"]
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
(async () => {
|
||||
if (nowConfig.saveDb) {
|
||||
output("Loading recalled msgs from db...");
|
||||
var counter = 0;
|
||||
for await (const value of db.values()) {
|
||||
counter++;
|
||||
recalledMsg.push(value);
|
||||
if (value.msg == null) continue;
|
||||
for (item of value.msg.elements) {
|
||||
if (item.picElement != null) {
|
||||
item.picElement = null;
|
||||
item.elementType = 1;
|
||||
item.textElement = {
|
||||
content:
|
||||
"[暂不支持图片消息恢复,请等待反撤回版本更新]",
|
||||
atType: 0,
|
||||
atUid: "0",
|
||||
atTinyId: "0",
|
||||
atNtUid: "",
|
||||
subElementType: 0,
|
||||
atChannelId: "0",
|
||||
atRoleId: "0",
|
||||
atRoleColor: 0,
|
||||
atRoleName: "",
|
||||
needNotify: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
output(`Loaded ${counter} msgs.`);
|
||||
} else {
|
||||
output("Db saving is disabled, continue.");
|
||||
}
|
||||
}
|
||||
output(`Loaded ${counter} msgs.`);
|
||||
})().catch((e) => {
|
||||
output("Error while loading recalled msgs from db: " + e.toString());
|
||||
});
|
||||
}
|
||||
|
||||
var msgFlow = [];
|
||||
@@ -134,6 +236,14 @@ async function getMsgById(id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function sendChatWindowsMessage(message) {
|
||||
for (var window of mainWindowObjs) {
|
||||
if (window.isDestroyed()) continue;
|
||||
window.webContents.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
var mainWindowObjs = [];
|
||||
function onBrowserWindowCreated(window) {
|
||||
window.webContents.on("did-stop-loading", () => {
|
||||
//只针对主界面和独立聊天界面生效
|
||||
@@ -141,6 +251,7 @@ function onBrowserWindowCreated(window) {
|
||||
window.webContents.getURL().indexOf("#/main/message") != -1 ||
|
||||
window.webContents.getURL().indexOf("#/chat/") != -1
|
||||
) {
|
||||
mainWindowObjs.push(window);
|
||||
// const proxyEvents = new Proxy(
|
||||
// window.webContents._events["-ipc-message"],
|
||||
// {
|
||||
@@ -260,21 +371,9 @@ function onBrowserWindowCreated(window) {
|
||||
|
||||
downloadPic(olderMsgFromRecalledMsg.msg);
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
olderMsgFromRecalledMsg.msg
|
||||
)
|
||||
);
|
||||
|
||||
args[1].msgList[i] =
|
||||
olderMsgFromRecalledMsg.msg;
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
olderMsgFromRecalledMsg.msg
|
||||
)
|
||||
);
|
||||
|
||||
output(
|
||||
"Detected recall, intercepted and recovered from old msg"
|
||||
);
|
||||
@@ -377,7 +476,9 @@ function onBrowserWindowCreated(window) {
|
||||
olderMsgFromRecalledMsg == null
|
||||
) {
|
||||
recalledMsg.push(olderMsg);
|
||||
insertDb(olderMsg);
|
||||
if (nowConfig.saveDb) {
|
||||
insertDb(olderMsg);
|
||||
}
|
||||
}
|
||||
|
||||
downloadPic(olderMsg?.msg);
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"name": "防撤回",
|
||||
"slug": "anti_recall",
|
||||
"description": "防止QQNT撤回消息",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.9",
|
||||
"thumbnail": "./icon.png",
|
||||
"author": {
|
||||
"name": "XiaoHe321",
|
||||
|
||||
+10
@@ -1,6 +1,16 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("anti_recall", {
|
||||
clearDb: () => ipcRenderer.invoke("LiteLoader.anti_recall.clearDb"),
|
||||
getNowConfig: () =>
|
||||
ipcRenderer.invoke("LiteLoader.anti_recall.getNowConfig"),
|
||||
saveConfig: (config) =>
|
||||
ipcRenderer.invoke("LiteLoader.anti_recall.saveConfig", config),
|
||||
repatchCss: (callback) =>
|
||||
ipcRenderer.on(
|
||||
"LiteLoader.anti_recall.mainWindow.repatchCss",
|
||||
callback
|
||||
),
|
||||
recallTip: (callback) =>
|
||||
ipcRenderer.on("LiteLoader.anti_recall.mainWindow.recallTip", callback),
|
||||
recallTipList: (callback) =>
|
||||
|
||||
+451
-49
@@ -1,7 +1,423 @@
|
||||
var recalledMsgList = [];
|
||||
export function onLoad() {
|
||||
|
||||
export async function onConfigView(view) {
|
||||
var nowConfig = await window.anti_recall.getNowConfig();
|
||||
|
||||
const new_navbar_item = `
|
||||
<body>
|
||||
<div class="config_view">
|
||||
<section class="path">
|
||||
<h1>主配置</h1>
|
||||
<div class="wrap">
|
||||
|
||||
<div class="list">
|
||||
<div class="vertical-list-item top-box">
|
||||
<h2>操作</h2>
|
||||
<div>
|
||||
<button id="clearDb" class="q-button q-button--small q-button--secondary">清空已储存的撤回消息</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="horizontal-dividing-line" />
|
||||
<div class="vertical-list-item">
|
||||
<div style="width:90%;" >
|
||||
<h2>是否将撤回消息存入数据库</h2>
|
||||
<span class="secondary-text">数据库不加密,若开启风险自负;若不开启,重启QQ后撤回消息会丢失;开启选项后,反撤回消息才会开始保存;若之前开过,现在关闭,储存的消息不会被删除,需要你手动清理。</span>
|
||||
</div>
|
||||
<div id="switchSaveDb" class="q-switch">
|
||||
<span class="q-switch__handle"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="path">
|
||||
<h1>样式配置</h1>
|
||||
<div class="wrap">
|
||||
|
||||
<div class="list">
|
||||
|
||||
<div class="vertical-list-item">
|
||||
<div>
|
||||
<h2>撤回主题色</h2></h2>
|
||||
<span class="secondary-text">将会同时影响阴影和“已撤回”提示的颜色</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="color" value="#ff0000" class="q-button q-button--small q-button--secondary pick-color" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="horizontal-dividing-line" />
|
||||
|
||||
<div class="vertical-list-item">
|
||||
<div>
|
||||
<h2>撤回后消息是否显示阴影</h2>
|
||||
<span class="secondary-text">修改将自动保存并实时生效</span>
|
||||
</div>
|
||||
<div id="switchShadow" class="q-switch">
|
||||
<span class="q-switch__handle"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="horizontal-dividing-line" />
|
||||
|
||||
<div class="vertical-list-item">
|
||||
<div>
|
||||
<h2>撤回后消息下方是否显示“已撤回”提示</h2>
|
||||
<span class="secondary-text">修改将自动保存并在重新滚动消息后生效</span>
|
||||
</div>
|
||||
<div id="switchTip" class="q-switch">
|
||||
<span class="q-switch__handle"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.img-hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.path-input {
|
||||
align-self: normal;
|
||||
flex: 1;
|
||||
border-radius: 4px;
|
||||
margin-right: 16px;
|
||||
transition: all 100ms ease-out;
|
||||
}
|
||||
|
||||
.path-input:focus {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.bq-icon {
|
||||
height:16px;
|
||||
width:16px;
|
||||
}
|
||||
|
||||
/* 通用 */
|
||||
.config_view {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.config_view h1 {
|
||||
color: var(--text_primary);
|
||||
font-weight: var(--font-bold);
|
||||
font-size: min(var(--font_size_3), 18px);
|
||||
line-height: min(var(--line_height_3), 24px);
|
||||
padding: 0px 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.config_view .wrap {
|
||||
/* Linux样式兼容:--fg_white */
|
||||
background-color: var(--fill_light_primary, var(--fg_white));
|
||||
border-radius: 8px;
|
||||
font-size: min(var(--font_size_3), 18px);
|
||||
line-height: min(var(--line_height_3), 24px);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.config_view .vertical-list-item {
|
||||
margin: 12px 0px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config_view .horizontal-dividing-line {
|
||||
border: unset;
|
||||
margin: unset;
|
||||
height: 1px;
|
||||
background-color: rgba(127, 127, 127, 0.15);
|
||||
}
|
||||
|
||||
.config_view .vertical-dividing-line {
|
||||
border: unset;
|
||||
margin: unset;
|
||||
width: 1px;
|
||||
background-color: rgba(127, 127, 127, 0.15);
|
||||
}
|
||||
|
||||
.config_view .ops-btns {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.config_view .hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.config_view .disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.config_view .secondary-text {
|
||||
color: var(--text_secondary);
|
||||
font-size: min(var(--font_size_2), 16px);
|
||||
line-height: min(var(--line_height_2), 22px);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.config_view .wrap .title {
|
||||
cursor: pointer;
|
||||
font-size: min(var(--font_size_3), 18px);
|
||||
line-height: min(var(--line_height_3), 24px);
|
||||
}
|
||||
|
||||
.config_view .wrap .title svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
transform: rotate(-180deg);
|
||||
transition-duration: 0.2s;
|
||||
transition-timing-function: ease;
|
||||
transition-delay: 0s;
|
||||
transition-property: transform;
|
||||
}
|
||||
|
||||
.config_view .wrap .title svg.is-fold {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
|
||||
/* 模态框 */
|
||||
.config_view .modal-window {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.config_view .modal-dialog {
|
||||
width: 480px;
|
||||
border-radius: 8px;
|
||||
/* Linux样式兼容:--fg_white */
|
||||
background-color: var(--bg_bottom_standard, var(--fg_white));
|
||||
}
|
||||
|
||||
.config_view .modal-dialog header {
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.config_view .modal-dialog main {
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.config_view .modal-dialog main p {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
.config_view .modal-dialog footer {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config_view .modal-dialog .q-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
|
||||
/* 版本号 */
|
||||
.config_view .versions .wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0px;
|
||||
}
|
||||
|
||||
.config_view .versions .wrap>div {
|
||||
flex: 1;
|
||||
margin: 0px 10px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* 数据目录 */
|
||||
.config_view .path .path-input {
|
||||
align-self: normal;
|
||||
flex: 1;
|
||||
border-radius: 4px;
|
||||
margin-right: 16px;
|
||||
transition: all 100ms ease-out;
|
||||
}
|
||||
|
||||
.config_view .path .path-input:focus {
|
||||
padding-left: 5px;
|
||||
background-color: rgba(127, 127, 127, 0.1);
|
||||
}
|
||||
|
||||
/* 选择框容器 */
|
||||
.config_view .list-ctl .ops-selects {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.text_color {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.text_color {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
const parser = new DOMParser();
|
||||
|
||||
const doc2 = parser.parseFromString(new_navbar_item, "text/html");
|
||||
const node2 = doc2.querySelector("body > div");
|
||||
|
||||
//清空消息
|
||||
node2.querySelector("#clearDb").onclick = async () => {
|
||||
await window.anti_recall.clearDb();
|
||||
};
|
||||
|
||||
//选择颜色
|
||||
const pickColor = node2.querySelector(".pick-color");
|
||||
pickColor.value = nowConfig.mainColor;
|
||||
pickColor.addEventListener("change", async (event) => {
|
||||
nowConfig.mainColor = event.target.value;
|
||||
await window.anti_recall.saveConfig(nowConfig);
|
||||
});
|
||||
|
||||
//存数据库开关
|
||||
var q_switch_savedb = node2.querySelector("#switchSaveDb");
|
||||
|
||||
if (nowConfig.saveDb == null || nowConfig.saveDb == true) {
|
||||
q_switch_savedb.classList.toggle("is-active");
|
||||
}
|
||||
|
||||
q_switch_savedb.addEventListener("click", async () => {
|
||||
if (q_switch_savedb.classList.contains("is-active")) {
|
||||
nowConfig.saveDb = false;
|
||||
} else {
|
||||
nowConfig.saveDb = true;
|
||||
}
|
||||
q_switch_savedb.classList.toggle("is-active");
|
||||
await window.anti_recall.saveConfig(nowConfig);
|
||||
});
|
||||
|
||||
//阴影开关
|
||||
var q_switch_shadow = node2.querySelector("#switchShadow");
|
||||
|
||||
if (nowConfig.enableShadow == null || nowConfig.enableShadow == true) {
|
||||
q_switch_shadow.classList.toggle("is-active");
|
||||
}
|
||||
|
||||
q_switch_shadow.addEventListener("click", async () => {
|
||||
if (q_switch_shadow.classList.contains("is-active")) {
|
||||
nowConfig.enableShadow = false;
|
||||
} else {
|
||||
nowConfig.enableShadow = true;
|
||||
}
|
||||
q_switch_shadow.classList.toggle("is-active");
|
||||
await window.anti_recall.saveConfig(nowConfig);
|
||||
});
|
||||
|
||||
//提示开关
|
||||
var q_switch_tip = node2.querySelector("#switchTip");
|
||||
|
||||
if (nowConfig.enableTip == null || nowConfig.enableTip == true) {
|
||||
q_switch_tip.classList.toggle("is-active");
|
||||
}
|
||||
|
||||
q_switch_tip.addEventListener("click", async () => {
|
||||
if (q_switch_tip.classList.contains("is-active")) {
|
||||
//取消
|
||||
nowConfig.enableTip = false;
|
||||
} else {
|
||||
//重新设置
|
||||
nowConfig.enableTip = true;
|
||||
}
|
||||
q_switch_tip.classList.toggle("is-active");
|
||||
await window.anti_recall.saveConfig(nowConfig);
|
||||
});
|
||||
|
||||
view.appendChild(node2);
|
||||
}
|
||||
|
||||
async function patchCss() {
|
||||
var nowConfig = await window.anti_recall.getNowConfig();
|
||||
|
||||
var cssNode = document
|
||||
.evaluate("/html/head/style[@id='anti-recall-css']", document)
|
||||
.iterateNext();
|
||||
if (cssNode) {
|
||||
cssNode.parentElement.removeChild(cssNode);
|
||||
}
|
||||
var stylee = document.createElement("style");
|
||||
stylee.type = "text/css";
|
||||
stylee.id = "anti-recall-css";
|
||||
var sHtml = `.message-content-recalled-parent {
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
${nowConfig.enableTip == true ? "margin-bottom: 15px;" : ""}
|
||||
overflow: unset !important;`;
|
||||
if (nowConfig.enableShadow == true) {
|
||||
sHtml += ` margin-top:3px;
|
||||
margin-left:3px;
|
||||
margin-right:3px;
|
||||
box-shadow: 0px 0px 20px 5px ${nowConfig.mainColor};`;
|
||||
}
|
||||
sHtml += ` }
|
||||
.message-content-recalled {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
color: var(--text-color);
|
||||
left: 0;
|
||||
background-color: var(--background-color-05);
|
||||
backdrop-filter: blur(28px);
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--box-shadow);
|
||||
transition: 300ms;
|
||||
transform: translateX(-30%);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
color:${nowConfig.mainColor};
|
||||
}
|
||||
`;
|
||||
stylee.innerHTML = sHtml;
|
||||
document.getElementsByTagName("head").item(0).appendChild(stylee);
|
||||
}
|
||||
|
||||
export async function onLoad() {
|
||||
anti_recall.repatchCss(async (event, _) => {
|
||||
await patchCss();
|
||||
});
|
||||
|
||||
//消息更新回调
|
||||
anti_recall.recallTip((event, msgId) => {
|
||||
anti_recall.recallTip(async (event, msgId) => {
|
||||
console.log("[Anti-Recall]", "尝试反撤回消息ID", msgId);
|
||||
|
||||
var oldElement = document.querySelector(
|
||||
@@ -16,20 +432,24 @@ export function onLoad() {
|
||||
`.message[id='ml-${msgId}'] .msg-content-container`
|
||||
);
|
||||
|
||||
if (oldElement != null) appendRecalledTag(oldElement);
|
||||
else if (newElement != null)
|
||||
appendRecalledTag(newElement.parentElement);
|
||||
else if (unixElement != null)
|
||||
appendRecalledTag(unixElement.parentElement);
|
||||
if (oldElement != null) {
|
||||
await appendRecalledTag(oldElement);
|
||||
} else if (newElement != null) {
|
||||
await appendRecalledTag(newElement.parentElement);
|
||||
} else if (unixElement != null) {
|
||||
await appendRecalledTag(unixElement.parentElement);
|
||||
}
|
||||
});
|
||||
//消息列表更新回调
|
||||
anti_recall.recallTipList((event, msgIdList) => {
|
||||
anti_recall.recallTipList(async (event, msgIdList) => {
|
||||
recalledMsgList = msgIdList;
|
||||
render();
|
||||
await render();
|
||||
});
|
||||
|
||||
await patchCss();
|
||||
|
||||
//监控消息列表,如果有撤回则渲染
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
const observer = new MutationObserver(async (mutationsList) => {
|
||||
for (let mutation of mutationsList) {
|
||||
if (mutation.type === "childList") {
|
||||
if (
|
||||
@@ -42,7 +462,7 @@ export function onLoad() {
|
||||
) {
|
||||
//是添加的撤回标记,直接忽略
|
||||
} else {
|
||||
render();
|
||||
await render();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,10 +474,10 @@ export function onLoad() {
|
||||
const config = { attributes: true, childList: true, subtree: true };
|
||||
observer.observe(targetNode, config);
|
||||
|
||||
function render() {
|
||||
async function render() {
|
||||
//console.log("[Anti-Recall]", "尝试反撤回消息列表", recalledMsgList);
|
||||
|
||||
recalledMsgList.forEach((msgId) => {
|
||||
for (var msgId of recalledMsgList) {
|
||||
try {
|
||||
var oldElement = document.querySelector(
|
||||
`div[id='${msgId}-msgContainerMsgContent']`
|
||||
@@ -71,56 +491,38 @@ export function onLoad() {
|
||||
`.message[id='ml-${msgId}'] .msg-content-container`
|
||||
);
|
||||
|
||||
if (oldElement != null) appendRecalledTag(oldElement);
|
||||
if (oldElement != null) await appendRecalledTag(oldElement);
|
||||
else if (newElement != null)
|
||||
appendRecalledTag(newElement.parentElement);
|
||||
await appendRecalledTag(newElement.parentElement);
|
||||
else if (unixElement != null)
|
||||
appendRecalledTag(unixElement.parentElement);
|
||||
await appendRecalledTag(unixElement.parentElement);
|
||||
} catch (e) {
|
||||
console.log("[Anti-Recall]", "反撤回消息时出错", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function appendRecalledTag(msgElement) {
|
||||
async function appendRecalledTag(msgElement) {
|
||||
if (!msgElement) return;
|
||||
var nowConfig = await window.anti_recall.getNowConfig();
|
||||
|
||||
var currRecalledTip = msgElement.querySelector(
|
||||
".message-content-recalled"
|
||||
);
|
||||
if (currRecalledTip == null) {
|
||||
msgElement.style = `
|
||||
position: relative;
|
||||
margin-bottom: 15px;
|
||||
overflow: unset !important;
|
||||
`;
|
||||
const recalledEl = document.createElement("div");
|
||||
recalledEl.innerText = "已撤回";
|
||||
recalledEl.classList.add("message-content-recalled");
|
||||
recalledEl.style = `
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
color: var(--text-color);
|
||||
left: 0;
|
||||
background-color: var(--background-color-05);
|
||||
backdrop-filter: blur(28px);
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--box-shadow);
|
||||
transition: 300ms;
|
||||
transform: translateX(-30%);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
color:red;
|
||||
`;
|
||||
msgElement.classList.add("message-content-recalled-parent");
|
||||
|
||||
msgElement.appendChild(recalledEl);
|
||||
setTimeout(() => {
|
||||
recalledEl.style.transform = "translateX(0)";
|
||||
recalledEl.style.opacity = "1";
|
||||
}, 5);
|
||||
if (nowConfig.enableTip == true) {
|
||||
const recalledEl = document.createElement("div");
|
||||
recalledEl.innerText = "已撤回";
|
||||
recalledEl.classList.add("message-content-recalled");
|
||||
|
||||
msgElement.appendChild(recalledEl);
|
||||
setTimeout(() => {
|
||||
recalledEl.style.transform = "translateX(0)";
|
||||
recalledEl.style.opacity = "1";
|
||||
}, 5);
|
||||
}
|
||||
} else {
|
||||
//已经有撤回标记了,不再重复添加
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user