feat: Get cookies support domain

This commit is contained in:
linyuchen 2024-05-15 17:57:15 +08:00
parent a3fc018186
commit 2e225045e6
10 changed files with 136 additions and 73 deletions

View File

@ -7,7 +7,7 @@ import path from 'node:path'
import { selfInfo } from './data'
import { DATA_DIR } from './utils'
export const HOOK_LOG = false
export const HOOK_LOG = true
export const ALLOW_SEND_TEMP_MSG = false

View File

@ -65,3 +65,33 @@ export function wrapText(str: string, maxLength: number): string {
return result
}
/**
* key生成缓存键
* @param ttl
* @param customKey
* @returns
*/
export function cacheFunc(ttl: number, customKey: string='') {
const cache = new Map<string, { expiry: number; value: any }>();
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
const originalMethod = descriptor.value;
const className = target.constructor.name; // 获取类名
const methodName = propertyKey; // 获取方法名
descriptor.value = async function (...args: any[]){
const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`;
const cached = cache.get(cacheKey);
if (cached && cached.expiry > Date.now()) {
return cached.value;
} else {
const result = await originalMethod.apply(this, args);
cache.set(cacheKey, { value: result, expiry: Date.now() + ttl });
return result;
}
};
return descriptor;
};
}

View File

@ -55,6 +55,7 @@ import { log } from '../common/utils/log'
import { getConfigUtil } from '../common/config'
import { checkFfmpeg } from '../common/utils/video'
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
import '../ntqqapi/native/wrapper'
let running = false
@ -481,9 +482,9 @@ function onLoad() {
// 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) {
if (selfInfo.uid) {
return
}
// if (selfInfo.uid) {
// return
// }
mainWindow = window
log('window create', window.webContents.getURL().toString())
try {

View File

@ -3,10 +3,19 @@ import { Group, SelfInfo, User } from '../types'
import { ReceiveCmdS } from '../hook'
import { selfInfo, uidMaps } from '../../common/data'
import { NTQQWindowApi, NTQQWindows } from './window'
import { isQQ998, log, sleep } from '../../common/utils'
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
import { wrapperApi } from '@/ntqqapi/native/wrapper'
import * as https from 'https'
let userInfoCache: Record<string, User> = {} // uid: User
export interface ClientKeyData extends GeneralCallResult {
url: string;
keyIndex: string;
clientKey: string;
expireTime: string;
}
export class NTQQUserApi {
static async setQQAvatar(filePath: string) {
return await callNTQQApi<GeneralCallResult>({
@ -28,6 +37,7 @@ export class NTQQUserApi {
timeoutSecond: 2,
})
}
static async getUserInfo(uid: string) {
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
methodName: NTQQApiMethod.USER_INFO,
@ -36,6 +46,7 @@ export class NTQQUserApi {
})
return result.profiles.get(uid)
}
static async getUserDetailInfo(uid: string, getLevel = false) {
// this.getUserInfo(uid);
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
@ -84,64 +95,46 @@ export class NTQQUserApi {
],
})
}
static async getSkey(groupName: string, groupCode: string): Promise<{ data: string }> {
return await NTQQWindowApi.openWindow<{ data: string }>(
NTQQWindows.GroupHomeWorkWindow,
[
{
groupName,
groupCode,
source: 'funcbar',
},
],
ReceiveCmdS.SKEY_UPDATE,
1,
)
// return await callNTQQApi<string>({
// className: NTQQApiClass.GROUP_HOME_WORK,
// methodName: NTQQApiMethod.UPDATE_SKEY,
// args: [
// {
// domain: "qun.qq.com"
// }
// ]
// })
// return await callNTQQApi<GeneralCallResult>({
// methodName: NTQQApiMethod.GET_SKEY,
// args: [
// {
// "domains": [
// "qzone.qq.com",
// "qlive.qq.com",
// "qun.qq.com",
// "gamecenter.qq.com",
// "vip.qq.com",
// "qianbao.qq.com",
// "qidian.qq.com"
// ],
// "isForNewPCQQ": false
// },
// null
// ]
// })
static async getSkey(): Promise<string> {
const clientKeyData = await this.getClientKey()
if (clientKeyData.result !== 0) {
throw new Error('获取clientKey失败')
}
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
+ '&clientkey=' + clientKeyData.clientKey
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
return new Promise((resolve, reject) => {
const req = https.get(url, (res) => {
const rawCookies = res.headers['set-cookie']
const cookies = {}
rawCookies.forEach(cookie => {
// 使用正则表达式匹配 cookie 名称和值
const regex = /([^=;]+)=([^;]*)/
const match = regex.exec(cookie)
if (match) {
cookies[match[1].trim()] = match[2].trim()
}
})
resolve(cookies['skey'])
})
req.on('error', e => {
reject(e)
});
req.end();
});
}
static async getCookie(group: Group) {
let cookies = await this.getCookieWithoutSkey()
let skey = ''
for (let i = 0; i < 2; i++) {
skey = (await this.getSkey(group.groupName, group.groupCode)).data
skey = skey.trim()
if (skey) {
break
}
await sleep(1000)
}
if (!skey) {
throw new Error('获取skey失败')
@cacheFunc(60 * 30 * 1000)
static async getCookies(domain: string) {
const skey = await this.getSkey();
const pskey= (await this.getPSkey([domain])).get(domain);
if (!pskey || !skey) {
throw new Error('获取Cookies失败')
}
const bkn = NTQQUserApi.genBkn(skey)
cookies = cookies.replace('skey=;', `skey=${skey};`)
const cookies = `p_skey=${pskey}; skey=${skey}; p_uin=o${selfInfo.uin}`;
return { cookies, bkn }
}
@ -156,4 +149,17 @@ export class NTQQUserApi {
return (hash & 0x7fffffff).toString()
}
static async getPSkey(domains: string[]): Promise<Map<string, string>> {
const res = await wrapperApi.NodeIQQNTWrapperSession.getTipOffService().getPskey(domains, true)
if (res.result !== 0) {
throw new Error(`获取Pskey失败: ${res.errMsg}`)
}
return res.domainPskeyMap
}
static async getClientKey(): Promise<ClientKeyData> {
return await wrapperApi.NodeIQQNTWrapperSession.getTicketService().forceFetchClientKey('')
}
}

View File

@ -0,0 +1,19 @@
let Process = require('process')
let os = require('os')
Process.dlopenOrig = Process.dlopen
export const wrapperApi: any = {}
Process.dlopen = function(module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
let dlopenRet = this.dlopenOrig(module, filename, flags)
for (let export_name in module.exports) {
module.exports[export_name] = new Proxy(module.exports[export_name], {
construct: (target, args, _newTarget) => {
let ret = new target(...args)
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
return ret
},
})
}
return dlopenRet
}

View File

@ -21,6 +21,7 @@ export enum NTQQApiClass {
}
export enum NTQQApiMethod {
TEST = 'NodeIKernelTipOffService/getPskey',
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
@ -81,7 +82,7 @@ export enum NTQQApiMethod {
OPEN_EXTRA_WINDOW = 'openExternalWindow',
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
GET_SKEY = 'nodeIKernelTipOffService/getPskey',
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
UPDATE_SKEY = 'updatePskey',
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的

View File

@ -1,12 +1,12 @@
import BaseAction from '../BaseAction'
import fs from 'fs/promises'
import { dbUtil } from '../../../common/db'
import { getConfigUtil } from '../../../common/config'
import { log, sleep, uri2local } from '../../../common/utils'
import { NTQQFileApi } from '../../../ntqqapi/api/file'
import { dbUtil } from '@/common/db'
import { getConfigUtil } from '@/common/config'
import { log, sleep, uri2local } from '@/common/utils'
import { NTQQFileApi } from '@/ntqqapi/api'
import { ActionName } from '../types'
import { FileElement, RawMessage, VideoElement } from '../../../ntqqapi/types'
import { FileCache } from '../../../common/types'
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
import { FileCache } from '@/common/types'
export interface GetFilePayload {
file: string // 文件名或者fileUuid

View File

@ -3,10 +3,15 @@ import { NTQQUserApi } from '../../../ntqqapi/api'
import { groups } from '../../../common/data'
import { ActionName } from '../types'
export class GetCookies extends BaseAction<null, { cookies: string; bkn: string }> {
interface Payload {
domain: string
}
export class GetCookies extends BaseAction<Payload, { cookies: string; bkn: string }> {
actionName = ActionName.GetCookies
protected async _handle() {
return NTQQUserApi.getCookie(groups[0])
protected async _handle(payload: Payload) {
const domain = payload.domain || 'qun.qq.com'
return NTQQUserApi.getCookies(domain);
}
}

View File

@ -1,14 +1,14 @@
import { Response } from 'express'
import { OB11Response } from '../action/OB11Response'
import { HttpServerBase } from '../../common/server/http'
import { HttpServerBase } from '@/common/server/http'
import { actionHandlers, actionMap } from '../action'
import { getConfigUtil } from '../../common/config'
import { getConfigUtil } from '@/common/config'
import { postOB11Event } from './postOB11Event'
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
import { selfInfo } from '../../common/data'
import { selfInfo } from '@/common/data'
class OB11HTTPServer extends HttpServerBase {
name = 'OneBot V11 server'
name = 'LLOneBot server'
handleFailed(res: Response, payload: any, e: any) {
res.send(OB11Response.error(e.stack.toString(), 200))

View File

@ -7,6 +7,7 @@
"esModuleInterop": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"paths": {
"@/common/*": [