SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

+579
View File
@@ -0,0 +1,579 @@
import type { ActionParams, ActionResult } from '@snowluma/sdk';
const album = { group_id: '10001', album_id: 'album' };
const mediaLike = { ...album, batch_id: '9007199254740993', lloc: 'photo' };
const mediaUpload = { ...album, album_name: 'Album', file: 'file:///fixture' };
const download = { file: 'https://example.com/file', file_id: 'cached', chunk_size: '65536' };
const fileset = { fileset_id: 'fileset' };
const flashFile = { ...fileset, file_name: 'file.txt', file_index: '0' };
const peer = { group_id: '10001', user_id: '10002', phone_number: '' };
const qzoneLike = { tid: 'post', target_uin: '10002', abstime: '1700000000' };
const qzonePublicFeed = {
cell_common: {
domain_type: 3,
union_id: { uf_key: '9007199254740993', unknown_identity: 'preserved' },
},
cell_feed_transfer: { opaque: 'fixture' },
unknown_cell: { decimal: '18446744073709551615' },
};
const qzonePublicComment = {
id: '9007199254740993',
user: { uid: 'public-reader' },
like: { id: 'comment-like-key' },
};
const qzonePublicReply = {
id: '18446744073709551615',
user: { uid: 'public-author' },
like: { id: 'reply-like-key' },
};
// The same examples are compiled against the published declarations and parsed
// by the real server action definitions. They never invoke QQ or perform I/O.
export const actionParams = {
get_friend_list: { with_profile: true },
get_friends_with_category: { with_profile: false },
get_group_admin_permissions: { group_id: '10001', user_id: '10002' },
set_group_admin_permissions: {
group_id: '10001',
user_id: '10002',
recall_member_message: false,
kick_member: true,
kick_member_per_day: '20',
},
set_group_admin_receive_verify: { group_id: '10001', user_id: '10002', enable: 'on' },
_get_friend_dress: { user_id: '10002' },
add_custom_face: { file: 'file:///fixture.png' },
cancel_group_album_media_like: mediaLike,
clean_stream_temp_file: {},
comment_qzone: {
tid: 'post',
content: 'Comment',
target_uin: '10002',
images: [],
comment_id: '0',
comment_uin: '10003',
reply_uin: '10004',
reply_name: 'Reader',
},
comment_qzone_public_feed: {
feed: qzonePublicFeed,
content: [{ type: 0, content: 'Comment', upstream_option: true }],
},
create_flash_task: {
files: ['file:///one', { file: 'file:///two', name: 'two.txt' }],
name: 'Files',
thumb_path: '',
},
del_group_album_media: { ...album, lloc: 'photo' },
delete_custom_face: { emoji_id: 'face' },
delete_flash_file: fileset,
delete_qzone_msg: { tid: 'post' },
delete_qzone_public_comment: { feed: qzonePublicFeed, comment: qzonePublicComment },
delete_qzone_public_reply: {
feed: qzonePublicFeed,
comment: qzonePublicComment,
reply: qzonePublicReply,
},
do_group_album_comment: { ...album, lloc: 'photo', content: 'Comment' },
download_file_image_stream: download,
download_file_record_stream: download,
download_file_stream: download,
download_fileset: flashFile,
fetch_custom_face_detail: { count: '48' },
fetch_ptt_text: { message_id: -42 },
forward_qzone_public_feed: { feed: qzonePublicFeed, reason: '' },
get_ptt_text: { message_id: '-42' },
get_record_text: { message_id: -42 },
get_doubt_friends_add_request: { count: '50' },
get_fileset_id: { share_code: 'share' },
get_fileset_info: fileset,
get_flash_file_list: fileset,
get_flash_file_url: flashFile,
get_group_album_list: { group_id: '10001' },
get_group_album_media_list: { ...album, attach_info: '' },
get_group_signed_list: { group_id: '10001' },
get_msg_emoji_likes: { message_id: '-42' },
get_qun_album_list: { group_id: '10001', attach_info: '' },
get_qzone_feeds: { page_num: '1', count: '10', cursor: 'cursor-from-previous-response' },
get_qzone_msg_list: { target_uin: '10002', pos: '0', num: '20' },
get_qzone_profile: { target_uin: '10002' },
get_qzone_public_capabilities: {},
get_qzone_home_feeds: { target_uin: '10001', cursor: 'home-feed-cursor' },
get_qzone_feed_detail: { tid: '1127000000f1536501000000' },
get_qzone_share_ark: { feed: qzonePublicFeed },
get_qzone_public_comments: { feed: qzonePublicFeed, cursor: 'comments-from-detail' },
get_qzone_public_feed_detail: { feed: qzonePublicFeed },
get_qzone_public_feeds: { cursor: 'public-feed-cursor' },
get_qzone_public_replies: {
feed: qzonePublicFeed,
comment: qzonePublicComment,
cursor: '',
page_size: '20',
},
get_rkey_server: {},
get_share_link: fileset,
like_qzone: qzoneLike,
list_filesets: {},
modify_custom_face: { emoji_id: 'face', desc: 'Description' },
move_custom_face_to_front: { emoji_id: 'face' },
rename_flash_file: { ...fileset, new_name: 'new.txt' },
reply_qzone_public_feed: {
feed: qzonePublicFeed,
comment: qzonePublicComment,
content: 'Reply',
reply: qzonePublicReply,
},
request_decrypt_key: { db_path: '/fixture.db' },
send_ark_share: peer,
send_flash_msg: { ...fileset, group_id: '10001', user_id: '10002' },
send_group_ark_share: { group_id: '10001' },
send_qzone_msg: {
content: 'Post',
images: [],
ugc_right: '16',
target_uins: ['10002'],
},
send_tuwen_ark: {
group_id: '10001',
user_id: '10002',
title: 'Title',
desc: 'Description',
summary: 'Summary',
preview_url: '',
jump_url: 'https://example.com/',
},
set_doubt_friends_add_request: { flag: 'u_fixture', approve: 'yes' },
set_group_album_media_like: mediaLike,
set_group_member_invite_policy: { group_id: '10001', policy: 'require_approval' },
set_group_member_permissions: {
group_id: '10001',
allow_member_upload_album: false,
allow_member_temporary_session: 'off',
allow_member_create_group: 1,
},
set_group_new_member_history_visibility: { group_id: '10001', visible: 'on' },
set_group_robot_add_option: {
group_id: '10001',
robot_member_switch: '1',
robot_member_examine: '0',
},
set_qzone_ban: { user_id: '10002', enable: false },
set_qzone_msg_right: { tid: 'post', ugc_right: '128', target_uins: ['10002'] },
set_qzone_profile: { name: 'Space', description: '' },
set_qzone_public_follow: { feed: qzonePublicFeed, enable: 'off' },
set_qzone_public_comment_like: {
feed: qzonePublicFeed,
comment: qzonePublicComment,
reply: qzonePublicReply,
enable: 'on',
},
set_qzone_public_like: { feed: qzonePublicFeed, enable: 0 },
set_qzone_public_upvote: { feed: qzonePublicFeed, enable: true },
share_group_ex: { group_id: '10001' },
share_peer: peer,
test_download_stream: { error: false },
unlike_qzone: qzoneLike,
upload_file_stream: {
stream_id: 'fixture',
chunk_data: 'YQ==',
chunk_index: '0',
total_chunks: '1',
file_size: '1',
expected_sha256: '',
is_complete: 'false',
filename: 'file.txt',
reset: false,
verify_only: 0,
file_retention: '0',
},
upload_image_to_qun_album: mediaUpload,
upload_video_to_qun_album: mediaUpload,
'.handle_quick_operation_async': { context: {}, operation: {} },
} satisfies { [K in CoveredAction]: ActionParams<K> };
// Keep this independent of SnowLumaActionMap: a missing map entry must fail the
// consumer's coverage check rather than silently remove that example.
export type CoveredAction =
| 'get_friend_list'
| 'get_friends_with_category'
| 'get_group_admin_permissions'
| 'set_group_admin_permissions'
| 'set_group_admin_receive_verify'
| '_get_friend_dress'
| 'add_custom_face'
| 'cancel_group_album_media_like'
| 'clean_stream_temp_file'
| 'comment_qzone'
| 'comment_qzone_public_feed'
| 'create_flash_task'
| 'del_group_album_media'
| 'delete_custom_face'
| 'delete_flash_file'
| 'delete_qzone_msg'
| 'delete_qzone_public_comment'
| 'delete_qzone_public_reply'
| 'do_group_album_comment'
| 'download_file_image_stream'
| 'download_file_record_stream'
| 'download_file_stream'
| 'download_fileset'
| 'fetch_custom_face_detail'
| 'fetch_ptt_text'
| 'forward_qzone_public_feed'
| 'get_ptt_text'
| 'get_record_text'
| 'get_doubt_friends_add_request'
| 'get_fileset_id'
| 'get_fileset_info'
| 'get_flash_file_list'
| 'get_flash_file_url'
| 'get_group_album_list'
| 'get_group_album_media_list'
| 'get_group_signed_list'
| 'get_msg_emoji_likes'
| 'get_qun_album_list'
| 'get_qzone_feeds'
| 'get_qzone_msg_list'
| 'get_qzone_profile'
| 'get_qzone_public_capabilities'
| 'get_qzone_home_feeds'
| 'get_qzone_feed_detail'
| 'get_qzone_share_ark'
| 'get_qzone_public_comments'
| 'get_qzone_public_feed_detail'
| 'get_qzone_public_feeds'
| 'get_qzone_public_replies'
| 'get_rkey_server'
| 'get_share_link'
| 'like_qzone'
| 'list_filesets'
| 'modify_custom_face'
| 'move_custom_face_to_front'
| 'rename_flash_file'
| 'reply_qzone_public_feed'
| 'request_decrypt_key'
| 'send_ark_share'
| 'send_flash_msg'
| 'send_group_ark_share'
| 'send_qzone_msg'
| 'send_tuwen_ark'
| 'set_doubt_friends_add_request'
| 'set_group_album_media_like'
| 'set_group_member_invite_policy'
| 'set_group_member_permissions'
| 'set_group_new_member_history_visibility'
| 'set_group_robot_add_option'
| 'set_qzone_ban'
| 'set_qzone_msg_right'
| 'set_qzone_profile'
| 'set_qzone_public_follow'
| 'set_qzone_public_comment_like'
| 'set_qzone_public_like'
| 'set_qzone_public_upvote'
| 'share_group_ex'
| 'share_peer'
| 'test_download_stream'
| 'unlike_qzone'
| 'upload_file_stream'
| 'upload_image_to_qun_album'
| 'upload_video_to_qun_album'
| '.handle_quick_operation_async';
const flashInfo = {
fileset_id: 'fileset',
file_name: 'file.txt',
orig_name: 'original.txt',
size: 42,
share_url: 'https://example.com/share',
file_id: 'file',
download_url: '',
};
export const actionResults = {
get_friend_list: [
{
user_id: 10001,
nickname: 'Friend',
remark: '',
category_id: 0,
long_nick: '',
age: 0,
is_qq_vip: false,
privilege_icons: {
open: [{ type: 1, icon_url: 'normal', deluxe_icon_url: 'deluxe' }],
closed: [],
},
},
],
get_friends_with_category: [
{
categoryId: 0,
categoryName: 'Friends',
categoryMbCount: 1,
buddyList: [{ user_id: 10001, nickname: 'Friend', remark: '', city: 'City' }],
},
],
get_group_admin_permissions: {
group_id: 10001,
user_id: 10002,
recall_member_message: null,
kick_member: true,
kick_member_per_day: 20,
receive_verify: false,
},
set_group_admin_permissions: null,
set_group_admin_receive_verify: null,
_get_friend_dress: {
target_uin: '10002',
is_svip: true,
avatar_url: '',
items: [
{
app_id: 15,
kind: 'card',
item_id: 1,
name: 'Card',
preview_url: '',
video_url: '',
price: 0,
},
],
},
add_custom_face: { emoji_id: 'face' },
cancel_group_album_media_like: {},
clean_stream_temp_file: { message: 'success', removed: 1, freed_bytes: 42, skipped_active: 1 },
comment_qzone: { comment_id: 'comment' },
comment_qzone_public_feed: { comment: qzonePublicComment },
create_flash_task: { fileset_id: 'fileset', task_id: 'fileset' },
del_group_album_media: { success: true },
delete_custom_face: null,
delete_flash_file: null,
delete_qzone_msg: null,
delete_qzone_public_comment: {},
delete_qzone_public_reply: {},
do_group_album_comment: {
id: 'comment',
user: { uin: '10002' },
content: [{ type: 0, content: 'Comment' }],
time: '1700000000',
clientKey: 'fixture',
},
download_file_image_stream: {
type: 'stream',
data_type: 'file_info',
file_name: 'image',
file_size: 1,
chunk_size: 1,
},
download_file_record_stream: {
type: 'stream',
data_type: 'file_chunk',
index: 0,
data: 'YQ==',
size: 1,
progress: 100,
base64_size: 4,
},
download_file_stream: {
type: 'response',
data_type: 'file_complete',
file_name: 'file',
total_chunks: 1,
total_bytes: 1,
message: 'Download completed',
},
download_fileset: { url: '', file_name: 'file.txt', file_size: 42 },
fetch_custom_face_detail: [{ emoji_id: 'face', resId: 'face', url: '', md5: '', desc: '' }],
fetch_ptt_text: { text: 'Speech' },
forward_qzone_public_feed: {},
get_ptt_text: { text: 'Speech' },
get_record_text: { text: 'Speech' },
get_doubt_friends_add_request: [
{
uid: 'u_fixture',
user_id: 10002,
nick: 'User',
source: '',
reason: '',
msg: '',
group_code: '',
reqTime: 0,
},
],
get_fileset_id: { fileset_id: 'fileset' },
get_fileset_info: { fileset_id: 'fileset', file_list: [flashInfo] },
get_flash_file_list: [flashInfo],
get_flash_file_url: { url: '' },
get_group_album_list: [
{
id: 'album',
name: 'Album',
picNum: 1,
createTime: 1,
desc: '',
owner: '10002',
createuin: '10002',
createnickname: 'User',
last_upload_time: 1,
cover: null,
},
],
get_group_album_media_list: {
mediaList: [
{
type: 2,
video: { id: 'video', videoTime: '1000' },
batchId: '9007199254740993',
uploadTime: '1',
},
],
nextAttachInfo: '',
},
get_group_signed_list: [{ user_id: 10002, nick: 'User', time: 1, rank: 1 }],
get_msg_emoji_likes: [
{
emoji_id: '1',
emoji_type: 1,
count: 1,
last_reaction_time: 1,
users: [{ user_id: 10002 }],
},
],
get_qun_album_list: {
album_list: [
{
album_id: 'album',
owner: '10002',
name: 'Album',
desc: '',
create_time: '1',
modify_time: '1',
last_upload_time: '1',
upload_number: '9007199254740993',
cover: {
type: 1,
image: {
name: '',
sloc: '',
lloc: '',
photoUrls: [],
defaultUrl: null,
isGif: false,
hasRaw: false,
},
},
top_flag: '0',
busi_type: 0,
status: 0,
allow_share: true,
is_subscribe: false,
bitmap: '',
is_share_album: false,
qz_album_type: 0,
cover_type: 0,
default_desc: '',
sort_type: 0,
},
],
attach_info: '',
has_more: false,
},
get_qzone_feeds: {
feeds: [{ uin: 10002, nickname: 'User', time: 1, appid: 311, key: 'post', html: '' }],
has_more: false,
},
get_qzone_msg_list: {
total: 1,
msglist: [
{
tid: 'post',
content: 'Post',
time: 1,
comment_num: 0,
is_private: false,
images: [],
},
],
},
get_qzone_profile: { name: 'Space', description: 'Description' },
get_qzone_public_capabilities: { public_qzone_entrance_switch: true, can_upvote: true },
get_qzone_home_feeds: { feeds: [qzonePublicFeed], has_more: false, next_cursor: null },
get_qzone_feed_detail: {
feed: {
cell_common: { domain_type: 1, union_id: { feed_id: '1127000000f1536501000000' } },
cell_pic: { future_image_field: true },
},
attach_info: 'comments-from-detail',
},
get_qzone_share_ark: {
ark: '{"app":"com.tencent.tuwen.lua"}',
requires_confirmation: false,
notice: null,
},
get_qzone_public_comments: {
cell_comment: { comments: [qzonePublicComment] },
attach_info: 'next-comments',
has_more: true,
},
get_qzone_public_feed_detail: {
feed: { feed: qzonePublicFeed },
attach_info: 'comments-from-detail',
has_more: true,
},
get_qzone_public_feeds: {
feeds: [qzonePublicFeed],
has_more: true,
next_cursor: 'public-feed-cursor',
unknown_page: { preserved: true },
},
get_qzone_public_replies: {
replys: [qzonePublicReply],
reply_attach_info: '',
has_more: false,
},
get_rkey_server: { expired_time: 1, name: 'SnowLuma', private_rkey: 'fixture' },
get_share_link: 'https://example.com/share',
like_qzone: null,
list_filesets: [flashInfo],
modify_custom_face: null,
move_custom_face_to_front: null,
rename_flash_file: null,
reply_qzone_public_feed: { reply: qzonePublicReply },
request_decrypt_key: { db_key: 'fixture' },
send_ark_share: { arkMsg: '{}' },
send_flash_msg: { message_id: 0 },
send_group_ark_share: '{}',
send_qzone_msg: { tid: 'post', time: 1 },
send_tuwen_ark: null,
set_doubt_friends_add_request: null,
set_group_album_media_like: { id: 'like', status: 1 },
set_group_member_invite_policy: null,
set_group_member_permissions: null,
set_group_new_member_history_visibility: null,
set_group_robot_add_option: null,
set_qzone_ban: null,
set_qzone_msg_right: { ugc_right: 128 },
set_qzone_profile: null,
set_qzone_public_follow: { ret_code: 0 },
set_qzone_public_comment_like: { like: { status: 1 } },
set_qzone_public_like: { like: { liked: true } },
set_qzone_public_upvote: { upvote: { upvoted: true } },
share_group_ex: '{}',
share_peer: { arkMsg: '{}' },
test_download_stream: {
type: 'response',
data_type: 'data_complete',
data: 'Stream transmission complete',
},
unlike_qzone: null,
upload_file_stream: {
type: 'stream',
stream_id: 'fixture',
status: 'chunk_received',
received_chunks: 1,
total_chunks: 1,
},
upload_image_to_qun_album: null,
upload_video_to_qun_album: { id: 'video' },
'.handle_quick_operation_async': null,
} satisfies { [K in CoveredAction]: ActionResult<K> };
+324
View File
@@ -0,0 +1,324 @@
import type {
ActionParams,
ActionResult,
DownloadFileStreamComplete,
JsonObject,
JsonValue,
SnowLumaActionMap,
SnowLumaHttpClient,
SnowLumaKnownAction,
SnowLumaWebSocketClient,
StreamApiResponse,
UploadFileStreamComplete,
} from '@snowluma/sdk';
import { actionParams, actionResults, type CoveredAction } from './action-contracts.js';
type AssertNever<T extends never> = T;
export type MissingActionTypes = AssertNever<Exclude<SnowLumaKnownAction, keyof SnowLumaActionMap>>;
export type UnregisteredActionTypes = AssertNever<
Exclude<keyof SnowLumaActionMap, SnowLumaKnownAction>
>;
export type UnspecifiedResults = AssertNever<
{
[K in CoveredAction]: JsonValue extends ActionResult<K> ? K : never;
}[CoveredAction]
>;
type AssertTrue<T extends true> = T;
type Equal<A, B> =
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
export type PttAliasParams = AssertTrue<
Equal<ActionParams<'get_ptt_text'>, ActionParams<'fetch_ptt_text'>>
>;
export type PttAliasResult = AssertTrue<
Equal<ActionResult<'get_record_text'>, ActionResult<'fetch_ptt_text'>>
>;
export type QuickAlias = AssertTrue<
Equal<
SnowLumaActionMap['.handle_quick_operation_async'],
SnowLumaActionMap['.handle_quick_operation']
>
>;
declare const http: SnowLumaHttpClient;
declare const ws: SnowLumaWebSocketClient;
const groupSystemMessages = await http.getGroupSystemMessages({ groupId: 10001 });
export const invitationTarget: number | undefined = groupSystemMessages[0]?.target_uin;
export const invitationOperator: number | undefined = groupSystemMessages[0]?.operator_uin;
export const invitationIdentityStatus: 'known' | 'unknown' | undefined =
groupSystemMessages[0]?.invitor_status;
const adminPermissions = await http.raw('get_group_admin_permissions', {
group_id: 10001,
user_id: 10002,
});
export const adminRecall: boolean | null = adminPermissions.recall_member_message;
export const adminKickLimit: number | null = adminPermissions.kick_member_per_day;
export const adminReceiveVerify: boolean | null = adminPermissions.receive_verify;
await http.raw('set_group_admin_permissions', {
group_id: 10001,
user_id: 10002,
recall_member_message: false,
});
await http.raw('set_group_admin_permissions', {
group_id: 10001,
user_id: 10002,
kick_member: false,
});
await http.raw('set_group_admin_permissions', {
group_id: 10001,
user_id: 10002,
kick_member: true,
kick_member_per_day: 0,
});
await http.raw('set_group_admin_receive_verify', {
group_id: 10001,
user_id: 10002,
enable: false,
});
// @ts-expect-error a patch must change a permission
http.raw('set_group_admin_permissions', { group_id: 10001, user_id: 10002 });
// @ts-expect-error enabling removal requires an explicit limit
http.raw('set_group_admin_permissions', { group_id: 10001, user_id: 10002, kick_member: true });
http.raw('set_group_admin_permissions', {
group_id: 10001,
user_id: 10002,
kick_member: false,
// @ts-expect-error a limit without enabling removal is invalid
kick_member_per_day: 20,
});
http.raw('set_group_admin_permissions', {
group_id: 10001,
user_id: 10002,
kick_member: true,
// @ts-expect-error only supported limits may be written
kick_member_per_day: 1,
});
// @ts-expect-error receive-verification enable is required
http.raw('set_group_admin_receive_verify', { group_id: 10001, user_id: 10002 });
export const uploadedFile: Promise<UploadFileStreamComplete> = http.uploadFileStream(
new Blob(['file']),
);
export const download: AsyncGenerator<Uint8Array, DownloadFileStreamComplete, void> =
ws.downloadFileStream({ file_id: 'media' }, { action: 'download_file_image_stream' });
export const rawFrames: AsyncIterableIterator<
StreamApiResponse<ActionResult<'test_download_stream'>>
> = http.requestStream('test_download_stream', {});
for await (const item of ws.stream('download_file_stream', { file: '/remote/file' })) {
if (item.type === 'stream' && item.data_type === 'file_chunk') {
const encoded: string = item.data;
void encoded;
}
}
// @ts-expect-error single-response actions use request()/call()
http.stream('get_status', {});
// @ts-expect-error uploads accept bytes rather than text chunks
http.uploadFileStream('text');
// @ts-expect-error downloads require a file or file ID
ws.downloadFileStream({});
const feeds = await http.raw('get_qzone_feeds', { page_num: '2', count: 10 });
const more: boolean = feeds.has_more;
const author: number = feeds.feeds[0].uin;
export const legacyFeedHtml: string = feeds.feeds[0].html;
export const feedContent: string | null | undefined = feeds.feeds[0].content;
export const feedPhotos: string[] | null | undefined = feeds.feeds[0].images;
export const feedLikeCount: number | null | undefined = feeds.feeds[0].like_count;
export const feedCursor: string | null | undefined = feeds.next_cursor;
if (feedCursor) {
await http.raw('get_qzone_feeds', { cursor: feedCursor, count: 10 });
}
export const legacyFeedsResult: ActionResult<'get_qzone_feeds'> = {
feeds: [{ uin: 10002, nickname: 'User', time: 1, appid: 311, key: 'post', html: '' }],
has_more: false,
};
// @ts-expect-error continuation tokens must be strings
http.raw('get_qzone_feeds', { cursor: 123 });
const qzoneReply = await http.raw('comment_qzone', actionParams.comment_qzone);
export const qzoneReplyId: string = qzoneReply.comment_id;
export const qzoneParentId: string | undefined = qzoneReply.parent_comment_id;
export const qzoneParentUin: number | undefined = qzoneReply.parent_comment_uin;
export const legacyQzoneComment: ActionParams<'comment_qzone'> = {
tid: 'post',
content: 'Comment',
images: ['https://example.com/image.png'],
};
export const legacyQzoneResult: ActionResult<'comment_qzone'> = { comment_id: '' };
const qzoneProfile = await http.raw('get_qzone_profile', actionParams.get_qzone_profile);
export const qzoneName: string = qzoneProfile.name;
export const qzoneDescription: string = qzoneProfile.description;
export const ownQzoneProfile = http.raw('get_qzone_profile', {});
export const qzoneProfileUpdate: Promise<null> = ws.raw('set_qzone_profile', {
name: 'Space',
description: '',
});
export const qzoneNameOnly: ActionParams<'set_qzone_profile'> = { name: 'Space' };
export const qzoneDescriptionOnly: ActionParams<'set_qzone_profile'> = { description: '' };
export const qzoneScalarFields: ActionParams<'set_qzone_profile'> = {
name: 123,
description: false,
};
// @ts-expect-error a space profile patch requires at least one field
export const emptyQzoneProfilePatch: ActionParams<'set_qzone_profile'> = {};
export const invalidQzoneProfilePatch: ActionParams<'set_qzone_profile'> = {
// @ts-expect-error profile fields accept string, number or boolean primitives
description: [],
};
const feedDetail = await http.raw('get_qzone_feed_detail', actionParams.get_qzone_feed_detail);
export const fullQzoneFeed: JsonObject = feedDetail.feed;
export const feedDetailCursor: string | null = feedDetail.attach_info;
export const feedDetailCard = await http.raw('get_qzone_share_ark', { feed: feedDetail.feed });
// @ts-expect-error querying a feed requires a post ID
http.raw('get_qzone_feed_detail', {});
// @ts-expect-error the post ID is hexadecimal text, not an array
http.raw('get_qzone_feed_detail', { tid: [] });
const publicPage = await http.raw('get_qzone_public_feeds', {});
export const publicCells: JsonObject[] = publicPage.feeds;
export const publicHasMore: boolean = publicPage.has_more;
export const publicCursor: string | null = publicPage.next_cursor;
if (publicPage.has_more && publicPage.next_cursor) {
await http.raw('get_qzone_public_feeds', { cursor: publicPage.next_cursor });
}
declare const publicFeed: JsonObject;
declare const publicComment: JsonObject;
export const publicReplyDefaults: ActionParams<'get_qzone_public_replies'> = {
feed: publicFeed,
comment: publicComment,
};
export const publicUpvoteDefault: ActionParams<'set_qzone_public_upvote'> = { feed: publicFeed };
export const publicLikeDefault: ActionParams<'set_qzone_public_like'> = { feed: publicFeed };
export const publicFollowDefault: ActionParams<'set_qzone_public_follow'> = { feed: publicFeed };
export const publicCommentLikeDefault: ActionParams<'set_qzone_public_comment_like'> = {
feed: publicFeed,
comment: publicComment,
};
export const publicReplyLike: ActionParams<'set_qzone_public_comment_like'> = {
feed: publicFeed,
comment: publicComment,
reply: { id: '18446744073709551615', like: { id: 'reply-like-key' } },
enable: false,
};
export const publicReplyToComment: ActionParams<'reply_qzone_public_feed'> = {
feed: publicFeed,
comment: publicComment,
content: 'Reply',
};
export const publicForwardDefault: ActionParams<'forward_qzone_public_feed'> = { feed: publicFeed };
export const publicReplyResult: JsonObject = await ws.raw(
'reply_qzone_public_feed',
actionParams.reply_qzone_public_feed,
);
// @ts-expect-error follow-up comments require attach_info as the cursor
export const publicCommentsWithoutCursor: ActionParams<'get_qzone_public_comments'> = {
feed: publicFeed,
};
export const invalidPublicContent: ActionParams<'comment_qzone_public_feed'> = {
feed: publicFeed,
// @ts-expect-error public content does not coerce numeric primitives
content: 123,
};
export const invalidPublicRichItems: ActionParams<'comment_qzone_public_feed'> = {
feed: publicFeed,
// @ts-expect-error rich-text arrays contain JSON objects, not strings
content: ['text'],
};
const speech = await ws.raw('get_record_text', { message_id: -42 });
const transcription: string = speech.text;
const flash = await http.raw('get_fileset_info', { fileset_id: 'files' });
const bytes: number = flash.file_list[0].size;
const envelope = await ws.rawResponse('get_fileset_info', actionParams.get_fileset_info);
const filesetId: string = envelope.data.fileset_id;
const album = await http.raw('get_qun_album_list', actionParams.get_qun_album_list);
const count: string = album.album_list[0].upload_number;
const cover: string | null | undefined = album.album_list[0].cover?.image?.defaultUrl?.url;
const upload = await ws.raw('upload_file_stream', { stream_id: 'fixture', verify_only: true });
if (upload.type === 'response') {
const checksum: string = upload.sha256;
const received: number = upload.received_chunks;
void [checksum, received];
} else if (upload.type === 'stream') {
const status: 'file_created' | 'chunk_received' = upload.status;
void status;
}
declare const frame: ActionResult<'download_file_stream'>;
if (frame.data_type === 'file_chunk') {
const chunk: string = frame.data;
const index: number = frame.index;
void [chunk, index];
}
// Extra JSON fields and all accepted flash-task forms remain available.
export const extra: ActionParams<'send_qzone_msg'> = { content: 'Post', vendor_option: true };
export const singlePath: ActionParams<'create_flash_task'> = { files: '/fixture' };
export const namedFile: ActionParams<'create_flash_task'> = {
files: { file: '/fixture', name: 'file' },
};
export const legacyScalars: ActionParams<'modify_custom_face'> = { emoji_id: 123, desc: false };
export const onePermission: ActionParams<'set_group_member_permissions'> = {
group_id: 10001,
allow_member_upload_album: false,
};
export const completed: ActionResult<'upload_file_stream'> = {
type: 'response',
stream_id: 'fixture',
status: 'file_complete',
received_chunks: 1,
total_chunks: 1,
file_path: '/fixture',
file_name: 'file',
file_size: 1,
sha256: 'fixture',
};
export const streamError: ActionResult<'download_file_stream'> = {
type: 'error',
data_type: 'error',
};
// @ts-expect-error publishing requires content
export const missingContent: ActionParams<'send_qzone_msg'> = {};
// @ts-expect-error flash files must be paths or named file objects
export const invalidFlashFile: ActionParams<'create_flash_task'> = { files: [123] };
// @ts-expect-error a permission patch needs at least one flag
export const missingPermission: ActionParams<'set_group_member_permissions'> = { group_id: 10001 };
export const invalidPolicy: ActionParams<'set_group_member_invite_policy'> = {
group_id: 10001,
// @ts-expect-error the policy is a closed server enum
policy: 'admin_only',
};
// @ts-expect-error uint64 album counters are returned as strings
export const invalidCount: ActionResult<'get_qun_album_list'>['album_list'][number]['upload_number'] = 1;
// @ts-expect-error voice transcription is an object with a text field
export const invalidSpeech: ActionResult<'fetch_ptt_text'> = 'Speech';
// The open action namespace and JSON escape hatch remain available.
declare const dynamicAction: string;
declare const dynamicParams: JsonObject;
const custom: Promise<JsonValue> = http.raw('consumer_extension', { custom: true });
const dynamic: Promise<JsonValue> = ws.raw(dynamicAction, dynamicParams);
export {
more,
author,
transcription,
bytes,
filesetId,
count,
cover,
custom,
dynamic,
actionResults,
};
export type NewParams = ActionParams<'send_qzone_msg'>;
export type NewResult = ActionResult<'send_qzone_msg'>;
+103
View File
@@ -0,0 +1,103 @@
import {
createEventContext,
isGroupMessageEvent,
quoteMessage,
text,
type EventHandler,
type EventMiddleware,
type EventWaitOptions,
type MediaSource,
type OneBotGroupMessageEvent,
type OneBotMessageEvent,
type SendMessageResult,
type SnowLumaEvent,
type SnowLumaEventContext,
type SnowLumaEventContextWithQuote,
type SnowLumaHttpClient,
type SnowLumaWebSocketClient,
} from '@snowluma/sdk';
import { localFile } from '@snowluma/sdk/node';
import type { EventWaitOptions as ClientWaitOptions } from '@snowluma/sdk/client';
import type { SendImageOptions } from '@snowluma/sdk/types';
import { quoteMessage as messageQuote } from '@snowluma/sdk/messages';
declare const http: SnowLumaHttpClient;
declare const ws: SnowLumaWebSocketClient;
declare const event: OneBotMessageEvent;
const source: MediaSource = await localFile('./picture.png');
const options: SendImageOptions = { image: { summary: 'picture' }, chunkSize: 16384 };
const sent: Promise<SendMessageResult> = http.sendGroupImage(123, source, options);
const sentPrivate: Promise<SendMessageResult> = ws.sendPrivateVideo(456, new Blob(['bytes']));
const file: Promise<{ file_id: string | null }> = ws.sendPrivateFile(456, new Uint8Array([1]));
ws.sendGroupFile(
123,
{ source: new Blob(['bytes']), filename: 'notes.txt' },
{ folderId: '/docs' },
);
// @ts-expect-error Strings remain server paths; opening a local file must be explicit.
http.sendPrivateImage(456, './picture.png');
// @ts-expect-error Media attributes remain checked.
http.sendGroupImage(123, source, { image: { subType: 'invalid' } });
const oldContext: SnowLumaEventContext<OneBotMessageEvent> = {
event,
client: http,
stopped: false,
stopPropagation() {},
async reply() {},
async approve() {},
async reject() {},
async quickOperation() {},
};
const oldHandler: EventHandler<OneBotMessageEvent> = async (_event, context) => {
await context.reply('unchanged');
};
oldHandler(event, oldContext);
ws.onMessage(oldHandler);
const oldMiddleware: EventMiddleware = async (_event, context, next) => {
await context.reply('unchanged');
await next();
};
oldMiddleware(event, oldContext, async () => {});
ws.use(oldMiddleware);
const context: SnowLumaEventContextWithQuote<OneBotMessageEvent> = createEventContext(event, http);
const quoted: Promise<SendMessageResult> = context.quote(text('hello'));
ws.onMessage(async (_event, ctx) => {
await ctx.quote('hello');
});
ws.onGroupMessage(async (_event, ctx) => {
await ctx.quote('hello');
});
ws.use(async (_event, ctx, next) => {
await ctx.quote('hello');
await next();
});
ws.command('ping', async (_event, ctx) => {
await ctx.quote('pong');
});
http.sendGroupMessage(123, quoteMessage(-42, 'body'));
http.sendPrivateMessage(456, messageQuote(-42, text('body')));
const waitOptions: EventWaitOptions & ClientWaitOptions = {
timeoutMs: 100,
signal: new AbortController().signal,
};
const group: Promise<OneBotGroupMessageEvent> = ws.waitForEvent(isGroupMessageEvent, waitOptions);
const groupMessage: Promise<OneBotGroupMessageEvent> = ws.waitForMessage(isGroupMessageEvent);
const message: Promise<OneBotMessageEvent> = ws.waitForMessage(
(e) => e.user_id === 456,
waitOptions,
);
const anyEvent: Promise<SnowLumaEvent> = ws.waitForEvent(waitOptions);
ws.waitForEvent(undefined, waitOptions);
ws.waitForMessage(waitOptions);
// @ts-expect-error A filter without a type guard does not promise group fields.
const wrong: Promise<OneBotGroupMessageEvent> = ws.waitForEvent(() => true);
// @ts-expect-error Wait predicates are synchronous.
ws.waitForMessage(async () => true);
export { sent, sentPrivate, file, quoted, group, groupMessage, message, anyEvent, wrong };