koishi-plugin-auto-welcome 1.0.0

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.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Doom
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,11 @@
1
+ import { Schema } from 'koishi'
2
+
3
+ export const name = 'koishi-plugin-auto-welcome'
4
+
5
+ export interface Config {
6
+ whitelist: string[]
7
+ welcomeTexts: Record<string, string>
8
+ welcomeImages: Record<string, string>
9
+ }
10
+
11
+ export const Config: Schema<Config>
package/lib/index.js ADDED
@@ -0,0 +1,239 @@
1
+ 'use strict'
2
+
3
+ const { Context, Schema, h } = require('koishi')
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const http = require('http')
7
+ const https = require('https')
8
+
9
+ /**
10
+ * 注意:
11
+ * 【该插件为自用插件】
12
+ *
13
+ * 该插件仅适用于以下环境
14
+ * 保存文件目录为静态网站
15
+ * 可通过修改源码实现
16
+ *
17
+ */
18
+
19
+ const SAVE_DIR = '/koishi/aw'
20
+ const BASE_URL = 'http(s)://example.com/'
21
+ const DATA_PATH = path.join(__dirname, 'welcome-data.json')
22
+
23
+ function loadData() {
24
+ try {
25
+ if (!fs.existsSync(DATA_PATH)) {
26
+ fs.writeFileSync(
27
+ DATA_PATH,
28
+ JSON.stringify({ texts: {}, images: {}, waitForImage: {} }, null, 2)
29
+ )
30
+ }
31
+ return JSON.parse(fs.readFileSync(DATA_PATH))
32
+ } catch {
33
+ return { texts: {}, images: {}, waitForImage: {} }
34
+ }
35
+ }
36
+
37
+ function saveData(data) {
38
+ fs.writeFileSync(DATA_PATH, JSON.stringify(data, null, 2))
39
+ }
40
+
41
+ // 封装下载图片函数
42
+ async function downloadImage(url, dest) {
43
+ return new Promise((resolve, reject) => {
44
+ const client = url.startsWith('https') ? https : http
45
+ client.get(url, (res) => {
46
+ if (res.statusCode !== 200) return reject(new Error(`下载失败: ${res.statusCode}`))
47
+ const fileStream = fs.createWriteStream(dest)
48
+ res.pipe(fileStream)
49
+ fileStream.on('finish', () => fileStream.close(resolve))
50
+ fileStream.on('error', reject)
51
+ }).on('error', reject)
52
+ })
53
+ }
54
+
55
+ // 插件注册
56
+ exports.name = 'koishi-plugin-auto-welcome'
57
+ exports.usage = '【自用插件】支持群内设置欢迎语与欢迎图片,并写入宿主机目录保存'
58
+
59
+ exports.Config = Schema.object({
60
+ EnterSendMsg: Schema.string().description('机器人入群欢迎文本,支持换行(使用 \\n)')
61
+ })
62
+
63
+ exports.apply = function apply(ctx, config) {
64
+ const logger = ctx.logger('auto-welcome')
65
+ let data = loadData()
66
+
67
+ // 机器人入群通知
68
+ ctx.on('guild-added', async (session) => {
69
+ if (!session.guildId) return
70
+ try {
71
+ let msg = config.EnterSendMsg.replace(/\\n/g, '\n')
72
+ await session.send(msg)
73
+ } catch (e) {
74
+ logger.warn(`发送机器人入群通知失败: ${e.message}`)
75
+ }
76
+ })
77
+
78
+ // 群聊新成员入群通知
79
+ ctx.on('guild-member-added', async (session) => {
80
+ const { guildId, userId } = session
81
+ if (!guildId || !userId) return
82
+
83
+ const text = data.texts[guildId]
84
+ if (!text) return
85
+
86
+ const image = data.images[guildId]
87
+ const welcomeText = text.replace('{user}', userId).replace('{group}', guildId)
88
+
89
+ const msg = [h('at', { id: userId }), ' ', welcomeText]
90
+
91
+ if (image) {msg.push(h('image', { url: image }))}
92
+
93
+ await session.send(msg)
94
+ })
95
+
96
+ ctx.on('guild-member-deleted', async (session) => {
97
+ const { guildId, userId } = session
98
+ if (!guildId || !userId) return
99
+ if (!data.texts[guildId]) return
100
+ await session.send(`用户 ${userId} 退出了本群喵...`)
101
+ })
102
+
103
+ // 命令注册
104
+ ctx.command('welcome', '设置欢迎信息')
105
+ .option('s', '-s <消息> 设置欢迎语')
106
+ .option('r', '-r 移除欢迎配置')
107
+ .option('t', '-t 测试欢迎语')
108
+ .option('p', '-p 设置欢迎图片')
109
+ .action(async ({ session, options }) => {
110
+ const guildId = session.guildId
111
+ if (!guildId) return '喵呜...这个命令只能在群里用喵...'
112
+
113
+ if (options.s !== undefined) {
114
+ const text = options.s.trim()
115
+ if (!text) return '欢迎语不能为空喵~'
116
+ data.texts[guildId] = text
117
+ saveData(data)
118
+ return '已经设置好欢迎语啦喵,要不要用 /welcome -t 试试看效果呀?'
119
+ }
120
+
121
+ if (options.r) {
122
+ delete data.texts[guildId]
123
+ delete data.images[guildId]
124
+ saveData(data)
125
+ return '欢迎语已经被我吃掉啦喵~'
126
+ }
127
+
128
+ if (options.t) {
129
+ const text = data.texts[guildId]
130
+ if (!text) return '本群尚未设置欢迎语喵...'
131
+ const userId = session.userId
132
+ const result = text.replace('{user}', userId).replace('{group}', guildId)
133
+ const msg = [h('at', { id: userId }), ' ', result]
134
+ if (data.images[guildId]) msg.push(h('image', { url: data.images[guildId] }))
135
+ return msg
136
+ }
137
+
138
+ if (options.p) {
139
+ data.waitForImage[session.userId] = guildId
140
+ saveData(data)
141
+ return '请在下一条消息发送图片喵'
142
+ }
143
+
144
+ return `当前欢迎语:${data.texts[guildId] || '未设置'}
145
+ 当前欢迎图片:${data.images[guildId] || '未设置'}
146
+
147
+ 指令列表:
148
+ welcome -s 文本 设置欢迎语
149
+ welcome -r 移除欢迎语与图片
150
+ welcome -t 测试欢迎设置
151
+ welcome -p 设置欢迎图片(消息发送)`
152
+ })
153
+
154
+ // 监听消息 处理图片
155
+ ctx.middleware(async (session, next) => {
156
+ if (!session.guildId || !session.userId) return next()
157
+ const waitGuild = data.waitForImage[session.userId]
158
+ if (!waitGuild) return next()
159
+
160
+ const msg = (session.content || '').trim()
161
+
162
+ function extractImageFromSession() {
163
+ if (Array.isArray(session.elements)) {
164
+ for (const e of session.elements) {
165
+ if (!e || !e.type) continue
166
+ const t = String(e.type).toLowerCase()
167
+ if (['image', 'img', 'picture', 'photo', 'image-node'].includes(t)) {
168
+ if (e.attrs && (e.attrs.url || e.attrs.file || e.attrs.src)) {
169
+ return e.attrs.url || e.attrs.src || e.attrs.file
170
+ }
171
+ if (e.url) return e.url
172
+ if (e.file) return e.file
173
+ if (e.content) return e.content
174
+ }
175
+ }
176
+ }
177
+
178
+ const cqMatch = msg.match(/\[CQ:image[^\]]*?(?:file=([^,\]\s]+))?(?:,?url=([^,\]\s]+))?/i)
179
+ if (cqMatch) {
180
+ if (cqMatch[2]) return cqMatch[2]
181
+ if (cqMatch[1]) return cqMatch[1]
182
+ }
183
+
184
+ const mdMatch = msg.match(/!\[[^\]]*\]\((https?:\/\/[^\s)]+)\)/i)
185
+ if (mdMatch) return mdMatch[1]
186
+
187
+ const urlMatch = msg.match(/(https?:\/\/\S+\.(?:png|jpe?g|gif|webp|bmp)(?:\?\S*)?)/i)
188
+ if (urlMatch) return urlMatch[1]
189
+
190
+ const genericUrl = msg.match(/(https?:\/\/\S+)/i)
191
+ if (genericUrl) return genericUrl[1]
192
+
193
+ if (session.attachments && Array.isArray(session.attachments) && session.attachments.length) {
194
+ const a = session.attachments[0]
195
+ if (a.url) return a.url
196
+ if (a.file) return a.file
197
+ }
198
+ return null
199
+ }
200
+
201
+ const imageUrl = extractImageFromSession()
202
+ if (!imageUrl) {
203
+ delete data.waitForImage[session.userId]
204
+ saveData(data)
205
+ await session.send('设置失败了喵...请发送图片喵~')
206
+ return
207
+ }
208
+
209
+ // 下载图片到宿主机
210
+ if (!fs.existsSync(SAVE_DIR)) fs.mkdirSync(SAVE_DIR, { recursive: true })
211
+ const ext = path.extname(imageUrl).split('?')[0] || '.jpg'
212
+ const localFile = path.join(SAVE_DIR, `${waitGuild}_${Date.now()}${ext}`)
213
+
214
+ try {
215
+ if (/^https?:\/\//.test(imageUrl)) {
216
+ await downloadImage(imageUrl, localFile)
217
+ } else if (session.bot.uploadFile || session.bot.upload) {
218
+ const tempFile = localFile // 先保存到宿主机
219
+ fs.copyFileSync(tempFile, localFile) // 保留原文件
220
+ } else {
221
+ throw new Error('无法识别图片来源')
222
+ }
223
+ } catch (e) {
224
+ delete data.waitForImage[session.userId]
225
+ saveData(data)
226
+ logger.warn('下载图片失败: %o', e.message || e)
227
+ await session.send('下载图片失败喵,请重试~')
228
+ return
229
+ }
230
+
231
+ // 保存图片 URL
232
+ const finalUrl = `${BASE_URL}/${path.basename(localFile)}`
233
+ data.images[waitGuild] = finalUrl
234
+ delete data.waitForImage[session.userId]
235
+ saveData(data)
236
+
237
+ await session.send('欢迎图片设置成功了喵~')
238
+ })
239
+ }
@@ -0,0 +1 @@
1
+ {}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "koishi-plugin-auto-welcome",
3
+ "version": "1.0.0",
4
+ "description": "【自用插件】机器人入群发送欢迎和成员加入/退出提示,支持文本和自动拼接图片 URL。",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "chatbot",
13
+ "koishi",
14
+ "auto",
15
+ "message",
16
+ "group",
17
+ "welcome",
18
+ "koishi-plugin"
19
+ ],
20
+ "homepage": "https://github.com/DoomVoss/Koishi-Plugins-CBS/tree/main/plugins/auto-welcome",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/DoomVoss/Koishi-Plugins-CBS.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/DoomVoss/Koishi-Plugins-CBS/issues"
27
+ },
28
+ "author": {
29
+ "name": "Doom",
30
+ "email": "satmtr.top@gmail.com",
31
+ "url": "https://github.com/DoomVoss"
32
+ },
33
+ "peerDependencies": {
34
+ "koishi": "^4.18.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
package/readme.md ADDED
@@ -0,0 +1,63 @@
1
+ <!--
2
+ 作者: Doom
3
+ 用途: 插件说明通用文档模板
4
+ 版本: 1.0.0
5
+ -->
6
+ # koishi-plugin-auto-welcome
7
+
8
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-auto-welcome?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-auto-welcome)
9
+
10
+ ## 注意:该插件为自用插件
11
+ ## 如需使用请自行修改源码!
12
+
13
+ ## 插件介绍
14
+ 本插件用于 **Koishi 机器人框架**,允许在群聊中通过指令设置群成员入群欢迎信息。
15
+ 支持以下功能:
16
+
17
+ - 欢迎信息支持发送图文消息
18
+ - 各群聊单独设置,互不影响
19
+ - 图片存储集中,URL图片发送
20
+
21
+ ---
22
+
23
+ ## 插件配置项说明
24
+ > 以下配置项说明将同时体现在源码注释中。
25
+
26
+ | 配置项 | 类型 | 默认值 | 说明 |
27
+ |:------|:------|:------|:------|
28
+ | EnterSendMsg | 文本 | - | 机器人入群欢迎文本,支持换行符 |
29
+
30
+ ---
31
+
32
+ ## 使用指令
33
+
34
+ | 参数 | 说明 |
35
+ |:------|:------|
36
+ | `-s` | 设置欢迎文本 |
37
+ | `-p` | 设置欢迎图片 |
38
+ | `-t` | 测试欢迎信息 |
39
+ | `-r` | 清除欢迎信息 |
40
+
41
+
42
+ **示例**:
43
+
44
+ ```
45
+ /welcome -s 欢迎入群,请查看群公告!
46
+ /welcome -p (在下一条消息发送图片)
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 帮助与反馈
52
+
53
+ - 如有Bug请前往[Github项目主页](https://github.com/DoomVoss/Koishi-Plugins-CBS)提交Issue
54
+
55
+ ---
56
+
57
+ <details>
58
+ <summary>点击此处 可查看更新日志</summary>
59
+
60
+ - **1.0.0**
61
+ - 首次上传插件
62
+
63
+ </details>