From c0a6eb2d21c9aeed85908eefc8c6e9bdbaab593a Mon Sep 17 00:00:00 2001 From: Hi Flysoft Date: Fri, 21 Jul 2023 00:01:31 +0800 Subject: [PATCH] release 1.0.0 --- .editorconfig | 2 +- .github/screenshot.png | Bin 0 -> 12790 bytes README.md | 14 ++- package.json | 4 +- publish/qqntim.json | 6 +- publish/style.css | 13 ++- src/_renderer.tsx | 218 +++++++++++++++++++++++++++++++++++++++++ src/config.ts | 16 +-- src/main.ts | 11 +-- src/renderer.ts | 12 +-- src/settings.tsx | 52 +--------- yarn.lock | 4 +- 12 files changed, 258 insertions(+), 94 deletions(-) create mode 100644 .github/screenshot.png create mode 100644 src/_renderer.tsx diff --git a/.editorconfig b/.editorconfig index ffe9a1f..ad95218 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true end_of_line = lf insert_final_newline = true -[*.{js,json,yml}] +[*.{ts,tsx,css,json,yml}] charset = utf-8 indent_style = space indent_size = 4 diff --git a/.github/screenshot.png b/.github/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..6a1127fcdc369386fd6ed9f2e136d05b1080097a GIT binary patch literal 12790 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 Q=RlEpCjYcR+`#vL0R3cJxc~qF literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 70dba72..90a909d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -# QQNTim 模板插件 +# QQNTim 消息增强 + +## 简介 + +本插件为 QQNT 提供复读消息、转发语音消息、撤回好友私聊消息等功能。需要安装 [QQNTim 3.0 及以上版本](https://github.com/Flysoft-Studio/QQNTim) 才能使用。 + +效果图: + +![截图](.github/screenshot.png) + +## 使用 + +安装此插件后对着消息右键即可看到添加的功能。 ## 开发 diff --git a/package.json b/package.json index bd04c3c..3f8ed81 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "qqntim-plugin-template", + "name": "qqntim-plugin-enhanced-messaging", "private": true, - "version": "2.0.0", + "version": "1.0.0", "packageManager": "yarn@3.6.1", "license": "MIT", "scripts": { diff --git a/publish/qqntim.json b/publish/qqntim.json index 8f560fc..2ff55e8 100644 --- a/publish/qqntim.json +++ b/publish/qqntim.json @@ -1,8 +1,8 @@ { "manifestVersion": "3.0", - "id": "template-plugin", - "name": "QQNTim 模板插件", - "description": "快速开始开发你自己的插件。", + "id": "enhanced-messaging", + "name": "消息增强", + "description": "提供复读消息、转发语音消息、撤回好友私聊消息等功能。", "version": "1.0.0", "author": "Flysoft", "injections": [ diff --git a/publish/style.css b/publish/style.css index 4ee87da..ae16cba 100644 --- a/publish/style.css +++ b/publish/style.css @@ -1,2 +1,13 @@ -/* Put your stylesheet here. */ +.enhanced-messaging-floating-ok { + position: fixed; + bottom: 30px; + right: 120px; + z-index: 10000; +} +.enhanced-messaging-floating-cancel { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 10000; +} diff --git a/src/_renderer.tsx b/src/_renderer.tsx new file mode 100644 index 0000000..6274042 --- /dev/null +++ b/src/_renderer.tsx @@ -0,0 +1,218 @@ +import { dialog, env, nt, utils } from "qqntim/renderer"; +import { getPluginConfig } from "./config"; +import { createRoot } from "react-dom/client"; + +function selectPeer() { + return new Promise | undefined>((resolve) => { + const currentSelected = document.querySelector(".recent-contact-item.recent-contact-item--selected"); + currentSelected?.classList.remove("list-item--selected", "recent-contact-item--selected"); + const selectedPeers = new Map(); + const mouseDownHandler = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + }; + const clickHandler = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + const element = (event.target as HTMLElement | undefined)?.closest?.(".recent-contact-item"); + const itemData = element?.__VUE__?.[0]?.props?.itemData; + if (itemData) + if (!selectedPeers.has(itemData.uid)) { + selectedPeers.set(itemData.uid, { chatType: itemData.type == 1 ? "friend" : itemData.type == 2 ? "group" : "others", uid: itemData.uid }); + element?.classList.add("list-item--selected", "recent-contact-item--selected"); + } else { + selectedPeers.delete(itemData.uid); + element?.classList.remove("list-item--selected", "recent-contact-item--selected"); + } + }; + const elements = document.querySelectorAll(".recent-contact-item"); + for (const element of elements) { + (element?.firstElementChild as HTMLElement | null)?.addEventListener("mousedown", mouseDownHandler); + (element?.firstElementChild as HTMLElement | null)?.addEventListener("click", clickHandler); + } + const exitSelect = () => { + for (const element of elements) { + (element?.firstElementChild as HTMLElement | null)?.removeEventListener("mousedown", mouseDownHandler); + (element?.firstElementChild as HTMLElement | null)?.removeEventListener("click", clickHandler); + element.classList.remove("list-item--selected", "recent-contact-item--selected"); + } + currentSelected?.classList.add("list-item--selected", "recent-contact-item--selected"); + okBtn.remove(); + cancelBtn.remove(); + }; + const okBtn = document.createElement("button"); + okBtn.classList.add("q-button", "q-button--default", "q-button--primary", "enhanced-messaging-floating-ok"); + const okBtnText = document.createElement("span"); + okBtnText.classList.add("q-button__slot-warp"); + okBtnText.innerText = "确定"; + okBtn.appendChild(okBtnText); + okBtn.addEventListener("click", () => { + exitSelect(); + resolve(selectedPeers); + }); + const cancelBtn = document.createElement("button"); + cancelBtn.classList.add("q-button", "q-button--default", "q-button--secondary", "enhanced-messaging-floating-cancel"); + const cancelBtnText = document.createElement("span"); + cancelBtnText.classList.add("q-button__slot-warp"); + cancelBtnText.innerText = "取消"; + cancelBtn.appendChild(cancelBtnText); + cancelBtn.addEventListener("click", () => { + exitSelect(); + resolve(undefined); + }); + document.body.appendChild(okBtn); + document.body.appendChild(cancelBtn); + }); +} + +function encodeMsg(msg: any) { + return msg.elements.map((_element) => { + const element = JSON.parse(JSON.stringify(_element)); + return { + type: "raw", + raw: { + ...element, + elementId: "", + extBufForUI: undefined, + picElement: element.picElement && { + ...element.picElement, + fileSubId: undefined, + fileUuid: undefined, + progress: undefined, + thumbPath: undefined, + transferStatus: undefined, + }, + }, + }; + }); +} + +function forwardMsg(msg: any) { + selectPeer().then((peers) => { + if (!peers) return; + Promise.all(Array.from(peers.values()).map((peer) => nt.sendMessage(peer, encodeMsg(msg)).catch((reason) => console.error(reason)))) + .then((array) => array.filter(Boolean)) + .then((array) => { + dialog.alert(`成功向 ${array.length} 个对象转发此消息。`); + }); + }); +} + +function repeatMsg(msg: any) { + nt.sendMessage({ uid: msg.peerUid, chatType: msg.chatType == 1 ? "friend" : msg.chatType == 2 ? "group" : "others" }, encodeMsg(msg)); +} + +function forceRevokeMsg(msg: any) { + nt.revokeMessage({ uid: msg.peerUid, chatType: msg.chatType == 1 ? "friend" : msg.chatType == 2 ? "group" : "others" }, msg.msgId); +} + +function ShareIcon() { + return ( + + + + + + ); +} + +function RevokeIcon() { + return ( + + + + + ); +} + +function Menu({ msg, showForwardBtn, showPlusOneBtn, showForceRevokeBtn, blur }: { msg: any; showForwardBtn: boolean; showPlusOneBtn: boolean; showForceRevokeBtn: boolean; blur: Function }) { + return ( + <> + {(showForwardBtn || showPlusOneBtn) &&
} + {([showForwardBtn && ["转发", , () => forwardMsg(msg)], showForceRevokeBtn && ["强制撤回", , () => forceRevokeMsg(msg)], showPlusOneBtn && ["+1", undefined, () => repeatMsg(msg)]].filter(Boolean) as [string, React.ReactNode, Function][]).map( + ([title, icon, onClick], idx) => ( + + key={idx} + className="q-context-menu-item q-context-menu-item--normal" + role="item" + onClick={() => { + onClick(); + blur(); + }} + > + + {icon} + + {title} + + ), + )} + + ); +} + +export default class Entry implements QQNTim.Entry.Renderer { + constructor() { + const config = getPluginConfig(env.config.plugins.config); + } + + onWindowLoaded(): void { + const handler = (event: MouseEvent) => { + const msg = (event.target as HTMLElement | undefined)?.closest(".msg-content-container")?.closest(".message")?.__VUE__?.[0]?.props?.msgRecord; + let showForwardBtn = true; + let showForceRevokeBtn = msg.chatType == 1; + if (msg) { + utils.waitForElement(".q-context-menu").then((menu) => { + const items = document.querySelectorAll(".q-context-menu .q-context-menu-item .q-context-menu-item__text"); + for (const item of items) { + if (item.innerText == "转发") showForwardBtn = false; + if (item.innerText == "撤回") showForceRevokeBtn = false; + } + const pluginMenuElement = document.createElement("div"); + const pluginMenuRoot = createRoot(pluginMenuElement); + pluginMenuRoot.render( + { + menu.blur(); + const mouseEvent = new MouseEvent("click", { + view: window, + bubbles: true, + cancelable: true, + }); + document.querySelector("#app")?.dispatchEvent(mouseEvent); + }} + />, + ); + menu.appendChild(pluginMenuElement); + requestAnimationFrame(() => { + const rect = menu.getBoundingClientRect(); + if (rect.bottom + 20 > document.body.clientHeight) { + menu.style.top = ""; + menu.style.bottom = "20px"; + } + if (rect.right + 20 > document.body.clientWidth) { + menu.style.left = ""; + menu.style.right = "20px"; + } + }); + }); + } + }; + window.addEventListener("contextmenu", handler); + // 由于图片比较特殊,会阻止右键消息传达到 window,所以需要单独设置 listener + new MutationObserver(() => { + const elements = document.querySelectorAll(".image-content:not(.enhanced-messaging-img-patched)"); + for (const element of elements) { + element.classList.add("enhanced-messaging-img-patched"); + element.addEventListener("contextmenu", handler); + } + }).observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true }); + } +} diff --git a/src/config.ts b/src/config.ts index 5c49c60..fcf1f48 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,21 +1,11 @@ -export const id = "my-template-plugin" as const; +export const id = "enhanced-messaging" as const; -export const defaults: PluginConfig = { - switchConfigItem: false, - anotherSwitchConfigItem: false, - inputConfigItem: "默认值", - dropdownConfigItem: "A", -}; +export const defaults: PluginConfig = {}; export function getPluginConfig(config: Config | undefined) { return Object.assign({}, defaults, config?.[id] || {}); } -export interface PluginConfig { - switchConfigItem: boolean; - anotherSwitchConfigItem: boolean; - inputConfigItem: string; - dropdownConfigItem: "A" | "B" | "C"; -} +export type PluginConfig = {}; export type Config = { [X in typeof id]?: Partial; }; diff --git a/src/main.ts b/src/main.ts index 5a1ec87..8109a45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1 @@ -import { getPluginConfig } from "./config"; -import * as qqntim from "qqntim/main"; - -export default class Entry implements QQNTim.Entry.Main { - constructor() { - const config = getPluginConfig(qqntim.env.config.plugins.config); - console.log("[Template] Hello world!", qqntim); - console.log("[Template] 当前插件配置:", config); - } -} +export default class Entry implements QQNTim.Entry.Main {} diff --git a/src/renderer.ts b/src/renderer.ts index 9bfc233..1027ebd 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,10 +1,2 @@ -import { getPluginConfig } from "./config"; -import * as qqntim from "qqntim/renderer"; - -export default class Entry implements QQNTim.Entry.Renderer { - constructor() { - const config = getPluginConfig(qqntim.env.config.plugins.config); - console.log("[Template] Hello world!", qqntim); - console.log("[Template] 当前插件配置:", config); - } -} +import Entry from "./_renderer"; +export default Entry; diff --git a/src/settings.tsx b/src/settings.tsx index 53cdab9..3c4544c 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -1,51 +1 @@ -import { usePluginConfig } from "./utils/hooks"; -import { defineSettingsPanels } from "qqntim-settings"; -import { Dropdown, Input, SettingsBox, SettingsBoxItem, SettingsSection, Switch } from "qqntim-settings/components"; -import { env } from "qqntim/renderer"; -import { useMemo } from "react"; -import { getPluginConfig } from "./config"; - -export default class Entry implements QQNTim.Entry.Renderer { - constructor() { - // 如果不需要设置界面,将下一行注释掉即可;如果需要在设置项目旁边加一个小图标,请将 `undefined` 改为一段 HTML 代码(可以是 ``, `` 等等)。 - defineSettingsPanels(["模板插件设置", SettingsPanel, undefined]); - } -} - -function SettingsPanel({ config: _config, setConfig: _setConfig }: QQNTim.Settings.PanelProps) { - const [pluginConfig, setPluginConfig] = usePluginConfig(_config, _setConfig); - const currentPluginConfigString = useMemo(() => JSON.stringify(getPluginConfig(env.config.plugins.config)), []); - - return ( - <> - - - - - setPluginConfig("switchConfigItem", state)} /> - - {pluginConfig.switchConfigItem && ( - - setPluginConfig("anotherSwitchConfigItem", state)} /> - - )} - - setPluginConfig("dropdownConfigItem", state)} - width="150px" - /> - - - setPluginConfig("inputConfigItem", state)} /> - - - - - ); -} +export default class Entry implements QQNTim.Entry.Renderer {} diff --git a/yarn.lock b/yarn.lock index f1fbb19..453618a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1277,9 +1277,9 @@ __metadata: languageName: node linkType: hard -"qqntim-plugin-template@workspace:.": +"qqntim-plugin-enhanced-messaging@workspace:.": version: 0.0.0-use.local - resolution: "qqntim-plugin-template@workspace:." + resolution: "qqntim-plugin-enhanced-messaging@workspace:." dependencies: "@flysoftbeta/qqntim-typings": ^3.1.2 "@types/node": ^20.4.2