有 bug 或维护方面的问题,请前往相关渠道反馈

注意事项

  • 在脚本环境中,注解是不可用的,写了会直接加载失败
  • 脚本环境可以使用 Java 标准类库,以及 Android 标准类库,如 org.json.JSONObjectTextView 等(需 import)
  • 脚本中的实体对象(如 GroupInfoGroupMemberInfo)直接使用 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

全局变量

直接引用即可,无需额外导入

String
myUin
当前用户的 QQ 号
Context
context
QQ 全局上下文对象 (android.content.Context)
String
appPath
脚本运行时的相对目录
ClassLoader
loader
QQ 的类加载器
String
pluginID
当前脚本 ID

示例:

toast("当前QQ" + myUin);

回调方法

直接在文件根定义即可,无需定义在类里面

onMsg

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 - 未解析的原消息

onForbiddenEvent

void onForbiddenEvent(String GroupUin, String UserUin, String OPUin, long time)

成员被禁言时调用

参数1: 群号 参数2: 被禁言的用户QQ 参数3: 执行禁言的管理员 参数4: 禁言时间(秒)

onTroopEvent

void onTroopEvent(String GroupUin, String UserUin, int type)

发生进群和退群时调用

参数1: 群号 参数2: 用户QQ 参数3: 进群为2,退群为1

onClickFloatingWindow

void onClickFloatingWindow(int type, String uin)

在脚本悬浮窗打开时调用

参数1: 聊天类型(私聊1,群聊2) 参数2: 群号或QQ号

getMsg

String getMsg(String msg, String GroupUin/FriendUin, int type)

点击发送按钮发送消息时调用

返回修改后的文本内容 参数3: 类型(2群组,1/100私聊)

onCreateMenu

void onCreateMenu(MessageData msg)

长按消息创建菜单时调用,用于创建一次性菜单

callbackOnRawMsg

void callbackOnRawMsg(Object msg)

收到未解析的消息时调用(需自己解析)

onLoad

void onLoad()

监听脚本完成加载

onUnLoad

void onUnLoad()

监听取消加载脚本

示例:

// 监听收到消息
void onMsg(Object msg) {
    toast("消息内容:" + msg.MessageContent);
}

发送消息 API

参数1为群号,参数2为QQ号,参数3为内容。QQ号为空时发送群消息,群号为空时发送私聊消息

sendMsg

sendMsg(String GroupUin, String UserUin, String msg)

发送文本、图片或图文消息

  • 图文消息写 [PicUrl=图片本地或网络地址]
  • 艾特写 [AtQQ=QQ号]

sendPic

sendPic(String GroupUin, String UserUin, String Path)

发送单张图片

参数3为图片本地或网络地址

sendCard

sendCard(String GroupUin, String UserUin, String card)

发送 JSON 卡片代码

参数3为卡片代码

sendReply

sendReply(String GroupUin, Object msg, String msg)

发送回复消息(仅支持群聊)

参数2为回复的消息对象,参数3为显示的回复文本

sendFile

sendFile(String GroupUin, String UserUin, String Path)

发送文件

sendVoice

sendVoice(String GroupUin, String UserUin, String Path)

发送语音

sendVideo

sendVideo(String group, String userUin, String path)

发送视频

sendLike

sendLike(String UserUin, int count)

点赞

参数1为QQ号,参数2为点赞数

sendPai

sendPai(String group, String uin)

拍一拍对方

私聊戳一戳时参数1留空

replyEmoji

void replyEmoji(Object msg, String emojiId) void replyEmoji(Object target, int emojiType, String emojiId)

发送表情回应

emojitype: 原生表情为2,QQ自带表情为1

forwardMsg

void forwardMsg(String group, String userUin, Object msg)

转发消息

sendProto

void sendProto(String cmd, String jsonBody)

发送 ProtoBuf 消息(实验性方法)

群聊操作 API

setCard

setCard(String GroupUin, String UserUin, String Name)

设置群名片(仅管理员可用,尚未维护)

setTitle

setTitle(String GroupUin, String UserUin, String title)

设置头衔(仅群主可用)

revokeMsg

revokeMsg(Object msg)

撤回一条消息

仅能撤回自己或管理员撤回群员的消息

deleteMsg

deleteMsg(Object msg)

删除一条消息

forbidden

forbidden(String GroupUin, String UserUin, int time)

禁言(仅管理员可用)

  • 时间单位为秒
  • 全体禁言时不写用户账号
  • 全体禁言时间结束后会变成假全体禁言

kick

kick(String GroupUin, String UserUin, boolean isBlack)

踢出(仅管理员可用)

参数3表示是否禁止再次申请

获取信息接口

获取的数据量越多,耗时越多。直接使用 Object 指代即可,无需强转类型

getMemberName

getMemberName(String group, String uin)

获取群内成员名称

getGroupList

ArrayList<GroupInfo> getGroupList()

获取群信息列表

GroupInfo 成员
  • String GroupUin - 群号
  • String GroupName - 群名
  • String GroupOwner - 群主账号
  • String[] AdminList - 管理员列表(约30分钟刷新)
  • boolean IsOwnerOrAdmin - 我是否是群主或管理
  • Object sourceInfo - 原对象

getGroupInfo

GroupInfo getGroupInfo(String GroupUin)

获取指定群信息

getGroupMemberList

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 - 是否管理

getMemberInfo

GroupMemberInfo getMemberInfo(String group, String uin)

获取指定群指定成员信息

getForbiddenList

ArrayList<ForbiddenInfo> getForbiddenList(String GroupUin)

获取群聊被禁言的成员列表

ForbiddenInfo 成员
  • String UserUin - 成员号码
  • String UserName - 成员名字
  • long Endtime - 禁言结束时间戳

getFriendList

ArrayList<FriendInfo> getFriendList()

获取好友列表

FriendInfo 成员
  • String uin - QQ号
  • String name - QQ昵称
  • String remark - 备注
  • boolean isVip - 是否会员
  • int vipLevel - 会员等级

isFriend

boolean isFriend(String uin)

判断是否是好友

数据存储

简单数据存储方法,用于持久化脚本配置

字符串存储

putString

void putString(String ConfigName, String key, String value)

存储文本数据

getString

String getString(String ConfigName, String key) String getString(String ConfigName, String key, String def)

读取文本数据

整数存储

putInt

void putInt(String ConfigName, String key, int value)

存储整数数据

getInt

int getInt(String ConfigName, String key, int def)

读取整数数据

长整数存储

putLong

void putLong(String ConfigName, String key, long value)

存储长整数数据

getLong

long getLong(String ConfigName, String key, long def)

读取长整数数据

布尔值存储

putBoolean

void putBoolean(String ConfigName, String key, boolean value)

存储布尔数据

getBoolean

boolean getBoolean(String ConfigName, String key, boolean def)

读取布尔数据

浮点数存储

putFloat

void putFloat(String ConfigName, String key, float value)

存储浮点数据

getFloat

float getFloat(String ConfigName, String key, float def)

读取浮点数据

putDouble

void putDouble(String ConfigName, String key, double value)

存储双精度浮点数据

getDouble

double getDouble(String ConfigName, String key, double def)

读取双精度浮点数据

Skey 类方法

getGroupRKey

String getGroupRKey()

获取群聊 rkey

getFriendRKey

String getFriendRKey()

获取私聊 rkey

getSkey

String getSkey()

获取标准 skey

getRealSkey

String getRealSkey()

获取可能是真实的 skey

getPskey

String getPskey(String url)

获取 pskey

getPT4Token

String getPT4Token(String str)

获取 PT4Token

getGTK

String getGTK(String str)

获取 GTK

getBKN

long getBKN(String pskey)

获取 BKN

其他方法

UI 方法

getActivity

Activity getActivity()

获取当前 QQ 顶层活动(QQ 在后台返回 null)

toast

toast(Object content)

弹出 toast 提示

加载方法

load

load(String Path)

加载一个 java 文件

loadJar

loadJar(String JarPath)

加载 Jar 文件

eval

eval(String code)

直接热加载一段 java 代码

日志方法

error

error(Throwable throwable)

打印异常到脚本目录

log

log(Object content)

输出日志到脚本目录

HTTP 请求方法

httpGet

发送 HTTP GET 请求

String httpGet(String url)

基础 GET 请求

String httpGet(String url, Map<String, String> headers)

带请求头的 GET 请求

返回响应内容,失败返回空字符串

httpPost

发送 HTTP POST 表单请求

String httpPost(String url, Map<String, String> data)

基础 POST 表单请求

String httpPost(String url, Map<String, String> headers, Map<String, String> data)

带请求头的 POST 表单请求

返回响应内容,失败返回空字符串

httpPostJson

发送 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

httpDownload

下载文件到本地

httpDownload(String url, String path)

基础文件下载

httpDownload(String url, String path, Map<String, String> headers)

带请求头的文件下载

path 必须是脚本内相对路径,失败抛出异常

Map 参数和响应结果仅支持字符串;get 和 post 请求异常返回空字符串;文件下载失败会抛出异常

文件操作方法

readFileText

String readFileText(String path)

读取文件文本

writeTextToFile

void writeTextToFile(String path, String text)

写文本到文件(覆盖模式)

writeTextAppendToFile

void writeTextAppendToFile(String path, String text)

写文本到文件(追加模式)

readFileBytes

byte[] readFileBytes(String path)

读取文件字节

writeBytesToFile

void writeBytesToFile(String path, byte[] bytes)

写入字节到文件

写入操作会自动创建父级文件夹和目标文件

长按消息菜单

addMenuItem

String addMenuItem(String Name, String CallbackName)

在长按消息的菜单中添加选项

必须在 onCreateMenu(MessageData msg) 中使用

示例(仅群内显示的菜单):

void onCreateMenu(MessageData msg) {
    if (msg.IsGroup) {
        addMenuItem("仅群", "showGroup");
    }
}

void showGroup(MessageData msg) {
    toast("提示在" + msg.GroupUin);
}

当前窗口方法

getChatType

int getChatType()

获取当前聊天类型

返回: 1 为私聊,2 为群聊

getCurrentGroupUin

String getCurrentGroupUin()

获取当前聊天的群号

私聊时返回空

getCurrentFriendUin

String getCurrentFriendUin()

获取当前聊天的好友 QQ

群聊时返回空

API 请求与 JSON 解析示例

使用 httpGet 请求第三方 API 并使用 org.json.JSONObject 解析返回的 JSON 数据

音乐搜索 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 Java 脚本示例代码

// 接收到消息时 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("发送 \"菜单\" 查看使用说明");