koishi-core
Advanced tools
Comparing version 0.2.1 to 0.2.2
/// <reference types="node" /> | ||
import { Server } from './server'; | ||
import { Server, ServerType } from './server'; | ||
import { Context, UserContext, GroupContext, DiscussContext, Middleware, Plugin, NoticeEvent, RequestEvent, MetaEventEvent, MessageEvent } from './context'; | ||
import { Command, ShortcutConfig } from './command'; | ||
import { Command, ShortcutConfig, ParsedCommandLine } from './command'; | ||
import { Database, DatabaseConfig } from './database'; | ||
import { EventEmitter } from 'events'; | ||
import { Meta } from './meta'; | ||
import { Meta, MessageMeta } from './meta'; | ||
export interface AppOptions { | ||
@@ -17,2 +17,3 @@ port?: number; | ||
database?: DatabaseConfig; | ||
type?: ServerType; | ||
} | ||
@@ -24,3 +25,3 @@ export declare const selfIds: number[]; | ||
export declare function onStart(hook: (...app: App[]) => void): void; | ||
export declare function startAll(): void; | ||
export declare function startAll(): Promise<void>; | ||
export declare function stopAll(): void; | ||
@@ -47,2 +48,4 @@ export declare class App extends Context { | ||
constructor(options?: AppOptions); | ||
get selfId(): number; | ||
set selfId(value: number); | ||
_registerSelfId(): void; | ||
@@ -53,9 +56,8 @@ _createContext<T extends Context>(path: string, id?: number): T; | ||
user(id: number): UserContext; | ||
start(): void; | ||
start(): Promise<void>; | ||
stop(): void; | ||
emitWarning(message: string): void; | ||
handleMeta(meta: Meta): Promise<void>; | ||
emitMeta(meta: Meta): void; | ||
dispatchMeta(meta: Meta, emitEvents?: boolean): Promise<void>; | ||
private _preprocess; | ||
private _parseCommandLine; | ||
parseCommandLine(message: string, meta: MessageMeta): ParsedCommandLine; | ||
private _applyMiddlewares; | ||
@@ -62,0 +64,0 @@ } |
@@ -44,8 +44,9 @@ "use strict"; | ||
exports.onStart = onStart; | ||
function startAll() { | ||
async function startAll() { | ||
const appList = []; | ||
for (const id in exports.apps) { | ||
exports.apps[id].start(); | ||
appList.push(exports.apps[id]); | ||
} | ||
await Promise.all(Object.keys(exports.apps).map(async (id) => { | ||
const app = exports.apps[id]; | ||
await app.start(); | ||
appList.push(app); | ||
})); | ||
for (const hook of onStartHooks) { | ||
@@ -100,3 +101,3 @@ hook(...appList); | ||
// parse as command | ||
if (canBeCommand && (parsedArgv = this._parseCommandLine(message, meta))) { | ||
if (canBeCommand && (parsedArgv = this.parseCommandLine(message, meta))) { | ||
fields.push(...parsedArgv.command._userFields); | ||
@@ -191,3 +192,3 @@ } | ||
const newMessage = suggestion + message.slice(target.length); | ||
const parsedArgv = this._parseCommandLine(newMessage, meta); | ||
const parsedArgv = this.parseCommandLine(newMessage, meta); | ||
return parsedArgv.command.execute(parsedArgv, next); | ||
@@ -229,10 +230,17 @@ }, | ||
this.database = database_1.createDatabase(options.database); | ||
if (options.port) | ||
this.server = server_1.createServer(this); | ||
if (options.selfId) | ||
this._registerSelfId(); | ||
this.sender = new sender_1.Sender(this); | ||
if (options.type) { | ||
this.server = server_1.createServer(this); | ||
this.sender = new sender_1.Sender(this); | ||
} | ||
this.receiver.on('message', this._applyMiddlewares); | ||
this.middleware(this._preprocess); | ||
} | ||
get selfId() { | ||
return this.options.selfId; | ||
} | ||
set selfId(value) { | ||
this.options.selfId = value; | ||
} | ||
_registerSelfId() { | ||
@@ -269,9 +277,9 @@ const atMeRE = `\\[CQ:at,qq=${this.options.selfId}\\]`; | ||
} | ||
start() { | ||
async start() { | ||
this.sender.start(); | ||
this.server.listen(this.options.port); | ||
await this.server.listen(); | ||
showLog('started'); | ||
} | ||
stop() { | ||
this.server.stop(); | ||
this.server.close(); | ||
this.sender.stop(); | ||
@@ -283,3 +291,3 @@ showLog('stopped'); | ||
} | ||
async handleMeta(meta) { | ||
async dispatchMeta(meta, emitEvents = true) { | ||
Object.defineProperty(meta, '$path', { | ||
@@ -326,4 +334,4 @@ value: '/', | ||
} | ||
} | ||
emitMeta(meta) { | ||
if (!emitEvents) | ||
return; | ||
for (const path in this._contexts) { | ||
@@ -337,3 +345,3 @@ const context = this._contexts[path]; | ||
} | ||
_parseCommandLine(message, meta) { | ||
parseCommandLine(message, meta) { | ||
const name = message.split(/\s/, 1)[0].toLowerCase(); | ||
@@ -340,0 +348,0 @@ const command = this._commandMap[name]; |
import { Context, NextFunction } from './context'; | ||
import { UserData, UserField } from './database'; | ||
import { MessageMeta } from './meta'; | ||
import { CommandOption, CommandArgument, OptionConfig, ParsedLine } from './parser'; | ||
import { CommandOption, OptionConfig, ParsedLine } from './parser'; | ||
export interface ParsedCommandLine extends ParsedLine { | ||
@@ -30,2 +30,3 @@ meta: MessageMeta; | ||
showWarning?: boolean; | ||
noHelpOption?: boolean; | ||
} | ||
@@ -50,10 +51,10 @@ export interface ShortcutConfig { | ||
_aliases: string[]; | ||
_action?: (this: Command, config: ParsedCommandLine, ...args: string[]) => any; | ||
_options: CommandOption[]; | ||
_argsDef: CommandArgument[]; | ||
_usage?: string; | ||
_examples: string[]; | ||
_shortcuts: Record<string, ShortcutConfig>; | ||
_optsDef: Record<string, CommandOption>; | ||
_userFields: Set<"id" | "name" | "flag" | "ignoreEnd" | "authority" | "usage" | "talkativeness">; | ||
private _argsDef; | ||
private _optsDef; | ||
private _action?; | ||
constructor(rawName: string, context: Context, config?: CommandConfig); | ||
@@ -60,0 +61,0 @@ private _registerAlias; |
@@ -19,3 +19,3 @@ "use strict"; | ||
const parser_1 = require("./parser"); | ||
const showCommandLog = debug_1.default('app:command'); | ||
const showCommandLog = debug_1.default('koishi:command'); | ||
const defaultConfig = { | ||
@@ -36,4 +36,4 @@ authority: 1, | ||
this._shortcuts = {}; | ||
this._userFields = new Set(); | ||
this._optsDef = {}; | ||
this._userFields = new Set(); | ||
this.name = parser_1.removeBrackets(rawName); | ||
@@ -49,4 +49,6 @@ if (!this.name) { | ||
this._registerAlias(this.name); | ||
this.option('-h, --help', messages.SHOW_THIS_MESSAGE); | ||
context.app._commands.push(this); | ||
if (!config.noHelpOption) { | ||
this.option('-h, --help', messages.SHOW_THIS_MESSAGE); | ||
} | ||
} | ||
@@ -151,5 +153,6 @@ _registerAlias(name) { | ||
const { meta, options, args, unknown } = config; | ||
config.next = next; | ||
if (!config.next) | ||
config.next = next; | ||
// show help when use `-h, --help` or when there is no action | ||
if (!this._action || options.help) { | ||
if (!this._action || options.help && !this.config.noHelpOption) { | ||
return this.context.runCommand('help', meta, [this.name]); | ||
@@ -156,0 +159,0 @@ } |
@@ -17,3 +17,2 @@ /// <reference types="node" /> | ||
export declare function isAncestor(ancestor: string, path: string): boolean; | ||
export declare const prefixTypes: string[]; | ||
export declare class Context { | ||
@@ -20,0 +19,0 @@ path: string; |
@@ -18,3 +18,3 @@ "use strict"; | ||
exports.isAncestor = isAncestor; | ||
exports.prefixTypes = ['user', 'discuss', 'group']; | ||
const prefixTypes = ['user', 'discuss', 'group']; | ||
class Context { | ||
@@ -124,3 +124,3 @@ constructor(path, app) { | ||
for (let segment of path.slice(this.path.length).split('/')) { | ||
if (!isNaN(segment) || exports.prefixTypes.includes(segment)) | ||
if (!isNaN(segment) || prefixTypes.includes(segment)) | ||
segment = lastEvent ? '*' : ''; | ||
@@ -127,0 +127,0 @@ if (segment) |
@@ -65,2 +65,4 @@ "use strict"; | ||
function injectMethods(name, methods) { | ||
if (!subdatabases[name]) | ||
return; | ||
Object.assign(subdatabases[name]._injections, methods); | ||
@@ -67,0 +69,0 @@ } |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { GroupMemberInfo, StatusInfo, VersionInfo, FriendInfo, GroupInfo, Credentials, AccountInfo, StrangerInfo, ListedGroupInfo } from './meta'; | ||
@@ -13,11 +12,17 @@ import { App } from './app'; | ||
export declare type DataDirectoryType = 'image' | 'record' | 'show' | 'bface'; | ||
export interface CQResponse { | ||
status: string; | ||
retcode: number; | ||
data: any; | ||
echo?: number; | ||
} | ||
export declare class Sender { | ||
app: App; | ||
messages: any[]; | ||
timer: NodeJS.Timeout; | ||
headers: Record<string, any>; | ||
private _messages; | ||
private _timer; | ||
private _post; | ||
constructor(app: App); | ||
private post; | ||
start(): void; | ||
stop(): void; | ||
protected _post(api: string, args?: Record<string, any>): Promise<any>; | ||
sendContextMsg(contextId: string, message: string, autoEscape?: boolean): Promise<number>; | ||
@@ -24,0 +29,0 @@ sendGroupMsg(groupId: number, message: string, autoEscape?: boolean): Promise<number>; |
@@ -9,3 +9,3 @@ "use strict"; | ||
const koishi_utils_1 = require("koishi-utils"); | ||
const showSenderLog = debug_1.default('app:sender'); | ||
const showSenderLog = debug_1.default('koishi:sender'); | ||
class SenderError extends Error { | ||
@@ -24,30 +24,31 @@ constructor(args, url, retcode) { | ||
this.app = app; | ||
this.messages = new Array(61).fill(0); | ||
this.headers = { | ||
Authorization: `Token ${app.options.token}`, | ||
}; | ||
this._messages = new Array(61).fill(0); | ||
const { type } = app.options; | ||
if (type === 'http') { | ||
const headers = {}; | ||
if (app.options.token) { | ||
headers.Authorization = `Token ${app.options.token}`; | ||
} | ||
this._post = async (action, params = {}) => { | ||
const uri = new URL(action, this.app.options.sendUrl).href; | ||
const { data } = await axios_1.default.get(uri, { params, headers }); | ||
return data; | ||
}; | ||
} | ||
else if (type === 'ws') { | ||
const server = app.server; | ||
this._post = (action, params = {}) => server.send({ action, params }); | ||
} | ||
} | ||
start() { | ||
this.timer = setInterval(() => { | ||
this.messages.unshift(0); | ||
this.messages.splice(-1, 1); | ||
}, 1000); | ||
} | ||
stop() { | ||
clearInterval(this.timer); | ||
} | ||
async _post(api, args = {}) { | ||
const uri = new URL(api, this.app.options.sendUrl).href; | ||
showSenderLog('request %s %o', api, args); | ||
async post(action, params) { | ||
showSenderLog('request %s %o', action, params); | ||
try { | ||
const { data } = await axios_1.default.get(uri, { | ||
params: koishi_utils_1.snakeCase(args), | ||
headers: this.headers, | ||
}); | ||
showSenderLog('response %o', data); | ||
if (data.retcode === 0) { | ||
return koishi_utils_1.camelCase(data.data); | ||
const response = await this._post(action, koishi_utils_1.snakeCase(params)); | ||
showSenderLog('response %o', response); | ||
const { data, retcode } = response; | ||
if (retcode === 0) { | ||
return koishi_utils_1.camelCase(data); | ||
} | ||
else { | ||
throw new SenderError(args, uri, data.retcode); | ||
throw new SenderError(params, action, retcode); | ||
} | ||
@@ -60,2 +61,11 @@ } | ||
} | ||
start() { | ||
this._timer = setInterval(() => { | ||
this._messages.unshift(0); | ||
this._messages.splice(-1, 1); | ||
}, 1000); | ||
} | ||
stop() { | ||
clearInterval(this._timer); | ||
} | ||
async sendContextMsg(contextId, message, autoEscape) { | ||
@@ -73,3 +83,3 @@ const type = contextId[0]; | ||
return; | ||
const response = await this._post('send_group_msg', { groupId, message, autoEscape }); | ||
const response = await this.post('send_group_msg', { groupId, message, autoEscape }); | ||
const meta = { | ||
@@ -82,4 +92,3 @@ $path: `/group/${groupId}/send`, | ||
}; | ||
this.app.handleMeta(meta); | ||
this.app.emitMeta(meta); | ||
await this.app.dispatchMeta(meta); | ||
return response.messageId; | ||
@@ -90,4 +99,4 @@ } | ||
return; | ||
this.messages[0] += 1; | ||
const response = await this._post('send_discuss_msg', { discussId, message, autoEscape }); | ||
this._messages[0] += 1; | ||
const response = await this.post('send_discuss_msg', { discussId, message, autoEscape }); | ||
const meta = { | ||
@@ -100,4 +109,3 @@ $path: `/discuss/${discussId}/send`, | ||
}; | ||
this.app.handleMeta(meta); | ||
this.app.emitMeta(meta); | ||
await this.app.dispatchMeta(meta); | ||
return response.messageId; | ||
@@ -108,4 +116,4 @@ } | ||
return; | ||
this.messages[0] += 1; | ||
const response = await this._post('send_private_msg', { userId, message, autoEscape }); | ||
this._messages[0] += 1; | ||
const response = await this.post('send_private_msg', { userId, message, autoEscape }); | ||
const meta = { | ||
@@ -118,17 +126,16 @@ $path: `/user/${userId}/send`, | ||
}; | ||
this.app.handleMeta(meta); | ||
this.app.emitMeta(meta); | ||
await this.app.dispatchMeta(meta); | ||
return response.messageId; | ||
} | ||
async deleteMsg(messageId) { | ||
await this._post('delete_msg', { messageId }); | ||
await this.post('delete_msg', { messageId }); | ||
} | ||
async sendLike(userId, times = 1) { | ||
await this._post('send_like', { userId, times }); | ||
await this.post('send_like', { userId, times }); | ||
} | ||
async setGroupKick(groupId, userId, rejectAddRequest = false) { | ||
await this._post('set_group_kick', { groupId, userId, rejectAddRequest }); | ||
await this.post('set_group_kick', { groupId, userId, rejectAddRequest }); | ||
} | ||
async setGroupBan(groupId, userId, duration = 36 * 60) { | ||
await this._post('set_group_ban', { groupId, userId, duration }); | ||
await this.post('set_group_ban', { groupId, userId, duration }); | ||
} | ||
@@ -138,95 +145,95 @@ async setGroupAnonymousBan(groupId, meta, duration = 36 * 60) { | ||
args[typeof meta === 'string' ? 'flag' : 'anomymous'] = meta; | ||
await this._post('set_group_anonymous_ban', args); | ||
await this.post('set_group_anonymous_ban', args); | ||
} | ||
async setGroupWholeBan(groupId, enable = true) { | ||
await this._post('set_group_whole_ban', { groupId, enable }); | ||
await this.post('set_group_whole_ban', { groupId, enable }); | ||
} | ||
async setGroupAdmin(groupId, userId, enable) { | ||
await this._post('set_group_admin', { groupId, userId, enable }); | ||
await this.post('set_group_admin', { groupId, userId, enable }); | ||
} | ||
async setGroupAnonymous(groupId, enable) { | ||
await this._post('set_group_anonymous', { groupId, enable }); | ||
await this.post('set_group_anonymous', { groupId, enable }); | ||
} | ||
async setGroupCard(groupId, userId, card = '') { | ||
await this._post('set_group_admin', { groupId, userId, card }); | ||
await this.post('set_group_admin', { groupId, userId, card }); | ||
} | ||
async setGroupLeave(groupId, isDismiss = false) { | ||
await this._post('set_group_leave', { groupId, isDismiss }); | ||
await this.post('set_group_leave', { groupId, isDismiss }); | ||
} | ||
async setGroupSpecialTitle(groupId, userId, specialTitle = '', duration = -1) { | ||
await this._post('set_group_special_title', { groupId, userId, specialTitle, duration }); | ||
await this.post('set_group_special_title', { groupId, userId, specialTitle, duration }); | ||
} | ||
async setDiscussLeave(discussId) { | ||
await this._post('set_discuss_leave', { discussId }); | ||
await this.post('set_discuss_leave', { discussId }); | ||
} | ||
async setFriendAddRequest(flag, approve = true, remark = '') { | ||
await this._post('set_friend_add_request', { flag, approve, remark }); | ||
await this.post('set_friend_add_request', { flag, approve, remark }); | ||
} | ||
async setGroupAddRequest(flag, subType, approve = true, reason = '') { | ||
await this._post('set_group_add_request', { flag, subType, approve, reason }); | ||
await this.post('set_group_add_request', { flag, subType, approve, reason }); | ||
} | ||
getLoginInfo() { | ||
return this._post('get_login_info'); | ||
return this.post('get_login_info'); | ||
} | ||
getStrangerInfo(userId, noCache = false) { | ||
return this._post('get_stranger_info', { userId, noCache }); | ||
return this.post('get_stranger_info', { userId, noCache }); | ||
} | ||
getFriendList() { | ||
return this._post('get_friend_list'); | ||
return this.post('get_friend_list'); | ||
} | ||
getGroupList() { | ||
return this._post('get_group_list'); | ||
return this.post('get_group_list'); | ||
} | ||
getGroupInfo(groupId, noCache = false) { | ||
return this._post('get_group_info', { groupId, noCache }); | ||
return this.post('get_group_info', { groupId, noCache }); | ||
} | ||
getGroupMemberInfo(groupId, userId, noCache = false) { | ||
return this._post('get_group_member_info', { groupId, userId, noCache }); | ||
return this.post('get_group_member_info', { groupId, userId, noCache }); | ||
} | ||
getGroupMemberList(groupId) { | ||
return this._post('get_group_member_list', { groupId }); | ||
return this.post('get_group_member_list', { groupId }); | ||
} | ||
async getCookies(domain) { | ||
const { cookies } = await this._post('get_cookies', { domain }); | ||
const { cookies } = await this.post('get_cookies', { domain }); | ||
return cookies; | ||
} | ||
async getCsrfToken() { | ||
const { token } = await this._post('get_csrf_token'); | ||
const { token } = await this.post('get_csrf_token'); | ||
return token; | ||
} | ||
getCredentials() { | ||
return this._post('get_credentials'); | ||
return this.post('get_credentials'); | ||
} | ||
async getRecord(file, outFormat, fullPath = false) { | ||
const response = await this._post('get_record', { file, outFormat, fullPath }); | ||
const response = await this.post('get_record', { file, outFormat, fullPath }); | ||
return response.file; | ||
} | ||
async getImage(file) { | ||
const response = await this._post('get_image', { file }); | ||
const response = await this.post('get_image', { file }); | ||
return response.file; | ||
} | ||
async canSendImage() { | ||
const { yes } = await this._post('can_send_image'); | ||
const { yes } = await this.post('can_send_image'); | ||
return yes; | ||
} | ||
async canSendRecord() { | ||
const { yes } = await this._post('can_send_record'); | ||
const { yes } = await this.post('can_send_record'); | ||
return yes; | ||
} | ||
getStatus() { | ||
return this._post('get_status'); | ||
return this.post('get_status'); | ||
} | ||
getVersionInfo() { | ||
return this._post('get_version_info'); | ||
return this.post('get_version_info'); | ||
} | ||
async setRestartPlugin(delay = 0) { | ||
await this._post('set_restart_plugin', { delay }); | ||
await this.post('set_restart_plugin', { delay }); | ||
} | ||
async cleanDataDir(dataDir) { | ||
await this._post('clean_data_dir', { dataDir }); | ||
await this.post('clean_data_dir', { dataDir }); | ||
} | ||
async cleanPluginLog() { | ||
await this._post('clean_plugin_log'); | ||
await this.post('clean_plugin_log'); | ||
} | ||
} | ||
exports.Sender = Sender; |
@@ -0,14 +1,34 @@ | ||
/// <reference types="node" /> | ||
import WebSocket from 'ws'; | ||
import { Response } from 'express'; | ||
import * as http from 'http'; | ||
import { App } from './app'; | ||
export declare function createServer(app: App): Server; | ||
export declare class Server { | ||
private _apps; | ||
import { CQResponse } from './sender'; | ||
export declare abstract class Server { | ||
protected _apps: App[]; | ||
private _appMap; | ||
private _server; | ||
private _socket; | ||
private _httpServer; | ||
private _isListening; | ||
protected abstract _listen(): Promise<void>; | ||
abstract close(): void; | ||
constructor(app: App); | ||
protected _handleData(data: any, res?: Response): void; | ||
bind(app: App): this; | ||
listen(port: number): void; | ||
stop(): void; | ||
listen(): Promise<void>; | ||
} | ||
export declare class HttpServer extends Server { | ||
express: import("express-serve-static-core").Express; | ||
httpServer: http.Server; | ||
constructor(app: App); | ||
_listen(): Promise<void>; | ||
close(): void; | ||
} | ||
export declare class WsClient extends Server { | ||
socket: WebSocket; | ||
private _listeners; | ||
constructor(app: App); | ||
send(data: any): Promise<CQResponse>; | ||
_listen(): Promise<void>; | ||
close(): void; | ||
} | ||
export declare type ServerType = 'http' | 'ws'; | ||
export declare function createServer(app: App): Server; |
@@ -15,11 +15,2 @@ "use strict"; | ||
showServerLog.inspectOpts.depth = 0; | ||
const serverMap = {}; | ||
function createServer(app) { | ||
const { port } = app.options; | ||
if (port in serverMap) { | ||
return serverMap[port].bind(app); | ||
} | ||
return serverMap[port] = new Server(app); | ||
} | ||
exports.createServer = createServer; | ||
class Server { | ||
@@ -29,43 +20,23 @@ constructor(app) { | ||
this._appMap = {}; | ||
this._server = express_1.default().use(body_parser_1.json()); | ||
this._isListening = false; | ||
if (app.options.wsServer) { | ||
this._socket = new ws_1.default(app.options.wsServer + '/event', { | ||
headers: { | ||
Authorization: `Token ${app.options.token}`, | ||
}, | ||
}); | ||
this._socket.on('message', (data) => { | ||
console.log(data); | ||
}); | ||
this.bind(app); | ||
} | ||
_handleData(data, res) { | ||
const meta = koishi_utils_1.camelCase(data); | ||
if (!this._appMap[meta.selfId]) { | ||
const index = this._apps.findIndex(app => !app.options.selfId); | ||
if (index < 0) { | ||
if (res) | ||
res.sendStatus(403); | ||
return; | ||
} | ||
this._appMap[meta.selfId] = this._apps[index]; | ||
this._apps[index].options.selfId = meta.selfId; | ||
this._apps[index]._registerSelfId(); | ||
} | ||
if (app.options.secret) { | ||
this._server.use((req, res, next) => { | ||
const signature = req.header('x-signature'); | ||
if (!signature) | ||
return res.sendStatus(401); | ||
const body = JSON.stringify(req.body); | ||
const sig = crypto_1.createHmac('sha1', app.options.secret).update(body).digest('hex'); | ||
if (signature !== `sha1=${sig}`) | ||
return res.sendStatus(403); | ||
return next(); | ||
}); | ||
} | ||
this._server.use(async (req, res) => { | ||
const meta = koishi_utils_1.camelCase(req.body); | ||
if (!this._appMap[meta.selfId]) { | ||
const index = this._apps.findIndex(app => !app.options.selfId); | ||
if (index < 0) | ||
return res.sendStatus(403); | ||
this._appMap[meta.selfId] = this._apps[index]; | ||
this._apps[index].options.selfId = meta.selfId; | ||
this._apps[index]._registerSelfId(); | ||
} | ||
const app = this._appMap[meta.selfId]; | ||
const app = this._appMap[meta.selfId]; | ||
if (res) | ||
res.sendStatus(200); | ||
showServerLog('receive %o', meta); | ||
await app.handleMeta(meta); | ||
app.emitMeta(meta); | ||
}); | ||
this.bind(app); | ||
showServerLog('receive %o', meta); | ||
app.dispatchMeta(meta); | ||
} | ||
@@ -79,8 +50,7 @@ bind(app) { | ||
} | ||
listen(port) { | ||
async listen() { | ||
if (this._isListening) | ||
return; | ||
this._isListening = true; | ||
this._httpServer = this._server.listen(port); | ||
showServerLog('listen to port', port); | ||
await this._listen(); | ||
for (const app of this._apps) { | ||
@@ -90,9 +60,107 @@ app.receiver.emit('connected', app); | ||
} | ||
stop() { | ||
if (!this._httpServer) | ||
return; | ||
this._httpServer.close(); | ||
showServerLog('closed'); | ||
} | ||
exports.Server = Server; | ||
class HttpServer extends Server { | ||
constructor(app) { | ||
super(app); | ||
this.express = express_1.default().use(body_parser_1.json()); | ||
if (app.options.secret) { | ||
this.express.use((req, res, next) => { | ||
const signature = req.header('x-signature'); | ||
if (!signature) | ||
return res.sendStatus(401); | ||
const body = JSON.stringify(req.body); | ||
const sig = crypto_1.createHmac('sha1', app.options.secret).update(body).digest('hex'); | ||
if (signature !== `sha1=${sig}`) | ||
return res.sendStatus(403); | ||
return next(); | ||
}); | ||
} | ||
this.express.use((req, res) => { | ||
this._handleData(req.body, res); | ||
}); | ||
} | ||
async _listen() { | ||
const { port } = this._apps[0].options; | ||
this.httpServer = this.express.listen(port); | ||
showServerLog('listen to port', port); | ||
} | ||
close() { | ||
if (this.httpServer) | ||
this.httpServer.close(); | ||
showServerLog('http server closed'); | ||
} | ||
} | ||
exports.Server = Server; | ||
exports.HttpServer = HttpServer; | ||
let counter = 0; | ||
class WsClient extends Server { | ||
constructor(app) { | ||
super(app); | ||
this._listeners = {}; | ||
this.socket = new ws_1.default(app.options.wsServer, { | ||
headers: { | ||
Authorization: `Bearer ${app.options.token}`, | ||
}, | ||
}); | ||
this.socket.on('message', (data) => { | ||
data = data.toString(); | ||
let parsed; | ||
try { | ||
parsed = JSON.parse(data); | ||
} | ||
catch (error) { | ||
throw new Error(data); | ||
} | ||
if ('post_type' in parsed) { | ||
this._handleData(parsed); | ||
} | ||
else if (parsed.echo in this._listeners) { | ||
this._listeners[parsed.echo](parsed); | ||
} | ||
}); | ||
} | ||
send(data) { | ||
data.echo = ++counter; | ||
return new Promise((resolve, reject) => { | ||
this._listeners[counter] = resolve; | ||
this.socket.send(JSON.stringify(data), (error) => { | ||
if (error) | ||
reject(error); | ||
}); | ||
}); | ||
} | ||
async _listen() { | ||
await new Promise((resolve, reject) => { | ||
this.socket.once('open', resolve); | ||
this.socket.once('error', reject); | ||
}); | ||
const { wsServer } = this._apps[0].options; | ||
showServerLog('connect to ws server:', wsServer); | ||
} | ||
close() { | ||
if (this.socket) | ||
this.socket.close(); | ||
showServerLog('ws client closed'); | ||
} | ||
} | ||
exports.WsClient = WsClient; | ||
const serverTypes = { | ||
http: ['port', {}, HttpServer], | ||
ws: ['wsServer', {}, WsClient], | ||
}; | ||
function createServer(app) { | ||
const { type } = app.options; | ||
if (!serverTypes[type]) { | ||
throw new Error(`server type "${type}" is not supported`); | ||
} | ||
const [key, serverMap, Server] = serverTypes[type]; | ||
const value = app.options[key]; | ||
if (!value) { | ||
throw new Error(`missing configuration "${key}"`); | ||
} | ||
if (value in serverMap) { | ||
return serverMap[value].bind(app); | ||
} | ||
return serverMap[value] = new Server(app); | ||
} | ||
exports.createServer = createServer; |
@@ -71,3 +71,3 @@ "use strict"; | ||
function assertContextType(ctx, type) { | ||
if (!ctx['id'] || !ctx.path.slice(1).startsWith(type)) { | ||
if (!ctx.id || !ctx.path.slice(1).startsWith(type)) { | ||
throw new Error(`expect a ${type} context, received path: ${ctx.path}`); | ||
@@ -74,0 +74,0 @@ } |
{ | ||
"name": "koishi-core", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"main": "dist/index.js", | ||
@@ -5,0 +5,0 @@ "typings": "dist/index.d.ts", |
Sorry, the diff of this file is not supported yet
248582
40
2594