mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
feat: Get cookies support domain
This commit is contained in:
@@ -7,7 +7,7 @@ import path from 'node:path'
|
|||||||
import { selfInfo } from './data'
|
import { selfInfo } from './data'
|
||||||
import { DATA_DIR } from './utils'
|
import { DATA_DIR } from './utils'
|
||||||
|
|
||||||
export const HOOK_LOG = false
|
export const HOOK_LOG = true
|
||||||
|
|
||||||
export const ALLOW_SEND_TEMP_MSG = false
|
export const ALLOW_SEND_TEMP_MSG = false
|
||||||
|
|
||||||
|
@@ -65,3 +65,33 @@ export function wrapText(str: string, maxLength: number): string {
|
|||||||
|
|
||||||
return result
|
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;
|
||||||
|
};
|
||||||
|
}
|
@@ -55,6 +55,7 @@ import { log } from '../common/utils/log'
|
|||||||
import { getConfigUtil } from '../common/config'
|
import { getConfigUtil } from '../common/config'
|
||||||
import { checkFfmpeg } from '../common/utils/video'
|
import { checkFfmpeg } from '../common/utils/video'
|
||||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||||
|
import '../ntqqapi/native/wrapper'
|
||||||
|
|
||||||
let running = false
|
let running = false
|
||||||
|
|
||||||
@@ -481,9 +482,9 @@ function onLoad() {
|
|||||||
|
|
||||||
// 创建窗口时触发
|
// 创建窗口时触发
|
||||||
function onBrowserWindowCreated(window: BrowserWindow) {
|
function onBrowserWindowCreated(window: BrowserWindow) {
|
||||||
if (selfInfo.uid) {
|
// if (selfInfo.uid) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
mainWindow = window
|
mainWindow = window
|
||||||
log('window create', window.webContents.getURL().toString())
|
log('window create', window.webContents.getURL().toString())
|
||||||
try {
|
try {
|
||||||
|
@@ -3,10 +3,19 @@ import { Group, SelfInfo, User } from '../types'
|
|||||||
import { ReceiveCmdS } from '../hook'
|
import { ReceiveCmdS } from '../hook'
|
||||||
import { selfInfo, uidMaps } from '../../common/data'
|
import { selfInfo, uidMaps } from '../../common/data'
|
||||||
import { NTQQWindowApi, NTQQWindows } from './window'
|
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
|
let userInfoCache: Record<string, User> = {} // uid: User
|
||||||
|
|
||||||
|
export interface ClientKeyData extends GeneralCallResult {
|
||||||
|
url: string;
|
||||||
|
keyIndex: string;
|
||||||
|
clientKey: string;
|
||||||
|
expireTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class NTQQUserApi {
|
export class NTQQUserApi {
|
||||||
static async setQQAvatar(filePath: string) {
|
static async setQQAvatar(filePath: string) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
@@ -28,6 +37,7 @@ export class NTQQUserApi {
|
|||||||
timeoutSecond: 2,
|
timeoutSecond: 2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserInfo(uid: string) {
|
static async getUserInfo(uid: string) {
|
||||||
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
||||||
methodName: NTQQApiMethod.USER_INFO,
|
methodName: NTQQApiMethod.USER_INFO,
|
||||||
@@ -36,6 +46,7 @@ export class NTQQUserApi {
|
|||||||
})
|
})
|
||||||
return result.profiles.get(uid)
|
return result.profiles.get(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserDetailInfo(uid: string, getLevel = false) {
|
static async getUserDetailInfo(uid: string, getLevel = false) {
|
||||||
// this.getUserInfo(uid);
|
// this.getUserInfo(uid);
|
||||||
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
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 }>(
|
static async getSkey(): Promise<string> {
|
||||||
NTQQWindows.GroupHomeWorkWindow,
|
const clientKeyData = await this.getClientKey()
|
||||||
[
|
if (clientKeyData.result !== 0) {
|
||||||
{
|
throw new Error('获取clientKey失败')
|
||||||
groupName,
|
}
|
||||||
groupCode,
|
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
|
||||||
source: 'funcbar',
|
+ '&clientkey=' + clientKeyData.clientKey
|
||||||
},
|
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
|
||||||
],
|
|
||||||
ReceiveCmdS.SKEY_UPDATE,
|
return new Promise((resolve, reject) => {
|
||||||
1,
|
const req = https.get(url, (res) => {
|
||||||
)
|
const rawCookies = res.headers['set-cookie']
|
||||||
// return await callNTQQApi<string>({
|
const cookies = {}
|
||||||
// className: NTQQApiClass.GROUP_HOME_WORK,
|
rawCookies.forEach(cookie => {
|
||||||
// methodName: NTQQApiMethod.UPDATE_SKEY,
|
// 使用正则表达式匹配 cookie 名称和值
|
||||||
// args: [
|
const regex = /([^=;]+)=([^;]*)/
|
||||||
// {
|
const match = regex.exec(cookie)
|
||||||
// domain: "qun.qq.com"
|
if (match) {
|
||||||
// }
|
cookies[match[1].trim()] = match[2].trim()
|
||||||
// ]
|
}
|
||||||
// })
|
})
|
||||||
// return await callNTQQApi<GeneralCallResult>({
|
resolve(cookies['skey'])
|
||||||
// methodName: NTQQApiMethod.GET_SKEY,
|
})
|
||||||
// args: [
|
req.on('error', e => {
|
||||||
// {
|
reject(e)
|
||||||
// "domains": [
|
});
|
||||||
// "qzone.qq.com",
|
req.end();
|
||||||
// "qlive.qq.com",
|
});
|
||||||
// "qun.qq.com",
|
|
||||||
// "gamecenter.qq.com",
|
|
||||||
// "vip.qq.com",
|
|
||||||
// "qianbao.qq.com",
|
|
||||||
// "qidian.qq.com"
|
|
||||||
// ],
|
|
||||||
// "isForNewPCQQ": false
|
|
||||||
// },
|
|
||||||
// null
|
|
||||||
// ]
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getCookie(group: Group) {
|
@cacheFunc(60 * 30 * 1000)
|
||||||
let cookies = await this.getCookieWithoutSkey()
|
static async getCookies(domain: string) {
|
||||||
let skey = ''
|
const skey = await this.getSkey();
|
||||||
for (let i = 0; i < 2; i++) {
|
const pskey= (await this.getPSkey([domain])).get(domain);
|
||||||
skey = (await this.getSkey(group.groupName, group.groupCode)).data
|
if (!pskey || !skey) {
|
||||||
skey = skey.trim()
|
throw new Error('获取Cookies失败')
|
||||||
if (skey) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await sleep(1000)
|
|
||||||
}
|
|
||||||
if (!skey) {
|
|
||||||
throw new Error('获取skey失败')
|
|
||||||
}
|
}
|
||||||
const bkn = NTQQUserApi.genBkn(skey)
|
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 }
|
return { cookies, bkn }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,4 +149,17 @@ export class NTQQUserApi {
|
|||||||
|
|
||||||
return (hash & 0x7fffffff).toString()
|
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('')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
19
src/ntqqapi/native/wrapper.ts
Normal file
19
src/ntqqapi/native/wrapper.ts
Normal 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
|
||||||
|
}
|
@@ -21,6 +21,7 @@ export enum NTQQApiClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum NTQQApiMethod {
|
export enum NTQQApiMethod {
|
||||||
|
TEST = 'NodeIKernelTipOffService/getPskey',
|
||||||
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
||||||
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
||||||
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
||||||
@@ -81,7 +82,7 @@ export enum NTQQApiMethod {
|
|||||||
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
||||||
|
|
||||||
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
||||||
GET_SKEY = 'nodeIKernelTipOffService/getPskey',
|
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
|
||||||
UPDATE_SKEY = 'updatePskey',
|
UPDATE_SKEY = 'updatePskey',
|
||||||
|
|
||||||
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import { dbUtil } from '../../../common/db'
|
import { dbUtil } from '@/common/db'
|
||||||
import { getConfigUtil } from '../../../common/config'
|
import { getConfigUtil } from '@/common/config'
|
||||||
import { log, sleep, uri2local } from '../../../common/utils'
|
import { log, sleep, uri2local } from '@/common/utils'
|
||||||
import { NTQQFileApi } from '../../../ntqqapi/api/file'
|
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import { FileElement, RawMessage, VideoElement } from '../../../ntqqapi/types'
|
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||||
import { FileCache } from '../../../common/types'
|
import { FileCache } from '@/common/types'
|
||||||
|
|
||||||
export interface GetFilePayload {
|
export interface GetFilePayload {
|
||||||
file: string // 文件名或者fileUuid
|
file: string // 文件名或者fileUuid
|
||||||
|
@@ -3,10 +3,15 @@ import { NTQQUserApi } from '../../../ntqqapi/api'
|
|||||||
import { groups } from '../../../common/data'
|
import { groups } from '../../../common/data'
|
||||||
import { ActionName } from '../types'
|
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
|
actionName = ActionName.GetCookies
|
||||||
|
|
||||||
protected async _handle() {
|
protected async _handle(payload: Payload) {
|
||||||
return NTQQUserApi.getCookie(groups[0])
|
const domain = payload.domain || 'qun.qq.com'
|
||||||
|
return NTQQUserApi.getCookies(domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { Response } from 'express'
|
import { Response } from 'express'
|
||||||
import { OB11Response } from '../action/OB11Response'
|
import { OB11Response } from '../action/OB11Response'
|
||||||
import { HttpServerBase } from '../../common/server/http'
|
import { HttpServerBase } from '@/common/server/http'
|
||||||
import { actionHandlers, actionMap } from '../action'
|
import { actionHandlers, actionMap } from '../action'
|
||||||
import { getConfigUtil } from '../../common/config'
|
import { getConfigUtil } from '@/common/config'
|
||||||
import { postOB11Event } from './postOB11Event'
|
import { postOB11Event } from './postOB11Event'
|
||||||
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
|
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
|
||||||
import { selfInfo } from '../../common/data'
|
import { selfInfo } from '@/common/data'
|
||||||
|
|
||||||
class OB11HTTPServer extends HttpServerBase {
|
class OB11HTTPServer extends HttpServerBase {
|
||||||
name = 'OneBot V11 server'
|
name = 'LLOneBot server'
|
||||||
|
|
||||||
handleFailed(res: Response, payload: any, e: any) {
|
handleFailed(res: Response, payload: any, e: any) {
|
||||||
res.send(OB11Response.error(e.stack.toString(), 200))
|
res.send(OB11Response.error(e.stack.toString(), 200))
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/common/*": [
|
"@/common/*": [
|
||||||
|
Reference in New Issue
Block a user