generated from tailored/router-template
266 lines
7.3 KiB
TypeScript
266 lines
7.3 KiB
TypeScript
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;
|
||
};
|