2025-06-21 18:21:49 +08:00

266 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { getApiInfo } from './xhs-api/api.ts';
import { XhsClient as XhsClientBase } from '@kevisual/xhs-core';
import { Mention, CommonentInfo, ResponseMession } from './xhs-type/mention.ts';
import { pick } from 'lodash-es';
import { getNote } from './modules/get-note.ts';
export type Result<T> = {
code: number; // 0: success
msg?: string;
data?: T;
success?: boolean;
};
type SignInfo = {
uri: string;
data: any;
a1: string;
web_session?: string;
};
type SignResponse = {
a1: string;
sign: {
b1: string;
b1b1: string;
['x-s']: string;
['x-t']: string;
};
[key: string]: any;
};
type SignOptions = {
signUrl?: string;
};
export const getSign = async (signInfo: SignInfo, options?: SignOptions): Promise<SignResponse> => {
const { uri, data, a1, web_session } = signInfo;
// console.log('getSign', uri, data, a1, web_session);
// let signUri = new URL(uri, 'http://light.xiongxiao.me:5006').pathname;
// signUri = '/api/sns/web/v2/user/me';
try {
let signUrl = options?.signUrl || 'http://localhost:5005/sign';
// signUrl = 'http://localhost:5005/sign';
// const urlA1 = ''http://light.xiongxiao.me:5006/a1';
// const urlA1 = 'http://localhost:5005/a1';
const signs = await fetch(signUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
uri: uri,
data,
a1,
web_session: web_session,
}),
}).then((res) => res.json());
return signs as SignResponse;
} catch (error) {
return null;
}
};
type XhsOptions = {
cookie?: string;
};
type XhsSign = {
signUrl?: string;
};
export class XhsClient extends XhsClientBase {
signConfig?: XhsSign;
constructor(opts: XhsOptions) {
super(opts as any);
}
setCookie(cookie: string) {
this.cookie = cookie;
}
getApiInfo = getApiInfo;
printResult(msg: string, data: any) {
if (msg === 'response') {
console.log('url', data.url);
console.log('status', data?.response?.status);
if (data.response) {
// console.log('data', data.response.data);
}
} else if (msg === 'request') {
const { method, url } = data || {};
const headers = pick(data?.headers || {}, ['Cookie', 'x-s', 'x-t', 'x-s-common']);
// console.log('request', { headers, method, url });
} else if (msg === 'html') {
// console.log('html', response);
}
switch (msg) {
case 'get':
// console.log('get', data);
break;
case 'sign':
// console.log('sign', data);
break;
}
}
/**
* 获取未读消息
* @returns
*/
async getUnread(): Promise<Result<UnreadCount>> {
const url = '/api/sns/web/unread_count';
const response = await this.get(url, null, { needSign: false });
return response;
}
async postRead(type: 'unread_count' | 'likes' | 'mentions' | 'unread_count' = 'mentions') {
const uri = '/api/sns/web/v1/message/read';
const data = {
type,
};
type RetrunData = {
code: number;
msg: string;
success: boolean;
};
const response = await this.post(uri, data, {
sign: this.sign.bind(this),
});
return response as Result<RetrunData>;
}
async getUserInfoFromHtml(userId: string) {
type ReturnData = {
ip_location: string;
desc: string;
imageb: string;
nickname: string;
images: string;
red_id: string;
gender: number;
};
const response = await super.getUserInfoFromHtml(userId);
return response as ReturnData;
}
/**
* 这个接口不能多用否则会出现封控406错误
* @param num
* @param cursor
* @returns
*/
async getFollowNotifications(num = 10, cursor = '') {
const url = '/api/sns/web/v1/you/connections';
type Connection = {
type: 'fllow/you';
title: string;
id: string;
track_type: string;
user: { fstatus: 'both' | 'follows' | 'fans'; red_official_verify_type: number; xsec_token: string; userid: string; nickname: string; images: string };
time: number;
score: number;
};
type ReturnData = {
message_list: Connection[];
has_more: boolean;
cursor: number;
strCursor: string;
};
const response = await this.get(url, { num, cursor }, { sign: this.sign.bind(this) });
return response as Result<ReturnData>;
}
async getComment(noteId: string, xsecToken?: string) {}
/**
*
* @uri /api/sns/web/v1/you/mentions
* @returns
*/
async getMention(num = 20): Promise<Result<ResponseMession>> {
const url = '/api/sns/web/v1/you/mentions';
const response = await this.get(
url,
{ num: num, cursor: '' },
{
sign: this.sign.bind(this),
needSign: true,
},
);
return response;
}
async sign(uri: string, data: any, config: any) {
let headers = config?.headers || {};
const cookieDist = this.getCookieMap();
const apiInfo = this.getApiInfo(uri);
if (apiInfo && !apiInfo?.needSign) {
return config || {};
}
const web_session = cookieDist['web_session'];
const a1 = cookieDist['a1'];
const res = await getSign({ uri, data, a1, web_session }, this.signConfig);
const _sign = res.sign;
this.printResult('sign', { uri, apiInfo, res });
const xs = _sign?.['x-s'];
const xt = _sign?.['x-t'];
const b1 = _sign?.['b1'];
const newA1 = _sign?.['a1'];
if (a1 !== newA1) {
this.setCookieMap({ a1: newA1 });
this.printResult('cookie change', a1);
}
if (res && xs) {
headers['x-s'] = xs;
headers['x-t'] = xt;
// headers['x-s-common'] = this.getXCommon(a1, b1, xs, xt);
config.headers = headers;
} else {
console.log('get sign error', res);
throw new Error('get sign error');
}
return config;
}
async getNoteById(noteId: string, xsecToken?: string) {
const data = {
source_note_id: noteId,
image_scenes: ['CRD_WM_WEBP'],
};
const uri = '/api/sns/web/v1/feed';
try {
const response = await this.post(uri, data, { sign: this.sign.bind(this) });
// return response['items'][0]['node_card'];
return response;
} catch (error) {
console.log(error);
}
}
/**
* 发送评论content最多为300个字符如果多余300个字符发送两次
* @param comment
* @returns
*/
async postComment(comment: { note_id: string; comment_id?: string; content: string; images_info?: any; images?: string[] }) {
const uri = '/api/sns/web/v1/comment/post';
try {
const data = {
note_id: comment.note_id,
content: comment.content,
at_users: [], //
};
if (comment.comment_id) {
data['target_comment_id'] = comment.comment_id;
}
if (comment.images_info) {
data['images_info'] = comment.images_info;
}
if (comment.images) {
data['images'] = comment.images;
}
type PostCommentResponse = {
comment: CommonentInfo;
time: number;
toast: string;
};
const response = await this.post(uri, data, { sign: this.sign.bind(this) });
return response as Result<PostCommentResponse>;
} catch (error) {
console.log(error);
}
}
getNote = getNote;
}
type UnreadCount = {
unread_count: number;
likes: number;
connections: number;
mentions: number;
};