├── plugins
├── button
│ └── .gitignore
└── 纯文模板.js
├── resources
├── icon.png
├── help
│ ├── icon.png
│ ├── imgs
│ │ ├── bg.jpg
│ │ ├── main.png
│ │ └── config.js
│ ├── version-info.html
│ ├── index.html
│ ├── version-info.css
│ ├── index.css
│ ├── index.less
│ └── version-info.less
├── DAU
│ ├── img
│ │ └── bg.jpg
│ ├── font
│ │ └── ruizizhenyan.ttf
│ └── index.html
├── admin
│ ├── imgs
│ │ ├── bg.png
│ │ ├── bg1.jpg
│ │ ├── cfg-right.jpg
│ │ └── cfg-right.png
│ ├── index.html
│ ├── index.css
│ └── index.less
├── default_avatar.jpg
├── common
│ ├── bg
│ │ ├── bg-geo.jpg
│ │ ├── bg-anemo.jpg
│ │ ├── bg-cryo.jpg
│ │ ├── bg-dendro.jpg
│ │ ├── bg-hydro.jpg
│ │ ├── bg-pyro.jpg
│ │ └── bg-electro.jpg
│ ├── cont
│ │ ├── logo.png
│ │ └── card-bg.png
│ ├── font
│ │ ├── NZBZ.ttf
│ │ ├── 华文中宋.TTF
│ │ ├── NZBZ.woff
│ │ ├── HYWH-65W.ttf
│ │ ├── HYWH-65W.woff
│ │ ├── tttgbnumber.ttf
│ │ └── tttgbnumber.woff
│ ├── base.less
│ ├── base.css
│ ├── layout
│ │ ├── default.html
│ │ └── elem.html
│ └── common.less
├── shamrock
│ ├── img
│ │ ├── star.png
│ │ ├── shamrock.webp
│ │ ├── github-logo-white.png
│ │ ├── eye.svg
│ │ └── code-branch.svg
│ ├── index.css
│ ├── index.html
│ └── index1.html
├── QRCode
│ └── QRCode.html
└── index.html
├── .npmrc
├── .github
└── dependabot.yml
├── .gitignore
├── config
└── defSet
│ ├── Config-Other.yaml
│ ├── Config-Server.yaml
│ ├── Config-Adapter.yaml
│ ├── Config-Guild.yaml
│ └── Token.yaml
├── docs
├── WeXin.md
├── OneBotV11.md
├── stdin.md
├── QQGuild.md
├── Shamrock.md
├── CHANGELOG_qg.md
├── WeChat.md
└── Lagrange.Core.md
├── .eslintrc.cjs
├── index.js
├── apps
├── clear_msgs.js
├── task.js
├── login.js
├── index.js
├── restart.js
├── help.js
├── master.js
└── shamrock.js
├── CHANGELOG.md
├── package.json
├── model
├── render.js
├── version.js
├── help.js
├── config.js
├── shamrock
│ ├── shamrock.js
│ ├── client.js
│ └── face.js
└── YamlHandler.js
├── adapter
├── adapter.js
├── Bot
│ └── icqq.js
├── QQBot
│ ├── plugins.js
│ └── QQSDK.js
├── WebSocket.js
├── WeChat
│ ├── api.js
│ ├── sendMsg.js
│ └── message.js
├── QQGuild
│ └── log.js
└── shamrock
│ ├── sendMsg.js
│ └── xiaofei
│ └── weather.js
├── lib
├── bot.js
├── init.js
└── config
│ └── config.js
├── README.md
└── lain.support.js
/plugins/button/.gitignore:
--------------------------------------------------------------------------------
1 | /lain.support.js
2 | *.js
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/icon.png
--------------------------------------------------------------------------------
/resources/help/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/help/icon.png
--------------------------------------------------------------------------------
/resources/DAU/img/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/DAU/img/bg.jpg
--------------------------------------------------------------------------------
/resources/admin/imgs/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/admin/imgs/bg.png
--------------------------------------------------------------------------------
/resources/admin/imgs/bg1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/admin/imgs/bg1.jpg
--------------------------------------------------------------------------------
/resources/default_avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/default_avatar.jpg
--------------------------------------------------------------------------------
/resources/help/imgs/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/help/imgs/bg.jpg
--------------------------------------------------------------------------------
/resources/help/imgs/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/help/imgs/main.png
--------------------------------------------------------------------------------
/resources/common/bg/bg-geo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-geo.jpg
--------------------------------------------------------------------------------
/resources/common/cont/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/cont/logo.png
--------------------------------------------------------------------------------
/resources/common/font/NZBZ.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/NZBZ.ttf
--------------------------------------------------------------------------------
/resources/common/font/华文中宋.TTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/华文中宋.TTF
--------------------------------------------------------------------------------
/resources/common/bg/bg-anemo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-anemo.jpg
--------------------------------------------------------------------------------
/resources/common/bg/bg-cryo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-cryo.jpg
--------------------------------------------------------------------------------
/resources/common/bg/bg-dendro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-dendro.jpg
--------------------------------------------------------------------------------
/resources/common/bg/bg-hydro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-hydro.jpg
--------------------------------------------------------------------------------
/resources/common/bg/bg-pyro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-pyro.jpg
--------------------------------------------------------------------------------
/resources/common/cont/card-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/cont/card-bg.png
--------------------------------------------------------------------------------
/resources/common/font/NZBZ.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/NZBZ.woff
--------------------------------------------------------------------------------
/resources/shamrock/img/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/shamrock/img/star.png
--------------------------------------------------------------------------------
/resources/DAU/font/ruizizhenyan.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/DAU/font/ruizizhenyan.ttf
--------------------------------------------------------------------------------
/resources/admin/imgs/cfg-right.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/admin/imgs/cfg-right.jpg
--------------------------------------------------------------------------------
/resources/admin/imgs/cfg-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/admin/imgs/cfg-right.png
--------------------------------------------------------------------------------
/resources/common/bg/bg-electro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/bg/bg-electro.jpg
--------------------------------------------------------------------------------
/resources/common/font/HYWH-65W.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/HYWH-65W.ttf
--------------------------------------------------------------------------------
/resources/common/font/HYWH-65W.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/HYWH-65W.woff
--------------------------------------------------------------------------------
/resources/common/font/tttgbnumber.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/tttgbnumber.ttf
--------------------------------------------------------------------------------
/resources/common/font/tttgbnumber.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/common/font/tttgbnumber.woff
--------------------------------------------------------------------------------
/resources/shamrock/img/shamrock.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/shamrock/img/shamrock.webp
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | sharp_binary_host = https://npmmirror.com/mirrors/sharp
2 | sharp_libvips_binary_host = https://npmmirror.com/mirrors/sharp-libvips
--------------------------------------------------------------------------------
/resources/shamrock/img/github-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/Lain-plugin/HEAD/resources/shamrock/img/github-logo-white.png
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | config/*
3 | !config/defSet/
4 | resources/image
5 | resources/QQBotApi
6 | resources/avatar.*
7 | plugins/button
8 | /.idea
9 | /src
10 | adapter/Bot/icqqs.js
11 | *.ts
12 | *.bak
13 |
--------------------------------------------------------------------------------
/config/defSet/Config-Other.yaml:
--------------------------------------------------------------------------------
1 | # 定时清理缓存临时文件夹 每天凌晨4点
2 | DelFileCron: 0 4 * * *
3 |
4 | # ICQQ魔法文件
5 | ICQQtoFile: false
6 |
7 | # QQBotdau统计
8 | QQBotdau: true
9 |
10 | # URL白名单,在白名单中的链接不会转为二维码
11 | WhiteLink:
12 | - https://www.lain.com
13 |
14 |
--------------------------------------------------------------------------------
/resources/common/base.less:
--------------------------------------------------------------------------------
1 | .font-YS {
2 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif;
3 | }
4 |
5 | .font-NZBZ {
6 | font-family: Number, "印品南征北战NZBZ体", NZBZ, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif;
7 | }
--------------------------------------------------------------------------------
/resources/common/base.css:
--------------------------------------------------------------------------------
1 | .font-ys {
2 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif;
3 | }
4 | .font-nzbz {
5 | font-family: Number, "印品南征北战NZBZ体", NZBZ, PingFangSC-Medium, "PingFang SC", sans-serif;
6 | }
7 | /*# sourceMappingURL=base.css.map */
--------------------------------------------------------------------------------
/docs/WeXin.md:
--------------------------------------------------------------------------------
1 | ## 简介
2 |
3 | - 不限制服务器类型,能跑云崽即可使用。
4 | - 需要实名:`钱包` -> `身份信息` -> `实名验证`
5 | - #微信登陆
6 | - #微信账号
7 | - #微信删除
8 |
9 |
10 | ## 微信登陆
11 |
12 | 安装插件之后直接发送指令 `#微信登陆` 即可
13 |
14 |
15 | ## 其他
16 |
17 | 相较于PC微信的,网页版稳定性更高,基本能做到`99.99%`不封号,但是缺点是无固定id,账号离线5分钟后重新登陆id就会发送变化,无at功能。
--------------------------------------------------------------------------------
/config/defSet/Config-Server.yaml:
--------------------------------------------------------------------------------
1 | port: 2955 # HTTP端口,ComWeChat、Shamrock、QQBot临时文件使用
2 | baseIP: # 临时文件服务器IP,与下方 baseUrl 二选一,推荐公网使用上方2955端口的配置,可填域名:127.0.0.1 或 www.lain.com
3 | baseUrl: # 临时文件服务器访问url,QQBot使用。端口转发、域名等使用:http://www.lain.com 或 http://192.168.0.1:2956
4 | InvalidTime: 30 # 临时文件服务器失效时间,QQBot使用
5 |
--------------------------------------------------------------------------------
/docs/OneBotV11.md:
--------------------------------------------------------------------------------
1 | ## 简介
2 |
3 | - 作为反向ws服务端,能跑云崽即可使用。
4 | - 当前 `YunzaiJS` 基础消息收发送测试初步通过。
5 | - 不保证所有 oneBotv11 标准实现的可用性,有事烧纸,不接受催更,仅欢迎pr完善。
6 |
7 | ## 连接方式
8 |
9 | 安装插件之后,在客户端反向ws连接添加: `ws://localhost:2955/OneBotV11` 即可,如自定义端口自行配置文件更换并更新链接地址端口
10 |
11 |
12 | ## 其他
13 |
14 | - OneBotv11 相关客户端的获取、使用,请遵守对应项目的使用范围和条款。
15 |
--------------------------------------------------------------------------------
/resources/shamrock/img/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/shamrock/img/code-branch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/stdin.md:
--------------------------------------------------------------------------------
1 | # 标准输入
2 | - 作用:在控制台和在QQ一样执行指令,用于无法登录QQ情况下想执行指令。
3 | - 主人:`标准输入`默认为主人
4 | - 支持大部分基础指令,类似于锅巴登录等,支持保存图片、视频、语音至`data/stdin/`目录。
5 |
6 | # 如何使用
7 |
8 | 请直接把`控制台`当成您的QQ`输入指令`即可!
9 |
10 | # 自定义椰奶状态头像
11 |
12 | 如何使用:在`./resources`文件夹下方创建一个名称为`avatar.jpg`的图片
13 |
14 | 也可通过`config/config/Config-Adapter.yaml`中修改`Stdin.avatar`配置,`./resources/avatar.jpg`优先级大于`Stdin.avatar`;`Stdin.avatar`可选绝对路径或以崽目录开始相对路径
15 |
16 | # 自动更换椰奶状态头像
17 | 每次启动会从目录中随机选择一张图片作为椰奶状态头像
18 |
19 | 如何使用:先配置`Stdin.avatar`为`auto`,在`resources/Avatar/`目录放置头像图片即可
20 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es2021: true,
4 | node: true
5 | },
6 | extends: ['standard'],
7 | parserOptions: {
8 | ecmaVersion: 'latest',
9 | sourceType: 'module'
10 | },
11 | globals: {
12 | Bot: true,
13 | redis: true,
14 | logger: true,
15 | plugin: true,
16 | Renderer: true,
17 | segment: true,
18 | lain: true
19 | },
20 | rules: {
21 | eqeqeq: ['off'],
22 | 'prefer-const': ['off'],
23 | 'arrow-body-style': 'off',
24 | camelcase: [0, {
25 | properties: 'always'
26 | }]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/help/imgs/config.js:
--------------------------------------------------------------------------------
1 | export const style = {
2 | // 主文字颜色
3 | fontColor: '#ceb78b',
4 | // 主文字阴影: 横向距离 垂直距离 阴影大小 阴影颜色
5 | // fontShadow: '0px 0px 1px rgba(6, 21, 31, .9)',
6 | fontShadow: 'none',
7 | // 描述文字颜色
8 | descColor: '#eee',
9 |
10 | /* 面板整体底色,会叠加在标题栏及帮助行之下,方便整体帮助有一个基础底色
11 | * 若无需此项可将rgba最后一位置为0即为完全透明
12 | * 注意若综合透明度较低,或颜色与主文字颜色过近或太透明可能导致阅读困难 */
13 | contBgColor: 'rgba(6, 21, 31, .5)',
14 |
15 | // 面板底图毛玻璃效果,数字越大越模糊,0-10 ,可为小数
16 | contBgBlur: 3,
17 |
18 | // 板块标题栏底色
19 | headerBgColor: 'rgba(6, 21, 31, .4)',
20 | // 帮助奇数行底色
21 | rowBgColor1: 'rgba(6, 21, 31, .2)',
22 | // 帮助偶数行底色
23 | rowBgColor2: 'rgba(6, 21, 31, .35)'
24 | }
25 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import './lib/init.js'
3 | import './lib/bot.js'
4 | import './model/config.js'
5 | import './adapter/adapter.js'
6 | import './adapter/Bot/bot.js'
7 | import './adapter/Bot/icqq.js'
8 |
9 | let ret = []
10 | let apps = {}
11 | const files = fs.readdirSync('./plugins/Lain-plugin/apps').filter(file => file.endsWith('.js'))
12 |
13 | files.forEach((file) => {
14 | ret.push(import(`./apps/${file}`))
15 | })
16 | ret = await Promise.allSettled(ret)
17 |
18 | for (let i in files) {
19 | let name = files[i].replace('.js', '')
20 | if (ret[i].status != 'fulfilled') {
21 | logger.error(`载入插件错误:${logger.red(name)}`)
22 | logger.error(ret[i].reason)
23 | continue
24 | }
25 |
26 | // apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]
27 | for (let clazz of Object.keys(ret[i].value)) {
28 | apps[clazz] = ret[i].value[clazz]
29 | }
30 | }
31 |
32 | export { apps }
33 |
--------------------------------------------------------------------------------
/config/defSet/Config-Adapter.yaml:
--------------------------------------------------------------------------------
1 | Stdin: # 标准输入
2 | state: true # 标准输入开关
3 | name: "标准输入" # 标准输入名称 => 椰奶状态显示
4 | avatar: # 标准输入自定义头像路径,auto: 自动从resources/Avatar目录中选择一张
5 |
6 | ComWeChat: # PC微信
7 | name: # PC微信名称 => 椰奶状态显示
8 | autoFriend: 1 # 自动同意加好友 1-同意 0-不处理
9 |
10 | WeXin: # 网页版微信
11 | name: # 网页版微信名称 => 椰奶状态显示
12 | autoFriend: 1 # 自动同意加好友 1-同意 0-不处理
13 |
14 | Shamrock: # Shamrock
15 | baseUrl: # shamrock主动http链接,例如http://localhost:5700。若填写将通过此端口进行文件上传等被动ws不支持的操作
16 | token: # 鉴权token,如果开放公网强烈建议配置
17 | githubKey: # Github personal access token, 用于查看和下载shamrock版本信息
18 |
19 | # 反向ws连接:
20 | # port: 2955 查看 Config-Server.yaml
21 | # ws://localhost:2955/LagrangeCore
22 | # ws://localhost:2955/ComWeChat
23 | # ws://localhost:2955/shamrock
24 | # ws://localhost:2955/OneBotV11
25 |
--------------------------------------------------------------------------------
/resources/common/layout/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | miao-plugin
12 | {{block 'css'}}
13 | {{/block}}
14 |
15 |
16 |
17 | {{block 'main'}}{{/block}}
18 |
{{@copyright || sys?.copyright}}
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/common/layout/elem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ws-plugin
12 | {{block 'css'}}
13 | {{/block}}
14 |
15 |
16 |
17 | {{block 'main'}}{{/block}}
18 |
{{@copyright || sys?.copyright}}
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/help/version-info.html:
--------------------------------------------------------------------------------
1 | {{extend elemLayout}}
2 |
3 | {{block 'css'}}
4 |
5 | {{/block}}
6 |
7 | {{block 'main'}}
8 | {{each changelogs ds idx}}
9 |
10 | {{set v = ds.version }}
11 | {{set isDev = v[v.length-1] === 'v'}}
12 |
13 | {{if idx === 0 }}
14 |
当前版本 {{v}}
15 | {{else}}
16 |
{{name || '铃音'}}版本 {{v}}
17 | {{/if}}
18 |
19 |
20 | {{each ds.logs log}}
21 | -
22 |
{{@log.title}}
23 | {{if log.logs.length > 0}}
24 |
25 | {{each log.logs ls}}
26 | - {{@ls}}
27 | {{/each}}
28 |
29 | {{/if}}
30 |
31 | {{/each}}
32 |
33 |
34 |
35 |
36 | {{/each}}
37 | {{/block}}
--------------------------------------------------------------------------------
/config/defSet/Config-Guild.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | ImageSize: 2.5 # 图片压缩阈值
3 | width: 1000 # 压缩后图片宽度像素大小
4 | quality: 100 # 压缩后的图片质量
5 | recallQR: 0 # 撤回url转换成二维码的时间(秒) 0表示不撤回
6 |
7 | blackGuild: # 黑名单频道
8 | - qg_6120782151088355109
9 |
10 | blackChannel: # 黑名单子频道
11 | - 514216167
12 |
13 | whiteGuild: # 白名单频道
14 |
15 | whiteChannel: # 白名单子频道
16 |
17 | '1234567':
18 | ImageSize: 2.5 # 图片压缩阈值
19 | width: 1000 # 压缩后图片宽度像素大小
20 | quality: 100 # 压缩后的图片质量
21 | recallQR: 0 # 撤回url转换成二维码的时间(秒) 0表示不撤回
22 |
23 | blackGuild: # 黑名单频道
24 | - qg_6120782151088355109
25 |
26 | blackChannel: # 黑名单子频道
27 | - 514216167
28 |
29 | whiteGuild: # 白名单频道
30 |
31 | whiteChannel: # 白名单子频道
32 |
--------------------------------------------------------------------------------
/apps/clear_msgs.js:
--------------------------------------------------------------------------------
1 | import api from '../adapter/shamrock/api.js'
2 |
3 | export class clear_msgs extends plugin {
4 | constructor () {
5 | super({
6 | name: '三叶草-清理缓存',
7 | priority: -50,
8 | rule: [
9 | {
10 | reg: /^#清理缓存$/,
11 | fnc: 'clearFile',
12 | permission: 'master'
13 | }
14 | ]
15 | })
16 | }
17 |
18 | async clearFile () {
19 | await this.reply('开始清理~,请等待完成', true, { at: true })
20 | /** 获取群列表 */
21 | const gl = this.e.bot.gl
22 | gl.forEach(async (value, key) => {
23 | try {
24 | await this.e.bot.api.clear_msgs(this.e.self_id, 'group', Number(key))
25 | } catch (error) {
26 | this.reply(`清理群${key}发生错误:${error?.message || error}`)
27 | }
28 | })
29 |
30 | /** 获取群列表 */
31 | const fl = this.e.bot.fl
32 | fl.forEach(async (value, key) => {
33 | try {
34 | await api.clear_msgs(this.e.self_id, 'private', Number(key))
35 | } catch (error) {
36 | this.reply(`清理好友${key}发生错误:${error?.message || error}`)
37 | }
38 | })
39 |
40 | return await this.reply('清理完成~', true, { at: true })
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.4.8
2 | * 修复 `oneBotV11` 转发消息支持
3 | * 合并 `sky-summer/Lain-plugin`的`master`分支
4 |
5 | # 1.4.7
6 | * add `oneBotV11` Adapter
7 |
8 | # 1.4.6
9 | * 适配`QQBot`私聊
10 | * 优化`QQBot`日志输出
11 | * 重构配置文件,重新适配锅巴
12 | * 本次改动较大,请谨慎更新
13 | * 优化`QQGuild`适配器
14 |
15 | # 1.4.5
16 | * 合并`QQbot`和`QQGuild`适配器
17 | * 资源更新,本次改动较大,请谨慎更新
18 |
19 | # 1.4.4
20 |
21 | * 添加`#shamrock版本`、`#shamrock(测试)安装包`**@ikechan8370**
22 |
23 | # 1.4.3
24 |
25 | * 添加`#铃音帮助`、`#铃音版本` **@小叶**
26 | * 小飞插件点歌、shamrock更多事件 **@ikechan8370**
27 |
28 |
29 | # 1.4.1 ~ 1.4.2
30 |
31 | * 1.4.2忘了...
32 | * 添加更多图片API
33 | * 点赞、ck、头衔等接口实现 **@ikechan8370**
34 |
35 | # 1.4.0
36 |
37 | * 添加`QQBot`适配器
38 |
39 | # 1.3.4
40 |
41 | * `Shamrock`:实现合并转发
42 | * `Shamrock`:获取历史记录、文件上传、清除本地缓存等接口实现 **@ikechan8370**
43 |
44 | # 1.3.3
45 |
46 | * `Shamrock`适配语音,整个适配器全部使用单独方法进行重启
47 |
48 | # 1.3.2
49 |
50 | * `Shamrock`适配椰奶引用撤回、踢人、禁言、修改群名称、设置管理、取消管理、适配meme表情包
51 |
52 | # 1.3.1
53 |
54 | * `Shamrock`适配拍一拍方法、适配icqq禁言、全体禁言接口,修改转发
55 |
56 | # 1.3.0
57 |
58 | * 添加`Shamrock`,QQ
59 |
60 | # 1.2.0
61 |
62 | * 添加`WeChat`,PC微信
63 |
64 | # 1.1.0
65 |
66 | * 添加`stdin`,控制台标准输入
67 |
68 | # 1.0.0
69 |
70 | * `QQGuild-plugin`合并到`Lain-plugin`
71 |
--------------------------------------------------------------------------------
/resources/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 |
12 |
14 |
15 | Lain-plugin
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{if head }} {{@head}} {{/if}}
23 |
24 | {{each box i}}
25 | {{@i}}
26 | {{/each}}
27 |
28 | {{if copyright }} {{@copyright}} {{/if}}
29 |
30 | ?
31 |
32 |
33 |
--------------------------------------------------------------------------------
/docs/QQGuild.md:
--------------------------------------------------------------------------------
1 | `请给予机器人基础的权限...什么权限都没有的发个鬼消息啊= =`
2 |
3 | ## 1.获取频道机器人
4 |
5 | 前往 [QQ开放平台](https://q.qq.com/) -> 登录 -> 应用管理 -> 创建机器人 -> 创建完成
6 |
7 | 前往应用管理 -> 选择你注册的机器人 -> 开发 -> 开发设置 -> 获取`AppID(机器人ID)`、`Token(机器人令牌)`。
8 |
9 | ## 2.机器人指令配置
10 |
11 | 如果你没有在登录QQ,可以在控制台使用 [标准输入](./stdin.md) 来执行指令,直接像QQ一样输入指令!
12 |
13 | 添加机器人(删除机器人同理):**是=1 否=0**
14 | ```
15 | #QQ频道设置 沙盒:私域:机器人ID:机器人令牌
16 | ```
17 |
18 | 查看机器人:
19 | ```
20 | #QQ频道账号
21 | ```
22 |
23 | ## 使用例子
24 |
25 | 展开/收起
26 |
27 | 是否沙盒:`否`
28 |
29 | 是否私域:`是`
30 |
31 | AppID(机器人ID):`123456789`
32 |
33 | Token(机器人令牌):`abcdefghijklmnopqrstuvwxyz123456`
34 |
35 |
36 | 添加机器人:
37 | ```
38 | #QQ频道设置 0:1:123456789:abcdefghijklmnopqrstuvwxyz123456
39 | ```
40 |
41 | 删除机器人(相同指令):
42 | ```
43 | #QQ频道设置 0:1:123456789:abcdefghijklmnopqrstuvwxyz123456
44 | ```
45 |
46 | 查看机器人:
47 | ```
48 | #QQ频道账号
49 | ```
50 |
51 |
52 | ## 其他
53 |
54 | - 更换所有指令前缀:请编辑 [config/config.yaml](../config/config.yaml) 配置文件,开启将 `/` 转换为 `#` ,推荐使用锅巴设置
55 | - #QQ频道解除私信 `//解除私信3条后等待回复问题...机器人每天仅可发送两次私信主动消息`
56 | - #QQ频道设置分片转发开启
57 | - #QQ频道设置分片转发关闭
58 | - #id | #我的id | #我的信息 | #ID | #信息 `//获取个人id、频道id`
59 | - #id@用户` //可查看他人id`
60 |
61 | ## 已知问题
62 |
63 | 目前如果两个机器人在同一个频道,并且其中一个非管理员,在非管理员不可见的频道触发指令后,会导致管理员的机器人报错`11263`,根据官方文档,这是系统错误,暂无法解决。
64 |
65 | 更新日志:[点击查看](./CHANGELOG_qg.md)
--------------------------------------------------------------------------------
/resources/help/index.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}}
2 |
3 | {{block 'css'}}
4 |
5 | <% style = style.replace(/{{_res_path}}/g, _res_path) %>
6 | {{@style}}
7 | {{/block}}
8 |
9 | {{block 'main'}}
10 |
11 |
12 |
13 |
{{helpCfg.title||"使用帮助"}}
14 |
{{helpCfg.subTitle || "Yunzai-Bot & Miao-Plugin"}}
15 |
16 |
17 |
18 | {{each helpGroup group}}
19 | {{set len = group?.list?.length || 0 }}
20 |
21 |
{{group.group}}
22 | {{if len > 0}}
23 |
24 |
25 | {{each group.list help idx}}
26 |
27 |
28 | {{help.title}}
29 | {{help.desc}}
30 |
31 | {{if idx%colCount === colCount-1 && idx>0 && idx< len-1}}
32 |
33 |
34 | {{/if}}
35 | {{/each}}
36 | <% for(let i=(len-1)%colCount; i< colCount-1 ; i++){ %>
37 |
38 | <% } %>
39 |
40 |
41 | {{/if}}
42 |
43 | {{/each}}
44 | {{/block}}
--------------------------------------------------------------------------------
/apps/task.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import Cfg from '../lib/config/config.js'
3 |
4 | export class LainTask extends plugin {
5 | constructor () {
6 | super({
7 | name: 'Lain-定时任务',
8 | priority: 0,
9 | rule: [
10 | {
11 | reg: /^#?清理缓存$/,
12 | fnc: 'TaskFile'
13 | }
14 | ]
15 | })
16 |
17 | /** 定时任务 */
18 | this.task = []
19 | for (let i of Array.isArray(Cfg.Other.DelFileCron) ? Cfg.Other.DelFileCron : [Cfg.Other.DelFileCron]) {
20 | this.task.push({
21 | /** 任务cron表达式 */
22 | cron: i,
23 | name: ' 清除临时文件',
24 | /** 任务方法名 */
25 | fnc: () => this.TaskFile()
26 | })
27 | }
28 | }
29 |
30 | TaskFile (e) {
31 | try {
32 | logger.mark(' <定时任务> 开始清理缓存文件')
33 | const _path = {
34 | './temp/FileToUrl': () => true,
35 | './resources/temp': (i) => i.endsWith('.silk'),
36 | './data/stdin': () => true
37 | }
38 | for (const i of Object.keys(_path)) {
39 | if (!fs.existsSync(i)) continue
40 | const files = fs.readdirSync(i)
41 | files.forEach(file => _path[i](file) && fs.promises.unlink(i + `/${file}`))
42 | }
43 | logger.mark(' <定时任务> 清理缓存文件完成~')
44 | if (e?.reply) e.reply('清理缓存文件完成~')
45 | } catch (error) {
46 | logger.error(' <定时任务> 清理缓存文件发送错误:', error.message)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/Shamrock.md:
--------------------------------------------------------------------------------
1 | ### 温馨提示:
2 | - 目前`Shamrock`搭建难度较高,不推荐小白现阶段进行迁移。
3 | - 目前本插件正在跟随上游`Shamrock`高速更新,追求稳定更建议您当前迁移至`QQNT`~
4 |
5 | ### 使用方法:
6 |
7 | - shamrock安装教程:[快速开始](https://whitechi73.github.io/OpenShamrock/guide/getting-started.html)
8 | - 安装好`Shamrock`并登录QQ之后,请打开`shamrock`,按照以下教程进行配置。
9 | - 启动`Shamrock`,打开`被动WebSocket`
10 | - 填写`被动WebSocket地址`:`ws://localhost:2955/Shamrock`
11 | - 彻底关闭`QQ`,注意需要彻底关闭
12 | - 彻底关闭`Shamrock`,注意需要彻底关闭
13 | - 启动`Shamrock`
14 | - 启动`QQ`
15 | - 启动喵崽即可
16 |
17 |
18 | 解释一下`ws://localhost:2955/Shamrock`这个地址
19 | - `ws://`这部分是固定的,无需更改
20 | - `localhost`这个是本地地址,如果你的喵崽在`云服务器`,请更换为云服务器的`公网IP地址`
21 | - `:2956`这部分是端口,需要使用`:`和`IP地址`连接起来,如需更改,请自行修改配置文件`config.yaml`或使用锅巴修改
22 | - `/Shamrock`这部分是固定的,无需更改
23 |
24 |
25 | 如果加载资源失败不想重启喵崽,可尝试使用`#重载资源`指令进行重新加载好友、群聊等。
26 |
27 | # 适配进度
28 |
29 | 没有注明的在下方的有需求并且我时间充裕的情况下会实现... 欢迎pr
30 |
31 | - [√] 接收`文本`、`表情`、`at`、`图片`、`语音`、`视频`、`文件`消息
32 | - [√] 发送`文本`、`表情`、`at`、`图片`、`语音`、`视频`、`戳一戳`消息
33 | - [√] 好友主动消息`Bot[BotQQ号].pickUser(user_id).sendMsg("主动消息")`
34 | - [√] 群聊主动消息`Bot[BotQQ号].pickGroup(group_id).sendMsg("主动消息")`
35 | - [√] 撤回消息
36 | - [ ] 临时会话消息收(√)发(×)
37 | - [√] 聊天记录
38 | - [√] 合并转发
39 | - [√] 禁言
40 | - [√] 戳一戳
41 | - [√] 点赞
42 | - [√] 群踢人、改群名片、设置精华消息、群头衔、设置群管理员
43 | - [√] 进退群、群撤回、禁言等事件
44 | - [√] cookie(`Bot[BotQQ号].cookies`)和bkn(`Bot[BotQQ号].bkn`)
45 | - [ ] OCR
46 | - [√] 查看群荣誉、群系统消息、群精华消息
47 | - [√] 发送天气、音乐、位置和分享卡片
48 | - [ ] 处理群申请和好友申请
49 | - [√] 上传和发送文件
50 |
51 | 如需使用`yenai-plugin`,请使用为`shamrock`专门适配的椰奶:[yenai-plugin](https://github.com/Zyy955/yenai-plugin)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lain-plugin",
3 | "version": "1.4.8",
4 | "author": "sky-summer, snowtafir Lain.",
5 | "gitee": "https://gitee.com/snowtafir/Lain-plugin.git",
6 | "type": "module",
7 | "scripts": {
8 | "commitmsg": "validate-commit-msg",
9 | "log": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1"
10 | },
11 | "imports": {
12 | "#Lain": "./lib/init.js"
13 | },
14 | "adapter": {
15 | "QQGuild": "0.5.3",
16 | "ComWeChat": "0.2.3",
17 | "CWeChatRobot": "0.0.8",
18 | "stdin": "0.1.3",
19 | "QQBot": "0.0.3",
20 | "WeXin": "0.0.3",
21 | "OneBotv11": "0.0.1",
22 | "ICQQ": "0.1.1"
23 | },
24 | "dependencies": {
25 | "axios": "^1.7.7",
26 | "conventional-changelog-cli": "^5.0.0",
27 | "express": "^4.21.0",
28 | "file-type": "^19.5.0",
29 | "@karinjs/geturls": "^1.0.2",
30 | "icqq": "0.6.10",
31 | "get-urls": "^12.1.0",
32 | "qq-official-bot": "1.0.7",
33 | "qrcode": "^1.5.4",
34 | "sharp": "^0.33.5",
35 | "silk-wasm": "^3.6.3",
36 | "wechat4u": "^0.7.14",
37 | "ws": "^8.18.0",
38 | "ulid": "2.3.0",
39 | "node-fetch": "3.3.2"
40 | },
41 | "devDependencies": {
42 | "cz-conventional-changelog": "^3.3.0",
43 | "eslint": "^9.9.0",
44 | "eslint-config-standard": "^17.1.0",
45 | "eslint-plugin-import": "^2.31.0",
46 | "eslint-plugin-n": "^16.0.0",
47 | "eslint-plugin-promise": "^6.0.0",
48 | "express-art-template": "^1.0.1",
49 | "husky": "^9.1.6",
50 | "validate-commit-msg": "^2.14.0"
51 | },
52 | "config": {
53 | "commitizen": {
54 | "path": "./node_modules/cz-conventional-changelog"
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/docs/CHANGELOG_qg.md:
--------------------------------------------------------------------------------
1 | # 0.5.2
2 |
3 | * 合并到`Lain-plugin`
4 |
5 | # 0.5.1
6 |
7 | * 兼容`ws-plugin`
8 | * 设置默认镜像源
9 | * 修复已知bug
10 |
11 | # 0.5.0
12 |
13 | * 适配锅巴
14 |
15 | # 0.4.8
16 |
17 | * 增加QQ频道、子频道黑白名单
18 |
19 | # 0.4.7
20 |
21 | * 增加在url转为二维码图片后,默认等待20秒后撤回,可关闭。
22 |
23 | # 0.4.6
24 |
25 | * 增加`sharp`,对过大的图像进行压缩,可自行调整参数。
26 |
27 | # 0.4.5
28 |
29 | * 修复`设置主人`指令在at已经是主人的用户时,依旧将其设置为主人
30 |
31 | # 0.4.4
32 |
33 | * 修复`前缀转换`在公域场景下失效问题
34 | * 修复指令添加机器人失败问题
35 |
36 | # 0.4.3
37 |
38 | * 适配椰奶的`撤回消息`,不支持私信
39 |
40 | # 0.4.2
41 |
42 | * 增加`#QQ频道账号`每个机器人前面显示名称
43 | * 增加在`README.md`中的机器人注册基本流程
44 | * 添加`CHANGELOG.md`
45 |
46 | # 0.4.1
47 |
48 | * 对`url替换`进行二次检测是否为`url链接`
49 | * 增加`二维码渲染模板`、转换的同时保持显示原url
50 | * 修复`设置主人`成功后无法输出提示词
51 | * 修复存在多个bot时,重启提示发送错误
52 | * 修复`appID`指向错误问题
53 |
54 | # 0.4.0
55 |
56 | * 第三次重构插件
57 | * 增加前缀`/`转换`#`
58 | * 修改分片转发为默认开启
59 | * 重写`Yaml`文件的添加、修改方式
60 |
61 | # 0.3.0 ~ 0.3.9
62 |
63 | * 取消接收图片后进行缓存
64 | * 修改`设置主人`
65 | * 将`设置成功的主人id`添加到最后一行
66 | * 增加`取消主人`功能
67 | * 修改`分片发送`延迟
68 | * 适配`chatgpt-plugin`的转发,修改分片转发为默认开启
69 | * 适配`椰奶状态`
70 | * 给`频道id`和`个人id`统一添加前缀`qg_`
71 | * 增加`米游社推送`
72 | * 修复`公域机器人`无法解除私信
73 | * 二次优化插件结构
74 | * 增加在`无权获取频道列表`场景下的支持
75 | * 适配`yenai-plugin`违禁词、踢出、禁言、解禁
76 | * 适配`chatgpt-plugin`
77 | * 适配`喵喵维护版云崽`
78 | * 劫持本体所有转发消息的处理方法
79 | * 适配`l-plugin`插件的塔罗牌显示效果
80 | * 去除`压缩图像依赖`、无科学上网用户安装失败
81 | * 增加`链接转二维码`功能
82 | * 修改`压缩图像依赖`为可选安装
83 | * 增加对`没有进行备案的url`进行`关键词替换`使其可进行发送
84 |
85 | # 0.2.0
86 |
87 | * 优化插件结构
88 | * 适配套娃转发
89 | * 增加`控制台执行#QQ频道设置...`
90 | * 适配`xiaoyao-cvs-plugin`转发消息
91 | * 增加`toString`方法
92 | * 增加一些基础功能
93 | * 增加`图像压缩`功能
94 | * 增加指令`#QQ频道更新`
95 | * 增加指令`#QQ频道解除私信`
96 |
97 | # 0.1.0
98 |
99 | * 初版发布
100 |
--------------------------------------------------------------------------------
/model/render.js:
--------------------------------------------------------------------------------
1 | import Version from './version.js'
2 |
3 | function scale (pct = 1) {
4 | let scale = 100
5 | scale = Math.min(2, Math.max(0.5, scale / 100))
6 | pct = pct * scale
7 | return `style=transform:scale(${pct})`
8 | }
9 |
10 | const Render = {
11 | async render (path, params, cfg) {
12 | let { e } = cfg
13 | if (!e.runtime) {
14 | console.log('未找到e.runtime,请升级至最新版Yunzai')
15 | }
16 |
17 | let BotName = 'Miao-Yunzai'
18 | return e.runtime.render('Lain-plugin', path, params, {
19 | retType: cfg.retMsgId ? 'msgId' : 'default',
20 | beforeRender ({ data }) {
21 | let pluginName = ''
22 | if (data.pluginName !== false) {
23 | pluginName = ` & ${data.pluginName || 'Lain-plugin'}`
24 | if (data.pluginVersion !== false) {
25 | pluginName += `${data.pluginVersion || Version.version}`
26 | }
27 | }
28 | let resPath = data.pluResPath
29 | const layoutPath = process.cwd() + '/plugins/Lain-plugin/resources/common/layout/'
30 | return {
31 | ...data,
32 | _res_path: resPath,
33 | _ws_path: resPath,
34 | _layout_path: layoutPath,
35 | _tpl_path: process.cwd() + '/plugins/Lain-plugin/resources/common/tpl/',
36 | defaultLayout: layoutPath + 'default.html',
37 | elemLayout: layoutPath + 'elem.html',
38 | sys: {
39 | scale: scale(cfg.scale || 1)
40 | },
41 | copyright: `Created By ${BotName}${Version.yunzai}${pluginName}`,
42 | pageGotoParams: {
43 | waitUntil: 'networkidle2'
44 | }
45 | }
46 | }
47 | })
48 | }
49 | }
50 |
51 | export default Render
52 |
--------------------------------------------------------------------------------
/resources/admin/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | transform: scale(1);
3 | width: 660px;
4 | }
5 | .container {
6 | background: url("./imgs/bg1.jpg") #6364a7 left top no-repeat;
7 | background-size: 700px auto;
8 | width: 660px;
9 | }
10 | .head-box {
11 | margin: 0 0 80px 0;
12 | }
13 | .cfg-box {
14 | border-radius: 15px;
15 | margin-top: 20px;
16 | margin-bottom: 20px;
17 | padding: 5px 15px;
18 | overflow: hidden;
19 | background: #f5f5f5;
20 | box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15);
21 | position: relative;
22 | background: rgba(35, 38, 57, 0.65);
23 | }
24 | .cfg-group {
25 | color: #ceb78b;
26 | font-size: 18px;
27 | font-weight: bold;
28 | padding: 10px 20px;
29 | }
30 | .cfg-li {
31 | border-radius: 18px;
32 | min-height: 36px;
33 | position: relative;
34 | overflow: hidden;
35 | margin-bottom: 10px;
36 | background: rgba(203, 196, 190, 0);
37 | }
38 | .cfg-line {
39 | color: #4e5769;
40 | line-height: 36px;
41 | padding-left: 20px;
42 | font-weight: bold;
43 | border-radius: 16px;
44 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
45 | background: url("./imgs/cfg-right.jpg") right top #cbc4be no-repeat;
46 | background-size: auto 36px;
47 | }
48 | .cfg-hint {
49 | font-size: 12px;
50 | font-weight: normal;
51 | margin-top: 3px;
52 | margin-bottom: -3px;
53 | }
54 | .cfg-status {
55 | position: absolute;
56 | top: 0;
57 | right: 0;
58 | height: 36px;
59 | width: 160px;
60 | text-align: center;
61 | line-height: 36px;
62 | font-size: 16px;
63 | color: #495366;
64 | font-weight: bold;
65 | border-radius: 0 16px 16px 0;
66 | }
67 | .cfg-status.status-off {
68 | color: #a95151;
69 | }
70 | .cfg-desc {
71 | font-size: 12px;
72 | color: #cbc4be;
73 | margin: 5px 0 5px 20px;
74 | }
75 | /*# sourceMappingURL=index.css.map */
--------------------------------------------------------------------------------
/resources/help/version-info.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | user-select: none;
6 | }
7 | body {
8 | font-size: 18px;
9 | color: #1e1f20;
10 | transform: scale(1.3);
11 | transform-origin: 0 0;
12 | width: 600px;
13 | }
14 | .container {
15 | width: 600px;
16 | padding: 10px 0 10px 0;
17 | background-size: 100% 100%;
18 | }
19 | .log-cont {
20 | background-size: cover;
21 | margin: 5px 15px 5px 10px;
22 | border-radius: 10px;
23 | }
24 | .log-cont .cont {
25 | margin: 0;
26 | }
27 | .log-cont .cont-title {
28 | font-size: 16px;
29 | padding: 10px 20px 6px;
30 | }
31 | .log-cont .cont-title.current-version {
32 | font-size: 20px;
33 | }
34 | .log-cont ul {
35 | font-size: 14px;
36 | padding-left: 20px;
37 | }
38 | .log-cont ul li {
39 | margin: 3px 0;
40 | }
41 | .log-cont ul.sub-log-ul li {
42 | margin: 1px 0;
43 | }
44 | .log-cont .cmd {
45 | color: #d3bc8e;
46 | display: inline-block;
47 | border-radius: 3px;
48 | background: rgba(0, 0, 0, 0.5);
49 | padding: 0 3px;
50 | margin: 1px 2px;
51 | }
52 | .log-cont .strong {
53 | color: #24d5cd;
54 | }
55 | .log-cont .new {
56 | display: inline-block;
57 | width: 18px;
58 | margin: 0 -3px 0 1px;
59 | }
60 | .log-cont .new:before {
61 | content: "NEW";
62 | display: inline-block;
63 | transform: scale(0.6);
64 | transform-origin: 0 0;
65 | color: #d3bc8e;
66 | white-space: nowrap;
67 | }
68 | .dev-cont {
69 | background: none;
70 | }
71 | .dev-cont .cont-title {
72 | background: rgba(0, 0, 0, 0.7);
73 | }
74 | .dev-cont .cont-body {
75 | background: rgba(0, 0, 0, 0.5);
76 | }
77 | .dev-cont .cont-body.dev-info {
78 | background: rgba(0, 0, 0, 0.2);
79 | }
80 | .dev-cont .strong {
81 | font-size: 15px;
82 | }
83 | /*# sourceMappingURL=version-info.css.map */
--------------------------------------------------------------------------------
/config/defSet/Token.yaml:
--------------------------------------------------------------------------------
1 | # 示例配置
2 | QQ_Default:
3 | "default":
4 | mode: "websocket" # 运行模式 websocket webhook middleware
5 | port: 0 # webhook监听端口 0-跟随Lain-plugin
6 | path: "/webhook" # webhook监听路径
7 | applacation: "express" # 中间件 express koa
8 | type: 0 # 0-全部启用 1-仅启用QQ频道 2-仅启用QQ群Bot 3-不启用
9 | appid: "default" # 机器人id
10 | sandbox: false # 沙盒 true-开启 false-关闭
11 | timeout: 10000 # 请求接口超时时间,默认10秒
12 | allMsg: true # QQ频道接收全部消息 true-私域 false-公域
13 | removeAt: false # 移除at true-开启 false-关闭
14 | token: # 机器人令牌
15 | secret: # 机器人密钥
16 | maxRetry: 10 # 重连次数
17 | autoRetry: true # 掉线自动重连 true-开启 false-关闭
18 | autoRetryTime: 60 # 掉线自动重连间隔,默认60秒
19 | markdown: # QQBot高阶能力
20 | id: # 模板ID
21 | type: 0 # 0-关闭 1-全局 2-正则模式 3-按钮模式 详情请查看文档
22 | text: text_start # markdown模板文字键
23 | img_dec: img_dec # markdown模板图片宽高键
24 | img_url: img_url # markdown模板图片url键
25 | other: # 其他配置
26 | Prefix: true # QQ频道、QQ群Bot前缀转换 [/] => [#] true-开启 false-关闭
27 | QQCloud: # QQ群Bot => QQ图床,填写QQ号。需使用QQ发送图片
28 | Tips: false # QQ群Bot => 进入新群后,发送防倒卖提示 true-开启 false-关闭
29 | Tips-GroupId: # QQ群Bot => 防倒卖提示中的QQ群号
30 |
31 | QQ_Token:
--------------------------------------------------------------------------------
/resources/admin/index.less:
--------------------------------------------------------------------------------
1 | body {
2 | transform: scale(1);
3 | width: 660px;
4 | }
5 |
6 | .container {
7 | background: url("./imgs/bg1.png") #000144 left top no-repeat;
8 | background-size: 700px auto;
9 | width:660px;
10 | }
11 |
12 | .head-box {
13 | margin: 0 0 80px 0;
14 | }
15 |
16 | .cfg-box {
17 | border-radius: 15px;
18 | margin-top: 20px;
19 | margin-bottom: 20px;
20 | padding: 5px 15px;
21 | overflow: hidden;
22 | background: #f5f5f5;
23 | box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%);
24 | position: relative;
25 | background: rgba(35, 38, 57, .8);
26 | }
27 |
28 |
29 | .cfg-group {
30 | color: #ceb78b;
31 | font-size: 18px;
32 | font-weight: bold;
33 | padding: 10px 20px;
34 | }
35 |
36 | .cfg-ul {
37 |
38 | }
39 |
40 | .cfg-li {
41 | border-radius: 18px;
42 | min-height: 36px;
43 | position: relative;
44 | overflow: hidden;
45 | margin-bottom: 10px;
46 | background: rgba(203, 196, 190, 0);
47 | }
48 |
49 | .cfg-line {
50 | color: #4e5769;
51 | line-height: 36px;
52 | padding-left: 20px;
53 | font-weight: bold;
54 | border-radius: 16px;
55 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
56 | background: url("./imgs/cfg-right.jpg") right top #cbc4be no-repeat;
57 | background-size: auto 36px;
58 | }
59 |
60 | .cfg-hint {
61 | font-size: 12px;
62 | font-weight: normal;
63 | margin-top: 3px;
64 | margin-bottom: -3px;
65 | }
66 |
67 | .cfg-status {
68 | position: absolute;
69 | top: 0;
70 | right: 0;
71 | height: 36px;
72 | width: 160px;
73 | text-align: center;
74 | line-height: 36px;
75 | font-size: 16px;
76 | color: #495366;
77 | font-weight: bold;
78 | border-radius: 0 16px 16px 0;
79 | }
80 |
81 | .cfg-status.status-off {
82 | color: #a95151;
83 | }
84 |
85 | .cfg-desc {
86 | font-size: 12px;
87 | color: #cbc4be;
88 | margin: 5px 0 5px 20px;
89 | }
--------------------------------------------------------------------------------
/docs/WeChat.md:
--------------------------------------------------------------------------------
1 | 微信应用端只支持在`Windows`环境运行,仅支持`Miao-Yunzai`
2 |
3 |
4 | ## 温馨提示
5 |
6 | 没有`Windows`环境,请使用[WeXin.md](./WeXin.md)
7 |
8 | # 使用必读
9 |
10 | 应用端和云崽都可以单独启动,并没有必须先启动谁的说法
11 |
12 | 应用端显示`远程计算机拒绝访问`是因为云崽这边没有启动或者没有安装插件。
13 |
14 | ## 1.下载微信
15 | 仅支持`3.7.0.30`版本,[点击下载](https://ghproxy.com/https://github.com/tom-snow/wechat-windows-versions/releases/download/v3.7.0.30/WeChatSetup-3.7.0.30.exe)
16 |
17 | 如果担心和电脑现有的高版本冲突可在下载安装包之后`直接解压exe安装包`,运行`WeChat.exe`即可
18 |
19 | ## 2.下载禁用更新补丁
20 |
21 | [点击跳转下载页面](https://cup.lanzoui.com/pcwxnoupdate),下载后启动禁用即可
22 |
23 | ## 3.微信机器人应用端
24 |
25 | 两个下载源任选其一
26 |
27 | [点击下载(无加速环境推荐使用)](https://ghproxy.com/https://github.com/JustUndertaker/ComWeChatBotClient/releases/download/v0.0.8/ComWeChat-Client-v0.0.8.zip)
28 |
29 | [初始源](https://github.com/JustUndertaker/ComWeChatBotClient/releases/v0.0.8)
30 |
31 | 下载后得到`ComWeChat-Client-v0.0.8.zip`
32 |
33 | 解压`ComWeChat-Client-v0.0.8.zip`
34 |
35 | #### 请严格按照我所给出的配置进行修改!
36 |
37 | 使用记事本打开`.env`文件,需要修改两个配置
38 | ```
39 | websocekt_type = "Unable"
40 | 修改为
41 | websocekt_type = "Backward"
42 |
43 |
44 | websocket_url = ["ws://127.0.0.1:8080/onebot/v12/ws/"]
45 | 修改为
46 | websocket_url = ["ws://localhost:2955/ComWeChat"]
47 |
48 | 可选:
49 | `如果经常发生连接已关闭,请增加缓冲区大小``
50 | # 反向 WebSocket 的缓冲区大小,单位(Mb)
51 | websocket_buffer_size = 4
52 |
53 | ```
54 | 修改完成保存
55 |
56 |
57 | ## 5.管理员运行`install.bat`
58 |
59 | 注:如运行`install.bat`报错或者`闪退`,如下:
60 | 
61 |
62 | 请安装[vc_redist.x86](https://download.microsoft.com/download/6/D/F/6DF3FF94-F7F9-4F0B-838C-A328D1A7D0EE/vc_redist.x86.exe)
63 |
64 | ## 6.管理员启动应用端
65 |
66 | 管理员运行`ComWeChat-Client-v0.0.8.exe`随后登录你的微信小号即可
67 |
68 |
69 | ## 其他
70 |
71 | - 好友申请:
72 | - 推荐使用锅巴设置,默认自动通过好友申请,如果关闭请前往配置修改`plugins/WeChat-plugin/config.yaml`
73 |
74 | - 修改椰奶状态显示名称
75 | - `#微信修改名称<新名称>`
--------------------------------------------------------------------------------
/resources/help/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | transform: scale(1);
3 | width: 830px;
4 | background: url("../common/theme/bg-01.jpg");
5 | }
6 | .container {
7 | background: url(../common/theme/main-01.png) top left no-repeat;
8 | background-size: 100% auto;
9 | width: 830px;
10 | }
11 | .head-box {
12 | margin: 60px 0 0 0;
13 | padding-bottom: 0;
14 | }
15 | .head-box .title {
16 | font-size: 50px;
17 | }
18 | .cont-box {
19 | border-radius: 15px;
20 | margin-top: 20px;
21 | margin-bottom: 20px;
22 | overflow: hidden;
23 | box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15);
24 | position: relative;
25 | }
26 | .help-group {
27 | font-size: 18px;
28 | font-weight: bold;
29 | padding: 15px 15px 10px 20px;
30 | }
31 | .help-table {
32 | text-align: center;
33 | border-collapse: collapse;
34 | margin: 0;
35 | border-radius: 0 0 10px 10px;
36 | display: table;
37 | overflow: hidden;
38 | width: 100%;
39 | color: #fff;
40 | }
41 | .help-table .tr {
42 | display: table-row;
43 | }
44 | .help-table .td,
45 | .help-table .th {
46 | font-size: 14px;
47 | display: table-cell;
48 | box-shadow: 0 0 1px 0 #888 inset;
49 | padding: 12px 0 12px 50px;
50 | line-height: 24px;
51 | position: relative;
52 | text-align: left;
53 | }
54 | .help-table .tr:last-child .td {
55 | padding-bottom: 12px;
56 | }
57 | .help-table .th {
58 | background: rgba(34, 41, 51, 0.5);
59 | }
60 | .help-icon {
61 | width: 40px;
62 | height: 40px;
63 | display: block;
64 | position: absolute;
65 | background: url("icon.png") 0 0 no-repeat;
66 | background-size: 500px auto;
67 | border-radius: 5px;
68 | left: 6px;
69 | top: 12px;
70 | transform: scale(0.85);
71 | }
72 | .help-title {
73 | display: block;
74 | color: #d3bc8e;
75 | font-size: 16px;
76 | line-height: 24px;
77 | }
78 | .help-desc {
79 | display: block;
80 | font-size: 13px;
81 | line-height: 18px;
82 | }
83 | /*# sourceMappingURL=index.css.map */
--------------------------------------------------------------------------------
/resources/help/index.less:
--------------------------------------------------------------------------------
1 | body {
2 | transform: scale(1);
3 | width: 830px;
4 | background: url("../common/theme/bg-01.jpg");
5 | }
6 |
7 | .container {
8 | background: url(../common/theme/main-01.png) top left no-repeat;
9 | background-size: 100% auto;
10 | width: 830px;
11 | }
12 |
13 | .head-box {
14 | margin: 60px 0 0 0;
15 | padding-bottom: 0;
16 | }
17 |
18 | .head-box .title {
19 | font-size: 50px;
20 | }
21 |
22 | .cont-box {
23 | border-radius: 15px;
24 | margin-top: 20px;
25 | margin-bottom: 20px;
26 | overflow: hidden;
27 | box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%);
28 | position: relative;
29 | }
30 |
31 | .help-group {
32 | font-size: 18px;
33 | font-weight: bold;
34 | padding: 15px 15px 10px 20px;
35 | }
36 |
37 | .help-table {
38 | text-align: center;
39 | border-collapse: collapse;
40 | margin: 0;
41 | border-radius: 0 0 10px 10px;
42 | display: table;
43 | overflow: hidden;
44 | width: 100%;
45 | color: #fff;
46 | }
47 |
48 | .help-table .tr {
49 | display: table-row;
50 | }
51 |
52 | .help-table .td,
53 | .help-table .th {
54 | font-size: 14px;
55 | display: table-cell;
56 | box-shadow: 0 0 1px 0 #888 inset;
57 | padding: 12px 0 12px 50px;
58 | line-height: 24px;
59 | position: relative;
60 | text-align: left;
61 | }
62 |
63 | .help-table .tr:last-child .td {
64 | padding-bottom: 12px;
65 | }
66 |
67 | .help-table .th {
68 | background: rgba(34, 41, 51, .5)
69 | }
70 |
71 | .help-icon {
72 | width: 40px;
73 | height: 40px;
74 | display: block;
75 | position: absolute;
76 | background: url("icon.png") 0 0 no-repeat;
77 | background-size: 500px auto;
78 | border-radius: 5px;
79 | left: 6px;
80 | top: 12px;
81 | transform: scale(0.85);
82 | }
83 |
84 | .help-title {
85 | display: block;
86 | color: #d3bc8e;
87 | font-size: 16px;
88 | line-height: 24px;
89 | }
90 |
91 | .help-desc {
92 | display: block;
93 | font-size: 13px;
94 | line-height: 18px;
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/adapter/adapter.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 |
3 | import Cfg from '../lib/config/config.js'
4 | import WebSocket from './WebSocket.js'
5 | import stdin from './stdin/index.js'
6 | import QQSDK from './QQBot/QQSDK.js'
7 | import QQBot from './QQBot/index.js'
8 | import QQGuild from './QQBot/QQGuild.js'
9 | import WeChat4u from './WeChat-Web/index.js'
10 |
11 | /** 启动HTTP服务器,加载shamrock、Com微信适配器 */
12 | WebSocket.start()
13 |
14 | /** 加载标准输入 */
15 | if (Cfg.Stdin.state) stdin()
16 |
17 | /** QQBot适配器 */
18 | const QQ_Token = Cfg.getToken('QQ_Token')
19 | if (QQ_Token && Object.keys(QQ_Token).length) {
20 | Object.keys(QQ_Token).forEach(async id => {
21 | if (id !== 'default') {
22 | let bot = Cfg.getToken('QQ_Token', id)
23 | if (bot.type == 0 || bot.type == 2) {
24 | try {
25 | const SDK = new QQSDK(bot)
26 | await SDK.start()
27 | lain.info(SDK.id, await new QQBot(SDK.sdk))
28 | lain.info(SDK.id, await new QQGuild(SDK.sdk))
29 | } catch (err) {
30 | lain.error('Lain-plugin', `QQBot <${bot.appid}> 启动失败`, err)
31 | }
32 | }
33 | if (bot.type == 1) {
34 | try {
35 | const SDK = new QQSDK(bot)
36 | await SDK.start()
37 | lain.info(SDK.id, await new QQGuild(SDK.sdk))
38 | } catch (err) {
39 | lain.error('Lain-plugin', `QQGuild <${bot.appid}> 启动失败`, err)
40 | }
41 | }
42 | }
43 | })
44 | }
45 |
46 | /** 加载微信 */
47 | const _path = fs.readdirSync('./plugins/Lain-plugin/config')
48 | const JSONFile = _path.filter(file => file.endsWith('.json'))
49 | if (JSONFile.length > 0) {
50 | JSONFile.forEach(async i => {
51 | const id = i.replace(/\.json$/gi, '')
52 | try {
53 | await new WeChat4u(id, i)
54 | } catch (error) {
55 | lain.error('Lain-plugin', `微信 ${id} 登录失败`, error)
56 | }
57 | })
58 | }
59 |
60 | lain.info('Lain-plugin', `Lain-plugin插件${Bot.lain.version}全部初始化完成~`)
61 | lain.info('Lain-plugin', 'https://gitee.com/snowtafir/Lain-plugin')
62 |
--------------------------------------------------------------------------------
/resources/help/version-info.less:
--------------------------------------------------------------------------------
1 | .linear-bg(@color) {
2 | background-image: linear-gradient(to right, @color, @color 80%, fade(@color, 0) 100%);
3 | }
4 |
5 | * {
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | user-select: none;
10 | }
11 |
12 | body {
13 | font-size: 18px;
14 | color: #1e1f20;
15 | transform: scale(1.3);
16 | transform-origin: 0 0;
17 | width: 600px;
18 | }
19 |
20 | .container {
21 | width: 600px;
22 | padding: 10px 0 10px 0;
23 | background-size: 100% 100%;
24 |
25 | }
26 |
27 | .log-cont {
28 | background-size: cover;
29 | margin: 5px 15px 5px 10px;
30 | border-radius: 10px;
31 |
32 | .cont {
33 | margin: 0;
34 | }
35 |
36 | .cont-title {
37 | font-size: 16px;
38 | padding: 10px 20px 6px;
39 |
40 | &.current-version {
41 | font-size: 20px;
42 | }
43 | }
44 |
45 | .cont-body {
46 | }
47 |
48 | ul {
49 | font-size: 14px;
50 | padding-left: 20px;
51 |
52 | li {
53 | margin: 3px 0;
54 | }
55 |
56 | &.sub-log-ul {
57 | li {
58 | margin: 1px 0;
59 | }
60 | }
61 | }
62 |
63 | .cmd {
64 | color: #d3bc8e;
65 | display: inline-block;
66 | border-radius: 3px;
67 | background: rgba(0, 0, 0, 0.5);
68 | padding: 0 3px;
69 | margin: 1px 2px;
70 | }
71 |
72 | .strong {
73 | color: #24d5cd;
74 | }
75 |
76 | .new {
77 | display: inline-block;
78 | width: 18px;
79 | margin: 0 -3px 0 1px;
80 | }
81 |
82 | .new:before {
83 | content: "NEW";
84 | display: inline-block;
85 | transform: scale(0.6);
86 | transform-origin: 0 0;
87 | color: #d3bc8e;
88 | white-space: nowrap;
89 | }
90 | }
91 |
92 | .dev-cont {
93 | background: none;
94 |
95 | .cont-title {
96 | background: rgba(0, 0, 0, .7);
97 | }
98 |
99 | .cont-body {
100 | background: rgba(0, 0, 0, .5);
101 |
102 | &.dev-info {
103 | background: rgba(0, 0, 0, .2);
104 | }
105 | }
106 |
107 | .strong {
108 | font-size: 15px;
109 | }
110 | }
--------------------------------------------------------------------------------
/lib/bot.js:
--------------------------------------------------------------------------------
1 | const CopyBot = Bot
2 |
3 | const botMethods = {
4 | pickGroup,
5 | pickFriend,
6 | pickMember,
7 | pickUser: pickFriend
8 | }
9 |
10 | Bot = new Proxy({}, {
11 | get (target, prop, receiver) {
12 | if (prop in botMethods) return botMethods[prop]
13 | if (prop in target) return target[prop]
14 | return Reflect.get(CopyBot, prop, receiver)
15 | }
16 | })
17 |
18 | /**
19 | * 得到一个群对象, 通常不会重复创建、调用
20 | * @param gid 群号
21 | * @param strict 严格模式,若群不存在会抛出异常
22 | * @returns 一个`Group`对象
23 | */
24 | function pickGroup (gid, strict) {
25 | gid = Number(gid) || String(gid)
26 | const group = Bot.gl.get(gid)
27 | if (group) return Bot[group.uin || Bot.uin].pickGroup(gid, strict)
28 | if (Number(gid)) return Bot[Bot.uin].pickGroup(gid, strict)
29 | logger.error(`获取群对象错误:找不到群 ${logger.red(gid)}`)
30 | }
31 |
32 | /**
33 | * 得到一个好友对象, 通常不会重复创建、调用
34 | * @param uid 好友账号
35 | * @param strict 严格模式,若好友不存在会抛出异常
36 | * @returns 一个`Friend`对象
37 | */
38 | function pickFriend (uid, strict) {
39 | uid = Number(uid) || String(uid)
40 | const user = Bot.fl.get(uid)
41 | if (user) return Bot[user.uin || Bot.uin].pickFriend(uid, strict)
42 | if (Number(uid)) return Bot[Bot.uin].pickFriend(uid, strict)
43 | logger.error(`获取好友对象错误:找不到好友 ${logger.red(uid)}`)
44 | }
45 |
46 | /**
47 | * 得到一个群员对象, 通常不会重复创建、调用
48 | * @param gid 群员所在的群号
49 | * @param uid 群员的账号
50 | * @param strict 严格模式,若群员不存在会抛出异常
51 | * @returns 一个`Member`对象
52 | */
53 | function pickMember (gid, uid, strict) {
54 | if (uid == 88888) {
55 | let nickname = 'Yunzai-Bot'
56 | return {
57 | group_id: gid,
58 | user_id: uid,
59 | nickname,
60 | card: nickname,
61 | sex: 'female',
62 | age: 6,
63 | join_time: '',
64 | last_sent_time: '',
65 | level: 1,
66 | role: 'member',
67 | title: '',
68 | title_expire_time: '',
69 | shutup_time: 0,
70 | update_time: '',
71 | area: '南极洲',
72 | rank: '潜水'
73 | }
74 | }
75 | const group = Bot.pickGroup(gid, strict)
76 | if (group) return group.pickMember(uid)
77 | logger.error(`获取群员对象错误:从群 ${logger.red(gid)} 中找不到群员 ${logger.red(uid)}`)
78 | }
79 |
--------------------------------------------------------------------------------
/apps/login.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import StartWeChat4u from '../adapter/WeChat-Web/index.js'
3 |
4 | export class WebWcChat extends plugin {
5 | constructor () {
6 | super({
7 | name: '微信',
8 | dsc: '网页版微信机器人',
9 | event: 'message',
10 | priority: 1,
11 | rule: [
12 | {
13 | reg: '^#微信登(录|陆)$',
14 | fnc: 'login',
15 | permission: 'master'
16 | },
17 | {
18 | reg: '^#微信账号$',
19 | fnc: 'account',
20 | permission: 'master'
21 | },
22 | {
23 | reg: '^#微信删除.*$',
24 | fnc: 'delUser',
25 | permission: 'master'
26 | }
27 | ]
28 | })
29 | }
30 |
31 | async login () {
32 | let login = false
33 | const id = `wx_${parseInt(Date.now() / 1000)}`
34 | await new StartWeChat4u(id)
35 |
36 | for (let i = 0; i < 60; i++) {
37 | if (!login && Bot.lain.loginMap.get(id)) {
38 | login = true
39 | const { url } = Bot.lain.loginMap.get(id)
40 | const msg = [
41 | '请于60秒内通过手机扫码登录微信~',
42 | segment.image(url)
43 | ]
44 | await this.e.reply(msg, false, { recall: 60 })
45 | break
46 | }
47 | await lain.sleep(1000)
48 | }
49 |
50 | for (let i = 0; i < 60; i++) {
51 | const bot = Bot.lain.loginMap.get(id)
52 | if (login && bot && bot.login) {
53 | return this.e.reply(`Bot:${id} 登录成功~`, true, { at: true })
54 | }
55 | await lain.sleep(1000)
56 | }
57 | }
58 |
59 | async account () {
60 | const _path = fs.readdirSync('./plugins/Lain-plugin/config')
61 | const Jsons = _path.filter(file => file.endsWith('.json')).map(file => file.replace('.json', ''))
62 | if (Jsons.length > 0) {
63 | return await this.reply(`微信账号:\n${Jsons.join('\n')}`, true)
64 | } else {
65 | return await this.reply('还没有账号呢~', true)
66 | }
67 | }
68 |
69 | async delUser () {
70 | const msg = this.e.msg.replace(/#微信删除/, '').trim()
71 | try {
72 | const _path = Bot.lain._path + `/${msg}.json`
73 | Bot[msg].stop()
74 | if (fs.existsSync(_path)) fs.unlinkSync(_path)
75 | return await this.reply(`已停止并删除${msg}`, true)
76 | } catch (error) {
77 | return await this.e.reply(`账号 ${msg} 不存在`)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/docs/Lagrange.Core.md:
--------------------------------------------------------------------------------
1 | # 请注意,`Lagrange.Core`作者不接受任何形式的传播
2 | # 请勿将`Lagrange.Core`在中国大陆任何公开的平台进行任何传播,特别是“B站”~
3 | # 请勿对`Lagrange.Core`进行制作任何教程,包括于本插件的任何教程
4 | # 谨记:上一个走的还是超时空猫猫。
5 |
6 | ### 使用方法:
7 |
8 | 你需要自行前往[Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core)找到文件、sign
9 |
10 | ### Linux
11 |
12 | 提权:
13 | ```bash
14 | chmod +777 Lagrange.OneBot
15 | ./Lagrange.OneBot
16 | ```
17 |
18 | 随后先进行关闭,进行下一步
19 |
20 | ### windows
21 |
22 | 解压,运行一次,随后关闭,进行下一步
23 |
24 |
25 | ### 编辑`appsettings.json`
26 |
27 | 在根目录打开`appsettings.json`
28 |
29 | 修改为以下这样
30 |
31 | ```json
32 | {
33 | "Logging": {
34 | "LogLevel": {
35 | "Default": "Information",
36 | "Microsoft": "Warning",
37 | "Microsoft.Hosting.Lifetime": "Information"
38 | }
39 | },
40 | "SignServerUrl": "你需要自己找到sign,填写到这里",
41 | "Account": {
42 | "Uin": 0,
43 | "Password": "",
44 | "Protocol": "Linux",
45 | "AutoReconnect": true,
46 | "GetOptimumServer": true
47 | },
48 | "Message": {
49 | "IgnoreSelf": true
50 | },
51 | "Implementations": [
52 | {
53 | "Type": "ReverseWebSocket",
54 | "Host": "127.0.0.1",
55 | "Port": 2955,
56 | "Suffix": "/LagrangeCore",
57 | "ReconnectInterval": 5000,
58 | "HeartBeatInterval": 5000,
59 | "AccessToken": ""
60 | }
61 | ]
62 | }
63 | ```
64 |
65 | **如果你的端口地址是云服务器,请自行更改**
66 | ```json
67 | {
68 | "Type": "ReverseWebSocket",
69 | "Host": "192.168.1.1",
70 | "Port": 8888,
71 | "Suffix": "/LagrangeCore",
72 | "ReconnectInterval": 5000,
73 | "HeartBeatInterval": 5000,
74 | "AccessToken": ""
75 | }
76 | ```
77 |
78 |
79 | 解释一下`ws://localhost:2955/LagrangeCore`这个地址
80 | - `ws://`这部分是固定的,无需更改
81 | - `localhost`这个是本地地址,如果你的喵崽在`云服务器`,请更换为云服务器的`公网IP地址`
82 | - `:2956`这部分是端口,需要使用`:`和`IP地址`连接起来,如需更改,请自行修改配置文件`config.yaml`或使用锅巴修改
83 | - `/Shamrock`这部分是固定的,无需更改
84 |
85 |
86 | # 适配进度
87 |
88 | 没有注明的在下方的有需求并且我时间充裕的情况下会实现... 欢迎pr
89 |
90 | - [√] 接收`文本`、`表情`、`at`、`图片`消息
91 | - [√] 发送`文本`、`表情`、`at`、`图片`消息
92 | - [ ] 语音、视频、文件、合并转发
93 | - [√] 好友主动消息`Bot[BotQQ号].pickUser(user_id).sendMsg("主动消息")`
94 | - [√] 群聊主动消息`Bot[BotQQ号].pickGroup(group_id).sendMsg("主动消息")`
95 | - [√] 撤回消息
96 |
97 |
98 | 如需使用`yenai-plugin`,请使用为`shamrock`专门适配的椰奶:[yenai-plugin](https://github.com/Zyy955/yenai-plugin)
--------------------------------------------------------------------------------
/apps/index.js:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import { update as Update } from '../../other/update.js'
3 | import { xiaofei_music } from '../adapter/shamrock/xiaofei/music.js'
4 | import { xiaofei_weather } from '../adapter/shamrock/xiaofei/weather.js'
5 |
6 | export class Lain extends plugin {
7 | constructor () {
8 | super({
9 | name: '铃音基本设置',
10 | priority: -50,
11 | rule: [
12 | {
13 | reg: /^#(Lain|铃音)(强制)?更新(日志)?$/gi,
14 | fnc: 'update',
15 | permission: 'master'
16 | },
17 | {
18 | reg: /^#(我的|当前)?(id|信息)$/gi,
19 | fnc: 'user_id'
20 | },
21 | {
22 | reg: /^#(重载|重新加载)资源/,
23 | fnc: 'loadRes',
24 | permission: 'master'
25 | }
26 | ]
27 | })
28 | }
29 |
30 | async update (e) {
31 | let new_update = new Update()
32 | new_update.e = e
33 | new_update.reply = this.reply
34 | const name = 'Lain-plugin'
35 | if (e.msg.includes('更新日志')) {
36 | if (new_update.getPlugin(name)) {
37 | this.e.reply(await new_update.getLog(name))
38 | }
39 | } else {
40 | if (new_update.getPlugin(name)) {
41 | if (this.e.msg.includes('强制')) { execSync('git reset --hard', { cwd: `${process.cwd()}/plugins/${name}/` }) }
42 | await new_update.runUpdate(name)
43 | if (new_update.isUp) { setTimeout(() => new_update.restart(), 2000) }
44 | }
45 | }
46 | return true
47 | }
48 |
49 | async user_id (e) {
50 | const msg = []
51 | if (e.isMaster) msg.push(`Bot:${e.bot.uin || e.self_id}`)
52 | msg.push(`您的个人ID:${e.user_id}`)
53 | if (e.guild_id) msg.push(`当前频道ID:${e.guild_id}`)
54 | if (e.channel_id) msg.push(`当前子频道ID:${e.channel_id}`)
55 | if (e.group_id) msg.push(`当前群聊ID:${e.group_id}`)
56 | if (e.isMaster && e?.adapter === 'QQGuild') msg.push('\n温馨提示:\n使用本体黑白名单请使用「群聊ID」\n使用插件黑白名单请按照配置文件说明进行添加~')
57 |
58 | /** at用户 */
59 | if (e.isMaster && e.at) msg.push(`\n目标用户ID:${e.at}`)
60 | return await e.reply(`\n${msg.join('\n')}`, true, { at: true })
61 | }
62 |
63 | /** shamrock重载资源 */
64 | // async loadRes (e) {
65 | // await e.reply('开始重载,请稍等...', true)
66 | // let res = (await import('../adapter/shamrock/bot.js')).default
67 | // res = new res(e.self_id)
68 | // const msg = await res.LoadList()
69 | // return await e.reply(msg, true)
70 | // }
71 | }
72 |
73 | export { xiaofei_music, xiaofei_weather }
74 |
--------------------------------------------------------------------------------
/model/version.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import lodash from 'lodash'
3 |
4 | let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
5 |
6 | const getLine = function (line) {
7 | line = line.replace(/(^\s*\*|\r)/g, '')
8 | line = line.replace(/\s*`([^`]+`)/g, '$1')
9 | line = line.replace(/`\s*/g, '')
10 | line = line.replace(/\s*\*\*([^*]+\*\*)/g, '$1')
11 | line = line.replace(/\*\*\s*/g, '')
12 | line = line.replace(/ⁿᵉʷ/g, '')
13 | return line
14 | }
15 |
16 | const readLogFile = function (root, versionCount = 5) {
17 | let logPath = `${root}/CHANGELOG.md`
18 | let logs = {}
19 | let changelogs = []
20 | let currentVersion
21 |
22 | try {
23 | if (fs.existsSync(logPath)) {
24 | logs = fs.readFileSync(logPath, 'utf8') || ''
25 | logs = logs.split('\n')
26 |
27 | let temp = {}
28 | let lastLine = {}
29 | lodash.forEach(logs, (line) => {
30 | if (versionCount <= -1) {
31 | return false
32 | }
33 | let versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line)
34 | if (versionRet && versionRet[1]) {
35 | let v = versionRet[1].trim()
36 | if (!currentVersion) {
37 | currentVersion = v
38 | } else {
39 | changelogs.push(temp)
40 | // if (/0\s*$/.test(v) && versionCount > 0) {
41 | // versionCount = 0
42 | // } else {
43 | versionCount--
44 | // }
45 | }
46 |
47 | temp = {
48 | version: v,
49 | logs: []
50 | }
51 | } else {
52 | if (!line.trim()) {
53 | return
54 | }
55 | if (/^\*/.test(line)) {
56 | lastLine = {
57 | title: getLine(line),
58 | logs: []
59 | }
60 | temp.logs.push(lastLine)
61 | } else if (/^\s{2,}\*/.test(line)) {
62 | lastLine.logs.push(getLine(line))
63 | }
64 | }
65 | })
66 | }
67 | } catch (e) {
68 | // do nth
69 | }
70 | return { changelogs, currentVersion }
71 | }
72 |
73 | const { changelogs, currentVersion } = readLogFile(`${process.cwd()}/plugins/Lain-plugin/`)
74 |
75 | const yunzaiVersion = packageJson.version
76 | const isMiao = !!packageJson.dependencies.sequelize
77 |
78 | let Version = {
79 | isMiao,
80 | get version () {
81 | return currentVersion
82 | },
83 | get yunzai () {
84 | return yunzaiVersion
85 | },
86 | get changelogs () {
87 | return changelogs
88 | },
89 | readLogFile
90 | }
91 |
92 | export default Version
93 |
--------------------------------------------------------------------------------
/model/help.js:
--------------------------------------------------------------------------------
1 | export const helpCfg = {
2 | themeSet: false,
3 | title: '铃音帮助',
4 | subTitle: 'Miao-Yunzai & Lain-plugin',
5 | colWidth: 265,
6 | theme: 'all',
7 | themeExclude: [
8 | 'default'
9 | ],
10 | colCount: 2,
11 | bgBlur: true
12 | }
13 | export const helpList = [
14 | {
15 | group: 'QQBot ---> #QQBot设置 沙盒:私域:appID:token:secret',
16 | auth: 'master',
17 | list: [
18 | {
19 | icon: 1,
20 | title: '#QQ群设置',
21 | desc: '是=1 否=0 再次添加为删除'
22 | },
23 | {
24 | icon: 13,
25 | title: '#QQ频道设置',
26 | desc: '是=1 否=0 再次添加为删除'
27 | },
28 | {
29 | icon: 23,
30 | title: '#QQBot设置',
31 | desc: '同时连接群和频道'
32 | },
33 | {
34 | icon: 3,
35 | title: '#QQBot账号',
36 | desc: '查看机器人'
37 | },
38 | {
39 | icon: 6,
40 | title: '#QQBot设置MD',
41 | desc: '机器人ID:模板ID'
42 | },
43 | {
44 | icon: 8,
45 | title: '#QQBotMD 2',
46 | desc: '0=关闭 1=全局 2=仅正则 3=与内容分离'
47 | },
48 | {
49 | icon: 10,
50 | title: '#QQBotDau',
51 | desc: '查看消息统计'
52 | }
53 | ]
54 | },
55 | {
56 | group: 'Shamrock',
57 | auth: 'master',
58 | list: [
59 | {
60 | icon: 2,
61 | title: '#重载资源',
62 | desc: '用于重新加载好友列表,群列表等。'
63 | },
64 | {
65 | icon: 5,
66 | title: '#shamrock版本',
67 | desc: '查询OpenShamrock官方库版本信息'
68 | },
69 | {
70 | icon: 4,
71 | title: '#shamrock(测试)安装包',
72 | desc: '从github下载apk安装包发送到群聊/私聊'
73 | }
74 | ]
75 | },
76 | {
77 | group: 'WeChat',
78 | auth: 'master',
79 | list: [
80 | {
81 | icon: 9,
82 | title: '#微信修改名称<新名称>',
83 | desc: '修改椰奶状态显示名称'
84 | }
85 | ]
86 | },
87 | {
88 | group: '其他',
89 | auth: 'master',
90 | list: [
91 | {
92 | icon: 16,
93 | title: '#设置主人',
94 | desc: '可以艾特指定用户'
95 | },
96 | {
97 | icon: 5,
98 | title: '#删除主人',
99 | desc: '艾特指定用户'
100 | },
101 | {
102 | icon: 18,
103 | title: '#铃音更新',
104 | desc: '更新插件'
105 | },
106 | {
107 | icon: 24,
108 | title: '#铃音版本',
109 | desc: '查看版本'
110 | },
111 | {
112 | icon: 7,
113 | title: '#ID',
114 | desc: '获取个人id、群id'
115 | }
116 | ]
117 | }
118 | ]
119 | export const style = {
120 | fontColor: '#ceb78b',
121 | fontShadow: 'none',
122 | descColor: '#eee',
123 | contBgColor: 'rgba(6, 21, 31, .5)',
124 | contBgBlur: 3,
125 | headerBgColor: 'rgba(6, 21, 31, .4)',
126 | rowBgColor1: 'rgba(6, 21, 31, .2)',
127 | rowBgColor2: 'rgba(6, 21, 31, .35)'
128 | }
129 |
--------------------------------------------------------------------------------
/resources/QRCode/QRCode.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
96 |
97 |
98 |
99 |
100 | 初始链接:
{{@link}}
101 |
102 | Miao-Yunzai & Lain-plugin {{@adapter.lain.version.version}}
103 |
--------------------------------------------------------------------------------
/model/config.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import Yaml from 'yaml'
3 |
4 | const _path = process.cwd() + '/plugins/Lain-plugin/config'
5 | if (!fs.existsSync(process.cwd() + '/temp/WeXin')) fs.mkdirSync(process.cwd() + '/temp/WeXin')
6 |
7 | const packYZ = JSON.parse(fs.readFileSync('./package.json', 'utf-8'))
8 | const BotCfg = Yaml.parse(fs.readFileSync('./config/config/bot.yaml', 'utf8'))
9 | const packLain = JSON.parse(fs.readFileSync('./plugins/Lain-plugin/package.json', 'utf-8'))
10 |
11 | const { name, version, adapter, dependencies } = packLain
12 | Bot.lain = {
13 | /** 云崽信息 */
14 | ...packLain,
15 | /** 配置文件夹路径 */
16 | _path,
17 | BotCfg,
18 | /** 全部频道列表 */
19 | guilds: {},
20 | /** 适配器版本及依赖 */
21 | adapter: {
22 | lain: {
23 | /** 插件 */
24 | version: {
25 | id: '云崽',
26 | name,
27 | version
28 | },
29 | /** 主体 */
30 | apk: {
31 | display: 'Yunzai-Bot',
32 | version: packYZ.version
33 | }
34 | },
35 | QQGuild: {
36 | /** 插件 */
37 | version: {
38 | id: '公域',
39 | name: 'QQ频道',
40 | version: adapter.QQGuild
41 | },
42 | /** 依赖包 */
43 | apk: {
44 | display: 'qq-official-bot',
45 | version: dependencies['qq-official-bot'].replace('^', '')
46 | }
47 | },
48 | ComWeChat: {
49 | /** 插件 */
50 | version: {
51 | id: 'PC',
52 | name: '微信',
53 | version: adapter.ComWeChat
54 | },
55 | /** 依赖包 */
56 | apk: {
57 | display: 'CWeChatRobot',
58 | version: adapter.CWeChatRobot.replace('^', '')
59 | }
60 | },
61 | stdin: {
62 | /** 插件 */
63 | version: {
64 | id: 'stdin',
65 | name: '标准输入',
66 | version: adapter.stdin
67 | },
68 | /** 依赖包 */
69 | apk: {
70 | display: 'stdin',
71 | version: adapter.stdin
72 | }
73 | },
74 | Shamrock: {
75 | /** 插件 */
76 | version: {
77 | id: 'Shamrock',
78 | name: '三叶草',
79 | version: adapter.stdin
80 | },
81 | /** 依赖包 */
82 | apk: {
83 | display: '',
84 | version: ''
85 | }
86 | },
87 | QQBot: {
88 | /** 插件 */
89 | version: {
90 | id: 'QQBot',
91 | name: 'QQBot',
92 | version: adapter.QQBot
93 | },
94 | /** 依赖包 */
95 | apk: {
96 | display: 'qq-official-bot',
97 | version: dependencies['qq-official-bot'].replace('^', '')
98 | }
99 | },
100 | WeXin: {
101 | /** 插件 */
102 | version: {
103 | id: 'WeXin',
104 | name: 'WeXin',
105 | version: adapter.WeXin
106 | },
107 | /** 依赖包 */
108 | apk: {
109 | display: 'wechat4u',
110 | version: dependencies.wechat4u.replace('^', '')
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 原仓库已删库跑路。
2 | ## 目前QQbot相关适配器维护代码来源于 [sky-summer](https://gitee.com/sky-summer/Lain-plugin.git)
3 | ## 简介
4 | - 插件更新日志:[点击查看](./CHANGELOG.md)
5 | - 本项目使用 [GPL-3.0](./LICENSE) 开源协议,欢迎任何形式的贡献!
6 |
7 | `Lain-plugin`是一个围绕云崽`Yunzai-Bot-V3`开发的多适配器插件,让喵崽有更多途径接入`QQ频道`、`微信`、`shamrock`等消息平台~,不再局限于ICQQ。
8 |
9 | 我正在为 [kritor](https://github.com/KarinJS/kritor) 开发新的机器人框架,如果您有时间且热爱开源并且想参与其中,您可以联系我~
10 |
11 | 新框架:[Karin](https://github.com/KarinJS/carrying)
12 |
13 | ### 这里特别声明:
14 |
15 | 不想登录ICQQ并继续使用本插件:
16 |
17 | - 更新云崽到最新
18 | - 打开云崽的`config/config/bot.yaml`文件将 `skip_login: false` 修改为 `skip_login: true`
19 | - 如果不存在这个,自行加一行 `skip_login: true` 即可。
20 |
21 | ## 1.安装插件
22 |
23 | 在`Yunzai`根目录执行
24 |
25 | `github:`
26 | ```
27 | git clone --depth=1 https://github.com/snowtafir/Lain-plugin ./plugins/Lain-plugin
28 | ```
29 |
30 | `gitee:`
31 | ```
32 | git clone --depth=1 https://gitee.com/snowtafir/Lain-plugin ./plugins/Lain-plugin
33 | ```
34 |
35 | ## 2.安装依赖
36 |
37 | ```
38 | pnpm install -P
39 | ```
40 |
41 | `安装失败再用这个:`
42 | ```
43 | pnpm config set sharp_binary_host "https://npmmirror.com/mirrors/sharp" && pnpm config set sharp_libvips_binary_host "https://npmmirror.com/mirrors/sharp-libvips" && pnpm install -P
44 | ```
45 |
46 | ## 3.使用适配器
47 |
48 | 请点击查看对应教程/说明~
49 |
50 | - [标准输入](./docs/stdin.md)
51 | - [QQ频道(旧版)](./docs/QQGuild.md)
52 | - [PC微信](./docs/WeChat.md)
53 | - [Shamrock](./docs/Shamrock.md)
54 | - [QQBot(群和频道)](./docs/QQBot.md)
55 | - [网页版微信](./docs/WeXin.md)
56 | - [Lagrange.Core](./docs/Lagrange.Core.md)
57 | - [OneBotV11](./docs/OneBotV11.md)
58 |
59 | ## 4.设置主人
60 |
61 | - 使用方法
62 | - 方法1:发送`#设置主人`,随后复制发送控制台的验证码即可成为主人
63 | - 方法2:发送`#设置主人@用户`,需要你是主人的情况下,指定此用户成为主人
64 |
65 | 主人可通过`#取消主人@用户`或者`#删除主人@用户`
66 |
67 | ## 插件更新
68 |
69 | - #铃音更新
70 | - #Lain更新
71 |
72 | ## 如何区分适配器
73 |
74 | - `e.adapter` || `Bot[uin].adapter`
75 | - 标准输入:`stdin`
76 | - QQ频道:`QQGuild`
77 | - Shamrock:`shamrock`
78 | - PC微信:`ComWeChat`
79 | - QQBot:`QQBot`
80 | - 网页版微信:`WeXin`
81 | - LagrangeCore: `LagrangeCore`
82 | - OneBotV11: `OneBotV11`
83 |
84 | ## 适配进度
85 | - [√] 标准输入
86 | - [√] 跳过登录QQ
87 | - [√] QQ频道适配器
88 | - [√] PC微信适配器
89 | - [√] 网页版微信适配器
90 | - [√] Shamrock适配器
91 | - [√] QQBot适配器
92 | - [√] LagrangeCore
93 | - [√] OneBotV11适配器
94 |
95 | ## 特别鸣谢
96 |
97 | 以下排名不分先后
98 |
99 | - [Trss-Yunzai](https://github.com/TimeRainStarSky/Yunzai)
100 | - [Miao-Yunzai](https://github.com/yoimiya-kokomi/Miao-Yunzai)
101 | - [索引库](https://github.com/yhArcadia/Yunzai-Bot-plugins-index)
102 | - [OpenShamrock](https://github.com/whitechi73/OpenShamrock)
103 | - [ComWeChat](https://github.com/JustUndertaker/ComWeChatBotClient)
104 | - [wechat4u](https://github.com/nodeWechat/wechat4u/blob/master/run-core.js)
105 | - [qq-official-bot](https://github.com/lc-cn/qq-official-bot)
106 | - [QQBot按钮库](https://gitee.com/lava081/button)
107 | - [xiaoye12123](https://gitee.com/xiaoye12123)
108 | - [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core)
109 | - [sky-summer | Lain-plugin](https://gitee.com/sky-summer/Lain-plugin.git) :QQbot适配器
110 |
--------------------------------------------------------------------------------
/apps/restart.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import pm2 from 'pm2'
3 | import { exec } from 'child_process'
4 |
5 | let state = false
6 |
7 | export class AdapterRestart extends plugin {
8 | constructor (e = '') {
9 | super({
10 | name: '铃音-重启',
11 | dsc: '适用于适配器重启',
12 | event: 'message',
13 | priority: 0,
14 | rule: [
15 | {
16 | reg: '^#重启$',
17 | fnc: 'restart',
18 | permission: 'master'
19 | }
20 | ]
21 | })
22 |
23 | if (e) this.e = e
24 |
25 | this.key = 'Lain:restart'
26 | }
27 |
28 | async restart () {
29 | if (state) return true
30 | state = true
31 | if (!this.e?.adapter) return false
32 | this.key = `Lain:restart:${this.e.adapter}`
33 | // "Lain:restart:QQBot"
34 | await this.e.reply('开始执行重启,请稍等...')
35 | logger.mark(`${this.e.logFnc} 开始执行重启,请稍等...`)
36 |
37 | const data = JSON.stringify({
38 | adapter: this.e.adapter,
39 | uin: this.e?.bot?.uin || this.e?.self_id || Bot.uin,
40 | isGroup: !!this.e.isGroup,
41 | id: this.e.isGroup ? this.e.group_id : this.e.user_id,
42 | time: Date.now(),
43 | msg_id: this.e.message_id
44 | })
45 |
46 | const npm = await this.checkPnpm()
47 |
48 | try {
49 | await redis.set(this.key, data, { EX: 24000 })
50 | pm2.connect((err) => {
51 | if (err) return logger.error(err)
52 |
53 | pm2.list((err, processList) => {
54 | if (err) {
55 | logger.error(err)
56 | } else {
57 | const PM2Data = JSON.parse(fs.readFileSync('./config/pm2/pm2.json'))
58 | const processExists = processList.some(processInfo => processInfo.name === PM2Data.apps[0].name)
59 | const cm = processExists ? `${npm} run restart` : `${npm} start`
60 | pm2.disconnect()
61 | exec(cm, { windowsHide: true }, (error, stdout) => {
62 | if (error) {
63 | redis.del(this.key)
64 | this.e.reply(`操作失败!\n${error.stack}`)
65 | logger.error(`重启失败\n${error.stack}`)
66 | } else if (stdout) {
67 | logger.mark('重启成功,运行已由前台转为后台')
68 | logger.mark(`查看日志请用命令:${npm} run log`)
69 | logger.mark(`停止后台运行命令:${npm} stop`)
70 | state = false
71 | process.exit()
72 | }
73 | })
74 | }
75 | })
76 | })
77 | } catch (error) {
78 | state = false
79 | redis.del(this.key)
80 | const errorMessage = error.stack ?? error
81 | this.e.reply(`操作失败!\n${errorMessage}`)
82 | logger.error(`重启失败\n${errorMessage}`)
83 | }
84 |
85 | return true
86 | }
87 |
88 | async checkPnpm () {
89 | let npm = 'npm'
90 | let ret = await this.execSync('pnpm -v')
91 | if (ret.stdout) npm = 'pnpm'
92 | return npm
93 | }
94 |
95 | async execSync (cmd) {
96 | return new Promise((resolve, reject) => {
97 | exec(cmd, { windowsHide: true }, (error, stdout, stderr) => {
98 | resolve({ error, stdout, stderr })
99 | })
100 | })
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/resources/shamrock/index.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | user-select: none;
8 | }
9 |
10 | body {
11 | font-size: 18px;
12 | color: #1e1f20;
13 | transform: scale(1.3);
14 | transform-origin: 0 0;
15 | width: 600px;
16 | }
17 | .container {
18 | width: 600px;
19 | padding: 10px 0 10px 0;
20 | background-size: 100% 100%;
21 | }
22 | .log-cont {
23 | background-size: cover;
24 | margin: 5px 15px 5px 10px;
25 | border-radius: 10px;
26 | /*padding-bottom: 300px;*/
27 | }
28 | .log-cont .cont {
29 | margin: 0;
30 | }
31 | .log-cont .cont-title {
32 | font-size: 16px;
33 | padding: 20px 20px 20px;
34 | display: flex;
35 | }
36 |
37 | .log-cont .cont-title .cont-logo {
38 | width: 70px;
39 | border-radius: 20%;
40 | }
41 |
42 | .log-cont .cont-title .cont-text {
43 | font-family: Number, serif;
44 | font-size: 24px;
45 | padding-left: 10px;
46 | color: #e3e3e3;
47 | padding-bottom: 3px;
48 | }
49 |
50 | .github {
51 | margin-top: 2px;
52 | display: flex;
53 | align-items: center; /* 将子元素垂直居中 */
54 | }
55 |
56 | .cont-text-sub {
57 | font-family: Number, serif;
58 | font-size: 14px !important;
59 | /*padding-left: 10px;*/
60 | margin-top: 2px;
61 | }
62 |
63 | .github-logo {
64 | width: 23px;
65 | padding-left: 10px;
66 | }
67 |
68 | .github-fork {
69 | margin-left: 20px;
70 | width: 25px;
71 | padding-left: 10px;
72 | }
73 |
74 | .github-subscribe {
75 | margin-left: 20px;
76 | width: 25px;
77 | padding-left: 10px;
78 | }
79 |
80 | .github-status {
81 | margin-top: 5px;
82 | display: flex;
83 | align-items: center; /* 将子元素垂直居中 */
84 | }
85 |
86 | .status-block {
87 | border-radius: 5px;
88 | margin: 10px 0;
89 | background: rgba(0, 0, 0, 20%);
90 | color: #d0d0d0;
91 | width: 575px;
92 | }
93 |
94 | .status-blck-title {
95 | background: rgba(0, 0, 0, 50%);
96 | padding: 10px;
97 | border-radius: 5px 5px 0 0;
98 | }
99 |
100 | .status-blck-content {
101 | padding: 10px;
102 | border-radius: 5px 5px 0 0;
103 | }
104 | .digest {
105 | font-size: 12px;
106 | }
107 | .release {
108 |
109 | }
110 |
111 | .release-version {
112 | color: #82d0ac;
113 | }
114 |
115 | .release-version-time {
116 | color: #aaaaaa;
117 | font-size: 12px;
118 | }
119 |
120 | .release-log {
121 | margin-top: 2px;
122 | font-size: 12px;
123 | }
124 |
125 | .strong {
126 | color: #24d5cd;
127 | }
128 | .sharp {
129 | color: #a05acb;
130 | }
131 | .commit {
132 | padding-bottom: 10px;
133 | }
134 |
135 | .commit-sha {
136 | display: flex;
137 | align-items: center; /* 将子元素垂直居中 */
138 |
139 | }
140 |
141 | .commit-content {
142 | display: flex;
143 | align-items: center; /* 将子元素垂直居中 */
144 |
145 | font-size: 12px;
146 | margin-top: 3px;
147 | }
148 | .commit-sha-value {
149 | /*margin-left: 8px;*/
150 | }
151 | .commit-time {
152 | margin-left: 8px;
153 | color: #aaaaaa;
154 | font-size: 12px;
155 | }
156 | .committer-avatar {
157 | width: 18px;
158 | border-radius: 5px;
159 | margin-right: 5px;
160 | }
161 |
162 | .help {
163 | padding-bottom: 10px;
164 | }
165 |
166 | .help-item {
167 | display: flex;
168 | align-items: center; /* 将子元素垂直居中 */
169 | }
170 |
171 | .help-item-main {
172 | font-size: 12px;
173 | }
--------------------------------------------------------------------------------
/adapter/Bot/icqq.js:
--------------------------------------------------------------------------------
1 | const _0x5173e7=_0x353d;function _0x353d(_0x162348,_0x2ed826){const _0x3f10f4=_0x3f10();return _0x353d=function(_0x353d02,_0x20ce00){_0x353d02=_0x353d02-0x151;let _0x34ca82=_0x3f10f4[_0x353d02];return _0x34ca82;},_0x353d(_0x162348,_0x2ed826);}(function(_0x3c6c9c,_0xc89b2){const _0x240e43=_0x353d,_0x424acb=_0x3c6c9c();while(!![]){try{const _0x2a5fce=parseInt(_0x240e43(0x155))/0x1*(parseInt(_0x240e43(0x159))/0x2)+-parseInt(_0x240e43(0x16c))/0x3*(parseInt(_0x240e43(0x174))/0x4)+-parseInt(_0x240e43(0x164))/0x5*(parseInt(_0x240e43(0x168))/0x6)+-parseInt(_0x240e43(0x172))/0x7*(parseInt(_0x240e43(0x175))/0x8)+parseInt(_0x240e43(0x15d))/0x9*(-parseInt(_0x240e43(0x171))/0xa)+parseInt(_0x240e43(0x167))/0xb*(-parseInt(_0x240e43(0x15a))/0xc)+parseInt(_0x240e43(0x16a))/0xd*(parseInt(_0x240e43(0x15e))/0xe);if(_0x2a5fce===_0xc89b2)break;else _0x424acb['push'](_0x424acb['shift']());}catch(_0x260cdb){_0x424acb['push'](_0x424acb['shift']());}}}(_0x3f10,0x9e64a));import{core,segment}from'icqq';class ICQQToFile{[_0x5173e7(0x179)](_0x3654bc){const _0x27dd06=_0x5173e7;return core['pb'][_0x27dd06(0x156)](Buffer['from'](_0x3654bc[_0x27dd06(0x163)](_0x27dd06(0x170),''),_0x27dd06(0x161)));}async[_0x5173e7(0x16e)](_0x31a1bd){const _0x4d02f5=_0x5173e7,_0x4828c7=Bot[Bot[_0x4d02f5(0x158)]][_0x4d02f5(0x151)](Math['ceil'](Math[_0x4d02f5(0x15f)]()*0xa**0x9)),_0x5c606c=(await _0x4828c7[_0x4d02f5(0x16b)](segment['image'](_0x31a1bd)))[_0x4d02f5(0x160)][0x0],_0x461e26=_0x4d02f5(0x166)+(_0x5c606c['md5'][_0x4d02f5(0x157)]('hex')||'')[_0x4d02f5(0x15c)]()+'/0';return{..._0x5c606c,'url':_0x461e26};}async[_0x5173e7(0x177)](_0xa3caf9){const _0x307c37=_0x5173e7,_0x16df8e=Bot[Bot[_0x307c37(0x158)]][_0x307c37(0x151)](Math[_0x307c37(0x16f)](Math[_0x307c37(0x15f)]()*0xa**0x9)),_0x460500=await _0x16df8e['uploadVideo']({'file':_0xa3caf9}),_0x5a5fc4=this['proto'](_0x460500[_0x307c37(0x173)]);return await _0x16df8e[_0x307c37(0x154)](_0x5a5fc4[0x1],_0x5a5fc4[0x2]);}async[_0x5173e7(0x178)](_0x3f5420,_0xb87a27=!![],_0x31cd4a=''){const _0x5d74f6=_0x5173e7,_0x486121=Bot[Bot[_0x5d74f6(0x158)]]['pickGroup'](Math[_0x5d74f6(0x16f)](Math[_0x5d74f6(0x15f)]()*0xa**0x9)),_0x362883=await _0x486121[_0x5d74f6(0x178)]({'file':_0x3f5420},_0xb87a27,_0x31cd4a),_0xea0ea9=this[_0x5d74f6(0x179)](_0x362883['file']);return await this['getPttUrl'](_0xea0ea9[0x3]);}async[_0x5173e7(0x15b)](_0x3971e1){const _0x2b800e=_0x5173e7,_0x945116=core['pb']['encode']({0x1:0x4b0,0x2:0x0,0xe:{0xa:Bot[_0x2b800e(0x158)],0x14:_0x3971e1,0x1e:0x2},0x65:0x11,0x66:0x68,0x1869f:{0x160bc:0x1,0x16378:0x2,0x163dc:0x1}}),_0x2cc480=core['pb']['decode'](await Bot[Bot[_0x2b800e(0x158)]][_0x2b800e(0x165)](_0x2b800e(0x176),_0x945116))[0xe];_0x2cc480[0xa]!==0x0&&logger[_0x2b800e(0x16d)](_0x2cc480,_0x2b800e(0x162));const _0x58702f=new URL(_0x2cc480[0x1e][0x32][_0x2b800e(0x157)]());return _0x58702f[_0x2b800e(0x153)]=_0x2b800e(0x152),_0x58702f[_0x2b800e(0x169)]='https',_0x58702f[_0x2b800e(0x157)]();}}function _0x3f10(){const _0x1e0e19=['9KNAHCZ','650594XpnMNI','random','imgs','base64','获取语音文件地址错误','replace','4915AzaaEc','sendUni','https://gchat.qpic.cn/gchatpic_new/0/0-0-','2308174NnNZGa','1014TOzyQo','protocol','1027LDKEBc','_preprocess','63ZIULTy','error','uploadImage','ceil','protobuf://','7100180AxpTit','21GVnnVP','file','237896ZPdKAw','2142176mimkAE','PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_DOWNLOAD-1200','uploadVideo','uploadPtt','proto','pickGroup','grouptalk.c2c.qq.com','host','getVideoUrl','194eEeynK','decode','toString','uin','1194BTxEew','12tptNrS','getPttUrl','toUpperCase'];_0x3f10=function(){return _0x1e0e19;};return _0x3f10();}Bot[_0x5173e7(0x158)]!=0x15b38&&(lain[_0x5173e7(0x173)]=new ICQQToFile());
--------------------------------------------------------------------------------
/apps/help.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import Render from '../model/render.js'
3 | import Version from '../model/version.js'
4 | import { helpCfg, helpList, style } from '../model/help.js'
5 |
6 | export class help extends plugin {
7 | constructor () {
8 | super({
9 | name: '铃音帮助',
10 | priority: -50,
11 | rule: [
12 | {
13 | reg: /^#(Lain|铃音)版本$/,
14 | fnc: 'version'
15 | },
16 | {
17 | reg: /^#(Lain|铃音)(.*)帮助$/,
18 | fnc: 'help'
19 | }
20 | ]
21 | })
22 | }
23 |
24 | async version (e) {
25 | return await Render.render('help/version-info', {
26 | currentVersion: Version.version,
27 | changelogs: Version.changelogs,
28 | name: 'Lain',
29 | elem: 'cryo'
30 | }, { e, scale: 1.2 })
31 | }
32 |
33 | async help (e) {
34 | if (!e.msg.match(/^#(Lain|铃音)帮助$/)) {
35 | return await this.reply(e.msg.replace(/帮助/, '菜单'))
36 | }
37 | let helpGroup = []
38 | _.forEach(helpList, (group) => {
39 | _.forEach(group.list, (help) => {
40 | let icon = help.icon * 1
41 | if (!icon) {
42 | help.css = 'display:none'
43 | } else {
44 | let x = (icon - 1) % 10
45 | let y = (icon - x - 1) / 10
46 | help.css = `background-position:-${x * 50}px -${y * 50}px`
47 | }
48 | })
49 |
50 | helpGroup.push(group)
51 | })
52 |
53 | let themeData = await this.getThemeData(helpCfg, helpCfg)
54 | return await Render.render('help/index', {
55 | helpCfg,
56 | helpGroup,
57 | ...themeData,
58 | element: 'default'
59 | }, { e, scale: 1.6 })
60 | }
61 |
62 | async getThemeData (diyStyle, sysStyle) {
63 | let helpConfig = _.extend({}, sysStyle, diyStyle)
64 | let colCount = Math.min(5, Math.max(parseInt(helpConfig?.colCount) || 3, 2))
65 | let colWidth = Math.min(500, Math.max(100, parseInt(helpConfig?.colWidth) || 265))
66 | let width = Math.min(2500, Math.max(800, colCount * colWidth + 30))
67 | let resPath = '{{_res_path}}/help/imgs/'
68 | let theme = {
69 | main: `${resPath}/main.png`,
70 | bg: `${resPath}/bg.jpg`,
71 | style
72 | }
73 | let themeStyle = theme.style || {}
74 | let ret = [`
75 | body{background-image:url(${theme.bg});width:${width}px;}
76 | .container{background-image:url(${theme.main});width:${width}px;}
77 | .help-table .td,.help-table .th{width:${100 / colCount}%}
78 | `]
79 | let css = function (sel, css, key, def, fn) {
80 | let val = getDef(themeStyle[key], diyStyle[key], sysStyle[key], def)
81 | if (fn) {
82 | val = fn(val)
83 | }
84 | ret.push(`${sel}{${css}:${val}}`)
85 | }
86 | css('.help-title,.help-group', 'color', 'fontColor', '#ceb78b')
87 | css('.help-title,.help-group', 'text-shadow', 'fontShadow', 'none')
88 | css('.help-desc', 'color', 'descColor', '#eee')
89 | css('.cont-box', 'background', 'contBgColor', 'rgba(43, 52, 61, 0.8)')
90 | css('.cont-box', 'backdrop-filter', 'contBgBlur', 3, (n) => diyStyle.bgBlur === false ? 'none' : `blur(${n}px)`)
91 | css('.help-group', 'background', 'headerBgColor', 'rgba(34, 41, 51, .4)')
92 | css('.help-table .tr:nth-child(odd)', 'background', 'rowBgColor1', 'rgba(34, 41, 51, .2)')
93 | css('.help-table .tr:nth-child(even)', 'background', 'rowBgColor2', 'rgba(34, 41, 51, .4)')
94 | return {
95 | style: ``,
96 | colCount
97 | }
98 | }
99 | }
100 |
101 | function getDef () {
102 | for (let idx in arguments) {
103 | if (!_.isUndefined(arguments[idx])) {
104 | return arguments[idx]
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/apps/master.js:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import Yaml from '../model/YamlHandler.js'
3 |
4 | /** 设置主人 */
5 | let sign = {}
6 |
7 | export class LainMaster extends plugin {
8 | constructor () {
9 | super({
10 | name: '铃音-设置主人',
11 | priority: -50,
12 | rule: [
13 | {
14 | reg: /^#设置主人.*/,
15 | fnc: 'master'
16 | },
17 | {
18 | reg: /^#(删除|取消)主人.*/,
19 | fnc: 'del_master',
20 | permission: 'master'
21 | },
22 | {
23 | reg: /^#(禁用|启用|恢复)主人$/,
24 | fnc: 'off_master'
25 | }
26 | ]
27 | })
28 | }
29 |
30 | async master (e) {
31 | let user_id = e.at || e.msg.replace(/#设置主人/, '') || e.user_id
32 | user_id = Number(user_id) || String(user_id)
33 |
34 | /** 检测是否为触发用户自身 */
35 | if (user_id === e.user_id) {
36 | if (e.isMaster) {
37 | return await e.reply([segment.at(user_id), "已经是主人了哦(〃'▽'〃)"])
38 | }
39 | } else {
40 | /** 如果不是触发用户自身,检测触发用户是否为主人 */
41 | if (!e.isMaster) return await e.reply('只有主人才能命令我哦~\n(*/ω\*)')
42 | const cfg = new Yaml('./config/config/other.yaml')
43 | /** 检查指定用户是否已经是主人 */
44 | if (cfg.value('masterQQ', user_id)) return e.reply([segment.at(user_id), "已经是主人了哦(〃'▽'〃)"])
45 | // return await this.e.reply(this.addmaster(user_id))
46 | }
47 |
48 | /** 生成验证码 */
49 | sign[e.user_id] = { user_id, sign: crypto.randomUUID() }
50 | logger.mark(`设置主人验证码:${logger.green(sign[e.user_id].sign)}`)
51 | await e.reply([segment.at(e.user_id), '请输入控制台的验证码'])
52 | /** 开始上下文 */
53 | return await this.setContext('SetAdmin')
54 | }
55 |
56 | async del_master (e) {
57 | let user_id = e.at || e.msg.replace(/#|删除|取消|主人/g, '')
58 | user_id = Number(user_id) || String(user_id)
59 |
60 | if (!user_id) return await e.reply('你都没有告诉我是谁!^_^')
61 | const cfg = new Yaml('./config/config/other.yaml')
62 | if (!cfg.value('masterQQ', user_id)) return await e.reply("这个人不是主人啦(〃'▽'〃)", false, { at: true })
63 | cfg.delVal('masterQQ', user_id)
64 | return await e.reply([segment.at(user_id), '拜拜~'])
65 | }
66 |
67 | async off_master (e) {
68 | let user_id = Number(e.user_id) || e.user_id
69 | if (/禁用/.test(e.msg)) {
70 | /** 检测用户是否是主人 */
71 | if (!e.isMaster) return e.reply([segment.at(e.user_id), '只有主人才能命令我哦~\n(*/ω\*)'])
72 | const cfg = new Yaml('./config/config/other.yaml')
73 | cfg.addVal('masterQQ', '--' + String(user_id), 'Array')
74 | cfg.delVal('masterQQ', user_id)
75 | return await e.reply([segment.at(user_id), '已临时禁用你的主人权限!\n如需恢复发送 #启用主人'])
76 | } else {
77 | /** 检测用户是否是主人 */
78 | if (e.isMaster) return e.reply([segment.at(e.user_id), "已经是主人了哦(〃'▽'〃)"])
79 | const cfg = new Yaml('./config/config/other.yaml')
80 | if (!cfg.value('masterQQ', '--' + String(user_id))) return e.reply([segment.at(user_id), '只有主人才能命令我哦~\n(*/ω\*)'])
81 | cfg.addVal('masterQQ', user_id, 'Array')
82 | cfg.delVal('masterQQ', '--' + String(user_id), 'Array')
83 | return await e.reply([segment.at(user_id), '已恢复你的主人权限~(*/ω\*)'])
84 | }
85 | }
86 |
87 | SetAdmin () {
88 | /** 结束上下文 */
89 | this.finish('SetAdmin')
90 | /** 判断验证码是否正确 */
91 | if (this.e.msg.trim() === sign[this.e.user_id]?.sign) {
92 | this.e.reply(this.addmaster(sign[this.e.user_id]?.user_id))
93 | } else {
94 | return this.reply([segment.at(this.e.user_id), '验证码错误'])
95 | }
96 | }
97 |
98 | /** 设置主人 */
99 | addmaster (user_id) {
100 | const cfg = new Yaml('./config/config/other.yaml')
101 | cfg.addVal('masterQQ', user_id, 'Array')
102 | return [segment.at(user_id), '新主人好~(*/ω\*)']
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/plugins/纯文模板.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 模板源码
3 | * {{.text_0}}{{.text_1}}{{.text_2}}{{.text_3}}{{.text_4}}{{.text_5}}{{.text_6}}{{.text_7}}{{.text_8}}{{.text_9}}
4 | * 正常设置模板ID 模式设置4:#QQBotMD4
5 | */
6 |
7 | import plugin from '../Lain-plugin/adapter/QQBot/plugins.js'
8 |
9 | Bot.Markdown = async function (e, data, button = []) {
10 | let text = []
11 | const image = []
12 | const message = []
13 |
14 | for (let i of data) {
15 | switch (i.type) {
16 | case 'text':
17 | text.push(i.text.replace(/\n/g, '\r').trim())
18 | break
19 | case 'image':
20 | image.push(i)
21 | break
22 | default:
23 | break
24 | }
25 | }
26 |
27 | /** 处理二笔语法,分割为数组 */
28 | text = parseMD(text.join(''))
29 |
30 | /** 先分个组吧! */
31 | if (image.length > text.length) {
32 | for (const i in image) message.push({ text: text?.[i], image: image?.[i] })
33 | } else {
34 | for (const i in text) message.push({ text: text?.[i], image: image?.[i] })
35 | }
36 |
37 | return await combination(e, message, button)
38 | }
39 |
40 | /** 处理md标记 */
41 | function parseMD (str) {
42 | /** 处理第一个标题 */
43 | str = str.replace(/^#/, '\r#')
44 | let msg = str.split(/(\*\*\*|\*\*|\*|__|_|~~|~|``)/).filter(Boolean)
45 |
46 | let mdSymbols = ['***', '**', '*', '__', '_', '~~', '~']
47 | let result = []
48 | let temp = ''
49 |
50 | for (let i = 0; i < msg.length; i++) {
51 | if (mdSymbols.includes(msg[i])) {
52 | temp += msg[i]
53 | } else {
54 | if (temp !== '') {
55 | result.push(temp)
56 | temp = ''
57 | }
58 | temp += msg[i]
59 | }
60 | }
61 |
62 | if (temp !== '') result.push(temp)
63 | return result
64 | }
65 |
66 | /** 按9进行分类 */
67 | function sort (arr) {
68 | const Array = []
69 | for (let i = 0; i < arr.length; i += 9) Array.push(arr.slice(i, i + 9))
70 | return Array
71 | }
72 |
73 | /** 组合 */
74 | async function combination (e, data, but) {
75 | const all = []
76 | /** 按9分类 */
77 | data = sort(data)
78 | for (let p of data) {
79 | const params = []
80 | const length = p.length
81 | /** 头要特殊处理 */
82 | params.push({ key: 'text_0', values: [(p[0]?.text || '') + (p[0].image ? `![图片 #${p[0].image?.width}px #${p[0].image?.height}px` : '')] })
83 | for (let i = 1; i < length; i++) {
84 | let val = []
85 | /** 上一个图片的后续链接 */
86 | if (p[i - 1]?.image) val.push(`](${p[i - 1].image.file})`)
87 | /** 当前对象的文字和图片的开头 */
88 | val.push(p[i]?.image ? `${(p[i].text || '')}![图片 #${p[i].image.width}px #${p[i].image.height}px` : (p[i].text || ''))
89 | params.push({ key: 'text_' + (i), values: [val.join('')] })
90 | }
91 |
92 | /** 尾巴也要! */
93 | if (p[length - 1]?.image) params.push({ key: `text_${length}`, values: [`](${p[length - 1].image.file})`] })
94 |
95 | /** 转为md */
96 | const markdown = {
97 | type: 'markdown',
98 | custom_template_id: e.bot.config.markdown.id,
99 | params
100 | }
101 |
102 | /** 按钮 */
103 | const button = await Button(e)
104 | button && button?.length ? all.push([markdown, ...button, ...but]) : all.push([markdown, ...but])
105 | }
106 | return all
107 | }
108 |
109 | /** 按钮添加 */
110 | async function Button (e) {
111 | try {
112 | for (let p of plugin) {
113 | for (let v of p.plugin.rule) {
114 | const regExp = new RegExp(v.reg)
115 | if (regExp.test(e.msg)) {
116 | p.e = e
117 | const button = await p[v.fnc](e)
118 | /** 无返回不添加 */
119 | if (button) return [...(Array.isArray(button) ? button : [button])]
120 | return false
121 | }
122 | }
123 | }
124 | } catch (error) {
125 | logger.error('Lain-plugin', error)
126 | return false
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/adapter/QQBot/plugins.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import moment from 'moment'
3 | import chokidar from 'chokidar'
4 |
5 | class Button {
6 | constructor () {
7 | this.plugin = './plugins'
8 | this.botModules = []
9 | this.initialize()
10 | }
11 |
12 | /** 加载按钮 */
13 | async loadModule (filePath) {
14 | filePath = filePath.replace(/\\/g, '/')
15 | try {
16 | let Plugin = (await import(`../../../.${filePath}?${moment().format('x')}`)).default
17 | Plugin = new Plugin()
18 | Plugin.plugin._path = filePath
19 | this.botModules.push(Plugin)
20 | /** 排序 */
21 | this.botModules.sort((a, b) => a.plugin.priority - b.plugin.priority)
22 | logger.debug(`按钮模块 ${filePath} 已加载。`)
23 | } catch (error) {
24 | logger.error(`导入按钮模块 ${filePath} 时出错:${error.message}`)
25 | }
26 | }
27 |
28 | /** 卸载指定文件路径的模块 */
29 | unloadModule (filePath) {
30 | const index = this.botModules.findIndex(module => module.plugin._path === filePath)
31 | if (index !== -1) this.botModules.splice(index, 1)
32 | /** 排序 */
33 | this.botModules.sort((a, b) => a.plugin.priority - b.plugin.priority)
34 | }
35 |
36 | /**
37 | * 处理文件变化事件
38 | * @param {string} filePath - 文件路径
39 | * @param {string} eventType - 事件类型 ('add', 'change', 'unlink')
40 | */
41 | async handleFileChange (filePath, eventType, state) {
42 | filePath = './' + filePath.replace(/\\/g, '/')
43 | if (filePath.endsWith('.js')) {
44 | if (eventType === 'add') {
45 | this.unloadModule(filePath)
46 | await this.loadModule(filePath)
47 | if (!state) logger.mark(`[Lain-plugin][新增按钮插件][${filePath}]`)
48 | } else if (eventType === 'add' || eventType === 'change') {
49 | this.unloadModule(filePath)
50 | await this.loadModule(filePath)
51 | logger.mark(`[Lain-plugin][修改按钮插件][${filePath}]`)
52 | } else if (eventType === 'unlink') {
53 | this.unloadModule(filePath)
54 | logger.mark(`[Lain-plugin][卸载按钮插件][${filePath}]`)
55 | }
56 | }
57 | }
58 |
59 | /** 初始化 */
60 | async initialize () {
61 | try {
62 | const filesList = []
63 | /** 遍历插件目录 */
64 | const List = fs.readdirSync(this.plugin)
65 | for (let folder of List) {
66 | const folderPath = this.plugin + `/${folder}`
67 | /** 检查是否为文件夹 */
68 | if (!fs.lstatSync(folderPath).isDirectory()) continue
69 | /** 保存插件包目录 */
70 | filesList.push(this.plugin + `/${folder}/lain.support.js`)
71 | }
72 |
73 | /** 获取插件包内的文件夹,进行热更 */
74 | const pluginList = fs.readdirSync(this.plugin + '/Lain-plugin/plugins')
75 | /** 支持插件包按钮 */
76 | for (let folder of pluginList) {
77 | const folderPath = this.plugin + `/Lain-plugin/plugins/${folder}`
78 | /** 检查是否为文件夹 */
79 | if (!fs.lstatSync(folderPath).isDirectory()) continue
80 | /** 保存 */
81 | filesList.push(folderPath)
82 | }
83 |
84 | /** 热更新 */
85 | filesList.map(folder => {
86 | let state = true
87 | const watcher = chokidar.watch(folder, { ignored: /[/\\]\./, persistent: true })
88 | watcher
89 | .on('add', async filePath => {
90 | await this.handleFileChange(filePath, 'add', state)
91 | if (state) state = false
92 | })
93 | .on('change', async filePath => await this.handleFileChange(filePath, 'change'))
94 | .on('unlink', async filePath => await this.handleFileChange(filePath, 'unlink'))
95 |
96 | return watcher
97 | })
98 |
99 | return this.botModules
100 | } catch (error) {
101 | logger.error(`读取插件目录时出错:${error.message}`)
102 | }
103 | }
104 | }
105 |
106 | const plugin = new Button()
107 | export default plugin.botModules
108 |
--------------------------------------------------------------------------------
/model/shamrock/shamrock.js:
--------------------------------------------------------------------------------
1 | import { GithubClient } from './client.js'
2 |
3 | export const SHAMROCK_OWNER = 'whitechi73'
4 | export const SHAMROCK_REPO = 'OpenShamrock'
5 |
6 | export class ShamrockRepoClient {
7 | constructor (key) {
8 | this.client = new GithubClient(key)
9 | this.cache = {
10 | commits: {}
11 | }
12 | }
13 |
14 | /**
15 | * 获取最近提交
16 | * @returns {Promise