koishi-plugin-watermark-remover 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/lib/index.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const name = "watermark-remover";
3
+ export declare const inject: string[];
4
+ export interface Config {
5
+ model: 'Standard V1' | 'Auto V2' | 'Auto V2.3' | 'Manual';
6
+ removeText: boolean;
7
+ removeLogo: boolean;
8
+ enhanceQuality: boolean;
9
+ debug: boolean;
10
+ timeout: number;
11
+ }
12
+ export declare const Config: Schema<Config>;
13
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,208 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ var __export = (target, all) => {
7
+ for (var name2 in all)
8
+ __defProp(target, name2, { get: all[name2], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ Config: () => Config,
24
+ apply: () => apply,
25
+ inject: () => inject,
26
+ name: () => name
27
+ });
28
+ module.exports = __toCommonJS(src_exports);
29
+ var import_koishi = require("koishi");
30
+ var import_path = require("path");
31
+ var import_fs = require("fs");
32
+ var name = "watermark-remover";
33
+ var inject = ["puppeteer"];
34
+ var Config = import_koishi.Schema.object({
35
+ model: import_koishi.Schema.union(["Standard V1", "Auto V2", "Auto V2.3", "Manual"]).default("Auto V2.3").description("选择使用的模型。"),
36
+ removeText: import_koishi.Schema.boolean().default(false).description("移除图片中的文字。"),
37
+ removeLogo: import_koishi.Schema.boolean().default(false).description("移除图片中的 Logo。"),
38
+ enhanceQuality: import_koishi.Schema.boolean().default(false).description("增强图片画质。"),
39
+ debug: import_koishi.Schema.boolean().default(false).description("开启调试模式(显示浏览器窗口和详细日志)。"),
40
+ timeout: import_koishi.Schema.number().default(18e4).description("处理超时时间(毫秒)。")
41
+ });
42
+ function apply(ctx, config) {
43
+ const logger = ctx.logger("watermark-remover");
44
+ ctx.command("wmremove [image:image]", "去除图片水印").alias("去水印").action(async ({ session }, image) => {
45
+ let imgUrl = image?.src;
46
+ if (!imgUrl) {
47
+ const imgElement = import_koishi.h.select(session.elements, "img")[0];
48
+ imgUrl = imgElement?.attrs.src;
49
+ }
50
+ if (!imgUrl) {
51
+ await session.send("请发送一张图片。");
52
+ const input = await session.prompt(3e4);
53
+ if (!input) return "输入超时。";
54
+ const img = import_koishi.h.select(input, "img")[0];
55
+ if (!img) return "未检测到图片。";
56
+ imgUrl = img.attrs.src;
57
+ }
58
+ const processingMsg = await session.send("正在处理图片,请稍候...");
59
+ const processingMsgId = processingMsg[0];
60
+ const page = await ctx.puppeteer.page({
61
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
62
+ });
63
+ const root = ctx.baseDir || process.cwd();
64
+ const tempDir = (0, import_path.resolve)(root, "data", "watermark-remover");
65
+ await import_fs.promises.mkdir(tempDir, { recursive: true });
66
+ const tempFile = (0, import_path.resolve)(tempDir, `unwatermark_${Date.now()}.png`);
67
+ try {
68
+ if (config.debug) logger.info("正在下载图片...");
69
+ const buffer = await ctx.http.get(imgUrl, { responseType: "arraybuffer" });
70
+ await import_fs.promises.writeFile(tempFile, Buffer.from(buffer));
71
+ if (config.debug) logger.info(`图片已保存至临时文件: ${tempFile}`);
72
+ if (config.debug) logger.info("正在打开目标网站...");
73
+ await page.goto("https://unwatermark.ai/", { waitUntil: "networkidle2" });
74
+ if (config.model !== "Auto V2.3") {
75
+ const modelSelector = `::-p-text(${config.model})`;
76
+ try {
77
+ if (config.debug) logger.info(`正在选择模型: ${config.model}`);
78
+ await page.waitForSelector(modelSelector, { timeout: 5e3 });
79
+ await page.click(modelSelector);
80
+ } catch (e) {
81
+ logger.warn(`无法选择模型 ${config.model}`);
82
+ }
83
+ }
84
+ const toggle = /* @__PURE__ */ __name(async (text, enable) => {
85
+ if (!enable) return;
86
+ const selector = `::-p-text(${text})`;
87
+ try {
88
+ if (config.debug) logger.info(`正在开启选项: ${text}`);
89
+ await page.waitForSelector(selector, { timeout: 2e3 });
90
+ await page.click(selector);
91
+ } catch (e) {
92
+ logger.warn(`无法开启选项 ${text}`);
93
+ }
94
+ }, "toggle");
95
+ await toggle("Remove Text", config.removeText);
96
+ await toggle("Remove Logo", config.removeLogo);
97
+ await toggle("Enhance Quality", config.enhanceQuality);
98
+ if (config.debug) logger.info("正在查找上传入口...");
99
+ const fileInput = await page.waitForSelector('input[type="file"]', { timeout: 1e4 });
100
+ if (config.debug) logger.info("找到上传入口,开始上传...");
101
+ await fileInput.uploadFile(tempFile);
102
+ if (config.debug) logger.info("等待处理结果(Download 按钮出现)...");
103
+ const downloadButtonSelector = "::-p-text(Download)";
104
+ await page.waitForSelector(downloadButtonSelector, { timeout: config.timeout, visible: true });
105
+ let resultData = null;
106
+ let resultBuffer = null;
107
+ let resultMimeType = "image/jpeg";
108
+ let resultSource = "unknown";
109
+ try {
110
+ if (config.debug) logger.info("处理完成,尝试通过 Download 按钮获取结果图...");
111
+ const responsePromise = page.waitForResponse((response2) => {
112
+ const url = response2.url();
113
+ const contentType = response2.headers()["content-type"] || "";
114
+ const isOutput = url.includes("/output/");
115
+ const isImageType = contentType.includes("image/");
116
+ const isImageExt = /\.(png|jpe?g|webp|bmp|gif)(\?|$)/i.test(url);
117
+ return isOutput && (isImageType || isImageExt);
118
+ }, { timeout: Math.min(config.timeout, 15e3) });
119
+ await page.click(downloadButtonSelector);
120
+ const response = await responsePromise;
121
+ const downloadUrl = response.url();
122
+ resultMimeType = (response.headers()["content-type"] || "image/jpeg").split(";")[0];
123
+ const downloadBuffer = await ctx.http.get(downloadUrl, { responseType: "arraybuffer" });
124
+ resultBuffer = Buffer.from(downloadBuffer);
125
+ resultSource = "download-response";
126
+ if (config.debug) logger.info(`通过 Download 响应获取结果图片: ${downloadUrl}`);
127
+ } catch (e) {
128
+ if (config.debug) logger.warn("通过 Download 按钮获取结果失败,回退到页面结果图解析。");
129
+ }
130
+ if (!resultBuffer) {
131
+ if (config.debug) logger.info("开始解析页面结果图片...");
132
+ await page.waitForFunction(() => {
133
+ const imgs = Array.from(document.querySelectorAll("img.comparison-image"));
134
+ return imgs.some((img) => {
135
+ const src = img.src || "";
136
+ if (src.includes("watermark_remove_before")) return false;
137
+ return src.includes("watermark_remove_after") || src.includes("/output/") || src.startsWith("blob:") || src.startsWith("data:image/");
138
+ });
139
+ }, { timeout: config.timeout });
140
+ resultData = await page.evaluate(async () => {
141
+ const images = Array.from(document.querySelectorAll("img.comparison-image"));
142
+ const getScore = /* @__PURE__ */ __name((src) => {
143
+ if (!src || src.includes("watermark_remove_before")) return -1;
144
+ if (src.includes("watermark_remove_after")) return 4;
145
+ if (src.includes("/output/")) return 3;
146
+ if (src.startsWith("blob:")) return 2;
147
+ if (src.startsWith("data:image/")) return 2;
148
+ if (src.startsWith("http://") || src.startsWith("https://")) return 1;
149
+ return 0;
150
+ }, "getScore");
151
+ const candidate = images.map((img) => img.src).sort((a, b) => getScore(b) - getScore(a))[0];
152
+ if (!candidate || getScore(candidate) < 0) return null;
153
+ if (candidate.startsWith("blob:")) {
154
+ const response = await fetch(candidate);
155
+ const blob = await response.blob();
156
+ return new Promise((resolve2, reject) => {
157
+ const reader = new FileReader();
158
+ reader.onloadend = () => resolve2(reader.result);
159
+ reader.onerror = reject;
160
+ reader.readAsDataURL(blob);
161
+ });
162
+ }
163
+ return candidate;
164
+ });
165
+ if (!resultData) {
166
+ throw new Error("未找到处理后的图片结果");
167
+ }
168
+ resultSource = typeof resultData === "string" && resultData.startsWith("data:") ? "dom-blob-fallback" : "dom-after";
169
+ }
170
+ if (config.debug) {
171
+ const resultDisplay = resultBuffer ? `[Buffer ${resultMimeType}]` : typeof resultData === "string" && resultData.startsWith("data:") ? "Base64 Data" : resultData;
172
+ logger.info(`找到结果图片地址/数据(${resultSource}): ${resultDisplay}`);
173
+ }
174
+ try {
175
+ if (session.bot.deleteMessage) {
176
+ await session.bot.deleteMessage(session.channelId, processingMsgId);
177
+ }
178
+ } catch (e) {
179
+ logger.warn("撤回消息失败", e);
180
+ }
181
+ if (resultBuffer) {
182
+ return import_koishi.h.image(resultBuffer, resultMimeType);
183
+ }
184
+ if (typeof resultData === "string" && resultData.startsWith("data:")) {
185
+ const base64Data = resultData.split(",")[1];
186
+ const buffer2 = Buffer.from(base64Data, "base64");
187
+ const mimeType = resultData.match(/data:([^;]+);/)?.[1] || "image/png";
188
+ return import_koishi.h.image(buffer2, mimeType);
189
+ }
190
+ return import_koishi.h.image(resultData);
191
+ } catch (e) {
192
+ logger.error("处理失败:", e);
193
+ return `处理失败: ${e.message || "未知错误"}`;
194
+ } finally {
195
+ await page.close();
196
+ await import_fs.promises.unlink(tempFile).catch(() => {
197
+ });
198
+ }
199
+ });
200
+ }
201
+ __name(apply, "apply");
202
+ // Annotate the CommonJS export names for ESM import in node:
203
+ 0 && (module.exports = {
204
+ Config,
205
+ apply,
206
+ inject,
207
+ name
208
+ });
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "koishi-plugin-watermark-remover",
3
+ "description": "",
4
+ "version": "0.0.1",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "dist"
10
+ ],
11
+ "license": "MIT",
12
+ "scripts": {},
13
+ "keywords": [
14
+ "chatbot",
15
+ "koishi",
16
+ "plugin"
17
+ ],
18
+ "devDependencies": {},
19
+ "peerDependencies": {
20
+ "koishi": "4.18.9",
21
+ "koishi-plugin-puppeteer": "*"
22
+ }
23
+ }
package/readme.md ADDED
@@ -0,0 +1,59 @@
1
+ # koishi-plugin-watermark-remover
2
+
3
+ 一个用于 Koishi 的去水印插件,基于 Puppeteer 自动操作 `https://unwatermark.ai/`。
4
+
5
+ ## 功能
6
+
7
+ - 命令:`wmremove [image:image]`
8
+ - 支持从参数或消息内容中读取图片
9
+ - 可选项:`Remove Text`、`Remove Logo`、`Enhance Quality`
10
+ - 处理完成后自动返回结果图片
11
+
12
+ ## 安装
13
+
14
+ 确保已安装并启用以下依赖:
15
+
16
+ - `koishi`
17
+ - `koishi-plugin-puppeteer`
18
+
19
+ 在 Koishi 中安装本插件后启用即可。
20
+
21
+ ## 配置
22
+
23
+ - `model`:模型选择(默认 `Auto V2.3`)
24
+ - `removeText`:是否移除文字
25
+ - `removeLogo`:是否移除 Logo
26
+ - `enhanceQuality`:是否增强画质
27
+ - `debug`:是否输出调试日志
28
+ - `timeout`:处理超时时间(毫秒)
29
+
30
+ ## 使用
31
+
32
+ 发送命令并附带图片:
33
+
34
+ ```text
35
+ wmremove
36
+ ```
37
+
38
+ 或:
39
+
40
+ ```text
41
+ wmremove [图片]
42
+ ```
43
+
44
+ ## 注意
45
+ unwatermark 每日有免费次数上限,请适当使用。
46
+
47
+ ## 免责声明
48
+
49
+ 本插件通过自动化方式调用第三方网站服务。使用者需自行承担以下责任:
50
+
51
+ - 遵守目标网站的服务条款、使用政策及法律法规
52
+ - 确认上传图片的版权、隐私和数据合规性
53
+ - 自行评估第三方服务稳定性、可用性和数据安全风险
54
+
55
+ 作者与贡献者不对因使用本插件导致的服务中断、数据泄露、侵权纠纷或其他损失承担责任。
56
+
57
+ ## License
58
+
59
+ MIT