Skip to content
This repository has been archived by the owner on Mar 22, 2024. It is now read-only.

Commit

Permalink
modify:changelog.md
Browse files Browse the repository at this point in the history
  • Loading branch information
nk-ava committed Dec 18, 2023
1 parent d000d2a commit 9ae0f7f
Show file tree
Hide file tree
Showing 13 changed files with 699 additions and 275 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
# v1.0.13-SNAPSHOT
# v1.0.13

***

#### 2023.12.18

* feat: 实现网页端登入
* feat: 支持读取消息,触发`message`事件
* future: 支持更多事件和发送消息

#### 2023.12.16

* feat: 获取历史聊天内容api

#### 2023.12.15

* feat: 支持扫码登入自动获取mys_ck
Expand Down
130 changes: 18 additions & 112 deletions lib/bot.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as log4js from "log4js";
import crypto from "crypto";
import axios, {AxiosResponse} from "axios";
import {Encode, fetchQrCode, getHeaders, lock, md5Stream, TMP_PATH} from "./common"
import axios from "axios";
import {Encode, getMysCk, lock, md5Stream, TMP_PATH} from "./common"
import EventEmitter from "node:events";
import {verifyPKCS1v15} from "./verify";
import {
Expand All @@ -24,7 +24,6 @@ import {Perm, Villa, VillaInfo} from "./villa";
import {Readable} from "node:stream";
import {WsClient} from "./ws";
import {HttpClient} from "./http";
import {UClient} from "./uClient";

const pkg = require("../package.json")

Expand Down Expand Up @@ -85,7 +84,7 @@ export interface Config {
pub_key: string

/** logger配置,默认info */
level?: LogLevel
log_level?: LogLevel

/** 启动的端口号,默认8081 */
port?: number
Expand All @@ -98,9 +97,6 @@ export interface Config {
/** 测试别野id,如果机器人未上线,则需要填入测试别野id,否则无法使用ws */
villa_id?: number

/** 是否用户登入 */
user_login?: boolean

/**
* 米游社上传图片需要ck,因为不是调用的官方开发api,后续补上官方开发api
* 优先使用官方接口进行图片上传,此上传接口仅在官方接口不可用时自动调用
Expand All @@ -124,7 +120,6 @@ export class Bot extends EventEmitter {
private readonly enSecret: string
private readonly jwkKey: crypto.JsonWebKey | undefined
private [INTERVAL]!: NodeJS.Timeout
private uClient!: UClient
private statistics = {
start_time: Date.now(),
send_msg_cnt: 0,
Expand All @@ -138,17 +133,15 @@ export class Bot extends EventEmitter {
readonly vl = new Map<number, VillaInfo>()
private client: HttpClient | WsClient | undefined;
private keepAlive: boolean
private keepUseAlive: boolean

public handler: Map<string, Function>

constructor(props: Config) {
super();
this.config = {
level: 'info',
log_level: 'info',
is_verify: true,
villa_id: 0,
user_login: false,
...props
}
if (!this.config?.pub_key?.length) throw new RobotRunTimeError(-1, '未配置公钥,请配置后重试')
Expand All @@ -161,11 +154,10 @@ export class Bot extends EventEmitter {
if (!this.config.ws) this.jwkKey = this.pubKey.export({format: "jwk"})
this.enSecret = this.encryptSecret()
this.logger = log4js.getLogger(`[BOT_ID:${this.config.bot_id}]`)
this.logger.level = this.config.level as LogLevel
this.logger.level = this.config.log_level as LogLevel
this.keepAlive = true
this.keepUseAlive = true
this.printPkgInfo()
if (this.config.mys_ck === "" || this.config.user_login) this.getMysCk((ck: string) => {
if (this.config.mys_ck === "") getMysCk.call(this, (ck: string) => {
this.config.mys_ck = ck
fs.writeFile(`${this.config.data_dir}/cookie`, ck, () => {})
this.run().then(() => this.emit("online"))
Expand All @@ -185,29 +177,28 @@ export class Bot extends EventEmitter {
return this.statistics
}

get interval() {
return this[INTERVAL]
}

set interval(i: NodeJS.Timeout) {
this[INTERVAL] = i
}

private run() {
return new Promise(resolve => {
const runs = () => {
if (this.config.ws) {
this.newWsClient(resolve).then()
} else {
this.client = new HttpClient(this, this.config, resolve)
}
if (this.config.ws) {
this.newWsClient(resolve).then()
} else {
this.client = new HttpClient(this, this.config, resolve)
}
if (this.config.user_login) {
this.newUClient(runs).then()
} else runs()
})
}

setKeepAlive(k: boolean) {
this.keepAlive = k
}

setUseAlive(s: boolean) {
this.keepUseAlive = s
}

private async newWsClient(cb: Function) {
try {
this.client = await WsClient.new(this, cb)
Expand All @@ -227,87 +218,6 @@ export class Bot extends EventEmitter {
}
}

private async newUClient(cb: Function) {
try {
this.uClient = await UClient.new(this, cb)
this.uClient.on("close", async (code, reason) => {
this.uClient.destroy()
if (!this.keepUseAlive) return
this.logger.error(`uclient连接已断开,reason ${reason.toString() || 'unknown'}(${code}),5秒后将自动重连...`)
setTimeout(async () => {
await this.newUClient(cb)
}, 5000)
})
} catch (err) {
if ((err as Error).message.includes("not login")) {
this.logger.error("mys_ck已失效,请重新删除cookie后扫码登入")
fs.unlinkSync(`${this.config.data_dir}/cookie`)
return
}
this.logger.error(`${(err as Error).message || "uclient建立连接失败"}, 5秒后将自动重连...`)
setTimeout(async () => {
await this.newUClient(cb)
}, 5000)
}
}

private getMysCk(cb: Function) {
if (!fs.existsSync(`${this.config.data_dir}/cookie`)) fs.writeFileSync(`${this.config.data_dir}/cookie`, "")
let ck: string = fs.readFileSync(`${this.config.data_dir}/cookie`, "utf-8")
if (ck && ck !== "") {
cb(ck)
return
}
const handler = async (data: Buffer) => {
clearInterval(this[INTERVAL])
this._QrCodeLogin().then()
}
process.stdin.on("data", handler)
this.on("qrLogin.success", ck => {
this.logger.info("二维码扫码登入成功")
process.stdin.off("data", handler)
cb(ck)
})
this.on("qrLogin.error", e => {
this.logger.error("登入失败:reason " + e)
})
this._QrCodeLogin().then()
}

private async _QrCodeLogin() {
const {img, ticket} = await fetchQrCode.call(this);
console.log("请用米游社扫描二维码,回车刷新二维码")
console.log(`二维码已保存到${img}`)
this[INTERVAL] = setInterval(async () => {
this.logger.debug('请求二维码状态...')
const res: AxiosResponse = await axios.post("https://passport-api.miyoushe.com/account/ma-cn-passport/web/queryQRLoginStatus?ticket=" + ticket, {}, {
headers: getHeaders()
})
let status = res?.data
if (!status) return
if (status.message !== 'OK') {
this.emit("qrLogin.error", status?.message || "unknown")
clearInterval(this[INTERVAL])
return
}
status = status?.data?.status
if (!status) return
if (status === 'Confirmed') {
const set_cookie = res.headers["set-cookie"]
if (!set_cookie) {
this.emit("qrLogin.error", "没有获取到cookie, 请刷新重试")
clearInterval(this[INTERVAL])
return
}
let cookie = ""
for (let ck of set_cookie) cookie += ck.split("; ")[0] + "; "
if (cookie === "") this.emit("qrLogin.error", "获取到的cookie为空,请刷新二维码重新获取")
else this.emit("qrLogin.success", cookie)
clearInterval(this[INTERVAL])
}
}, 1000)
}

/** 输出包信息 */
private printPkgInfo() {
this.logger.mark("---------------")
Expand Down Expand Up @@ -488,10 +398,6 @@ export class Bot extends EventEmitter {
/** ws退出登入,只有回调是ws才有用 */
async logout() {
if (this.client instanceof WsClient) {
if (this.config.user_login) {
this.keepUseAlive = false
this.uClient.close()
}
if (!(await (this.client as WsClient).doPLogout())) {
this.logger.warn("本地将直接关闭连接...")
this.keepAlive = false
Expand Down
73 changes: 71 additions & 2 deletions lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import qr, {Bitmap} from "qr-image"
import {promisify} from "util";
import axios, {AxiosResponse} from "axios";
import fs from "node:fs";
import {UClient} from "./uClient";

export const UintSize = 32 << (~0 >>> 63)
export const _W = UintSize - 1
Expand Down Expand Up @@ -237,7 +238,7 @@ export function localIP(): string | undefined {
}

/** 获取二维码 */
export async function fetchQrCode(this: Bot) {
export async function fetchQrCode(this: Bot | UClient) {
let res = await axios.post("https://passport-api.miyoushe.com/account/ma-cn-passport/web/createQRLogin", {}, {
headers: getHeaders()
})
Expand All @@ -252,7 +253,7 @@ export async function fetchQrCode(this: Bot) {
size: 1,
customize: logQrcode
})
const f = `./data/${this.config.bot_id}/mysQr.png`
const f = `${this.config.data_dir}/mysQr.png`
await promisify(stream.pipeline)(io, fs.createWriteStream(f));
return {img: f, ticket: info.ticket}
}
Expand Down Expand Up @@ -299,6 +300,74 @@ export function getHeaders() {
}
}

/** 获取米游社cookie */
export function getMysCk(this: any, cb: Function) {
if (fs.existsSync(`${this.config.data_dir}/cookie`)) {
const ck = fs.readFileSync(`${this.config.data_dir}/cookie`, "utf-8")
if (ck && ck !== "") {
cb(ck)
return
}
}
const handler = async (data: Buffer) => {
clearInterval(this.interval)
_QrCodeLogin.call(this).then()
}
process.stdin.on("data", handler)
this.on("qrLogin.success", (ck: any) => {
this.logger.info("二维码扫码登入成功")
process.stdin.off("data", handler)
cb(ck)
})
this.on("qrLogin.error", (e: any) => {
this.logger.error("登入失败:reason " + e)
})
_QrCodeLogin.call(this).then()
}

async function _QrCodeLogin(this: Bot | UClient) {
const {img, ticket} = await fetchQrCode.call(this);
console.log("请用米游社扫描二维码,回车刷新二维码")
console.log(`二维码已保存到${img}`)
this.interval = setInterval(async () => {
this.logger.debug('请求二维码状态...')
const res: AxiosResponse = await axios.post("https://passport-api.miyoushe.com/account/ma-cn-passport/web/queryQRLoginStatus?ticket=" + ticket, {}, {
headers: getHeaders()
})
let status = res?.data
if (!status) return
if (status.message !== 'OK') {
this.emit("qrLogin.error", status?.message || "unknown")
clearInterval(this.interval)
return
}
status = status?.data?.status
if (!status) return
if (status === 'Confirmed') {
const set_cookie = res.headers["set-cookie"]
if (!set_cookie) {
this.emit("qrLogin.error", "没有获取到cookie, 请刷新重试")
clearInterval(this.interval)
return
}
let cookie = ""
for (let ck of set_cookie) cookie += ck.split("; ")[0] + "; "
if (cookie === "") this.emit("qrLogin.error", "获取到的cookie为空,请刷新二维码重新获取")
else this.emit("qrLogin.success", cookie)
clearInterval(this.interval)
}
}, 1000)
}

/** clientUniqueId */
export function ZO(Un = 0) {
let e = ((4294967295 & Date.now()) >>> 0).toString(2)
, t = Math.floor(Math.random() * (Math.pow(2, 20) - 1))
, n = e + Un.toString(2).padStart(11, "0") + t.toString(2).padStart(20, "0");
return Un = 2047 & ++Un,
parseInt(n, 2)
}

function shouldEscape(s: string): boolean {
return /[^a-zA-Z0-9\-_\.~]/.test(s)
}
Expand Down
46 changes: 46 additions & 0 deletions lib/core/deivce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function genDeviceId() {
let e = Sr();
if (e = "".concat(e.replace(/-/g, ""), "a"),
e = function (r) {
let i = "0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZa0".split("")
, o = i.length + 1
, s = +r
, a = [];
do {
let c = s % o;
s = (s - c) / o,
a.unshift(i[c])
} while (s);
return a.join("")
}(parseInt(e, 16)),
e.length > 22 && (e = e.slice(0, 22)),
e.length < 22)
for (let t = 22 - e.length, n = 0; n < t; n++)
e += "0";
return e
}

function Sr() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (e) {
const t = 16 * Math.random() | 0;
return (e === "x" ? t : 3 & t | 8).toString(16)
})
}

export interface Device {
deviceId: string
model: string
platform: string
timestamp: number
version: string
}

export function genDeviceConfig(): Device {
return {
deviceId: genDeviceId(),
model: "Web|Chrome|119.0.0.0",
platform: "web",
timestamp: 0,
version: "5.9.0"
}
}
2 changes: 2 additions & 0 deletions lib/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as Network from "./network"
export {genDeviceConfig} from "./deivce"
Loading

0 comments on commit 9ae0f7f

Please sign in to comment.