有 bug 或维护方面的问题,请前往相关渠道反馈
- 在脚本环境中,注解是不可用的,写了会直接加载失败
-
脚本环境可以使用 Java 标准类库,以及 Android 标准类库,如
org.json.JSONObject、TextView等(需 import) -
脚本中的实体对象(如
GroupInfo、GroupMemberInfo)直接使用Object指代即可 - 脚本的 Java 运行环境为 JDK 9,不支持较新的 API
main.java
文件在点击加载时加载
desc.txt
脚本描述文件,用于在列表中显示
info.prop
脚本信息配置文件
info.prop 配置格式:
name=脚本名称
type=1
version=1.0
author=作者名
id=脚本ID(确保唯一性)
date=2026-01-05
tags=群聊辅助,娱乐功能
目录结构:
├── info.prop
├── desc.txt
├── main.java
└── images/
├── icon.png
├── preview1.png
└── preview2.png
示例:
toast("当前QQ" + myUin);
void onMsg(MessageData msg)
收到消息时调用
MessageData 成员
String MessageContent - 消息内容
String GroupUin - 群号
String PeerUin - 对方QQ号(私聊)
String UserUin - 发送者QQ号
int MessageType - 消息类型
boolean IsGroup - 是否群组消息
boolean IsChannel - 是否频道消息
String SenderNickName - 发送者昵称
long MessageTime - 消息时间戳
ArrayList<String> mAtList - 艾特列表
boolean IsSend - 是否为自己发送
String FileName - 文件名
long FileSize - 文件大小
String LocalPath - 本地文件路径
String ReplyTo - 回复的用户账号
MessageData RecordMsg - 回复的消息
String GuildID - 频道ID
String ChannelID - 子频道ID
String[] PicList - 图片MD5列表
ArrayList<String> PicUrlList - 图片链接列表
Object msg - 未解析的原消息
void onForbiddenEvent(String GroupUin, String UserUin, String OPUin, long time)
成员被禁言时调用
参数1: 群号
参数2: 被禁言的用户QQ
参数3: 执行禁言的管理员
参数4: 禁言时间(秒)
void onTroopEvent(String GroupUin, String UserUin, int type)
发生进群和退群时调用
参数1: 群号
参数2: 用户QQ
参数3: 进群为2,退群为1
void onClickFloatingWindow(int type, String uin)
在脚本悬浮窗打开时调用
参数1: 聊天类型(私聊1,群聊2)
参数2: 群号或QQ号
String getMsg(String msg, String GroupUin/FriendUin, int type)
点击发送按钮发送消息时调用
返回修改后的文本内容
参数3: 类型(2群组,1/100私聊)
示例:
// 监听收到消息
void onMsg(Object msg) {
toast("消息内容:" + msg.MessageContent);
}
sendMsg(String GroupUin, String UserUin, String msg)
发送文本、图片或图文消息
- 图文消息写
[PicUrl=图片本地或网络地址] - 艾特写
[AtQQ=QQ号]
void replyEmoji(Object msg, String emojiId)
void replyEmoji(Object target, int emojiType, String emojiId)
发送表情回应
emojitype: 原生表情为2,QQ自带表情为1
forbidden(String GroupUin, String UserUin, int time)
禁言(仅管理员可用)
- 时间单位为秒
- 全体禁言时不写用户账号
- 全体禁言时间结束后会变成假全体禁言
ArrayList<GroupInfo> getGroupList()
获取群信息列表
GroupInfo 成员
String GroupUin- 群号String GroupName- 群名String GroupOwner- 群主账号String[] AdminList- 管理员列表(约30分钟刷新)boolean IsOwnerOrAdmin- 我是否是群主或管理Object sourceInfo- 原对象
ArrayList<GroupMemberInfo> getGroupMemberList(String GroupUin)
获取群成员信息列表
GroupMemberInfo 成员
String UserUin- 成员账号String NickName- 群内昵称String UserName- 成员名字int UserLevel- 成员群聊等级long Join_Time- 成员加群时间long Last_AvtivityTime- 最后发言时间Object sourceInfo- 原对象boolean IsOwner- 是否群主boolean IsAdmin- 是否管理
ArrayList<ForbiddenInfo> getForbiddenList(String GroupUin)
获取群聊被禁言的成员列表
ForbiddenInfo 成员
String UserUin- 成员号码String UserName- 成员名字long Endtime- 禁言结束时间戳
ArrayList<FriendInfo> getFriendList()
获取好友列表
FriendInfo 成员
String uin- QQ号String name- QQ昵称String remark- 备注boolean isVip- 是否会员int vipLevel- 会员等级
字符串存储
整数存储
长整数存储
布尔值存储
浮点数存储
UI 方法
加载方法
HTTP 请求方法
发送 HTTP GET 请求
String httpGet(String url)
基础 GET 请求
String httpGet(String url, Map<String, String> headers)
带请求头的 GET 请求
返回响应内容,失败返回空字符串
发送 HTTP POST 表单请求
String httpPost(String url, Map<String, String> data)
基础 POST 表单请求
String httpPost(String url, Map<String, String> headers, Map<String, String> data)
带请求头的 POST 表单请求
返回响应内容,失败返回空字符串
发送 HTTP POST JSON 请求
String httpPostJson(String url, String data)
基础 POST JSON 请求
String httpPostJson(String url, Map<String, String> headers, String data)
带请求头的 POST JSON 请求
Content-Type 自动设为 application/json
Map 参数和响应结果仅支持字符串;get 和 post 请求异常返回空字符串;文件下载失败会抛出异常
文件操作方法
写入操作会自动创建父级文件夹和目标文件
音乐搜索 API 示例
该示例演示如何调用网易云音乐搜索 API,并解析返回的 JSON 数据获取歌曲信息。
API 地址:
https://oiapi.net/api/Music_163?name=crush
返回的 JSON 结构:
{
"code": 0,
"message": "搜索成功",
"data": [
{
"name": "Crush",
"picurl": "http://p1.music.126.net/.../18201315486510082.jpg",
"id": 405599687,
"jumpurl": "https://music.163.com/#/song?id=405599687",
"singers": [
{"name": "Yuna", "id": 106626},
{"name": "Usher", "id": 45564}
]
}
]
}
完整代码示例:
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
// 搜索音乐并发送结果
public void searchMusic(String keyword, String groupUin) {
try {
// 1. 发送 HTTP GET 请求
String url = "https://oiapi.net/api/Music_163?name=" + keyword;
String response = httpGet(url);
// 检查响应是否为空
if (response == null || response.isEmpty()) {
sendMsg(groupUin, "", "请求失败,请稍后重试");
return;
}
// 2. 解析 JSON 响应
JSONObject json = new JSONObject(response);
// 获取返回码
int code = json.getInt("code");
if (code != 0) {
sendMsg(groupUin, "", "搜索失败:" + json.getString("message"));
return;
}
// 获取数据数组
JSONArray dataArray = json.getJSONArray("data");
// 检查是否有搜索结果
if (dataArray.length() == 0) {
sendMsg(groupUin, "", "未找到相关歌曲");
return;
}
// 3. 遍历结果并格式化输出
StringBuilder result = new StringBuilder();
result.append("找到 ").append(dataArray.length()).append(" 首歌曲:\n\n");
// 最多显示前5首
int maxResults = Math.min(dataArray.length(), 5);
for (int i = 0; i < maxResults; i++) {
JSONObject song = dataArray.getJSONObject(i);
String name = song.getString("name");
String picUrl = song.getString("picurl");
int id = song.getInt("id");
String jumpUrl = song.getString("jumpurl");
// 获取歌手信息
JSONArray singers = song.getJSONArray("singers");
StringBuilder singerNames = new StringBuilder();
for (int j = 0; j < singers.length(); j++) {
if (j > 0) singerNames.append(", ");
singerNames.append(singers.getJSONObject(j).getString("name"));
}
// 格式化单条结果
result.append(i + 1).append(". ")
.append(name)
.append(" - ")
.append(singerNames)
.append("\n")
.append(jumpUrl)
.append("\n\n");
}
// 发送结果
sendMsg(groupUin, "", result.toString());
} catch (JSONException e) {
error(e);
sendMsg(groupUin, "", "JSON解析出错:" + e.getMessage());
}
}
// 在消息回调中调用
void onMsg(Object msg) {
String text = msg.MessageContent;
String qun = msg.GroupUin;
// 检测搜索命令,格式:点歌 歌名
if (text.startsWith("点歌 ")) {
String keyword = text.substring(3).trim();
if (!keyword.isEmpty()) {
searchMusic(keyword, qun);
} else {
sendMsg(qun, "", "请输入歌曲名称,例如:点歌 crush");
}
}
}
JSONObject 常用方法
JSONObject(String json)
从 JSON 字符串创建 JSONObject
String getString(String name)
获取字符串字段值
int getInt(String name)
获取整数字段值
boolean getBoolean(String name)
获取布尔字段值
JSONArray getJSONArray(String name)
获取数组字段值
JSONObject getJSONObject(String name)
获取嵌套对象值
JSONArray 常用方法
int length()
获取数组长度
JSONObject getJSONObject(int index)
获取指定索引的对象元素
String getString(int index)
获取指定索引的字符串元素
// 接收到消息时 QStory 会调用此方法
public void onMsg(Object msg) {
// 消息内容
String text = msg.MessageContent;
// 发送者QQ
String qq = msg.UserUin;
// 发送者群聊
String qun = msg.GroupUin;
if (text.equals("菜单") && qq.equals(myUin)) {
String reply = "TG频道:https://t.me/QStoryPlugin\n交流群:979938489\n---------\n这是菜单 你可以发送下面的指令来进行测试\n艾特我\n回复我\n私聊我";
if (msg.IsGroup) {
sendMsg(qun, "", reply);
} else {
sendMsg("", qq, reply);
}
}
if (text.equals("艾特我") && msg.IsGroup && qq.equals(myUin)) {
sendMsg(qun, "", "[AtQQ=" + qq + "] 嗯呐");
}
if (text.equals("回复我") && msg.IsGroup && qq.equals(myUin)) {
sendReply(qun, msg, "好啦");
}
if (text.equals("私聊我")) {
sendMsg(qun, qq, "我已经私聊你咯");
}
// 正则表达式 + 解析时间格式来进行禁言
// 可以响应 "禁言@xxx 1天" 这样的消息
if (msg.IsSend // 是自己发送
&& msg.MessageContent.matches("禁言 ?@[\\s\\S]+[0-9]+(天|分|时|小时|分钟|秒)") // 是"禁言@xxx 1天"这样的消息
&& msg.mAtList.size() >= 1 // 艾特列表中 艾特人数至少有1个
) {
int banTime = parseTimeBymessage(msg);
if (banTime >= 60 * 60 * 24 * 30 + 1) {
sendMsg(msg.GroupUin, "", "请控制在30天以内");
return;
} else {
for (String atUin : msg.mAtList) {
forbidden(msg.GroupUin, atUin, banTime);
}
}
}
}
// 将"禁言@xxx 1天"解析成 86400 这样的秒格式
public int parseTimeBymessage(Object msg) {
int timeStartIndex = msg.MessageContent.lastIndexOf(" ");
String date = msg.MessageContent.substring(timeStartIndex + 1);
date = date.trim();
String t = "";
if (date != null && !"".equals(date)) {
for (int i = 0; i < date.length(); i++) {
if (date.charAt(i) >= 48 && date.charAt(i) <= 57) {
t += date.charAt(i);
}
}
}
int time = Integer.parseInt(t);
if (date.contains("天")) {
return time * 60 * 60 * 24;
} else if (date.contains("时") || date.contains("小时")) {
return 60 * 60 * time;
} else if (date.contains("分") || date.contains("分钟")) {
return 60 * time;
}
return time;
}
// 添加脚本悬浮窗菜单项
addItem("开关加载提示", "加载提示");
// 对应 "加载提示" 这个方法名
public void 加载提示(String s) {
// getString的参数分别是 配置文件名 Key键名
if (getString("加载提示", "开关") == null) {
putString("加载提示", "开关", "关");
toast("已关闭加载提示");
} else {
putString("加载提示", "开关", null);
toast("已开启加载提示");
}
}
if (getString("加载提示", "开关") == null)
toast("发送 \"菜单\" 查看使用说明");