Add group album media actions and API integration

Introduces new OneBot actions for group album media: listing, commenting, liking, and deleting. Adds supporting API methods and data structures for album media operations in NTQQWebApi and NodeIKernelAlbumService. Updates action router and index to register new actions.
This commit is contained in:
手瓜一十雪 2025-08-26 14:58:11 +08:00
parent f5052935bd
commit 75d26465f1
9 changed files with 444 additions and 6 deletions

View File

@ -12,6 +12,7 @@ import { createReadStream, readFileSync, statSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { basename } from 'node:path';
import { qunAlbumControl } from '../data/webapi';
import { createAlbumCommentRequest, createAlbumFeedPublish, createAlbumMediaFeed } from '../data/album';
export class NTQQWebApi {
context: InstanceContext;
core: NapCatCore;
@ -443,4 +444,73 @@ export class NTQQWebApi {
if (!session) throw new Error('创建群相册会话失败');
await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 16384);
}
async getAlbumMediaListByNTQQ(gc: string, albumId: string, attach_info: string = '') {
return (await this.context.session.getAlbumService().getMediaList({
qun_id: gc,
attach_info: attach_info,
seq: 0,
request_time_line: {
request_invoke_time: "0"
},
album_id: albumId,
lloc: '',
batch_id: ''
})).response;
}
async doAlbumMediaPlainCommentByNTQQ(
qunId: string,
albumId: string,
lloc: string,
content: string) {
const random_seq = Math.floor(Math.random() * 9000) + 1000;
const uin = this.core.selfInfo.uin || '10001';
//16位number数字
const client_key = Date.now() * 1000
return await this.context.session.getAlbumService().doQunComment(
random_seq, {
map_info: [],
map_bytes_info: [],
map_user_account: []
},
qunId,
2,
createAlbumMediaFeed(uin, albumId, lloc),
createAlbumCommentRequest(uin, content, client_key)
);
}
async deleteAlbumMediaByNTQQ(
qunId: string,
albumId: string,
lloc: string) {
const random_seq = Math.floor(Math.random() * 9000) + 1000;
return await this.context.session.getAlbumService().deleteMedias(
random_seq,
qunId,
albumId,
[lloc],
[]
);
}
async doAlbumMediaLikeByNTQQ(
qunId: string,
albumId: string,
lloc: string,
id: string) {
const random_seq = Math.floor(Math.random() * 9000) + 1000;
const uin = this.core.selfInfo.uin || '10001';
return await this.context.session.getAlbumService().doQunLike(
random_seq, {
map_info: [],
map_bytes_info: [],
map_user_account: []
}, {
id: id,
status: 1
},
createAlbumFeedPublish(qunId, uin, albumId, lloc)
)
}
}

221
src/core/data/album.ts Normal file
View File

@ -0,0 +1,221 @@
/**
*
*/
export interface AlbumListRequest {
qun_id: string;
attach_info: string;
seq: number;
request_time_line: {
request_invoke_time: string;
};
album_id: string;
lloc: string;
batch_id: string;
}
/**
*
* @param qunId
* @param albumId ID
* @param seq 0
* @returns
*/
export function createAlbumListRequest(
qunId: string,
albumId: string,
seq: number = 0
): AlbumListRequest {
return {
qun_id: qunId,
attach_info: "",
seq: seq,
request_time_line: {
request_invoke_time: "0"
},
album_id: albumId,
lloc: "",
batch_id: ""
};
}
/**
*
*/
export interface AlbumMediaFeed {
cell_common: {
time: string;
};
cell_user_info: {
user: {
uin: string;
};
};
cell_media: {
album_id: string;
batch_id: string;
media_items: Array<{
image: {
lloc: string;
};
}>;
};
}
/**
*
* @param uin QQ号
* @param albumId ID
* @param lloc
* @returns
*/
export function createAlbumMediaFeed(
uin: string,
albumId: string,
lloc: string
): AlbumMediaFeed {
return {
cell_common: {
time: ""
},
cell_user_info: {
user: {
uin: uin
}
},
cell_media: {
album_id: albumId,
batch_id: "",
media_items: [{
image: {
lloc: lloc
}
}]
}
};
}
/**
*
*/
export interface AlbumCommentContent {
type: number;
content: string;
who: number;
uid: string;
name: string;
url: string;
}
/**
*
*/
export interface AlbumCommentReplyContent {
client_key: number;
content: AlbumCommentContent[];
user: {
uin: string;
};
}
export enum RichMsgType {
KRICHMSGTYPEPLAINTEXT,
KRICHMSGTYPEAT,
KRICHMSGTYPEURL,
KRICHMSGTYPEMEDIA
}
/**
*
* @param uin QQ号
* @param content
* @param client_key
* @returns
*/
export function createAlbumCommentRequest(
uin: string,
content: string,
client_key: number
): AlbumCommentReplyContent {
return {
client_key: client_key,
//暂时只支持纯文本吧
content: [{
type: RichMsgType.KRICHMSGTYPEPLAINTEXT,
content: content,
who: 0,
uid: "",
name: "",
url: ""
}],
user: {
uin: uin
}
};
}
export interface AlbumFeedLikePublish {
cell_common: {
time: number;
feed_id: string;
};
cell_user_info: {
user: {
uin: string;
};
};
cell_media: {
album_id: string;
batch_id: number;
media_items: Array<{
type: number;
image: {
lloc: string;
sloc: string;
};
}>;
};
cell_qun_info: {
qun_id: string;
};
}
/**
*
* @param qunId
* @param uin QQ号
* @param albumId ID
* @param lloc
* @param sloc (lloc相同)
* @returns
*/
export function createAlbumFeedPublish(
qunId: string,
uin: string,
albumId: string,
lloc: string,
sloc?: string
): AlbumFeedLikePublish {
return {
cell_common: {
time: Date.now(),
feed_id: ""
},
cell_user_info: {
user: {
uin: uin
}
},
cell_media: {
album_id: albumId,
batch_id: 0,
media_items: [{
type: 0,
image: {
lloc: lloc,
sloc: sloc || lloc
}
}]
},
cell_qun_info: {
qun_id: qunId
}
};
}

View File

@ -1,3 +1,5 @@
import { AlbumCommentReplyContent, AlbumFeedLikePublish, AlbumListRequest, AlbumMediaFeed } from "../data/album";
export interface NodeIKernelAlbumService {
setAlbumServiceInfo(...args: unknown[]): unknown;// needs 3 arguments
@ -19,7 +21,7 @@ export interface NodeIKernelAlbumService {
trace_id: string,
is_from_cache: boolean,
request_time_line: unknown,
album_list: Array<unknown>,
album_list: Array<{ name: string, album_id: string }>,
attach_info: string,
has_more: boolean,
right: unknown,
@ -32,11 +34,19 @@ export interface NodeIKernelAlbumService {
addAlbum(...args: unknown[]): unknown;// needs 2 arguments
deleteMedias(...args: unknown[]): unknown;// needs 4 arguments
deleteMedias(seq: number, group_code: string, album_id: string, media_ids: string[], ban_ids: unknown[]): Promise<unknown>;// needs 4 arguments
modifyAlbum(...args: unknown[]): unknown;// needs 3 arguments
getMediaList(...args: unknown[]): unknown;// needs 1 arguments
getMediaList(param: AlbumListRequest): Promise<{
response: {
seq: number,
result: number,
errMs: string,//没错就是errMs不是errMsg
trace_id: string,
request_time_line: unknown,
}
}>;// needs 1 arguments
quoteToQzone(...args: unknown[]): unknown;// needs 1 arguments
@ -55,12 +65,36 @@ export interface NodeIKernelAlbumService {
getQunLikes(...args: unknown[]): unknown;// needs 4 arguments
deleteQunFeed(...args: unknown[]): unknown;// needs 1 arguments
doQunComment(...args: unknown[]): unknown;// needs 6 arguments
//seq random
//stCommonExt {"map_info":[],"map_bytes_info":[],"map_user_account":[]}
//qunId string
doQunComment(seq: number, ext: {
map_info: unknown[],
map_bytes_info: unknown[],
map_user_account: unknown[]
},
qunId: string,
commentType: number,
feed: AlbumMediaFeed,
content: AlbumCommentReplyContent,
): Promise<unknown>;// needs 6 arguments
doQunReply(...args: unknown[]): unknown;// needs 7 arguments
doQunLike(...args: unknown[]): unknown;// needs 5 arguments
doQunLike(
seq: number,
ext: {
map_info: unknown[],
map_bytes_info: unknown[],
map_user_account: unknown[]
},
param: {
//{"id":"421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3e*KqaLVYdic!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560336594^||^1","status":1}
id: string,
status: number
},
like: AlbumFeedLikePublish
): Promise<unknown>;// needs 5 arguments
getRedPoints(...args: unknown[]): unknown;// needs 3 arguments

View File

@ -0,0 +1,24 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
album_id: Type.String(),
lloc: Type.String()
});
type Payload = Static<typeof SchemaData>;
export class DelGroupAlbumMedia extends OneBotAction<Payload, unknown> {
override actionName = ActionName.DelGroupAlbumMedia;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.WebApi.deleteAlbumMediaByNTQQ(
payload.group_id,
payload.album_id,
payload.lloc
);
}
}

View File

@ -0,0 +1,26 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
album_id: Type.String(),
lloc: Type.String(),
content: Type.String(),
});
type Payload = Static<typeof SchemaData>;
export class DoGroupAlbumComment extends OneBotAction<Payload, unknown> {
override actionName = ActionName.DoGroupAlbumComment;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.WebApi.doAlbumMediaPlainCommentByNTQQ(
payload.group_id,
payload.album_id,
payload.lloc,
payload.content
);
}
}

View File

@ -0,0 +1,24 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
album_id: Type.String(),
attach_info: Type.String({ default: "" }),
});
type Payload = Static<typeof SchemaData>;
export class GetGroupAlbumMediaList extends OneBotAction<Payload, unknown> {
override actionName = ActionName.GetGroupAlbumMediaList;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.WebApi.getAlbumMediaListByNTQQ(
payload.group_id,
payload.album_id,
payload.attach_info
);
}
}

View File

@ -0,0 +1,27 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
album_id: Type.String(),
lloc: Type.String(),
id: Type.String(),//421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1
set: Type.Boolean({default: true})//true=点赞 false=取消点赞 未实现
});
type Payload = Static<typeof SchemaData>;
export class SetGroupAlbumMediaLike extends OneBotAction<Payload, unknown> {
override actionName = ActionName.SetGroupAlbumMediaLike;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ(
payload.group_id,
payload.album_id,
payload.lloc,
payload.id
);
}
}

View File

@ -126,10 +126,18 @@ import { GetCollectionList } from './extends/GetCollectionList';
import { SetGroupTodo } from './packet/SetGroupTodo';
import { GetQunAlbumList } from './extends/GetQunAlbumList';
import { UploadImageToQunAlbum } from './extends/UploadImageToQunAlbum';
import { DoGroupAlbumComment } from './extends/DoGroupAlbumComment';
import { GetGroupAlbumMediaList } from './extends/GetGroupAlbumMediaList';
import { SetGroupAlbumMediaLike } from './extends/SetGroupAlbumMediaLike';
import { DelGroupAlbumMedia } from './extends/DelGroupAlbumMedia';
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [
new DelGroupAlbumMedia(obContext, core),
new SetGroupAlbumMediaLike(obContext, core),
new DoGroupAlbumComment(obContext, core),
new GetGroupAlbumMediaList(obContext, core),
new GetQunAlbumList(obContext, core),
new UploadImageToQunAlbum(obContext, core),
new SetGroupTodo(obContext, core),

View File

@ -10,6 +10,10 @@ export interface InvalidCheckResult {
}
export const ActionName = {
DelGroupAlbumMedia: 'del_group_album_media',
SetGroupAlbumMediaLike: 'set_group_album_media_like',
DoGroupAlbumComment: 'do_group_album_comment',
GetGroupAlbumMediaList: 'get_group_album_media_list',
UploadImageToQunAlbum: 'upload_image_to_qun_album',
GetQunAlbumList: 'get_qun_album_list',
SetGroupTodo: 'set_group_todo',