Commit f8ba79a2 authored by nanahira's avatar nanahira

first

parents
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/data
/output
/config.yaml
.git*
Dockerfile
.dockerignore
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/data
/output
/config.yaml
stages:
- build
- combine
- deploy
variables:
GIT_DEPTH: "1"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_TEST_ARM_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
CONTAINER_TEST_X86_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-x86:
stage: build
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_TEST_X86_IMAGE
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
build-arm:
stage: build
tags:
- docker-arm
script:
- TARGET_IMAGE=$CONTAINER_TEST_ARM_IMAGE
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
combine:
stage: combine
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_TEST_IMAGE
- SOURCE_IMAGE_2=$CONTAINER_TEST_ARM_IMAGE
- SOURCE_IMAGE_1=$CONTAINER_TEST_X86_IMAGE
- docker pull $SOURCE_IMAGE_1
- docker pull $SOURCE_IMAGE_2
- docker manifest create $TARGET_IMAGE --amend $SOURCE_IMAGE_1 --amend
$SOURCE_IMAGE_2
- docker manifest push $TARGET_IMAGE
deploy_latest:
stage: deploy
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_RELEASE_IMAGE
- SOURCE_IMAGE=$CONTAINER_TEST_IMAGE
- docker pull $SOURCE_IMAGE
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
- docker push $TARGET_IMAGE
only:
- master
deploy_tag:
stage: deploy
tags:
- docker
script:
- TARGET_IMAGE=$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
- SOURCE_IMAGE=$CONTAINER_TEST_IMAGE
- docker pull $SOURCE_IMAGE
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
- docker push $TARGET_IMAGE
only:
- tags
/install-npm.sh
.git*
/output
/dest
/config.yaml
.idea
.dockerignore
Dockerfile
\ No newline at end of file
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
FROM node:bullseye-slim
LABEL Author="Nanahira <nanahira@momobako.com>"
RUN apt update && apt -y install python3 build-essential && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /usr/src/app
COPY ./package*.json ./
RUN npm ci
COPY . ./
RUN npm run build
CMD ["npm", "run", "start:prod"]
This diff is collapsed.
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).
bots:
- protocol: 'ws'
endpoint: 'ws://localhost:6700'
selfId: '1111111111'
token: 'token'
routes: []
#!/bin/bash
npm install --save \
class-validator \
class-transformer \
@nestjs/swagger \
swagger-ui-express \
npm install --save-dev \
@types/express
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
This diff is collapsed.
{
"name": "onebot-lb",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@koishijs/plugin-adapter-onebot": "^4.0.0-alpha.10",
"@nestjs/common": "^8.0.0",
"@nestjs/config": "^1.0.3",
"@nestjs/core": "^8.0.0",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/swagger": "^5.1.4",
"@nestjs/websockets": "^8.1.2",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"koishi": "^4.0.0-alpha.12",
"koishi-nestjs": "^1.4.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"swagger-ui-express": "^4.1.6",
"yaml": "^1.10.2"
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.1",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.6",
"prettier": "^2.3.2",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { loadConfig } from './utility/config';
import { KoishiModule } from 'koishi-nestjs';
import { BotLoaderService } from './bot-loader/bot-loader.service';
import { RouteService } from './route/route.service';
import { OnebotGateway } from './onebot.gateway';
import { Adapter, Session } from 'koishi';
import { MessageService } from './message/message.service';
declare module 'koishi' {
interface EventMap {
dispatch: (session: Session) => void;
}
}
const originalDispatch = Adapter.prototype.dispatch;
Adapter.prototype.dispatch = function (this: Adapter, session: Session) {
if (!this.ctx.app.isActive) return;
originalDispatch.call(this, session);
this.ctx.emit(session, 'dispatch', session);
};
@Module({
imports: [
ConfigModule.forRoot({
ignoreEnvVars: true,
load: [loadConfig],
}),
KoishiModule.register({
prefix: '__never_prefix',
minSimilarity: 1,
useWs: true,
}),
],
providers: [BotLoaderService, RouteService, OnebotGateway, MessageService],
})
export class AppModule {}
import { Test, TestingModule } from '@nestjs/testing';
import { BotLoaderService } from './bot-loader.service';
describe('BotLoaderService', () => {
let service: BotLoaderService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [BotLoaderService],
}).compile();
service = module.get<BotLoaderService>(BotLoaderService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable, OnModuleInit } from '@nestjs/common';
import * as PluginOnebot from '@koishijs/plugin-adapter-onebot';
import { ConfigService } from '@nestjs/config';
import { InjectContext, PluginDef, UsePlugin } from 'koishi-nestjs';
import { BotConfig } from '@koishijs/plugin-adapter-onebot/lib/bot';
import { Context } from 'koishi';
@Injectable()
export class BotLoaderService implements OnModuleInit {
constructor(
private config: ConfigService,
@InjectContext() private ctx: Context,
) {}
@UsePlugin()
loadBots() {
const bots = this.config.get<BotConfig[]>('bots');
return PluginDef(PluginOnebot, { bots });
}
onModuleInit() {
const helpCommand = this.ctx.command('help');
if (!helpCommand) {
return;
}
const helpCtx = helpCommand.context;
helpCommand.context = helpCtx.never();
}
}
import { ApiProperty } from '@nestjs/swagger';
import { HttpException } from '@nestjs/common';
export interface BlankReturnMessage {
statusCode: number;
message: string;
success: boolean;
}
export interface ReturnMessage<T> extends BlankReturnMessage {
data?: T;
}
export class BlankReturnMessageDto implements BlankReturnMessage {
@ApiProperty({ description: '返回状态' })
statusCode: number;
@ApiProperty({ description: '返回信息' })
message: string;
@ApiProperty({ description: '是否成功' })
success: boolean;
constructor(statusCode: number, message?: string) {
this.statusCode = statusCode;
this.message = message || 'success';
this.success = statusCode < 400;
}
toException() {
return new HttpException(this, this.statusCode);
}
}
export class ReturnMessageDto<T>
extends BlankReturnMessageDto
implements ReturnMessage<T> {
@ApiProperty({ description: '返回内容' })
data?: T;
constructor(statusCode: number, message?: string, data?: T) {
super(statusCode, message);
this.data = data;
}
}
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { KoishiWsAdapter } from 'koishi-nestjs';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useWebSocketAdapter(new KoishiWsAdapter(app));
/*
app.enableCors();
app.set('trust proxy', ['172.16.0.0/12', 'loopback']);
const documentConfig = new DocumentBuilder()
.setTitle('app')
.setDescription('The app')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, documentConfig);
SwaggerModule.setup('docs', app, document);
*/
await app.listen(3000);
}
bootstrap();
import { Test, TestingModule } from '@nestjs/testing';
import { MessageService } from './message.service';
describe('MessageService', () => {
let service: MessageService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MessageService],
}).compile();
service = module.get<MessageService>(MessageService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { ConsoleLogger, Injectable } from '@nestjs/common';
import { InjectContext } from 'koishi-nestjs';
import { Context } from 'koishi';
import WebSocket from 'ws';
import { Route } from '../route/Route';
import { genMetaEvent } from '../utility/oicq';
import { OnebotProtocol } from '../utility/onebot-protocol';
import { OneBotBot } from '@koishijs/plugin-adapter-onebot/lib/bot';
@Injectable()
export class MessageService extends ConsoleLogger {
constructor(@InjectContext() private ctx: Context) {
super('message');
}
registerWsEvent(client: WebSocket, route: Route) {
client.on('message', async (data) => {
if (typeof data !== 'string') {
this.warn(`Got non-string.`);
client.send(
JSON.stringify({
retcode: 1400,
status: 'failed',
data: null,
error: {
code: 1404,
message: `Got non-string`,
},
}),
);
return;
}
try {
const parsedData = JSON.parse(data as string);
const message = JSON.stringify(await this.onWsEvent(route, parsedData));
client.send(message);
} catch (e) {
this.warn(`Got bad JSON ${data}`);
client.send(
JSON.stringify({
retcode: 1400,
status: 'failed',
data: null,
error: {
code: 1404,
message: `Got bad JSON.`,
},
}),
);
}
});
client.send(JSON.stringify(genMetaEvent(route.botId, 'connect')));
client.send(JSON.stringify(genMetaEvent(route.botId, 'enable')));
}
private async onWsEvent(route: Route, data: OnebotProtocol) {
const bot = this.ctx.bots.find(
(b) => b.selfId === route.botId && b.platform === 'onebot',
) as OneBotBot;
if (!bot) {
this.error(`Bot ${route.botId} not found`);
return {
retcode: 1404,
status: 'failed',
data: null,
error: {
code: 1404,
message: `Bot ${route.botId} not found.`,
},
echo: data?.echo,
};
}
try {
const result = await bot.internal._request(data.action, data.params);
return {
...result,
echo: data?.echo,
};
} catch (e) {
this.error(`Bot ${route.botId} timed out.`);
return {
retcode: 1404,
status: 'failed',
data: null,
error: {
code: 1404,
message: `Bot ${route.botId} timed out.`,
},
echo: data?.echo,
};
}
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { OnebotGateway } from './onebot.gateway';
describe('OnebotGateway', () => {
let gateway: OnebotGateway;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [OnebotGateway],
}).compile();
gateway = module.get<OnebotGateway>(OnebotGateway);
});
it('should be defined', () => {
expect(gateway).toBeDefined();
});
});
import {
OnGatewayConnection,
OnGatewayDisconnect,
WebSocketGateway,
WsException,
} from '@nestjs/websockets';
import { IncomingMessage } from 'http';
import { RouteService } from './route/route.service';
import { Route } from './route/Route';
import { ConsoleLogger } from '@nestjs/common';
import type WebSocket from 'ws';
import { AddressInfo } from 'net';
import { MessageService } from './message/message.service';
interface ClientInfo {
routeName: string;
route: Route;
ip: string;
}
@WebSocketGateway({ path: '^/route/(.+?)/?$' })
export class OnebotGateway
extends ConsoleLogger
implements OnGatewayConnection, OnGatewayDisconnect {
constructor(
private routeService: RouteService,
private messageService: MessageService,
) {
super('ws');
}
private clientRouteMap = new Map<WebSocket, ClientInfo>();
private matchingRegex = new RegExp('^/route/(.+?)/?$');
handleConnection(client: WebSocket, request: IncomingMessage) {
const baseUrl = 'ws://' + request.headers.host + '/';
const url = new URL(request.url, baseUrl);
const pathname = url.pathname;
const pathMatch = pathname.match(this.matchingRegex);
if (!pathMatch) {
return client.close(1002, 'empty route name');
}
const routeName = pathMatch[1];
const route = this.routeService.getRouteFromName(routeName);
if (!route) {
return client.close(1002, 'route not found');
}
if (
route.token &&
!request.headers['authorization']?.includes(route.token) &&
url.searchParams.get('access_token') !== route.token
) {
return client.close(1002, 'wrong access token');
}
const clientInfo = {
routeName,
route,
ip: (request.socket.address() as AddressInfo).address,
};
this.clientRouteMap.set(client, clientInfo);
this.messageService.registerWsEvent(client, route);
this.warn(
`Client ${clientInfo.ip} of route ${clientInfo.routeName} connected.`,
);
}
handleDisconnect(client: WebSocket) {
const clientInfo = this.clientRouteMap.get(client);
if (!clientInfo) {
return;
}
this.warn(
`Client ${clientInfo.ip} of route ${clientInfo.routeName} disconnected.`,
);
clientInfo.route.removeConnection(client);
this.clientRouteMap.delete(client);
}
}
import { Selection } from 'koishi-nestjs';
import type WebSocket from 'ws';
import { Context, Session } from 'koishi';
import { Random, remove } from 'koishi';
import { createHash } from 'crypto';
import { OneBotBot } from '@koishijs/plugin-adapter-onebot/lib/bot';
export type HashPolicy = 'broadcast' | 'random' | 'round-robin' | 'hash';
export interface RouteConfig {
name: string;
botId: string;
token?: string;
select?: Selection;
hashPolicy?: HashPolicy;
heartbeat?: number;
}
export class Route implements RouteConfig {
connections: WebSocket[] = [];
private roundCount = 0;
ctx: Context;
name: string;
botId: string;
token?: string;
select?: Selection;
hashPolicy?: HashPolicy;
heartbeat?: number;
constructor(routeConfig: RouteConfig, ctx: Context) {
Object.assign(this, routeConfig);
this.hashPolicy ||= 'hash';
this.ctx = this.getFilteredContext(ctx);
if (this.heartbeat) {
setInterval(() => {
this.broadcast({
self_id: this.botId,
time: Math.floor(Date.now() / 1000),
post_type: 'meta_event',
meta_event_type: 'heartbeat',
interval: this.heartbeat,
});
}, this.heartbeat);
}
}
send(data: any, sess: Session) {
const message = JSON.stringify(data);
for (const conn of this.getRelatedConnections(sess)) {
conn.send(message, (err) => {});
}
}
broadcast(data: any) {
const message = JSON.stringify(data);
for (const conn of this.connections) {
conn.send(message, (err) => {});
}
}
getFilteredContext(ctx: Context) {
if (!this.select) {
return ctx;
}
return ctx.select(this.select);
}
private getSequenceFromSession(sess: Session) {
const hash = createHash('md5');
for (const key of ['selfId', 'guildId', 'userId', 'channelId']) {
if (sess[key]) {
hash.update(sess[key]);
}
}
return parseInt(hash.digest('hex'), 16) % 4294967295;
}
getRelatedConnections(sess: Session): WebSocket[] {
switch (this.hashPolicy) {
case 'broadcast':
return this.connections;
case 'round-robin':
const index = this.roundCount++ % this.connections.length;
return [this.connections[index]];
case 'random':
return [Random.pick(this.connections)];
case 'hash':
return [
this.connections[
this.getSequenceFromSession(sess) % this.connections.length
],
];
}
return [];
}
addConnection(conn: WebSocket) {
this.connections.push(conn);
return conn;
}
removeConnection(conn: WebSocket) {
remove(this.connections, conn);
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { RouteService } from './route.service';
describe('RouteService', () => {
let service: RouteService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RouteService],
}).compile();
service = module.get<RouteService>(RouteService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import {
ConsoleLogger,
Injectable,
OnApplicationBootstrap,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Route, RouteConfig } from './Route';
import { InjectContextPlatform } from 'koishi-nestjs';
import { Context, Session } from 'koishi';
@Injectable()
export class RouteService
extends ConsoleLogger
implements OnApplicationBootstrap {
private routes = new Map<string, Route>();
constructor(
config: ConfigService,
@InjectContextPlatform('onebot') private ctx: Context,
) {
super('route');
const routeConfs = config.get<RouteConfig[]>('routes');
for (const routeConf of routeConfs) {
this.log(`Loaded route ${routeConf.name} for ${routeConf.botId}`);
this.routes.set(routeConf.name, new Route(routeConf, ctx));
}
}
getRouteFromName(name: string) {
return this.routes.get(name);
}
getRoutesFromBot(botId: string) {
return Array.from(this.routes.values()).filter((r) => r.botId === botId);
}
onApplicationBootstrap() {
for (const route of this.routes.values()) {
route.ctx.on('dispatch', (session) => this.onOnebotEvent(session, route));
}
}
private onOnebotEvent(session: Session, route: Route) {
const data = session.onebot;
if (!data) {
this.warn(`Got empty data from ${session.selfId}`);
return;
}
if (data.post_type === 'meta_event') {
return;
}
route.send(data, session);
}
}
import { BotConfig } from '@koishijs/plugin-adapter-onebot/lib/bot';
import yaml from 'yaml';
import * as fs from 'fs';
import { RouteConfig } from '../route/Route';
export interface LbConfig {
bots: BotConfig[];
routes: RouteConfig[];
}
export async function loadConfig(): Promise<LbConfig> {
return yaml.parse(await fs.promises.readFile('./config.yaml', 'utf-8'));
}
export function toHump(action: string) {
return action.replace(/_[\w]/g, (s) => {
return s[1].toUpperCase();
});
}
export const BOOLS = [
'no_cache',
'auto_escape',
'as_long',
'enable',
'reject_add_request',
'is_dismiss',
'approve',
'block',
];
export function toBool(v: any) {
if (v === '0' || v === 'false') v = false;
return Boolean(v);
}
export function genMetaEvent(uin: string | number, type: string) {
return {
self_id: parseInt(<string>uin),
time: Math.floor(Date.now() / 1000),
post_type: 'meta_event',
meta_event_type: 'lifecycle',
sub_type: type,
};
}
export function transNotice(data: any) {
if (data.sub_type === 'poke') {
data.notice_type = 'notify';
data.target_id = data.user_id;
data.user_id = data.operator_id;
data.operator_id = undefined;
return;
}
if (data.notice_type === 'friend') {
if (data.sub_type === 'increase')
(data.sub_type = undefined), (data.notice_type = 'friend_add');
else if (data.sub_type === 'recall')
(data.sub_type = undefined), (data.notice_type = 'friend_recall');
} else if (data.notice_type === 'group') {
if (data.sub_type === 'increase') {
(data.sub_type = undefined), (data.notice_type = 'group_increase');
} else if (data.sub_type === 'decrease') {
data.notice_type = 'group_decrease';
if (data.operator_id === data.user_id) data.sub_type = 'leave';
else if (data.self_id === data.user_id) data.sub_type = 'kick_me';
else data.sub_type = 'kick';
} else if (data.sub_type === 'recall') {
(data.sub_type = undefined), (data.notice_type = 'group_recall');
} else if (data.sub_type === 'ban') {
data.notice_type = 'group_ban';
data.sub_type = data.duration ? 'ban' : 'lift_ban';
} else if (data.sub_type === 'admin') {
data.notice_type = 'group_admin';
data.sub_type = data.set ? 'set' : 'unset';
}
}
}
export interface OnebotProtocol {
action: string;
params: any;
echo?: any;
}
export const OnebotWsResponse = {
retcode: 1,
status: 'async',
data: null,
error: null,
};
export const OnebotWsResponseString = JSON.stringify(OnebotWsResponse);
export function OnebotWsResponseWithEcho(echo: any) {
return {
...OnebotWsResponse,
echo,
};
}
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
/* it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
}); */
});
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
class A {
foo = 'bar';
foo1 = 'baz';
someMethod(param: string) {
console.log(this.foo, param);
}
}
class B extends A {
foo = 'bar B';
foo1 = 'baz B';
}
const theMethod = A.prototype.someMethod;
A.prototype.someMethod = function (this: A, param: string) {
console.log(this.foo1, param);
};
const a = new A();
a.someMethod('hi');
const b = new B();
b.someMethod('hey');
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"compileOnSave": true,
"allowJs": true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment