文章
QStory 开发文档
发布于 2026-04-05 02:26
QStory Java脚本开发文档(新)
适用于新版本 QStory 脚本开发。
- 最近一次文档更新:2026-04-01
- 维护与 Bug 反馈:请通过能联系到我的渠道反馈
- 变更说明:QStory 脚本变更
1. 开发前必读
1.1 环境限制
- 脚本环境不支持注解,写入后会直接导致加载失败。
- 可使用 Java 标准类库与 Android 标准类库,例如
org.json.JSONObject、TextView,但需要自行
import。 - 脚本中的实体对象,如
GroupInfo、GroupMemberInfo,通常可直接用Object指代,并直接访问字段。 - Java 运行环境为 JDK 9,不支持较新的 API。
- 频道相关内容当前没有系统性维护
1.2 编写方式
- 回调方法直接定义在文件根作用域,不需要写在类中。
- 全局变量可直接引用。
- 文档中的示例以
main.java为主文件。
2. 脚本目录结构
运行脚本时至少需要以下文件:
main.java:点击加载时执行的主脚本文件。desc.txt:脚本描述文件,用于列表展示。info.prop:脚本信息文件,格式为key=value。
2.1 info.prop 必填字段
| 键 | 示例 | 说明 |
|---|---|---|
name |
name=脚本名称 |
脚本名称 |
type |
type=1 |
直接写 1 |
version |
version=1.0 |
版本号 |
author |
author=作者名 |
作者名 |
id |
id=脚本唯一ID |
脚本唯一标识,必须唯一 |
date |
date=2025-12-1 |
格式固定为 yyyy-M-d,日期过旧可能导致脚本无法加载 |
tags |
tags=群聊辅助,娱乐功能 |
当前常见标签:群聊辅助、娱乐功能、功能扩展、综合脚本、官方脚本,也可自定义 |
info.prop
name=脚本名称
type=1
version=1.0
author=作者名
id=脚本唯一ID
date=2025-12-1
tags=群聊辅助,娱乐功能
2.2 预览图目录
- 在脚本目录下创建
images/文件夹。 icon.png作为图标。- 其他图片作为预览图。
- 预览图按
A-Z、a-z、0-9排序,后缀不限。
├── info.prop
├── desc.txt
├── main.java
└── images/
├── icon.png
├── preview1.png
└── preview2.png
3. 全局变量
以下变量可直接在脚本中使用:
| 变量 | 类型 | 说明 |
|---|---|---|
myUin |
String |
当前用户 QQ 号 |
context |
Context |
QQ 全局上下文对象,即 android.content.Context |
appPath |
String |
脚本运行时的相对目录 |
loader |
ClassLoader |
QQ 的类加载器 |
pluginID |
String |
当前脚本 ID |
示例:
toast("当前QQ:"+myUin);
4. 回调方法
回调直接定义在文件根作用域即可,相关事件触发时会自动调用。
基础示例:
void onMsg(Object msg) {
toast("消息内容:" + msg.MessageContent);
}
4.1 onMsg(MessageData msg)
收到消息时调用。
MessageData 常用字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
MessageContent |
String |
消息内容。文本、图片下载地址、语音 MD5、卡片代码等 |
GroupUin |
String |
群号。群消息、私聊消息、频道消息时有效 |
PeerUin |
String |
私聊时可用,通常为对方 QQ 号 |
UserUin |
String |
发送者 QQ 号 |
MessageType |
int |
消息类型:1 文本/图片,2 卡片,3 图文,4 语音,5 文件,6 回复 |
IsGroup |
boolean |
是否群消息。频道消息中也可能为 true |
IsChannel |
boolean |
是否频道消息 |
SenderNickName |
String |
发送者昵称 |
MessageTime |
long |
消息时间戳,单位毫秒 |
mAtList |
ArrayList<String> |
被艾特列表 |
IsSend |
boolean |
是否为自己发送的消息 |
FileName |
String |
文件名,仅群文件消息有效 |
FileSize |
long |
文件大小,仅群文件消息有效 |
LocalPath |
String |
本地文件路径,仅语音消息有效 |
ReplyTo |
String |
被回复用户账号,仅回复消息有效 |
RecordMsg |
MessageData |
被回复的原消息 |
GuildID |
String |
频道 ID,仅频道消息有效 |
ChannelID |
String |
子频道 ID,仅频道消息有效 |
PicList |
String[] |
图片 MD5 列表 |
PicUrlList |
ArrayList<String> |
图片链接列表 |
msg |
Object |
未解析的原始消息对象 |
4.2 其他回调
void onForbiddenEvent(String groupUin, String userUin, String opUin, long time)
成员被禁言时调用。
groupUin:群号userUin:被禁言的用户 QQopUin:执行禁言的管理员 QQtime:禁言时长,单位秒
void onTroopEvent(String groupUin, String userUin, int type)
发生进群或退群时调用。
groupUin:群号userUin:用户 QQtype=2:进群type=1:退群
void onClickFloatingWindow(int type, String uin)
脚本悬浮窗打开时调用。
type=1:私聊type=2:群聊uin:私聊时为 QQ 号,群聊时为群号
通常配合 addTemporaryItem(String itemName, String callbackName) 使用。
String getMsg(String msg, String peerUin, int chatType)
点击发送按钮发送消息时调用。
- 仅支持处理纯文本消息
- 参数 1:即将发送的文本
- 参数 2:好友号码或群号
- 参数 3:类型,
2为群聊,1和100代表私聊 - 返回值:返回后的文本会替换原文本内容
void onCreateMenu(MessageData msg)
长按消息准备创建消息长按菜单时调用。
msg与onMsg(MessageData msg)中的对象一致- 通常用于结合
addMenuItem(...)动态创建一次性长按菜单
void callbackOnRawMsg(Object msg)
收到未解析原始消息时调用,包括灰字、文本等。
- 对应 QQ 类:
com.tencent.qqnt.kernel.nativeinterface.MsgRecord - 需要自行解析
void onLoad()
脚本加载完成时调用。
void onUnLoad()
脚本取消加载时调用。
5. API 方法
5.1 发送消息相关
默认约定:
- 参数 1 为群号
- 参数 2 为 QQ 号
- 参数 3 为内容或路径
- QQ 号为空时发送群消息
- 群号为空时发送私聊消息
| 方法 | 说明 |
|---|---|
sendMsg(String groupUin, String userUin, String msg) |
发送文本、图片或图文消息。图文格式可写 [PicUrl=图片本地或网络地址],艾特格式可写 [AtQQ=QQ号] |
sendPic(String groupUin, String userUin, String path) |
发送单张图片,本地或网络地址均可 |
sendSticker(String groupUin, String userUin, String path, String summary) |
以表情方式发送图片,summary 不填时默认显示 [动画表情] |
sendCard(String groupUin, String userUin, String card) |
发送 JSON 卡片代码 |
sendReply(String groupUin, Object msg, String text) |
发送回复消息,仅支持群聊 |
sendFile(String groupUin, String userUin, String path) |
发送文件 |
sendVoice(String groupUin, String userUin, String path) |
发送语音 |
sendVideo(String groupUin, String userUin, String path) |
发送视频 |
sendLike(String userUin, int count) |
给指定 QQ 点赞 |
sendPai(String groupUin, String userUin) |
拍一拍或戳一戳。群聊时传群号,私聊时群号留空 |
replyEmoji(Object msg, String emojiId) |
对消息发送表情回应 |
replyEmoji(Object target, int emojiType, String emojiId) |
对消息发送表情回应。已知原生表情 emojiType=2,QQ 自带表情 emojiType=1 |
forwardMsg(String groupUin, String userUin, Object msg) |
转发消息 |
sendProto(String cmd, String jsonBody) |
发送 ProtoBuf 消息,实验性方法 |
5.2 群聊操作
| 方法 | 说明 |
|---|---|
setCard(String groupUin, String userUin, String name) |
设置群名片,仅管理员可用,当前未维护 |
setTitle(String groupUin, String userUin, String title) |
设置头衔,仅群主可用 |
revokeMsg(Object msg) |
撤回消息。只能撤回自己发的消息,或管理员撤回群员消息 |
deleteMsg(Object msg) |
删除消息 |
forbidden(String groupUin, String userUin, int time) |
禁言,单位秒。管理员可用;全体禁言时用户账号可留空 |
kick(String groupUin, String userUin, boolean isBlack) |
踢出群成员,isBlack 表示是否禁止再次申请 |
补充说明:
- 全体禁言填1以上的时间,禁言结束后,可能会出现“显示全体禁言但仍可发消息”的假全体禁言状态。
5.3 信息获取
说明:获取的数据越多,耗时越高。通常可以直接使用 Object,无需强转。
消息与成员基础信息
| 方法 | 说明 |
|---|---|
List<MessageData> getMessageList(String groupUin, String userUin, int count) |
获取消息列表,结构与 onMsg 的 MessageData 一致 |
String getMemberName(String groupUin, String uin) |
获取群成员名称 |
群信息
| 方法 | 说明 |
|---|---|
ArrayList<GroupInfo> getGroupList() |
获取群信息列表 |
GroupInfo getGroupInfo(String groupUin) |
获取指定群信息 |
GroupInfo 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
GroupUin |
String |
群号 |
GroupName |
String |
群名 |
GroupOwner |
String |
群主账号 |
AdminList |
String[] |
管理员列表,包含群主;不一定实时,通常约 30 分钟刷新一次 |
IsOwnerOrAdmin |
boolean |
当前账号在该群是否为群主或管理员 |
sourceInfo |
Object |
原对象,对应 com.tencent.mobileqq.data.troop.TroopMemberInfo |
群成员信息
| 方法 | 说明 |
|---|---|
ArrayList<GroupMemberInfo> getGroupMemberList(String groupUin) |
获取群成员信息列表 |
GroupMemberInfo getMemberInfo(String groupUin, String uin) |
获取指定群成员信息 |
GroupMemberInfo 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
UserUin |
String |
成员账号 |
NickName |
String |
群内昵称 |
UserName |
String |
成员名字,通常为好友备注 |
UserLevel |
int |
群聊等级 |
Join_Time |
long |
加群时间 |
Last_AvtivityTime |
long |
最后发言时间,不一定实时刷新 |
sourceInfo |
Object |
原对象,对应 com.tencent.mobileqq.data.troop.TroopInfo |
IsOwner |
boolean |
是否群主 |
IsAdmin |
boolean |
是否管理员 |
禁言信息
| 方法 | 说明 |
|---|---|
ArrayList<ForbiddenInfo> getForbiddenList(String groupUin) |
获取群内被禁言成员列表 |
ForbiddenInfo 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
UserUin |
String |
成员账号 |
UserName |
String |
成员名字 |
Endtime |
long |
禁言结束时间戳 |
好友信息
| 方法 | 说明 |
|---|---|
ArrayList<FriendInfo> getFriendList() |
获取好友列表 |
boolean isFriend(String uin) |
判断是否为好友 |
List<NewFriendInfo> getNewFriendList() |
新版获取好友列表方法 |
NewFriendInfo getNewFriendInfo(String uin) |
获取单个好友信息 |
FriendInfo 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
uin |
String |
QQ 号 |
name |
String |
QQ 昵称 |
remark |
String |
备注 |
isVip |
boolean |
是否会员 |
vipLevel |
int |
会员等级 |
NewFriendInfo 结构:
class NewFriendInfo {
public String uin; // QQ号
public String nickname; // QQ昵称
public String remark; // 备注
public int sex; // 性别
public int age; // 年龄
}
5.4 简单数据存储
| 方法 | 说明 |
|---|---|
void putString(String configName, String key, String value) |
存储字符串 |
String getString(String configName, String key) |
读取字符串 |
String getString(String configName, String key, String def) |
读取字符串并指定默认值 |
void putInt(String configName, String key, int value) |
存储整数 |
int getInt(String configName, String key, int def) |
读取整数 |
void putLong(String configName, String key, long value) |
存储长整数 |
long getLong(String configName, String key, long def) |
读取长整数 |
void putBoolean(String configName, String key, boolean value) |
存储布尔值 |
boolean getBoolean(String configName, String key, boolean def) |
读取布尔值 |
void putFloat(String configName, String key, float value) |
存储浮点数 |
float getFloat(String configName, String key, float def) |
读取浮点数 |
void putDouble(String configName, String key, double value) |
存储双精度浮点数 |
double getDouble(String configName, String key, double def) |
读取双精度浮点数 |
5.5 SKey 相关
| 方法 | 说明 |
|---|---|
String getGroupRKey() |
获取群聊 rkey |
String getFriendRKey() |
获取私聊 rkey |
String getSkey() |
获取标准 skey |
String getRealSkey() |
获取可能更真实的 skey |
String getPskey(String url) |
获取指定域名相关 pskey |
String getPT4Token(String str) |
懂的都懂 |
String getGTK(String str) |
可能懂了 |
long getBKN(String pskey) |
不懂 |
5.6 其他能力
| 方法 | 说明 |
|---|---|
Activity getActivity() |
获取当前 QQ 顶层 Activity;若 QQ 在后台则返回 null |
toast(Object content) |
弹出 Toast |
load(String path) |
在当前脚本环境再加载一个 Java 文件。建议使用 load(appPath + "/dir/Utils.java"); |
loadJar(String jarPath) |
加载 Jar |
loadDex(String path) |
加载 Dex |
eval(String code) |
热加载一段 Java 代码 |
error(Throwable throwable) |
将异常输出到脚本目录 |
log(Object content) |
将日志输出到脚本目录 |
HTTP 方法
| 方法 | 说明 |
|---|---|
String httpGet(String url) |
内置 HTTP GET,请求浏览器可访问的链接内容 |
String httpGet(String url, Map<String, String> headers) |
带请求头的 GET |
String httpPost(String url, Map<String, String> data) |
内置 HTTP POST,发送表单 |
String httpPost(String url, Map<String, String> headers, Map<String, String> data) |
带请求头的 POST 表单 |
String httpPostJson(String url, String data) |
发送 application/json; charset=utf-8 请求 |
String httpPostJson(String url, Map<String, String> headers, String data) |
带请求头的 JSON POST |
void httpDownload(String url, String path) |
下载文件到脚本目录内的相对路径 |
void httpDownload(String url, String path, Map<String, String> headers) |
带请求头下载文件 |
HTTP 相关注意事项:
Map参数和响应结果仅支持字符串。GET和POST请求异常时会返回空字符串。- 文件下载失败时会抛出异常。
- 下载路径必须是脚本目录中的相对路径,其他路径 QQ 可能无权限读写。
OCR 方法
| 方法 | 说明 |
|---|---|
String scanImageText(String path) |
扫描图片中的文字,直接返回文本 |
List<OcrText> scanImageTextDetail(String path) |
返回更详细的 OCR 结果,包括文本、方向、可信度、位置 |
OcrText 结构:
public class OcrText {
public Float confidence; // 置信度
public int orientation; // 方向
public List<Point> points; // 位置,android.graphics.Point
public String text; // 文本
public List<OcrTextResult> textList; // 更细粒度的文本列表
public static class OcrTextResult {
public Float confidence; // 置信度
public String text; // 单个字符
public int id; // ID
}
}
文件操作
写入操作会自动创建父级目录和目标文件。
| 方法 | 说明 |
|---|---|
String readFileText(String path) |
读取文本文件 |
void writeTextToFile(String path, String text) |
覆盖写入文本 |
void writeTextAppendToFile(String path, String text) |
追加写入文本 |
byte[] readFileBytes(String path) |
读取字节 |
void writeBytesToFile(String path, byte[] bytes) |
写入字节 |
6. 菜单与交互
6.1 悬浮窗菜单
| 方法 | 说明 |
|---|---|
String addItem(String name, String callbackName) |
添加一个常驻菜单,由模块显示在聊天窗口中 |
void addTemporaryItem(String itemName, String callbackName) |
添加一个临时菜单,脚本菜单弹窗关闭后自动删除 |
void removeItem(String itemID) |
删除菜单 |
规则:
- 参数 1 为显示名称。
- 参数 2 为回调方法名。
- 被调用的回调方法需要提供 3 个参数:群号、QQ 号、聊天类型。
- 私聊
chatType=1,群聊chatType=2。
示例:
addItem("开关加载提示","toggleLoadToast");
public void toggleLoadToast(String groupUin, String uin, int chatType) {
if (getString("加载提示", "开关") == null) {
putString("加载提示", "开关", "关");
toast("已关闭加载提示");
} else {
putString("加载提示", "开关", null);
toast("已开启加载提示");
}
}
if(
getString("加载提示","开关") ==null){
toast("加载成功");
}
6.2 常规开关功能示例
String configName = "开关";
addItem("开关","open");
public void open(String groupUin, String uin, int chatType) {
if (chatType != 2) {
toast("不支持私聊开启");
return;
}
if (getBoolean(configName, groupUin, false)) {
putBoolean(configName, groupUin, false);
toast("已关闭" + groupUin);
} else {
putBoolean(configName, groupUin, true);
toast("已开启" + groupUin);
}
}
public boolean isOpen(String groupUin) {
return getBoolean(configName, groupUin, false);
}
void onTroopEvent(String groupUin, String userUin, int type) {
if (!isOpen(groupUin)) {
return;
}
if (type == 2) {
sendMsg(groupUin, "", "有人加入:" + userUin);
}
if (type == 1) {
sendMsg(groupUin, "", "有人退出:" + userUin);
}
}
6.3 长按消息菜单
addMenuItem 只能在 onCreateMenu(MessageData msg) 中使用。
| 方法 | 说明 |
|---|---|
String addMenuItem(String name, String callbackName) |
在长按消息菜单中添加选项 |
规则:
- 参数 1 为菜单名称。
- 参数 2 为回调方法名。
- 回调方法只需要 1 个参数,参数类型与
onMsg(MessageData msg)中的msg一致。
示例:
void onCreateMenu(MessageData msg) {
if (msg.IsGroup) {
addMenuItem("仅群", "showGroup");
}
}
void showGroup(MessageData msg) {
toast("提示在" + msg.GroupUin);
}
6.4 当前窗口信息
| 方法 | 说明 |
|---|---|
int getChatType() |
获取当前聊天类型,1 为私聊,2 为群聊 |
String getCurrentGroupUin() |
获取当前群号;若当前为私聊则返回空 |
String getCurrentFriendUin() |
获取当前好友 QQ;若当前为群聊则返回空 |
7. 完整示例
下面是一个基础的 QStory Java 脚本示例:
public void onMsg(Object msg) {
String text = msg.MessageContent;
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("", qq, "我已经私聊你咯");
}
if (
msg.IsSend
&& msg.MessageContent.matches("禁言 ?@[\\s\\S]+[0-9]+(天|分|时|小时|分钟|秒)")
&& msg.mAtList.size() >= 1
) {
int banTime = parseTimeByMessage(msg);
if (banTime >= 60 * 60 * 24 * 30 + 1) {
sendMsg(msg.GroupUin, "", "请控制在30天以内");
return;
}
for (String atUin : msg.mAtList) {
forbidden(msg.GroupUin, atUin, banTime);
}
}
}
public int parseTimeByMessage(Object msg) {
int timeStartIndex = msg.MessageContent.lastIndexOf(" ");
String date = msg.MessageContent.substring(timeStartIndex + 1).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 groupUin, String uin, int chatType) {
if (getString("加载提示", "开关") == null) {
putString("加载提示", "开关", "关");
toast("已关闭加载提示");
} else {
putString("加载提示", "开关", null);
toast("已开启加载提示");
}
}
if(
getString("加载提示","开关") ==null){
toast("发送\"菜单\" 查看使用说明");
}
8. 阅读建议
如果你是开发者,建议按以下顺序阅读:
- 先看“开发前必读”和“脚本目录结构”。
- 再看“全局变量”和“回调方法”。
- 根据需求查阅对应 API 分类。
- 最后对照“菜单与交互”和“完整示例”开始编写脚本。
- 查看本地脚本创建示例脚本,在线脚本,搜索示例类脚本,可能会有想要的答案,
如果你是 AI 助手,建议优先提取以下信息:
- 运行环境限制:无注解、JDK 9、支持 Java/Android 标准类库。
- 编写方式:方法定义在文件根作用域,实体对象可直接按字段访问。
- 核心入口:
onMsg、菜单回调、悬浮窗回调。 - 常用能力:发送消息、群操作、信息获取、存储、HTTP、文件读写。
评论 {{ comments.length }}
暂无评论,来抢沙发~