Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
duan602728596 committed Jan 22, 2024
2 parents 9333a6e + 1cdfed8 commit 79ae160
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 11 deletions.
3 changes: 2 additions & 1 deletion packages/main/src/channelEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const enum XiaohongshuHandleChannel {
XiaohongshuDestroy = 'xiaohongshu-destroy',
XiaohongshuChromeRemoteInit = 'xiaohongshu-chrome-remote-init',
XiaohongshuChromeRemoteCookie = 'xiaohongshu-chrome-remote-cookie',
XiaohongshuChromeRemoteSign = 'xiaohongshu-chrome-remote-sign'
XiaohongshuChromeRemoteSign = 'xiaohongshu-chrome-remote-sign',
XiaohongshuChromeRemoteRequestHtml = 'xiaohongshu-chrome-remote-request-html'
}

// Electron ipc channel
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/ipcHandle/CDPHelper.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function chromeStart(executablePath: string, port: number): Promise<Launc
return ChromeLauncher.launch({
chromePath: executablePath,
port,
chromeFlags: ['--disable-gpu', '--window-size=400,400']
chromeFlags: ['--disable-gpu', '--window-size=400,400', '--disable-web-security', '--allow-file-access-from-files']
});
}

Expand Down
61 changes: 60 additions & 1 deletion packages/main/src/ipcHandle/xiaohongshuHandle.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as path from 'node:path';
import * as process from 'node:process';
import { setTimeout } from 'node:timers';
import * as fsP from 'node:fs/promises';
import { ipcMain, BrowserWindow, type IpcMainInvokeEvent, type Cookie } from 'electron';
Expand Down Expand Up @@ -93,6 +92,44 @@ function ipcXiaohongshuHandle(): void {
client.Network.enable(),
client.Runtime.enable()
]);

// 拦截并修改响应
// https://fe-static.xhscdn.com/formula-static/xhs-pc-web/public/js/vendor-dynamic.327e555.js
await client.Network.setRequestInterception({
patterns: [
{
urlPattern: '*.js',
resourceType: 'Script',
interceptionStage: 'HeadersReceived'
}
]
});

// @ts-ignore
client.Network.requestIntercepted(async ({ interceptionId, request }: {
interceptionId: string;
request: Protocol.Network.Request;
}): Promise<void> => {
const url: string = request.url;

if (url.includes('fe-static.xhscdn.com') && url.includes('vendor-dynamic')) {
const response: Protocol.Network.GetResponseBodyForInterceptionResponse = await client!.Network.getResponseBodyForInterception({ interceptionId });
const scriptText: string = atob(response.body);
const newScriptText: string = scriptText.replace(
/function xsCommon/i, 'window._xsCommon=xsCommon;function xsCommon');

await client!.Network.continueInterceptedRequest({
interceptionId,
rawResponse: btoa(newScriptText),
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/javascript'
}
});
} else {
await client!.Network.continueInterceptedRequest({ interceptionId });
}
});
await client.Page.navigate({ url: 'https://www.xiaohongshu.com/user/profile/594099df82ec393174227f18' });
await client.Page.loadEventFired();
await waitingDomFunction(client, `
Expand Down Expand Up @@ -135,6 +172,28 @@ function ipcXiaohongshuHandle(): void {

return undefined;
});

ipcMain.handle(
XiaohongshuHandleChannel.XiaohongshuChromeRemoteRequestHtml,
async function(event: IpcMainInvokeEvent, url: string): Promise<string | undefined> {
await clientSwitch(client, 'www.xiaohongshu.com');

if (client) {
const signResult: Protocol.Runtime.EvaluateResponse = await client.Runtime.evaluate({
expression: `async function requestHtml() {
var request = await fetch('${ url }', { credentials: 'include' });
return request.text();
}
requestHtml();`,
awaitPromise: true
});

return signResult.result.value;
}

return undefined;
}
);
}

export default ipcXiaohongshuHandle;
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { ipcRenderer } from 'electron';
import { message } from 'antd';
import type { MessageInstance } from 'antd/es/message/interface';
import { XiaohongshuHandleChannel } from '@qqtools3/main/src/channelEnum';
import type { SignResult } from '@qqtools-api/xiaohongshu';
import type { SignResult, UserPostedResponse, PostedNoteItem } from '@qqtools-api/xiaohongshu';
import getXiaohongshuWorker from './xiaohongshu.worker/getXiaohongshuWorker';
import { parseHtml, type XiaohongshuInitialState, type NoteItem } from './function/parseHtml';
import type { QQProtocol, QQModals } from '../../../QQBotModals/ModalTypes';
import type { OptionsItemXiaohongshu } from '../../../../commonTypes';
import type { XHSProtocol } from './xiaohongshu.worker/messageTypes';
Expand Down Expand Up @@ -47,6 +48,30 @@ class XiaohongshuExpand {
return JSON.parse(result);
}

static async chromeDevtoolsRequestHtml(u: string): Promise<UserPostedResponse | undefined> {
const result: string | void = await ipcRenderer.invoke(XiaohongshuHandleChannel.XiaohongshuChromeRemoteRequestHtml, u);

if (result) {
const initialState: XiaohongshuInitialState | undefined = parseHtml(result);

if (initialState) {
return {
code: 0,
success: true,
data: {
cursor: '',
has_more: false,
notes: initialState.user.notes.flat().map((o: NoteItem): PostedNoteItem => ({
type: o.noteCard.type,
note_id: o.noteCard.noteId,
cover: { url: o.noteCard.cover.urlDefault }
}))
}
};
}
}
}

// 图片转base64
static imageToBase64(url: string): Promise<string> {
return new Promise((resolve: Function, reject: Function): void => {
Expand Down Expand Up @@ -108,6 +133,12 @@ class XiaohongshuExpand {
id: event.data.id,
result: await XiaohongshuExpand.imageToBase64(event.data.imageUrl)
});
} else if (event.data.type === 'requestHtml') {
this.xiaohongshuWorker?.postMessage({
type: 'requestHtml',
id: event.data.id,
result: await XiaohongshuExpand.chromeDevtoolsRequestHtml(event.data.url)
});
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export interface NoteItem {
id: string;
noteCard: {
type: 'normal' | 'video';
noteId: string;
cover: {
urlDefault: string;
urlPre: string;
};
displayTitle: string;
user: {
avatar: string;
nickName: string;
nickname: string;
userId: string;
};
};
}

export interface XiaohongshuInitialState {
user: {
notes: Array<Array<NoteItem>>;
};
}

/* 解析小红书的html并获取window.__INITIAL_STATE__的值 */
export function parseHtml(html: string): XiaohongshuInitialState | undefined {
const parseDocument: Document = new DOMParser().parseFromString(html, 'text/html');
const scripts: NodeListOf<HTMLScriptElement> = parseDocument.querySelectorAll('script');
let initialState: XiaohongshuInitialState | undefined = undefined;

for (const script of scripts) {
const scriptStr: string = script.innerHTML;

if (/^window\._{2}INITIAL_STATE_{2}\s*=\s*.+$/.test(scriptStr)) {
const str: string = scriptStr
.replace(/window\._{2}INITIAL_STATE_{2}\s*=\s*/, ''); // 剔除"="前面的字符串

// eslint-disable-next-line no-eval
initialState = eval(`(${ str })`);
break;
}
}

return initialState;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ export interface ImageToBase64Message {
result: string;
}

export type MessageObject = CloseMessage | BaseInitMessage | SignMessage | ImageToBase64Message;
// 获取html
export interface RequestHtmlMessage {
type: 'requestHtml';
id: string;
result: string;
}

export type MessageObject = CloseMessage | BaseInitMessage | SignMessage | ImageToBase64Message | RequestHtmlMessage;

// 判断各种类型
type IsMessageFunction<T extends MessageObject> = (d: MessageObject) => d is T;
Expand All @@ -50,6 +57,9 @@ export const isSignMessage: IsMessageFunction<SignMessage> = (d: MessageObject):
export const isImageToBase64Message: IsMessageFunction<ImageToBase64Message>
= (d: MessageObject): d is ImageToBase64Message => d['type'] === 'imageToBase64';

export const isRequestHtmlMessage: IsMessageFunction<RequestHtmlMessage>
= (d: MessageObject): d is RequestHtmlMessage => d['type'] === 'requestHtml';

// 组合数据
export interface MergeData {
noteId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isCloseMessage,
isSignMessage,
isImageToBase64Message,
isRequestHtmlMessage,
XHSProtocol,
type MessageObject,
type BaseInitMessage,
Expand Down Expand Up @@ -72,6 +73,22 @@ function imageToBase64(imageUrl: string): Promise<string> {
});
}

function requestHtml(): Promise<UserPostedResponse | undefined> {
const id: string = `${ Math.random() }`;

return new Promise((resolve: Function, reject: Function): void => {
function handleSignMessage(event: MessageEvent<MessageObject>): void {
if (isRequestHtmlMessage(event.data) && id === event.data.id) {
removeEventListener('message', handleSignMessage);
resolve(event.data.result);
}
}

addEventListener('message', handleSignMessage);
postMessage({ id, url: `https://www.xiaohongshu.com/user/profile/${ userId }`, type: 'requestHtml' });
});
}

/* 发送数据 */
async function QQSendGroup(item: Required<MergeData>): Promise<string> {
const sendMessageGroup: Array<string> = [
Expand Down Expand Up @@ -182,9 +199,9 @@ async function getMergeData(data: Array<PostedNoteItem>): Promise<GetMergeDataRe
/* 小红书轮询 */
async function xiaohongshuListener(): Promise<void> {
try {
const userPostedRes: UserPostedResponse = await requestUserPosted(userId, cookieString, signProtocol, port);
const userPostedRes: UserPostedResponse | undefined = await requestHtml();

if (userPostedRes.success) {
if (userPostedRes && userPostedRes.success) {
_isSendDebugMessage && (_debugTimes = 0);

const data: Array<PostedNoteItem> = userPostedRes.data.notes ?? [];
Expand Down Expand Up @@ -252,9 +269,9 @@ EndTime: ${ _endTime }`,
/* 初始化小红书 */
async function xiaohongshuInit(): Promise<void> {
try {
const userPostedRes: UserPostedResponse = await requestUserPosted(userId, cookieString, signProtocol, port);
const userPostedRes: UserPostedResponse | undefined = await requestHtml();

if (userPostedRes.success) {
if (userPostedRes && userPostedRes.success) {
const data: Array<PostedNoteItem> = userPostedRes.data.notes ?? [];

if (data.length) {
Expand Down Expand Up @@ -283,7 +300,7 @@ addEventListener('message', function(event: MessageEvent<MessageObject>): void {
try {
xiaohongshuTimer && clearTimeout(xiaohongshuTimer);
} catch { /* noop */ }
} else if (isSignMessage(event.data) || isImageToBase64Message(event.data)) {
} else if (isSignMessage(event.data) || isImageToBase64Message(event.data) || isRequestHtmlMessage(event.data)) {
/* noop */
} else {
const {
Expand Down
6 changes: 5 additions & 1 deletion packages/qqtools/src/services/xiaohongshu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ export async function requestFeed(
userId: string
): Promise<NoteFeedResponse> {
const reqPath: string = '/api/sns/web/v1/feed';
const json: { source_note_id: string } = { source_note_id: sourceNoteId };
const json: Record<string, any> = {
source_note_id: sourceNoteId,
image_formats: ['jpg', 'webp', 'avif'],
extra: { need_body_topic: 1 }
};
const headers: SignResult = signProtocol === XHSProtocol.ChromeDevtoolsProtocol
? await invokeSign(reqPath, JSON.stringify(json))
: await requestSign(port, reqPath, json);
Expand Down

0 comments on commit 79ae160

Please sign in to comment.