feat(onebot): add group share links
Retrieve group invitation links with compatible request defaults and explicit error reporting. Add a typed SDK helper and discoverable action documentation. Fixes #521
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
} from '@snowluma/protocol/services/qidian/fetch-corp-info';
|
||||
import { GetBuddyRecommendArk } from '@snowluma/protocol/oidb-services/contacts/get-buddy-recommend-ark';
|
||||
import { GetGroupRecommendArk } from '@snowluma/protocol/oidb-services/contacts/get-group-recommend-ark';
|
||||
import { GetGroupShareLink } from '@snowluma/protocol/oidb-services/contacts/get-group-share-link';
|
||||
import {
|
||||
SendTuwenArk,
|
||||
type SendTuwenArkParams,
|
||||
@@ -157,6 +158,20 @@ export class ContactsApi {
|
||||
return GetGroupRecommendArk.invoke(this.ctx, { groupId });
|
||||
}
|
||||
|
||||
getGroupShareLink(
|
||||
groupId: number,
|
||||
needShortUrl = true,
|
||||
srcId = 73,
|
||||
additionalParam = '',
|
||||
): Promise<string> {
|
||||
return GetGroupShareLink.invoke(this.ctx, {
|
||||
groupId,
|
||||
needShortUrl,
|
||||
srcId,
|
||||
additionalParam,
|
||||
});
|
||||
}
|
||||
|
||||
/** Send a custom 图文 ark card to a C2C peer or group (0xdc2_34). */
|
||||
sendTuwenArk(params: SendTuwenArkParams): Promise<void> {
|
||||
return SendTuwenArk.invoke(this.ctx, params);
|
||||
|
||||
@@ -5981,6 +5981,104 @@
|
||||
},
|
||||
"category": "群文件"
|
||||
},
|
||||
{
|
||||
"name": "get_group_share_link",
|
||||
"aliases": [],
|
||||
"summary": "获取群分享链接",
|
||||
"returns": "用于分享或邀请加群的链接,可作为群二维码内容。",
|
||||
"returnsSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "加群分享链接"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"readOnly": true,
|
||||
"params": [
|
||||
{
|
||||
"name": "group_id",
|
||||
"type": "uint",
|
||||
"required": true,
|
||||
"role": "group_id",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"desc": "群号"
|
||||
},
|
||||
{
|
||||
"name": "need_short_url",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"default": true,
|
||||
"desc": "是否获取短链接"
|
||||
},
|
||||
{
|
||||
"name": "src_id",
|
||||
"type": "int",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295
|
||||
},
|
||||
"default": 73,
|
||||
"desc": "来源标识(兼容参数)"
|
||||
},
|
||||
{
|
||||
"name": "additional_param",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": "",
|
||||
"desc": "附加参数"
|
||||
}
|
||||
],
|
||||
"invariants": [],
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"group_id": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "群号",
|
||||
"x-role": "group_id"
|
||||
},
|
||||
"need_short_url": {
|
||||
"type": "boolean",
|
||||
"description": "是否获取短链接",
|
||||
"default": true
|
||||
},
|
||||
"src_id": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295,
|
||||
"description": "来源标识(兼容参数)",
|
||||
"default": 73
|
||||
},
|
||||
"additional_param": {
|
||||
"type": "string",
|
||||
"description": "附加参数",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"group_id"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"category": "群信息"
|
||||
},
|
||||
{
|
||||
"name": "get_group_shut_list",
|
||||
"aliases": [],
|
||||
@@ -13326,7 +13424,7 @@
|
||||
},
|
||||
{
|
||||
"category": "群信息",
|
||||
"count": 6
|
||||
"count": 7
|
||||
},
|
||||
{
|
||||
"category": "群管理",
|
||||
|
||||
@@ -5984,6 +5984,104 @@ export const ACTIONS: CatalogAction[] = [
|
||||
},
|
||||
"category": "群文件"
|
||||
},
|
||||
{
|
||||
"name": "get_group_share_link",
|
||||
"aliases": [],
|
||||
"summary": "获取群分享链接",
|
||||
"returns": "用于分享或邀请加群的链接,可作为群二维码内容。",
|
||||
"returnsSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "加群分享链接"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"readOnly": true,
|
||||
"params": [
|
||||
{
|
||||
"name": "group_id",
|
||||
"type": "uint",
|
||||
"required": true,
|
||||
"role": "group_id",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"desc": "群号"
|
||||
},
|
||||
{
|
||||
"name": "need_short_url",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"default": true,
|
||||
"desc": "是否获取短链接"
|
||||
},
|
||||
{
|
||||
"name": "src_id",
|
||||
"type": "int",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295
|
||||
},
|
||||
"default": 73,
|
||||
"desc": "来源标识(兼容参数)"
|
||||
},
|
||||
{
|
||||
"name": "additional_param",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": "",
|
||||
"desc": "附加参数"
|
||||
}
|
||||
],
|
||||
"invariants": [],
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"group_id": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "群号",
|
||||
"x-role": "group_id"
|
||||
},
|
||||
"need_short_url": {
|
||||
"type": "boolean",
|
||||
"description": "是否获取短链接",
|
||||
"default": true
|
||||
},
|
||||
"src_id": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295,
|
||||
"description": "来源标识(兼容参数)",
|
||||
"default": 73
|
||||
},
|
||||
"additional_param": {
|
||||
"type": "string",
|
||||
"description": "附加参数",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"group_id"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"category": "群信息"
|
||||
},
|
||||
{
|
||||
"name": "get_group_shut_list",
|
||||
"aliases": [],
|
||||
@@ -13330,7 +13428,7 @@ export const CATEGORIES: CatalogCategory[] = [
|
||||
},
|
||||
{
|
||||
"category": "群信息",
|
||||
"count": 6
|
||||
"count": 7
|
||||
},
|
||||
{
|
||||
"category": "群管理",
|
||||
|
||||
@@ -31,6 +31,32 @@ export const groupInfoReturnsSchema = {
|
||||
};
|
||||
|
||||
export const actions = [
|
||||
groupAction({
|
||||
name: 'get_group_share_link',
|
||||
summary: '获取群分享链接',
|
||||
readOnly: true,
|
||||
params: {
|
||||
need_short_url: f.bool().default(true).describe('是否获取短链接'),
|
||||
src_id: f.int({ min: 0, max: 0xffffffff }).default(73).describe('来源标识(兼容参数)'),
|
||||
additional_param: f.string().default('').describe('附加参数'),
|
||||
},
|
||||
returns: '用于分享或邀请加群的链接,可作为群二维码内容。',
|
||||
returnsSchema: {
|
||||
type: 'object',
|
||||
properties: { url: { type: 'string', description: '加群分享链接' } },
|
||||
required: ['url'],
|
||||
},
|
||||
run: async (p, ctx) =>
|
||||
okResponse({
|
||||
url: await ctx.bridge.apis.contacts.getGroupShareLink(
|
||||
p.group_id,
|
||||
p.need_short_url,
|
||||
p.src_id,
|
||||
p.additional_param,
|
||||
),
|
||||
}),
|
||||
}),
|
||||
|
||||
defineAction({
|
||||
name: 'get_group_list',
|
||||
summary: '获取群列表',
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ApiHandler, type ApiActionContext } from '../src/api-handler';
|
||||
import { ACTION_REGISTRY } from '../src/actions';
|
||||
import { RETCODE, type JsonObject } from '../src/types';
|
||||
|
||||
function setup() {
|
||||
const getGroupShareLink = vi.fn().mockResolvedValue('https://qm.qq.com/q/example');
|
||||
const api = new ApiHandler({
|
||||
bridge: { apis: { contacts: { getGroupShareLink } } },
|
||||
} as unknown as ApiActionContext);
|
||||
return { api, getGroupShareLink };
|
||||
}
|
||||
|
||||
describe('get_group_share_link', () => {
|
||||
it('returns the requested result shape with the documented defaults', async () => {
|
||||
const { api, getGroupShareLink } = setup();
|
||||
const response = await api.handle('get_group_share_link', { group_id: 12345 });
|
||||
|
||||
expect(response).toMatchObject({
|
||||
status: 'ok',
|
||||
retcode: 0,
|
||||
data: { url: 'https://qm.qq.com/q/example' },
|
||||
});
|
||||
expect(getGroupShareLink).toHaveBeenCalledExactlyOnceWith(12345, true, 73, '');
|
||||
});
|
||||
|
||||
it('accepts numeric strings and preserves explicit false, zero, and additional parameters', async () => {
|
||||
const { api, getGroupShareLink } = setup();
|
||||
const response = await api.handle('get_group_share_link', {
|
||||
group_id: '12345',
|
||||
need_short_url: 'false',
|
||||
src_id: '0',
|
||||
additional_param: 'source=二维码&test=1',
|
||||
});
|
||||
|
||||
expect(response.status).toBe('ok');
|
||||
expect(getGroupShareLink).toHaveBeenCalledExactlyOnceWith(
|
||||
12345,
|
||||
false,
|
||||
0,
|
||||
'source=二维码&test=1',
|
||||
);
|
||||
});
|
||||
|
||||
it.each<{ field: string; params: JsonObject }>([
|
||||
{ field: 'group_id', params: {} },
|
||||
{ field: 'group_id', params: { group_id: 0 } },
|
||||
{ field: 'group_id', params: { group_id: -1 } },
|
||||
{ field: 'group_id', params: { group_id: 'invalid' } },
|
||||
{ field: 'need_short_url', params: { group_id: 12345, need_short_url: 'invalid' } },
|
||||
{ field: 'src_id', params: { group_id: 12345, src_id: -1 } },
|
||||
{ field: 'src_id', params: { group_id: 12345, src_id: 0x100000000 } },
|
||||
{ field: 'src_id', params: { group_id: 12345, src_id: 'invalid' } },
|
||||
{ field: 'additional_param', params: { group_id: 12345, additional_param: {} } },
|
||||
])('rejects invalid $field before requesting a link', async ({ field, params }) => {
|
||||
const { api, getGroupShareLink } = setup();
|
||||
const response = await api.handle('get_group_share_link', params);
|
||||
|
||||
expect(response).toMatchObject({ status: 'failed', retcode: RETCODE.BAD_REQUEST });
|
||||
expect(response.wording).toContain(field);
|
||||
expect(getGroupShareLink).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('surfaces the failure through the shared action error handler', async () => {
|
||||
const { api, getGroupShareLink } = setup();
|
||||
getGroupShareLink.mockRejectedValue(new Error('group share link unavailable'));
|
||||
|
||||
await expect(
|
||||
api.handle('get_group_share_link', { group_id: 12345 }),
|
||||
).resolves.toMatchObject({
|
||||
status: 'failed',
|
||||
retcode: RETCODE.ACTION_FAILED,
|
||||
data: null,
|
||||
wording: 'group share link unavailable',
|
||||
});
|
||||
});
|
||||
|
||||
it('publishes its complete parameter and result contract', () => {
|
||||
const action = ACTION_REGISTRY.actions.find(
|
||||
(entry) => entry.canonical === 'get_group_share_link',
|
||||
);
|
||||
expect(action).toBeDefined();
|
||||
const doc = action!.spec.describe();
|
||||
|
||||
expect(doc.readOnly).toBe(true);
|
||||
expect(doc.params).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ name: 'group_id', required: true }),
|
||||
expect.objectContaining({ name: 'need_short_url', default: true }),
|
||||
expect.objectContaining({ name: 'src_id', default: 73 }),
|
||||
expect.objectContaining({ name: 'additional_param', default: '' }),
|
||||
]),
|
||||
);
|
||||
expect(doc.returnsSchema).toMatchObject({
|
||||
properties: { url: { type: 'string' } },
|
||||
required: ['url'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { bool, pb, pb_optional, uint_64 } from '@snowluma/proton';
|
||||
|
||||
export interface GroupShareLinkRequest {
|
||||
groupCode?: pb<1, uint_64>;
|
||||
additionalParam?: pb_optional<3, string>;
|
||||
needShortUrl?: pb_optional<4, bool>;
|
||||
}
|
||||
|
||||
export interface OidbGroupShareLinkRequest {
|
||||
type?: pb<1, uint_64>;
|
||||
group?: pb<12, GroupShareLinkRequest>;
|
||||
}
|
||||
|
||||
export interface GroupShareLinkResponse {
|
||||
url?: pb<1, string>;
|
||||
}
|
||||
|
||||
export interface OidbGroupShareLinkResponse {
|
||||
group?: pb<12, GroupShareLinkResponse>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
|
||||
import type { OidbBase } from '@snowluma/proto-defs/oidb';
|
||||
import type {
|
||||
OidbGroupShareLinkRequest,
|
||||
OidbGroupShareLinkResponse,
|
||||
} from '@snowluma/proto-defs/oidb-actions/group-share-link';
|
||||
import { invokeOidb, type OidbSender } from '../../oidb-service';
|
||||
|
||||
export namespace GetGroupShareLink {
|
||||
export const command = 0x9060;
|
||||
export const subCommand = 3;
|
||||
export const uinForm = true;
|
||||
|
||||
export interface Params {
|
||||
groupId: number;
|
||||
needShortUrl: boolean;
|
||||
srcId: number;
|
||||
additionalParam: string;
|
||||
}
|
||||
|
||||
export type Deps = OidbSender;
|
||||
|
||||
export const serialize = (_ctx: Deps, p: Params): OidbGroupShareLinkRequest => ({
|
||||
type: 2n,
|
||||
group: {
|
||||
groupCode: BigInt(p.groupId),
|
||||
additionalParam: p.additionalParam,
|
||||
needShortUrl: p.needShortUrl,
|
||||
},
|
||||
});
|
||||
|
||||
export const deserialize = (_ctx: Deps, body: OidbGroupShareLinkResponse): string => {
|
||||
const url = body.group?.url;
|
||||
|
||||
if (!url?.trim()) {
|
||||
throw new Error('group share link response did not include a URL');
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const encode = (env: OidbBase<OidbGroupShareLinkRequest>): Uint8Array =>
|
||||
protobuf_encode<OidbBase<OidbGroupShareLinkRequest>>(env);
|
||||
|
||||
export const decode = (bytes: Uint8Array): OidbBase<OidbGroupShareLinkResponse> =>
|
||||
protobuf_decode<OidbBase<OidbGroupShareLinkResponse>>(bytes);
|
||||
|
||||
export const invoke = (deps: Deps, params: Params): Promise<string> =>
|
||||
invokeOidb(deps, GetGroupShareLink, params);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
||||
import { GetGroupShareLink } from '../../../src/oidb-services/contacts/get-group-share-link';
|
||||
import type { OidbSender } from '../../../src/oidb-service';
|
||||
import { env, m, s, v } from '../_pb-oracle';
|
||||
|
||||
const shortUrl = 'https://qm.qq.com/q/example';
|
||||
const params: GetGroupShareLink.Params = {
|
||||
groupId: 12345,
|
||||
needShortUrl: true,
|
||||
srcId: 73,
|
||||
additionalParam: '',
|
||||
};
|
||||
|
||||
function sender(body: number[] = m(12, s(1, shortUrl))) {
|
||||
const packet: SendPacketResult = {
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData: Buffer.from(env(0x9060, 3, body, true), 'hex'),
|
||||
};
|
||||
return {
|
||||
packet,
|
||||
sendRawPacket: vi.fn<OidbSender['sendRawPacket']>().mockResolvedValue(packet),
|
||||
};
|
||||
}
|
||||
|
||||
describe('GetGroupShareLink', () => {
|
||||
it('requests a short group link using the verified packet layout', async () => {
|
||||
const deps = sender();
|
||||
|
||||
await expect(GetGroupShareLink.invoke(deps, params)).resolves.toBe(shortUrl);
|
||||
|
||||
const body = [...v(1, 2), ...m(12, [...v(1, 12345), ...s(3, ''), ...v(4, 1)])];
|
||||
expect(deps.sendRawPacket).toHaveBeenCalledExactlyOnceWith(
|
||||
'OidbSvcTrpcTcp.0x9060_3',
|
||||
new Uint8Array(Buffer.from(env(0x9060, 3, body, true), 'hex')),
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves a false short-link flag, additional parameters, and the returned long URL', async () => {
|
||||
const url = 'https://qm.qq.com/cgi-bin/qm/qr?k=example&jump_from=webapi';
|
||||
const additionalParam = 'source=群二维码&test=1';
|
||||
const deps = sender(m(12, s(1, url)));
|
||||
|
||||
await expect(
|
||||
GetGroupShareLink.invoke(deps, { ...params, needShortUrl: false, additionalParam }),
|
||||
).resolves.toBe(url);
|
||||
|
||||
const body = [...v(1, 2), ...m(12, [...v(1, 12345), ...s(3, additionalParam), ...v(4, 0)])];
|
||||
expect(Buffer.from(deps.sendRawPacket.mock.calls[0]![1]).toString('hex')).toBe(
|
||||
env(0x9060, 3, body, true),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([0, 73, 0xffffffff])(
|
||||
'retains client-compatible behavior for source %s',
|
||||
async (srcId) => {
|
||||
const deps = sender();
|
||||
await GetGroupShareLink.invoke(deps, { ...params, srcId });
|
||||
|
||||
const body = [...v(1, 2), ...m(12, [...v(1, 12345), ...s(3, ''), ...v(4, 1)])];
|
||||
expect(Buffer.from(deps.sendRawPacket.mock.calls[0]![1]).toString('hex')).toBe(
|
||||
env(0x9060, 3, body, true),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
['missing result', []],
|
||||
['missing URL', m(12, [])],
|
||||
['empty URL', m(12, s(1, ''))],
|
||||
['blank URL', m(12, s(1, ' '))],
|
||||
] as const)('rejects a successful response with %s', async (_name, body) => {
|
||||
await expect(GetGroupShareLink.invoke(sender([...body]), params)).rejects.toThrow(
|
||||
'group share link response did not include a URL',
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves server errors even if the response includes a URL', async () => {
|
||||
const deps = sender();
|
||||
deps.packet.responseData = Buffer.from([
|
||||
...v(1, 0x9060),
|
||||
...v(2, 3),
|
||||
...v(3, 403),
|
||||
...m(4, m(12, s(1, shortUrl))),
|
||||
...s(5, 'permission denied'),
|
||||
]);
|
||||
|
||||
await expect(GetGroupShareLink.invoke(deps, params)).rejects.toMatchObject({
|
||||
name: 'OidbError',
|
||||
code: 403,
|
||||
serverMsg: 'permission denied',
|
||||
});
|
||||
});
|
||||
|
||||
it('reports a transport timeout instead of inventing a URL', async () => {
|
||||
const deps = sender();
|
||||
deps.packet.success = false;
|
||||
deps.packet.gotResponse = false;
|
||||
deps.packet.errorMessage = 'request timed out';
|
||||
|
||||
await expect(GetGroupShareLink.invoke(deps, params)).rejects.toThrow('request timed out');
|
||||
});
|
||||
|
||||
it('rejects a truncated response', async () => {
|
||||
const deps = sender();
|
||||
deps.packet.responseData = Buffer.from([0x22, 0x03, 0x62, 0xff]);
|
||||
|
||||
await expect(GetGroupShareLink.invoke(deps, params)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Add `getGroupShareLink()` and typed `get_group_share_link` support for group invitation links.
|
||||
- Breaking: require Node.js 24 or newer.
|
||||
- Add `CustomReplySegment` and `message.customReply()` for inline quote previews inside merged-forward nodes.
|
||||
- Expose optional inline content and quote metadata on received `ReplySegment` values.
|
||||
|
||||
@@ -62,6 +62,8 @@ const bot = new SnowLumaHttpClient({
|
||||
|
||||
await bot.sendPrivateMessage(10001, text('pong'));
|
||||
|
||||
const { url } = await bot.getGroupShareLink(123456);
|
||||
|
||||
await bot.sendGroupMessage(123456, [
|
||||
message.text('Hello'),
|
||||
message.at('all'),
|
||||
|
||||
@@ -153,6 +153,26 @@ export abstract class SnowLumaApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
getGroupShareLink(
|
||||
groupId: number,
|
||||
options?: RequestOptions & {
|
||||
needShortUrl?: boolean;
|
||||
srcId?: number;
|
||||
additionalParam?: string;
|
||||
},
|
||||
) {
|
||||
return this.call(
|
||||
'get_group_share_link',
|
||||
{
|
||||
group_id: groupId,
|
||||
need_short_url: options?.needShortUrl,
|
||||
src_id: options?.srcId,
|
||||
additional_param: options?.additionalParam,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
getGroupMemberInfo(
|
||||
groupId: number,
|
||||
userId: number,
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
GetGroupHonorInfoParams,
|
||||
GetGroupSystemMsgParams,
|
||||
GetGroupInfoParams,
|
||||
GetGroupShareLinkParams,
|
||||
GetGroupListParams,
|
||||
GetGroupMemberInfoParams,
|
||||
GetGroupMemberListParams,
|
||||
@@ -134,6 +135,7 @@ export interface SnowLumaActionMap {
|
||||
delete_friend: { params: DeleteFriendParams; data: EmptyData };
|
||||
get_group_list: { params: GetGroupListParams; data: JsonObject[] };
|
||||
get_group_info: { params: GetGroupInfoParams; data: JsonObject };
|
||||
get_group_share_link: { params: GetGroupShareLinkParams; data: { url: string } };
|
||||
get_group_member_list: { params: GetGroupMemberListParams; data: JsonObject[] };
|
||||
get_group_member_info: { params: GetGroupMemberInfoParams; data: JsonObject };
|
||||
get_group_honor_info: { params: GetGroupHonorInfoParams; data: JsonValue };
|
||||
|
||||
@@ -52,6 +52,12 @@ export interface GetGroupInfoParams extends GroupIdParams {
|
||||
no_cache?: boolean;
|
||||
}
|
||||
|
||||
export interface GetGroupShareLinkParams extends GroupIdParams {
|
||||
need_short_url?: boolean;
|
||||
src_id?: number;
|
||||
additional_param?: string;
|
||||
}
|
||||
|
||||
export interface GetGroupMemberListParams extends GroupIdParams {
|
||||
no_cache?: boolean;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,41 @@ import {
|
||||
} from '../src';
|
||||
|
||||
describe('SnowLumaHttpClient', () => {
|
||||
it('requests group share links with default and explicit options', async () => {
|
||||
const url = 'https://qm.qq.com/q/example';
|
||||
const fetchImpl = vi
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
async () =>
|
||||
new Response(JSON.stringify({ status: 'ok', retcode: 0, data: { url } })),
|
||||
);
|
||||
const client = new SnowLumaHttpClient({ fetch: fetchImpl });
|
||||
|
||||
await expect(client.getGroupShareLink(12345)).resolves.toEqual({ url });
|
||||
await expect(
|
||||
client.getGroupShareLink(12345, {
|
||||
needShortUrl: false,
|
||||
srcId: 0,
|
||||
additionalParam: 'source=二维码',
|
||||
}),
|
||||
).resolves.toEqual({ url });
|
||||
|
||||
expect(
|
||||
fetchImpl.mock.calls.map((call) => JSON.parse((call[1] as RequestInit).body as string)),
|
||||
).toEqual([
|
||||
{ action: 'get_group_share_link', params: { group_id: 12345 } },
|
||||
{
|
||||
action: 'get_group_share_link',
|
||||
params: {
|
||||
group_id: 12345,
|
||||
need_short_url: false,
|
||||
src_id: 0,
|
||||
additional_param: 'source=二维码',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('maps extended group-info refresh options to OneBot field names', async () => {
|
||||
const fetchImpl = vi.fn().mockImplementation(
|
||||
async () =>
|
||||
|
||||
Reference in New Issue
Block a user