Update
parent
87a3012897
commit
f2258cca35
@ -1,9 +1,18 @@
|
||||
robot:
|
||||
host: "192.168.0.14:5700"
|
||||
pusher:
|
||||
qq:
|
||||
type: "qq"
|
||||
user: 123456789
|
||||
host: "127.0.0.1:8094"
|
||||
service:
|
||||
pusher:
|
||||
app_id: ""
|
||||
key: ""
|
||||
secret: ""
|
||||
cluster: "ap1"
|
||||
http_api:
|
||||
host: "0.0.0.0"
|
||||
port: 8902
|
||||
tokens:
|
||||
test: abc
|
||||
channel_config_path: "./channels"
|
||||
debug: false
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,138 @@
|
||||
import App from "./App";
|
||||
import Koa from 'koa';
|
||||
import { RestfulApiConfig } from "./Config";
|
||||
import Router from "koa-router";
|
||||
import { makeRoutes } from "./restful/routes";
|
||||
|
||||
export interface RestfulContext {
|
||||
app: App
|
||||
}
|
||||
|
||||
export type FullRestfulContext = RestfulContext & Koa.BaseContext;
|
||||
|
||||
export class RestfulApiManager {
|
||||
private app: App;
|
||||
private config: RestfulApiConfig;
|
||||
|
||||
private koa: Koa;
|
||||
private router: Router<any, RestfulContext>;
|
||||
|
||||
constructor(app: App, config: Pick<RestfulApiConfig, any>) {
|
||||
this.app = app;
|
||||
|
||||
this.config = {
|
||||
host: '0.0.0.0',
|
||||
port: 8082,
|
||||
...config
|
||||
} as any;
|
||||
|
||||
this.koa = new Koa();
|
||||
this.router = new Router<any, RestfulContext>();
|
||||
}
|
||||
|
||||
public initialize(): Promise<void> {
|
||||
makeRoutes(this.router, this, this.app);
|
||||
|
||||
this.koa.use(this.globalMiddleware);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.koa.use(this.router.routes());
|
||||
this.koa.listen(this.config.port, () => {
|
||||
console.log(`Restful API 启动于:${this.config.port}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局变量中间件
|
||||
* @param ctx
|
||||
* @param next
|
||||
*/
|
||||
public globalMiddleware = async (ctx: FullRestfulContext, next: () => Promise<any>) => {
|
||||
ctx.app = this.app; // 注入全局app
|
||||
await next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造token检测中间件
|
||||
* @param tokenType
|
||||
* @returns
|
||||
*/
|
||||
public tokenMiddlewareFactory(tokenType: string | string[]) {
|
||||
if (typeof tokenType === "string") {
|
||||
tokenType = [tokenType];
|
||||
}
|
||||
tokenType.push('root'); // 永远允许Root Token
|
||||
|
||||
let allowedTokens = [];
|
||||
tokenType.forEach((tokenName) => {
|
||||
let token = this.config.tokens[tokenName];
|
||||
if (token) {
|
||||
allowedTokens.push(token);
|
||||
}
|
||||
});
|
||||
if (allowedTokens.length === 0) { // 无Token校验
|
||||
return async (ctx: FullRestfulContext, next: Koa.Next) => {
|
||||
|
||||
};
|
||||
} else {
|
||||
return async (ctx: FullRestfulContext, next: Koa.Next) => {
|
||||
await next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测请求token
|
||||
* @param token
|
||||
* @param tokenType 允许的Token类型
|
||||
*/
|
||||
public verifyToken(token: string, tokenType: string | string[]) {
|
||||
if (typeof tokenType === "string") {
|
||||
tokenType = [tokenType];
|
||||
}
|
||||
tokenType.push('root'); // 永远允许Root Token
|
||||
|
||||
let allowedTokens = [];
|
||||
tokenType.forEach((tokenName) => {
|
||||
let token = this.config.tokens[tokenName];
|
||||
if (token) {
|
||||
allowedTokens.push(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public verifyTokenByTokenList(ctx: FullRestfulContext, tokenList: string[]): boolean
|
||||
public verifyTokenByTokenList(token: string, tokenList: string[]): boolean
|
||||
public verifyTokenByTokenList(ctx: FullRestfulContext | string, tokenList: string[]): boolean {
|
||||
let verifyType: "token" | "token-hash" = "token";
|
||||
let token: string | undefined;
|
||||
if (typeof ctx === "string") {
|
||||
token = ctx;
|
||||
} else {
|
||||
let authHeader = ctx.headers.authorization;
|
||||
if (!authHeader) {
|
||||
return false;
|
||||
}
|
||||
let [authMode, authInfo] = authHeader.split(" ");
|
||||
switch (authMode.toLowerCase()) {
|
||||
case "token":
|
||||
token = authInfo;
|
||||
break;
|
||||
case "token-hash":
|
||||
verifyType = "token-hash";
|
||||
token = authInfo;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (verifyType === "token") {
|
||||
return tokenList.includes(token);
|
||||
} else if (verifyType === "token-hash") {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
var Channel = require('./Channel');
|
||||
|
||||
class BroadcastChannel extends Channel {
|
||||
constructor(app){
|
||||
super(app, {});
|
||||
}
|
||||
|
||||
initialize(){
|
||||
this.channelName = 'broadcast';
|
||||
this.baseTemplate = '{{data.message}}';
|
||||
this.parseTemplate = this.buildTemplateCallback(this.baseTemplate);
|
||||
|
||||
this.initPush();
|
||||
}
|
||||
|
||||
onMessage(data){
|
||||
try {
|
||||
let finalMessage = this.parseMessage(data);
|
||||
if(data.target.group){
|
||||
this.app.robot.sendToGroup(data.target.group, finalMessage);
|
||||
}
|
||||
if(data.target.user){
|
||||
this.app.robot.sendToUser(data.target.group, finalMessage);
|
||||
}
|
||||
} catch(ex){
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BroadcastChannel;
|
@ -0,0 +1,176 @@
|
||||
var utils = require('../Utils');
|
||||
|
||||
class Channel {
|
||||
constructor(app, config){
|
||||
this.app = app;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
static checkConfig(data){
|
||||
if(typeof data !== 'object') return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setData(data){
|
||||
this.config = data;
|
||||
|
||||
this.channelName = data.channel;
|
||||
this.baseTemplates = data.templates;
|
||||
this.prepareFileList = data.files;
|
||||
this.receiver = data.receiver;
|
||||
|
||||
this.initTemplates();
|
||||
this.initReceiver();
|
||||
}
|
||||
|
||||
bind(){
|
||||
this.channel = this.app.pusher.subscribe(this.channelName);
|
||||
this.channel.bind_global(this.onPush.bind(this));
|
||||
}
|
||||
|
||||
unbind(){
|
||||
this.channel.unbind();
|
||||
}
|
||||
|
||||
initTemplates(){
|
||||
this.template = {};
|
||||
for(let key in this.baseTemplates){
|
||||
let one = this.baseTemplates[key];
|
||||
this.template[key] = this.buildTemplateCallback(one);
|
||||
}
|
||||
}
|
||||
|
||||
initReceiver(){
|
||||
this.getReceiver = this.buildGetReceiver();
|
||||
}
|
||||
|
||||
initPrepareFileList(){
|
||||
this.prepareFileCallback = {};
|
||||
for(let key in this.prepareFileList){
|
||||
let one = this.prepareFileList[key];
|
||||
this.prepareFileCallback[key] = this.buildPrepareFileCallback(one);
|
||||
}
|
||||
}
|
||||
|
||||
destory(){
|
||||
this.app.pusher.unsubscribe(this.channelName);
|
||||
this.channel.unbind();
|
||||
}
|
||||
|
||||
parseTemplate(template){
|
||||
template = template.replace(/\\/g, "\\\\").replace(/\r\n/g, "\n").replace(/\n/g, "\\n").replace(/'/g, "\\'");
|
||||
if(template.indexOf('{{') == 0){ //开头是{{
|
||||
template = template.substr(2);
|
||||
} else {
|
||||
template = "'" + template;
|
||||
}
|
||||
|
||||
if(template.indexOf('}}') == template.length - 2){ //结尾是}}
|
||||
template = template.substr(0, template.length - 2);
|
||||
} else {
|
||||
template = template + "'";
|
||||
}
|
||||
|
||||
template = template.replace(/\{\{/g, "' + ").replace(/\}\}/g, " + '");
|
||||
return template;
|
||||
}
|
||||
|
||||
buildTemplateCallback(template){
|
||||
return eval('(function(data){ return ' + this.parseTemplate(template) + '; })').bind(this);
|
||||
}
|
||||
|
||||
buildPrepareFileCallback(cond){
|
||||
return eval('(function(data){ return ' + cond + '; })').bind(this);
|
||||
}
|
||||
|
||||
parseMessage(data){
|
||||
try {
|
||||
return this.parseTemplate(data);
|
||||
} catch(ex){
|
||||
return this.baseTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
getDataVal(data, key, defaultVal = undefined){
|
||||
let keyList = key.split('.');
|
||||
let finded = data;
|
||||
for(let key of keyList){
|
||||
if(typeof finded === 'object' && key in finded){
|
||||
finded = finded[key];
|
||||
} else {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
return finded;
|
||||
}
|
||||
|
||||
buildGetReceiver(){
|
||||
if(typeof this.receiver === 'string'){
|
||||
return (data) => {
|
||||
return this.getDataVal(data, this.receiver);
|
||||
};
|
||||
} else {
|
||||
let resultFunc = {};
|
||||
for(let type of ['group', 'user']){
|
||||
if(type in this.receiver){
|
||||
if(typeof this.receiver[type] === 'string'){
|
||||
resultFunc[type] = (data) => {
|
||||
return this.getDataVal(data, this.receiver[type]);
|
||||
};
|
||||
} else if(Array.isArray(this.receiver[type])) {
|
||||
let staticTargets = [];
|
||||
let paramTargets = [];
|
||||
for(let val of this.receiver[type]){
|
||||
if(typeof val === "number"){
|
||||
staticTargets.push(val);
|
||||
} else {
|
||||
paramTargets.push(val);
|
||||
}
|
||||
}
|
||||
resultFunc[type] = (data) => {
|
||||
let targets = staticTargets.slice();
|
||||
for(let key of paramTargets){
|
||||
targets.push(this.getDataVal(data, key))
|
||||
}
|
||||
return targets;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return (data) => {
|
||||
let ret = {};
|
||||
if('group' in resultFunc){
|
||||
ret.group = resultFunc.group();
|
||||
}
|
||||
if('user' in resultFunc){
|
||||
ret.user = resultFunc.user();
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onPush(type, data){
|
||||
try {
|
||||
if(type.indexOf('pusher:') == 0 || !this.template[type]){
|
||||
return;
|
||||
}
|
||||
|
||||
let finalMessage = this.template[type](data);
|
||||
let receiver = this.getReceiver();
|
||||
if(typeof receiver === 'object'){
|
||||
if('group' in receiver){
|
||||
this.app.robot.sendToGroup(receiver.group, finalMessage);
|
||||
}
|
||||
if('user' in receiver){
|
||||
this.app.robot.sendToUser(receiver.user, finalMessage);
|
||||
}
|
||||
}
|
||||
} catch(ex){
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Channel;
|
@ -0,0 +1,8 @@
|
||||
import koa from "koa";
|
||||
import { FullRestfulContext } from "../../RestfulApiManager";
|
||||
|
||||
export class IndexController {
|
||||
public static async index(ctx: FullRestfulContext, next: koa.Next) {
|
||||
ctx.body = "Isekai Feedbot endpoint.";
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import Koa from 'koa';
|
||||
import { FullRestfulContext } from '../../RestfulApiManager';
|
||||
|
||||
export class SubscribeController {
|
||||
static async getTargetList(ctx: FullRestfulContext, next: Koa.Next) {
|
||||
|
||||
}
|
||||
|
||||
static async getTargetSubscribeList(ctx: FullRestfulContext, next: Koa.Next) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import Router from "koa-router";
|
||||
import App from "../App";
|
||||
import { RestfulApiManager, RestfulContext } from "../RestfulApiManager";
|
||||
import { SubscribeController } from "./controller/SubscribeController";
|
||||
|
||||
export function makeRoutes(routes: Router<any, RestfulContext>, manager: RestfulApiManager, app: App) {
|
||||
// 订阅管理
|
||||
routes.all('/subscribe', manager.tokenMiddlewareFactory(['subscribe'])); // 权限检测
|
||||
routes.get('/subscribe', SubscribeController.getTargetList); // 获取订阅目标列表
|
||||
routes.get('/subscribe/:robot/:targetType/:targetId', SubscribeController.getTargetSubscribeList); // 获取订阅列表
|
||||
|
||||
// 推送消息
|
||||
routes.all('/push', manager.tokenMiddlewareFactory(['push'])); // 权限检测
|
||||
}
|
Loading…
Reference in New Issue