From a3920c84e126ac750b2262ff62453c3c632f32b4 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 13:17:42 +0800 Subject: [PATCH 01/17] Init HAWechaty Architecture --- bin/main.ts | 8 +- package.json | 1 + src/chatops.ts | 18 +++-- src/config.ts | 1 + src/get-wechaty.ts | 23 +++--- src/ha-wechaty.ts | 176 ++++++++++++++++++++++++++++++++++++++++++ src/issue-handlers.ts | 24 +++--- src/routers.ts | 152 ++++++++++++++++++++++++------------ src/start-bot.ts | 18 ++--- src/start-finis.ts | 15 ++-- src/typings.d.ts | 1 + 11 files changed, 339 insertions(+), 98 deletions(-) create mode 100644 src/ha-wechaty.ts create mode 100644 src/typings.d.ts diff --git a/bin/main.ts b/bin/main.ts index ecef59f..35d1d6c 100644 --- a/bin/main.ts +++ b/bin/main.ts @@ -6,7 +6,7 @@ import { log, VERSION, } from '../src/config' -import { getWechaty } from '../src/get-wechaty' +import { getHAWechaty } from '../src/get-wechaty' import { startBot } from '../src/start-bot' import { startFinis } from '../src/start-finis' @@ -40,7 +40,7 @@ export = async (app: Application) => { log.verbose('main', 'main()') - const bot = getWechaty() + const bot = getHAWechaty() await Promise.all([ bot.start(), @@ -48,7 +48,5 @@ export = async (app: Application) => { startFinis(bot), ]) - while (bot.state.on()) { - await new Promise(resolve => setTimeout(resolve, 1000)) - } + await bot.state.ready('off') } diff --git a/package.json b/package.json index 496d582..b8df094 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "homepage": "https://github.com/kaiyuanshe/OSSChat#readme", "dependencies": { + "array-flatten": "^3.0.0", "brolog": "^1.6.5", "commander": "^4.0.1", "finis": "^0.4.3", diff --git a/src/chatops.ts b/src/chatops.ts index c8bcdfd..47f05dc 100644 --- a/src/chatops.ts +++ b/src/chatops.ts @@ -1,5 +1,4 @@ import { - Wechaty, UrlLink, Message, } from 'wechaty' @@ -12,19 +11,20 @@ import { DEV_ROOM_ID, HEARTBEAT_ROOM_ID, } from './config' +import { HAWechaty } from './ha-wechaty' export class Chatops { private static singleton: Chatops public static instance ( - bot?: Wechaty, + haBot?: HAWechaty, ) { if (!this.singleton) { - if (!bot) { + if (!haBot) { throw new Error('instance need a Wechaty instance to initialize') } - this.singleton = new Chatops(bot) + this.singleton = new Chatops(haBot) } return this.singleton } @@ -38,7 +38,7 @@ export class Chatops { private delayQueueExecutor: DelayQueueExecutor private constructor ( - private bot: Wechaty, + private haBot: HAWechaty, ) { this.delayQueueExecutor = new DelayQueueExecutor(5 * 1000) // set delay period time to 5 seconds } @@ -61,13 +61,17 @@ export class Chatops { ): Promise { log.info('Chatops', 'roomMessage(%s, %s)', roomId, info) - const online = this.bot.logonoff() + const online = this.haBot.logonoff() if (!online) { log.error('Chatops', 'roomMessage() this.bot is offline') return } - const room = this.bot.Room.load(roomId) + const room = await this.haBot.Room.load(roomId) + if (!room) { + log.error('Chatops', 'roomMessage() no bot found in room %s', roomId) + return + } if (typeof info === 'string') { await room.say(info) diff --git a/src/config.ts b/src/config.ts index fd0ed63..50774a3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,4 @@ +/// /** * VERSION */ diff --git a/src/get-wechaty.ts b/src/get-wechaty.ts index c3f4dfc..28fa673 100644 --- a/src/get-wechaty.ts +++ b/src/get-wechaty.ts @@ -1,7 +1,3 @@ -import { - Wechaty, -} from 'wechaty' - import { log, } from './config' @@ -10,26 +6,29 @@ import { } from './get-memory' import { Chatops } from './chatops' -let wechaty: Wechaty +import { HAWechaty } from './ha-wechaty' + +// let wechaty: Wechaty +let haWechaty: HAWechaty -export function getWechaty (): Wechaty { - log.verbose('getWechaty', 'getWechaty()') +export function getHAWechaty (): HAWechaty { + log.verbose('getWechaty', 'getHAWechaty()') - if (wechaty) { - return wechaty + if (haWechaty) { + return haWechaty } const name = process.env.WECHATY_NAME || 'heroku-wechaty' const memory = getMemory(name) - wechaty = new Wechaty({ + haWechaty = new HAWechaty({ memory, name, }) // Initialize Chatops Instance: - Chatops.instance(wechaty) + Chatops.instance(haWechaty) - return wechaty + return haWechaty } diff --git a/src/ha-wechaty.ts b/src/ha-wechaty.ts new file mode 100644 index 0000000..e6c830c --- /dev/null +++ b/src/ha-wechaty.ts @@ -0,0 +1,176 @@ +import { + Wechaty, + WechatyOptions, + Room, +} from 'wechaty' + +import { StateSwitch } from 'state-switch' + +import flattenArray from 'flatten-array' + +import { + log, +} from './config' +import { WechatyEventName } from 'wechaty/dist/src/wechaty' + +export class HAWechaty { + + public state: StateSwitch + + public wechatyList: Wechaty[] + + public Room = { + findAll : this.roomFindAll.bind(this), + load : this.roomLoad.bind(this), + } + + public async roomFindAll (): Promise { + log.verbose('HAWechaty', 'roomFindAll()') + const roomListList = Promise.all( + this.wechatyList.map( + wechaty => wechaty.Room.findAll() + ) + ) + + const roomList = [] as Room[] + + /** + * allRoomList may contain one room for multiple times + * because we have more than one bot in the same room + */ + const allRoomList = flattenArray(roomListList) as Room[] + for (const room of allRoomList) { + const exist = roomList.some(r => r.id === room.id) + if (exist) { + // We have a room in our list, so skip this one + continue + } + roomList.push(room) + } + return roomList + } + + public async roomLoad (id: string): Promise { + log.verbose('HAWechaty', 'roomLoad(%s)', id) + const roomList = this.wechatyList.map( + wechaty => wechaty.Room.load(id) + ) + + for (const room of roomList) { + try { + await room.ready() + if (room.isReady()) { + log.verbose('HAWechaty', 'roomLoad() %s has room id %s', room.wechaty, room.id) + return room + } + } catch (e) { + log.verbose('HAWechaty', 'roomLoad() %s has no room id %s', room.wechaty, room.id) + } + } + + return null + } + + constructor ( + public options: WechatyOptions, + ) { + log.verbose('HAWechaty', 'constructor("%s")', JSON.stringify(options)) + this.wechatyList = [] + this.state = new StateSwitch('HAWechaty') + } + + public async start () { + log.verbose('HAWechaty', 'start()') + + try { + this.state.on('pending') + + if (process.env.WECHATY_PUPPET_HOSTIE_TOKEN) { + this.wechatyList.push( + new Wechaty({ + ...this.options, + puppet: 'wechaty-puppet-hostie', + }), + ) + } + + if (process.env.WECHATY_PUPPET_PADPLUS_TOKEN) { + this.wechatyList.push( + new Wechaty({ + ...this.options, + puppet: 'wechaty-puppet-padplus', + }), + ) + } + + if (this.wechatyList.length <= 0) { + throw new Error('no wechaty puppet found') + } + + await Promise.all( + this.wechatyList.map( + wechaty => wechaty.start() + ) + ) + + this.state.on(true) + + } catch (e) { + log.warn('HAWechaty', 'start() rejection: %s', e) + this.state.off(true) + } + + } + + public async stop () { + log.verbose('HAWechaty', 'stop()') + + try { + this.state.off('pending') + + await Promise.all( + this.wechatyList.map( + wechaty => wechaty.stop() + ) + ) + } catch (e) { + log.warn('HAWechaty', 'stop() rejection: %s', e) + throw e + } finally { + this.state.off(true) + } + } + + public logonoff (): boolean { + log.verbose('HAWechaty', 'logonoff()') + return this.wechatyList.some(wechaty => wechaty.logonoff()) + } + + public on ( + eventName : WechatyEventName, + handlerModule : string | Function, + ): this { + this.wechatyList.forEach(wechaty => wechaty.on(eventName as any, handlerModule as any)) + return this + } + + public logout (): void { + log.verbose('HAWechaty', 'logout()') + + this.wechatyList.forEach( + wechaty => wechaty.logout() + ) + } + + public async say (text: string): Promise { + log.verbose('HAWechaty', 'say(%s)', text) + this.wechatyList.forEach(wechaty => wechaty.say(text)) + } + + public name (): string { + return this.wechatyList + .map(wechaty => wechaty.name()) + .join(',') + } + +} diff --git a/src/issue-handlers.ts b/src/issue-handlers.ts index 6b14cdf..b803104 100644 --- a/src/issue-handlers.ts +++ b/src/issue-handlers.ts @@ -9,7 +9,7 @@ import { Room, } from 'wechaty' -import { getWechaty } from './get-wechaty' +import { getHAWechaty } from './get-wechaty' import { Chatops } from './chatops' @@ -99,10 +99,10 @@ export const commentIssue: OnCallback = asy // console.info(context) } -function getRoomList ( +async function getRoomList ( owner : string, repository : string, -): Room[] { +): Promise { log.verbose('issue-handler', 'getRoom(%s, %s, config)', owner, repository) const managedList = Object.keys(managedRepoConfig) @@ -130,15 +130,17 @@ function getRoomList ( // matchedList = exactMatchList // } - const idsToRooms = (idOrList: string | string[]) => { + const idsToRooms = async (idOrList: string | string[]) => { if (Array.isArray(idOrList)) { - return idOrList.map( - id => getWechaty().Room.load(id) + const roomList = await Promise.all( + idOrList.map( + id => getHAWechaty().Room.load(id) + ) ) + return roomList.filter(r => !!r) as Room[] } else { - return [ - getWechaty().Room.load(idOrList), - ] + const room = await getHAWechaty().Room.load(idOrList) + return room ? [ room ] : [] } } @@ -154,7 +156,7 @@ function getRoomList ( } // make the id unique (in case an id appear in different repo configs) - const roomList = idsToRooms( + const roomList = await idsToRooms( [...new Set(roomIdList)], ) @@ -183,7 +185,7 @@ async function manageIssue ( 'issue card for chatops', ) - const roomList = getRoomList(owner, repository) + const roomList = await getRoomList(owner, repository) if (roomList.length <= 0) { return } diff --git a/src/routers.ts b/src/routers.ts index ac65934..c87cb0e 100644 --- a/src/routers.ts +++ b/src/routers.ts @@ -5,31 +5,66 @@ import { Response, } from 'express' +import { + Room, + ScanStatus, + Contact, + Wechaty, +} from 'wechaty' + import { log, VERSION, } from './config' import { Chatops } from './chatops' -import { getWechaty } from './get-wechaty' -import { Room } from 'wechaty' - -const bot = getWechaty() - -bot.on('scan', qrcode => { - qrcodeValue = qrcode - userName = undefined -}) -bot.on('login', user => { - qrcodeValue = undefined - userName = user.name() -}) -bot.on('logout', () => { - qrcodeValue = undefined - userName = undefined -}) - -let qrcodeValue: undefined | string -let userName: undefined | string +import { getHAWechaty } from './get-wechaty' + +const haBot = getHAWechaty() + +interface BotInfo { + qrcode?: string + userName?: string +} + +const botInfo = new WeakMap() + +haBot.on( + 'scan', + function ( + this: Wechaty, + qrcode: string, + status: ScanStatus, + ) { + void status + const info = { ...botInfo.get(this) } as BotInfo + info.qrcode = qrcode + info.userName = undefined + botInfo.set(this, info) + }, +) +haBot.on( + 'login', + function ( + this: Wechaty, + user: Contact, + ) { + const info = { ...botInfo.get(this) } as BotInfo + info.qrcode = undefined + info.userName = user.name() + botInfo.set(this, info) + }, +) +haBot.on( + 'logout', + function ( + this: Wechaty, + ) { + const info = { ...botInfo.get(this) } as BotInfo + info.qrcode = undefined + info.userName = undefined + botInfo.set(this, info) + }, +) const FORM_HTML = `
@@ -60,7 +95,7 @@ async function logoutHandler ( } = req.query as { secret?: string } if (secret && secret === process.env.HUAN_SECRET) { - await bot.logout() + await haBot.logout() await Chatops.instance().say('Logout request from web accepted') res.end('logged out') @@ -91,25 +126,65 @@ async function chatopsHandler ( return res.redirect('/') } -async function rootHandler (_req: Request, res: Response) { +async function rootHandler ( + _req: Request, + res: Response, +) { + let html = '' + for (const wechaty of haBot.wechatyList) { + const info = botInfo.get(wechaty) + html += [ + '
\n', + await rootHtml(wechaty, info), + '
\n', + ].join('') + } + + const htmlHead = ` + + + + + + + ` + + const htmlFoot = ` + + ` + + res.end( + [ + htmlHead, + html, + htmlFoot, + ].join('\n') + ) + +} + +async function rootHtml ( + wechaty: Wechaty, + info: BotInfo = {}, +) { let html - if (qrcodeValue) { + if (info.qrcode) { html = [ `

OSSChat v${VERSION}

`, 'Scan QR Code:
', - qrcodeValue + '
', + info.qrcode + '
', 'http://goqr.me/
', '\n\n', '', ].join('') - } else if (userName) { - let rooms = await bot.Room.findAll() + } else if (info.userName) { + let rooms = await wechaty.Room.findAll() rooms = rooms.sort((a, b) => a.id > b.id ? 1 : -1) let roomHtml = `The rooms I have joined are as follows:
    ` @@ -128,7 +203,7 @@ async function rootHandler (_req: Request, res: Response) { roomHtml = roomHtml + `
` html = [ - `

OSSChat v${VERSION} User ${userName} logined.

`, + `

OSSChat v${VERSION} User ${info.userName} logined.

`, FORM_HTML, roomHtml, ].join('') @@ -138,24 +213,5 @@ async function rootHandler (_req: Request, res: Response) { } - const htmlHead = ` - - - - - - - ` - - const htmlFoot = ` - - ` - - res.end( - [ - htmlHead, - html, - htmlFoot, - ].join('\n') - ) + return html } diff --git a/src/start-bot.ts b/src/start-bot.ts index 08c504a..022b5d8 100644 --- a/src/start-bot.ts +++ b/src/start-bot.ts @@ -1,5 +1,4 @@ import { - Wechaty, Contact, } from 'wechaty' @@ -12,11 +11,12 @@ import { import { Wtmp, } from './wtmp' +import { HAWechaty } from './ha-wechaty' -export async function startBot (wechaty: Wechaty): Promise { - log.verbose('startBot', 'startBot(%s)', wechaty) +export async function startBot (haWechaty: HAWechaty): Promise { + log.verbose('startBot', 'startBot(%s)', haWechaty) - wechaty + haWechaty .on('scan', './handlers/on-scan') .on('error', './handlers/on-error') .on('friendship', './handlers/on-friendship') @@ -33,14 +33,14 @@ export async function startBot (wechaty: Wechaty): Promise { } const ONE_HOUR = 60 * 60 * 1000 setInterval(heartbeat('💖'), ONE_HOUR) - wechaty.on('login', heartbeat(`🙋 (${wechaty.name()})`)) - wechaty.on('ready', heartbeat(`💪 (${wechaty.name()})`)) - wechaty.on('logout', heartbeat(`😪 (${wechaty.name()})`)) + haWechaty.on('login', heartbeat(`🙋 (${haWechaty.name()})`)) + haWechaty.on('ready', heartbeat(`💪 (${haWechaty.name()})`)) + haWechaty.on('logout', heartbeat(`😪 (${haWechaty.name()})`)) const wtmp = Wtmp.instance() const loginWtmp = (user: Contact) => wtmp.login(user.name()) const logoutWtmp = (user: Contact) => wtmp.logout(user.name()) - wechaty.on('login', loginWtmp) - wechaty.on('logout', logoutWtmp) + haWechaty.on('login', loginWtmp) + haWechaty.on('logout', logoutWtmp) } diff --git a/src/start-finis.ts b/src/start-finis.ts index 0476248..23c76ed 100644 --- a/src/start-finis.ts +++ b/src/start-finis.ts @@ -1,5 +1,7 @@ import { finis } from 'finis' -import { Wechaty } from 'wechaty' +import { + Contact, +} from 'wechaty' import { Chatops, @@ -9,6 +11,7 @@ import { VERSION, debug, } from './config' +import { HAWechaty } from './ha-wechaty' const BOT_NAME = 'OSSChat' @@ -16,16 +19,16 @@ const LOGIN_ANNOUNCEMENT = `Der! I just got online!\n${BOT_NAME} v${VERSION}` // const LOGOUT_ANNOUNCEMENT = `Der! I'm going to offline now, see you, bye!\nOSSChat v${VERSION}` const EXIT_ANNOUNCEMENT = `Der! I'm going to exit now, see you, bye!\n${BOT_NAME} v${VERSION}` -let bot: undefined | Wechaty +let bot: undefined | HAWechaty -export async function startFinis (wechaty: Wechaty): Promise { +export async function startFinis (haWechaty: HAWechaty): Promise { if (bot) { throw new Error('startFinis should only init once') } - bot = wechaty + bot = haWechaty - bot.on('login', _ => Chatops.instance().say(LOGIN_ANNOUNCEMENT)) - bot.on('logout', user => log.info('RestartReporter', 'startFinis() bot %s logout', user)) + bot.on('login', async function () { await Chatops.instance().say(LOGIN_ANNOUNCEMENT) }) + bot.on('logout', function (user: Contact) { log.info('RestartReporter', 'startFinis() bot %s logout', user) }) } /** diff --git a/src/typings.d.ts b/src/typings.d.ts new file mode 100644 index 0000000..6b8c4c2 --- /dev/null +++ b/src/typings.d.ts @@ -0,0 +1 @@ +declare module 'flatten-array' From 82fb5a261ce09850e001e5f9af0e27029b57e35d Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 13:17:58 +0800 Subject: [PATCH 02/17] v0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8df094..f200a7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.2.53", + "version": "0.3.0", "description": "Apache OSSChat", "main": "index.js", "engines": { From 12e19ca1448683df9cfa3a88229bd1b43c6fa0c5 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 13:18:14 +0800 Subject: [PATCH 03/17] 0.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f200a7a..e8f798b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.3.0", + "version": "0.3.1", "description": "Apache OSSChat", "main": "index.js", "engines": { From 6c7d5be538b883d58ff1ecdebed46fd5467988d4 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 13:24:03 +0800 Subject: [PATCH 04/17] fix smoke testing --- tests/fixtures/smoke-testing.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/fixtures/smoke-testing.ts b/tests/fixtures/smoke-testing.ts index 2006662..50496ce 100644 --- a/tests/fixtures/smoke-testing.ts +++ b/tests/fixtures/smoke-testing.ts @@ -1,18 +1,18 @@ #!/usr/bin/env ts-node -import { getWechaty } from '../../src/get-wechaty' +import { getHAWechaty } from '../../src/get-wechaty' import { startBot } from '../../src/start-bot' import { startFinis } from '../../src/start-finis' process.env.WECHATY_PUPPET = 'wechaty-puppet-mock' async function main () { - const bot = getWechaty() + const haBot = getHAWechaty() await Promise.all([ - bot.start(), - startBot(bot), - startFinis(bot), + haBot.start(), + startBot(haBot), + startFinis(haBot), ]) return 0 From 3976c6af2a5bf38850694698c73136ce3a68eb03 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 13:24:16 +0800 Subject: [PATCH 05/17] 0.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8f798b..c26af58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.3.1", + "version": "0.3.2", "description": "Apache OSSChat", "main": "index.js", "engines": { From 80bafc4c81193a5f36bfa9075ad5f405c5a00728 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 14:27:43 +0800 Subject: [PATCH 06/17] fix smoke testing --- .travis.yml | 2 +- package.json | 3 +++ src/ha-wechaty.ts | 11 +++++++++++ tests/fixtures/smoke-testing.ts | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e7d137a..23db68a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ script: - node --version - npm --version - npm test - - ./node_modules/.bin/ts-node tests/fixtures/smoke-testing.ts + - ./node_modules/.bin/ts-node --files tests/fixtures/smoke-testing.ts notifications: email: diff --git a/package.json b/package.json index c26af58..afadefc 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "node": "10" }, "scripts": { + "clean": "shx rm -fr dist/*", "build": "tsc", "lint": "npm run lint:es && npm run lint:ts", "lint:ts": "tsc --noEmit", @@ -30,6 +31,7 @@ "brolog": "^1.6.5", "commander": "^4.0.1", "finis": "^0.4.3", + "flatten-array": "^1.0.0", "micromatch": "^4.0.2", "moment": "^2.24.0", "node-cache": "^5.1.0", @@ -49,6 +51,7 @@ "@types/read-pkg-up": "^6.0.0", "eslint": "^6.8.0", "express": "^4.17.1", + "shx": "^0.3.2", "tstest": "^0.4.2" }, "git": { diff --git a/src/ha-wechaty.ts b/src/ha-wechaty.ts index e6c830c..fae4ab8 100644 --- a/src/ha-wechaty.ts +++ b/src/ha-wechaty.ts @@ -103,10 +103,21 @@ export class HAWechaty { ) } + if (process.env.WECHATY_PUPPET_MOCK_TOKEN) { + this.wechatyList.push( + new Wechaty({ + ...this.options, + puppet: 'wechaty-puppet-mock', + }), + ) + } + if (this.wechatyList.length <= 0) { throw new Error('no wechaty puppet found') } + log.info('HAWechaty', 'start() %s puppet inited', this.wechatyList.length) + await Promise.all( this.wechatyList.map( wechaty => wechaty.start() diff --git a/tests/fixtures/smoke-testing.ts b/tests/fixtures/smoke-testing.ts index 50496ce..c237398 100644 --- a/tests/fixtures/smoke-testing.ts +++ b/tests/fixtures/smoke-testing.ts @@ -5,6 +5,7 @@ import { startBot } from '../../src/start-bot' import { startFinis } from '../../src/start-finis' process.env.WECHATY_PUPPET = 'wechaty-puppet-mock' +process.env.WECHATY_PUPPET_MOCK_TOKEN = 'mock-token' async function main () { const haBot = getHAWechaty() From a8c7765d90366290feb0775dc1487eb94a9658ef Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 14:27:57 +0800 Subject: [PATCH 07/17] 0.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afadefc..3a97512 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.3.2", + "version": "0.3.3", "description": "Apache OSSChat", "main": "index.js", "engines": { From e6dcfb75e2a59e96f70c50ac9f29a7d4cbcf8ebe Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 14:39:54 +0800 Subject: [PATCH 08/17] Support to specify puppet type list --- src/ha-wechaty.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ha-wechaty.ts b/src/ha-wechaty.ts index fae4ab8..7dd2d54 100644 --- a/src/ha-wechaty.ts +++ b/src/ha-wechaty.ts @@ -85,7 +85,16 @@ export class HAWechaty { try { this.state.on('pending') - if (process.env.WECHATY_PUPPET_HOSTIE_TOKEN) { + const wechatyPuppet = process.env.WECHATY_PUPPET || '' + const wechatyPuppetList = wechatyPuppet + .split(':') + .filter(v => !!v) + .map(v => v.toUpperCase()) + .map(v => v.replace(/-/g, '_')) + + if (wechatyPuppetList.includes('WECHATY_PUPPET_HOSTIE') + && process.env.WECHATY_PUPPET_HOSTIE_TOKEN + ) { this.wechatyList.push( new Wechaty({ ...this.options, @@ -94,7 +103,9 @@ export class HAWechaty { ) } - if (process.env.WECHATY_PUPPET_PADPLUS_TOKEN) { + if (wechatyPuppetList.includes('WECHATY_PUPPET_PADPLUS') + && process.env.WECHATY_PUPPET_PADPLUS_TOKEN + ) { this.wechatyList.push( new Wechaty({ ...this.options, @@ -103,7 +114,9 @@ export class HAWechaty { ) } - if (process.env.WECHATY_PUPPET_MOCK_TOKEN) { + if (wechatyPuppetList.includes('WECHATY_PUPPET_MOCK') + && process.env.WECHATY_PUPPET_MOCK_TOKEN + ) { this.wechatyList.push( new Wechaty({ ...this.options, From 1d1586c62830f566faa9167b1c9e508a173e77f7 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 14:40:08 +0800 Subject: [PATCH 09/17] 0.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a97512..9380d53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.3.3", + "version": "0.3.4", "description": "Apache OSSChat", "main": "index.js", "engines": { From 6db51ce619806f57a06e44a39589ad4f2c4c14d7 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 19:02:06 +0800 Subject: [PATCH 10/17] add Redux --- README.md | 5 ++ package.json | 2 + src/redux.ts | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 src/redux.ts diff --git a/README.md b/README.md index 36a8ded..0ae6b03 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ We are current DevOps the master branch from the repo to Heroku under the protec You can visit the staging system at ## How to use + use osschat is so easy, just need 4 steps, please refer [How to use](https://github.com/kaiyuanshe/osschat/blob/master/docs/pages/how-to-use.md) ## Meeting Notes @@ -98,6 +99,10 @@ To be added... - Wechaty Puppet Padplus sponsored by: [JuziBot](https://www.juzi.bot) - Heroku Getting Started Template from [Wechaty](https://github.com/wechaty/) +## Links + +- [Scaling your Redux App with ducks](https://www.freecodecamp.org/news/scaling-your-redux-app-with-ducks-6115955638be/) + ## Copyright & License - Code & Docs © 2019-now 开源社 diff --git a/package.json b/package.json index 9380d53..84e9816 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "clean": "shx rm -fr dist/*", + "dist": "npm run build", "build": "tsc", "lint": "npm run lint:es && npm run lint:ts", "lint:ts": "tsc --noEmit", @@ -37,6 +38,7 @@ "node-cache": "^5.1.0", "probot": "^9.8.1", "qrcode-terminal": "^0.12.0", + "redux": "^4.0.5", "smee-client": "^1.1.0", "wechaty": "^0.37.8", "wechaty-puppet-padplus": "^0.5.27" diff --git a/src/redux.ts b/src/redux.ts new file mode 100644 index 0000000..c98cc72 --- /dev/null +++ b/src/redux.ts @@ -0,0 +1,126 @@ +import { + combineReducers, + createStore, +} from 'redux' + +interface LogonoffState { + [k: string]: { + qrcode? : string, + userName? : string, + } +} + +interface CounterState { + mt: number, + mo: number, +} + +// interface OssChatState { +// logonoff : LogonoffState, +// counter : CounterState, +// } + +function logonoff ( + state: LogonoffState = {}, + action: Action, +) { + if (!action) { + return state + } + + const newState = { ...state } as LogonoffState + + switch (action.type) { + case 'SCAN': + newState[action.wechaty.id] = { + qrcode: action.qrcode, + } + return newState + case 'LOGIN': + newState[action.wechaty.id] = { + userName: action.userName, + } + return newState + case 'LOGOUT': + newState[action.wechaty.id] = {} + return newState + default: + return state + } +} + +const DEFAULT_COUNTER_STATE = { + mo: 0, + mt: 0, +} as CounterState + +function counter ( + state = DEFAULT_COUNTER_STATE, + action: any, +) { + switch (action.type) { + case 'MESSAGE/MO': + return { + ...state, + mo: state.mo + 1, + } + case 'MESSAGE/MT': + return { + ...state, + mt: state.mt + 1, + } + default: + return state + } +} + +// const DEFAULT_OSSCHAT_STATE = { +// counter: { +// mo: 0, +// mt: 0, +// }, +// logonoff: {}, +// } as OssChatState + +// function osschatApp( +// state: OssChatState = DEFAULT_OSSCHAT_STATE, +// action: Action, +// ) { +// return { +// counter : counter(state.counter, action), +// logonoff : logonoff(state.logonoff, action), +// } +// } + +const reducer = combineReducers({ + counter, + logonoff, +}) + +const store = createStore(reducer) + +interface Action { + type: string, + [k: string]: any, +} + +// You can use subscribe() to update the UI in response to state changes. +// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly. +// However it can also be handy to persist the current state in the localStorage. + +store.subscribe(() => console.info(store.getState())) + +// The only way to mutate the internal state is to dispatch an action. +// The actions can be serialized, logged or stored and later replayed. +store.dispatch({ type: 'MESSAGE/MO' }) +// 1 +store.dispatch({ type: 'MESSAGE/MT' }) +// 2 +store.dispatch({ + type: 'LOGIN', + userName: 'test name', + wechaty: { + id: 'wechatyid', + }, +}) +// 1 From f8a1d96121524f6e0202c3e776cd7301a7796e15 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 19:02:28 +0800 Subject: [PATCH 11/17] add ducks --- src/ducks/counter/actions.ts | 9 ++++++++ src/ducks/counter/index.ts | 14 ++++++++++++ src/ducks/counter/operations.ts | 14 ++++++++++++ src/ducks/counter/reducers.ts | 30 ++++++++++++++++++++++++ src/ducks/counter/selectors.ts | 9 ++++++++ src/ducks/counter/tests.ts | 16 +++++++++++++ src/ducks/counter/types.ts | 2 ++ src/ducks/create-reducer.ts | 4 ++++ src/ducks/index.ts | 25 ++++++++++++++++++++ src/ducks/logonoff/actions.ts | 19 ++++++++++++++++ src/ducks/logonoff/index.ts | 14 ++++++++++++ src/ducks/logonoff/operations.ts | 14 ++++++++++++ src/ducks/logonoff/reducers.ts | 39 ++++++++++++++++++++++++++++++++ src/ducks/logonoff/selectors.ts | 9 ++++++++ src/ducks/logonoff/tests.ts | 16 +++++++++++++ src/ducks/logonoff/types.ts | 13 +++++++++++ 16 files changed, 247 insertions(+) create mode 100644 src/ducks/counter/actions.ts create mode 100644 src/ducks/counter/index.ts create mode 100644 src/ducks/counter/operations.ts create mode 100644 src/ducks/counter/reducers.ts create mode 100644 src/ducks/counter/selectors.ts create mode 100644 src/ducks/counter/tests.ts create mode 100644 src/ducks/counter/types.ts create mode 100644 src/ducks/create-reducer.ts create mode 100644 src/ducks/index.ts create mode 100644 src/ducks/logonoff/actions.ts create mode 100644 src/ducks/logonoff/index.ts create mode 100644 src/ducks/logonoff/operations.ts create mode 100644 src/ducks/logonoff/reducers.ts create mode 100644 src/ducks/logonoff/selectors.ts create mode 100644 src/ducks/logonoff/tests.ts create mode 100644 src/ducks/logonoff/types.ts diff --git a/src/ducks/counter/actions.ts b/src/ducks/counter/actions.ts new file mode 100644 index 0000000..56b3137 --- /dev/null +++ b/src/ducks/counter/actions.ts @@ -0,0 +1,9 @@ +import * as types from './types' + +export const mt = () => ({ + type: types.MT, +}) + +export const mo = () => ({ + type: types.MO, +}) diff --git a/src/ducks/counter/index.ts b/src/ducks/counter/index.ts new file mode 100644 index 0000000..b65c5b4 --- /dev/null +++ b/src/ducks/counter/index.ts @@ -0,0 +1,14 @@ +import reducer from './reducers' + +// export { default as counterSelectors } from './selectors' +// export { default as counterOperations } from './operations' + +import * as counterActions from './actions' +import * as counterTypes from './types' + +export { + counterActions, + counterTypes, +} + +export default reducer diff --git a/src/ducks/counter/operations.ts b/src/ducks/counter/operations.ts new file mode 100644 index 0000000..fbb7c7d --- /dev/null +++ b/src/ducks/counter/operations.ts @@ -0,0 +1,14 @@ +// import { + +// } from './actions' + +// // This is a link to an action defined in actions.js. +// export const simpleQuack = actions.quack + +// // This is a thunk which dispatches multiple actions from actions.js +// export const complexQuack = ( distance ) => ( dispatch ) => { +// dispatch( actions.quack( ) ).then( ( ) => { +// dispatch( actions.swim( distance ) ); +// dispatch( /* any action */ ); +// } ); +// } diff --git a/src/ducks/counter/reducers.ts b/src/ducks/counter/reducers.ts new file mode 100644 index 0000000..729a659 --- /dev/null +++ b/src/ducks/counter/reducers.ts @@ -0,0 +1,30 @@ +import createReducer from '../create-reducer' + +import * as types from './types' + +interface CounterState { + mt: number, + mo: number, +} + +const initialState = { + mo: 0, + mt: 0, +} as CounterState + +const moReducer = (state: CounterState) => ({ + ...state, + mo: state.mo + 1, +}) + +const mtReducer = (state: CounterState) => ({ + ...state, + mt: state.mt + 1, +}) + +const counterReducer = createReducer(initialState)({ + [types.MO]: moReducer, + [types.MT]: mtReducer, +}) + +export default counterReducer diff --git a/src/ducks/counter/selectors.ts b/src/ducks/counter/selectors.ts new file mode 100644 index 0000000..1f9d889 --- /dev/null +++ b/src/ducks/counter/selectors.ts @@ -0,0 +1,9 @@ + +// export function online (duck: any) { +// return Object.keys(duck.logonoff).filter( +// id => { +// const info = duck.logonoff[id] +// return info.qrcode === undefined && info.userName !== undefined +// } +// ) +// } diff --git a/src/ducks/counter/tests.ts b/src/ducks/counter/tests.ts new file mode 100644 index 0000000..d2a076a --- /dev/null +++ b/src/ducks/counter/tests.ts @@ -0,0 +1,16 @@ +// import expect from 'expect.js' +// import reducer from './reducers' +// import actions from './actions' + +// describe('duck reducer', function( ) { +// describe('quack', function( ) { +// const quack = actions.quack( ) +// const initialState = false + +// const result = reducer( initialState, quack ) + +// it( 'should quack', function( ) { +// expect( result ).to.be( true ) +// } ) +// } ) +// } ) diff --git a/src/ducks/counter/types.ts b/src/ducks/counter/types.ts new file mode 100644 index 0000000..e4ae49d --- /dev/null +++ b/src/ducks/counter/types.ts @@ -0,0 +1,2 @@ +export const MT = 'MESSAGE/MT' +export const MO = 'MESSAGE/MO' diff --git a/src/ducks/create-reducer.ts b/src/ducks/create-reducer.ts new file mode 100644 index 0000000..e440cfc --- /dev/null +++ b/src/ducks/create-reducer.ts @@ -0,0 +1,4 @@ +export default (initialState: any) => (reducerMap: any) => (state = initialState, action: any) => { + const reducer = reducerMap[action.type] + return reducer ? reducer(state, action) : state +} diff --git a/src/ducks/index.ts b/src/ducks/index.ts new file mode 100644 index 0000000..5c86ee8 --- /dev/null +++ b/src/ducks/index.ts @@ -0,0 +1,25 @@ +import { combineReducers } from 'redux' + +import logonoff, { + login, + logout, + scan, +} from './logonoff' + +import counter, { + mo, + mt, +} from './counter' + +export { + login, + logout, + scan, + mo, + mt, +} + +export default combineReducers({ + counter, + logonoff, +}) diff --git a/src/ducks/logonoff/actions.ts b/src/ducks/logonoff/actions.ts new file mode 100644 index 0000000..6406834 --- /dev/null +++ b/src/ducks/logonoff/actions.ts @@ -0,0 +1,19 @@ +import * as types from './types' + +export const scan = ( + payload: types.ActionScanPayload, +) => ({ + ...payload, + type: types.SCAN, +}) + +export const login = ( + payload: types.ActionLoginPayload, +) => ({ + ...payload, + type: types.LOGIN, +}) + +export const logout = () => ({ + type: types.LOGOUT, +}) diff --git a/src/ducks/logonoff/index.ts b/src/ducks/logonoff/index.ts new file mode 100644 index 0000000..1ab8096 --- /dev/null +++ b/src/ducks/logonoff/index.ts @@ -0,0 +1,14 @@ +import reducer from './reducers' + +import * as logonoffSelectors from './selectors' +// export { default as duckOperations } from './operations' +import * as logonoffTypes from './types' +import * as logonoffActions from './actions' + +export { + logonoffActions, + logonoffSelectors, + logonoffTypes, +} + +export default reducer diff --git a/src/ducks/logonoff/operations.ts b/src/ducks/logonoff/operations.ts new file mode 100644 index 0000000..fbb7c7d --- /dev/null +++ b/src/ducks/logonoff/operations.ts @@ -0,0 +1,14 @@ +// import { + +// } from './actions' + +// // This is a link to an action defined in actions.js. +// export const simpleQuack = actions.quack + +// // This is a thunk which dispatches multiple actions from actions.js +// export const complexQuack = ( distance ) => ( dispatch ) => { +// dispatch( actions.quack( ) ).then( ( ) => { +// dispatch( actions.swim( distance ) ); +// dispatch( /* any action */ ); +// } ); +// } diff --git a/src/ducks/logonoff/reducers.ts b/src/ducks/logonoff/reducers.ts new file mode 100644 index 0000000..94b3ebe --- /dev/null +++ b/src/ducks/logonoff/reducers.ts @@ -0,0 +1,39 @@ +import createReducer from '../create-reducer' + +import * as types from './types' + +interface LogonoffState { + [k: string]: { + qrcode? : string, + userName? : string, + } +} + +const initialState: LogonoffState = {} + +const scanReducer = (state: LogonoffState, action: any) => ({ + ...state, + [action.wechaty.id]: { + qrcode: action.qrcode, + }, +}) as LogonoffState + +const loginReducer = (state: LogonoffState, action: any) => ({ + ...state, + [action.wechaty.id]: { + userName: action.userName, + }, +}) as LogonoffState + +const logoutReducer = (state: LogonoffState, action: any) => ({ + ...state, + [action.wechaty.id]: {}, +}) as LogonoffState + +const logonoffReducer = createReducer(initialState)({ + [types.SCAN] : scanReducer, + [types.LOGIN] : loginReducer, + [types.LOGOUT] : logoutReducer, +}) + +export default logonoffReducer diff --git a/src/ducks/logonoff/selectors.ts b/src/ducks/logonoff/selectors.ts new file mode 100644 index 0000000..0098e10 --- /dev/null +++ b/src/ducks/logonoff/selectors.ts @@ -0,0 +1,9 @@ + +export function online (duck: any) { + return Object.keys(duck.logonoff).filter( + id => { + const info = duck.logonoff[id] + return info.qrcode === undefined && info.userName !== undefined + } + ) +} diff --git a/src/ducks/logonoff/tests.ts b/src/ducks/logonoff/tests.ts new file mode 100644 index 0000000..d2a076a --- /dev/null +++ b/src/ducks/logonoff/tests.ts @@ -0,0 +1,16 @@ +// import expect from 'expect.js' +// import reducer from './reducers' +// import actions from './actions' + +// describe('duck reducer', function( ) { +// describe('quack', function( ) { +// const quack = actions.quack( ) +// const initialState = false + +// const result = reducer( initialState, quack ) + +// it( 'should quack', function( ) { +// expect( result ).to.be( true ) +// } ) +// } ) +// } ) diff --git a/src/ducks/logonoff/types.ts b/src/ducks/logonoff/types.ts new file mode 100644 index 0000000..95d55a0 --- /dev/null +++ b/src/ducks/logonoff/types.ts @@ -0,0 +1,13 @@ +export const SCAN = 'SCAN' +export const LOGIN = 'LOGIN' +export const LOGOUT = 'LOGOUT' + +export interface ActionScanPayload { + id: string, + qrcode? : string, + userName? : string, +} + +export interface ActionLoginPayload { + userName: string, +} From afed781ad025d2393c257bf50e84e8a9794efed5 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 19:12:56 +0800 Subject: [PATCH 12/17] Redux with Ducks --- src/ducks/index.ts | 18 ++---- src/ducks/logonoff/types.ts | 3 + src/redux.ts | 126 ++++-------------------------------- 3 files changed, 23 insertions(+), 124 deletions(-) diff --git a/src/ducks/index.ts b/src/ducks/index.ts index 5c86ee8..7a9fbac 100644 --- a/src/ducks/index.ts +++ b/src/ducks/index.ts @@ -1,22 +1,16 @@ import { combineReducers } from 'redux' import logonoff, { - login, - logout, - scan, -} from './logonoff' + logonoffActions, +} from './logonoff' import counter, { - mo, - mt, -} from './counter' + counterActions, +} from './counter' export { - login, - logout, - scan, - mo, - mt, + logonoffActions, + counterActions, } export default combineReducers({ diff --git a/src/ducks/logonoff/types.ts b/src/ducks/logonoff/types.ts index 95d55a0..d25c5c6 100644 --- a/src/ducks/logonoff/types.ts +++ b/src/ducks/logonoff/types.ts @@ -10,4 +10,7 @@ export interface ActionScanPayload { export interface ActionLoginPayload { userName: string, + wechaty: { + id: string, + } } diff --git a/src/redux.ts b/src/redux.ts index c98cc72..6ca9619 100644 --- a/src/redux.ts +++ b/src/redux.ts @@ -1,126 +1,28 @@ import { - combineReducers, createStore, } from 'redux' -interface LogonoffState { - [k: string]: { - qrcode? : string, - userName? : string, - } -} - -interface CounterState { - mt: number, - mo: number, -} - -// interface OssChatState { -// logonoff : LogonoffState, -// counter : CounterState, -// } - -function logonoff ( - state: LogonoffState = {}, - action: Action, -) { - if (!action) { - return state - } - - const newState = { ...state } as LogonoffState - - switch (action.type) { - case 'SCAN': - newState[action.wechaty.id] = { - qrcode: action.qrcode, - } - return newState - case 'LOGIN': - newState[action.wechaty.id] = { - userName: action.userName, - } - return newState - case 'LOGOUT': - newState[action.wechaty.id] = {} - return newState - default: - return state - } -} - -const DEFAULT_COUNTER_STATE = { - mo: 0, - mt: 0, -} as CounterState - -function counter ( - state = DEFAULT_COUNTER_STATE, - action: any, -) { - switch (action.type) { - case 'MESSAGE/MO': - return { - ...state, - mo: state.mo + 1, - } - case 'MESSAGE/MT': - return { - ...state, - mt: state.mt + 1, - } - default: - return state - } -} - -// const DEFAULT_OSSCHAT_STATE = { -// counter: { -// mo: 0, -// mt: 0, -// }, -// logonoff: {}, -// } as OssChatState - -// function osschatApp( -// state: OssChatState = DEFAULT_OSSCHAT_STATE, -// action: Action, -// ) { -// return { -// counter : counter(state.counter, action), -// logonoff : logonoff(state.logonoff, action), -// } -// } - -const reducer = combineReducers({ - counter, - logonoff, -}) +import reducer, { + logonoffActions, + counterActions, +} from './ducks/' const store = createStore(reducer) -interface Action { - type: string, - [k: string]: any, -} - -// You can use subscribe() to update the UI in response to state changes. -// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly. -// However it can also be handy to persist the current state in the localStorage. - store.subscribe(() => console.info(store.getState())) // The only way to mutate the internal state is to dispatch an action. // The actions can be serialized, logged or stored and later replayed. -store.dispatch({ type: 'MESSAGE/MO' }) +store.dispatch(counterActions.mo()) // 1 -store.dispatch({ type: 'MESSAGE/MT' }) +store.dispatch(counterActions.mt()) // 2 -store.dispatch({ - type: 'LOGIN', - userName: 'test name', - wechaty: { - id: 'wechatyid', - }, -}) +store.dispatch( + logonoffActions.login({ + userName: 'test name', + wechaty: { + id: 'wechatyid', + }, + }), +) // 1 From c2a7da4eb17fbd07f46a52aadd3db7501ffc8b1a Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 22:28:55 +0800 Subject: [PATCH 13/17] use @reduxjs/toolkit --- package.json | 1 + src/ducks/counter/actions.ts | 11 ++--- src/ducks/counter/reducers.ts | 18 ++++--- src/ducks/create-reducer.ts | 4 -- src/ducks/index.ts | 14 +++++- src/ducks/logonoff/actions.ts | 51 ++++++++++++++------ src/ducks/logonoff/reducers.ts | 87 ++++++++++++++++++++++++---------- src/ducks/logonoff/types.ts | 13 ----- src/redux.ts | 31 +++++------- 9 files changed, 142 insertions(+), 88 deletions(-) delete mode 100644 src/ducks/create-reducer.ts diff --git a/package.json b/package.json index 84e9816..3516fe9 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "homepage": "https://github.com/kaiyuanshe/OSSChat#readme", "dependencies": { + "@reduxjs/toolkit": "^1.3.2", "array-flatten": "^3.0.0", "brolog": "^1.6.5", "commander": "^4.0.1", diff --git a/src/ducks/counter/actions.ts b/src/ducks/counter/actions.ts index 56b3137..34643ca 100644 --- a/src/ducks/counter/actions.ts +++ b/src/ducks/counter/actions.ts @@ -1,9 +1,6 @@ -import * as types from './types' +import { createAction } from '@reduxjs/toolkit' -export const mt = () => ({ - type: types.MT, -}) +import * as types from './types' -export const mo = () => ({ - type: types.MO, -}) +export const mo = createAction(types.MO) +export const mt = createAction(types.MT) diff --git a/src/ducks/counter/reducers.ts b/src/ducks/counter/reducers.ts index 729a659..0412fca 100644 --- a/src/ducks/counter/reducers.ts +++ b/src/ducks/counter/reducers.ts @@ -1,8 +1,11 @@ -import createReducer from '../create-reducer' +import { + createReducer, +} from '@reduxjs/toolkit' import * as types from './types' +// import * as actions from './actions' -interface CounterState { +export interface CounterState { mt: number, mo: number, } @@ -22,9 +25,12 @@ const mtReducer = (state: CounterState) => ({ mt: state.mt + 1, }) -const counterReducer = createReducer(initialState)({ - [types.MO]: moReducer, - [types.MT]: mtReducer, -}) +const counterReducer = createReducer( + initialState, + { + [types.MO]: moReducer, + [types.MT]: mtReducer, + }, +) export default counterReducer diff --git a/src/ducks/create-reducer.ts b/src/ducks/create-reducer.ts deleted file mode 100644 index e440cfc..0000000 --- a/src/ducks/create-reducer.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default (initialState: any) => (reducerMap: any) => (state = initialState, action: any) => { - const reducer = reducerMap[action.type] - return reducer ? reducer(state, action) : state -} diff --git a/src/ducks/index.ts b/src/ducks/index.ts index 7a9fbac..bbf846c 100644 --- a/src/ducks/index.ts +++ b/src/ducks/index.ts @@ -1,3 +1,13 @@ +/** + * Huan(202003): Redux with Ducks + * + * Scaling your Redux App with ducks: + * https://www.freecodecamp.org/news/scaling-your-redux-app-with-ducks-6115955638be/ + * + * Redux Toolkit - Usage With TypeScript: + * https://redux-toolkit.js.org/usage/usage-with-typescript + * + */ import { combineReducers } from 'redux' import logonoff, { @@ -13,7 +23,9 @@ export { counterActions, } -export default combineReducers({ +const reducer = combineReducers({ counter, logonoff, }) + +export default reducer diff --git a/src/ducks/logonoff/actions.ts b/src/ducks/logonoff/actions.ts index 6406834..f1f92f3 100644 --- a/src/ducks/logonoff/actions.ts +++ b/src/ducks/logonoff/actions.ts @@ -1,19 +1,40 @@ +import { + createAction, +} from '@reduxjs/toolkit' + import * as types from './types' -export const scan = ( - payload: types.ActionScanPayload, -) => ({ - ...payload, - type: types.SCAN, -}) +const prepareScan = ( + id: string, + qrcode: string, +) => { + const payload = { + id, + qrcode, + } + return { payload } +} + +const prepareLogin = ( + id: string, + userName: string, +) => { + const payload = { + id, + userName, + } + return { payload } +} -export const login = ( - payload: types.ActionLoginPayload, -) => ({ - ...payload, - type: types.LOGIN, -}) +const prepareLogout = ( + id: string, +) => { + const payload = { + id, + } + return { payload } +} -export const logout = () => ({ - type: types.LOGOUT, -}) +export const scan = createAction(types.SCAN, prepareScan) +export const login = createAction(types.LOGIN, prepareLogin) +export const logout = createAction(types.LOGOUT, prepareLogout) diff --git a/src/ducks/logonoff/reducers.ts b/src/ducks/logonoff/reducers.ts index 94b3ebe..51968ac 100644 --- a/src/ducks/logonoff/reducers.ts +++ b/src/ducks/logonoff/reducers.ts @@ -1,8 +1,12 @@ -import createReducer from '../create-reducer' +import { + createReducer, + Action, +} from '@reduxjs/toolkit' import * as types from './types' +import * as actions from './actions' -interface LogonoffState { +export interface LogonoffState { [k: string]: { qrcode? : string, userName? : string, @@ -11,29 +15,64 @@ interface LogonoffState { const initialState: LogonoffState = {} -const scanReducer = (state: LogonoffState, action: any) => ({ - ...state, - [action.wechaty.id]: { - qrcode: action.qrcode, - }, -}) as LogonoffState +const scanReducer = (state: LogonoffState, action: Action) => { + if (actions.scan.match(action)) { + return { + ...state, + [action.payload.id]: { + qrcode: action.payload.qrcode, + }, + } + } + return state +} + +const loginReducer = (state: LogonoffState, action: Action) => { + if (actions.login.match(action)) { + return { + ...state, + [action.payload.id]: { + userName: action.payload.userName, + }, + } + } + return state +} + +const logoutReducer = (state: LogonoffState, action: Action) => { + if (actions.logout.match(action)) { + return { + ...state, + [action.payload.id]: {}, + } + } + return state +} + +/** + * https://redux-toolkit.js.org/usage/usage-with-typescript#building-type-safe-reducer-argument-objects + */ +// const logonoffReducder = createReducer(0, builder => +// builder +// .addCase(types.SCAN, (state, action) => { +// // action is inferred correctly here +// console.info(state, action.payload) +// }) +// .addCase(types.LOGIN, (state, action) => { +// console.info(state, action) +// }) +// .addCase(types.LOGOUT, (state, action) => { +// console.info(state, action) +// }) +// ) -const loginReducer = (state: LogonoffState, action: any) => ({ - ...state, - [action.wechaty.id]: { - userName: action.userName, +const logonoffReducer = createReducer( + initialState, + { + [types.SCAN] : scanReducer, + [types.LOGIN] : loginReducer, + [types.LOGOUT] : logoutReducer, }, -}) as LogonoffState - -const logoutReducer = (state: LogonoffState, action: any) => ({ - ...state, - [action.wechaty.id]: {}, -}) as LogonoffState - -const logonoffReducer = createReducer(initialState)({ - [types.SCAN] : scanReducer, - [types.LOGIN] : loginReducer, - [types.LOGOUT] : logoutReducer, -}) +) export default logonoffReducer diff --git a/src/ducks/logonoff/types.ts b/src/ducks/logonoff/types.ts index d25c5c6..2f81db8 100644 --- a/src/ducks/logonoff/types.ts +++ b/src/ducks/logonoff/types.ts @@ -1,16 +1,3 @@ export const SCAN = 'SCAN' export const LOGIN = 'LOGIN' export const LOGOUT = 'LOGOUT' - -export interface ActionScanPayload { - id: string, - qrcode? : string, - userName? : string, -} - -export interface ActionLoginPayload { - userName: string, - wechaty: { - id: string, - } -} diff --git a/src/redux.ts b/src/redux.ts index 6ca9619..ca74d26 100644 --- a/src/redux.ts +++ b/src/redux.ts @@ -1,28 +1,23 @@ -import { - createStore, -} from 'redux' +import { configureStore } from '@reduxjs/toolkit' +// import { +// createStore, +// } from 'redux' + +// import reducer from './ducks/' import reducer, { logonoffActions, counterActions, } from './ducks/' -const store = createStore(reducer) +const store = configureStore({ + reducer, +}) store.subscribe(() => console.info(store.getState())) -// The only way to mutate the internal state is to dispatch an action. -// The actions can be serialized, logged or stored and later replayed. store.dispatch(counterActions.mo()) -// 1 -store.dispatch(counterActions.mt()) -// 2 -store.dispatch( - logonoffActions.login({ - userName: 'test name', - wechaty: { - id: 'wechatyid', - }, - }), -) -// 1 +store.dispatch(logonoffActions.scan('1', '2')) + +export type RootState = ReturnType +export type AppDispatch = typeof store.dispatch From 7e1e7b28f799ca779cd033e75704c5491fe7b2ad Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 22:29:09 +0800 Subject: [PATCH 14/17] 0.3.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3516fe9..81d2026 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.3.4", + "version": "0.3.5", "description": "Apache OSSChat", "main": "index.js", "engines": { From aac07ff51e4230762cf1524ea43e9bd31c6c2ea9 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Tue, 31 Mar 2020 23:56:11 +0800 Subject: [PATCH 15/17] Enable Redux & follow styles --- README.md | 1 + src/ducks/counter/reducers.ts | 11 ++---- src/ducks/counter/types.ts | 9 +++-- src/ducks/index.ts | 17 ++++++--- src/ducks/logonoff/reducers.ts | 15 +++----- src/ducks/logonoff/selectors.ts | 16 +++++---- src/ducks/logonoff/types.ts | 13 +++++-- src/handlers/on-login.ts | 13 +++++++ src/handlers/on-logout.ts | 12 +++++++ src/handlers/on-scan.ts | 12 +++++++ src/routers.ts | 64 +++++++-------------------------- 11 files changed, 96 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 0ae6b03..3a5d8a9 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ To be added... ## Links - [Scaling your Redux App with ducks](https://www.freecodecamp.org/news/scaling-your-redux-app-with-ducks-6115955638be/) +- [Redux Style Guide](https://redux.js.org/style-guide/style-guide#do-not-put-non-serializable-values-in-state-or-actions) ## Copyright & License diff --git a/src/ducks/counter/reducers.ts b/src/ducks/counter/reducers.ts index 0412fca..e6d62a5 100644 --- a/src/ducks/counter/reducers.ts +++ b/src/ducks/counter/reducers.ts @@ -5,22 +5,17 @@ import { import * as types from './types' // import * as actions from './actions' -export interface CounterState { - mt: number, - mo: number, -} - const initialState = { mo: 0, mt: 0, -} as CounterState +} as types.CounterState -const moReducer = (state: CounterState) => ({ +const moReducer = (state: types.CounterState) => ({ ...state, mo: state.mo + 1, }) -const mtReducer = (state: CounterState) => ({ +const mtReducer = (state: types.CounterState) => ({ ...state, mt: state.mt + 1, }) diff --git a/src/ducks/counter/types.ts b/src/ducks/counter/types.ts index e4ae49d..3dfadcf 100644 --- a/src/ducks/counter/types.ts +++ b/src/ducks/counter/types.ts @@ -1,2 +1,7 @@ -export const MT = 'MESSAGE/MT' -export const MO = 'MESSAGE/MO' +export const MT = 'osschat/wechaty/message/mt' +export const MO = 'osschat/wechaty/message/mo' + +export interface CounterState { + mt: number, + mo: number, +} diff --git a/src/ducks/index.ts b/src/ducks/index.ts index bbf846c..c0d1654 100644 --- a/src/ducks/index.ts +++ b/src/ducks/index.ts @@ -8,18 +8,20 @@ * https://redux-toolkit.js.org/usage/usage-with-typescript * */ -import { combineReducers } from 'redux' +import { combineReducers } from 'redux' +import { configureStore } from '@reduxjs/toolkit' import logonoff, { logonoffActions, -} from './logonoff' - + logonoffSelectors, +} from './logonoff' import counter, { counterActions, -} from './counter' +} from './counter' export { logonoffActions, + logonoffSelectors, counterActions, } @@ -28,4 +30,9 @@ const reducer = combineReducers({ logonoff, }) -export default reducer +export const store = configureStore({ + reducer, +}) + +export type RootState = ReturnType +export type AppDispatch = typeof store.dispatch diff --git a/src/ducks/logonoff/reducers.ts b/src/ducks/logonoff/reducers.ts index 51968ac..d2a0ecb 100644 --- a/src/ducks/logonoff/reducers.ts +++ b/src/ducks/logonoff/reducers.ts @@ -6,16 +6,9 @@ import { import * as types from './types' import * as actions from './actions' -export interface LogonoffState { - [k: string]: { - qrcode? : string, - userName? : string, - } -} - -const initialState: LogonoffState = {} +const initialState: types.LogonoffState = {} -const scanReducer = (state: LogonoffState, action: Action) => { +const scanReducer = (state: types.LogonoffState, action: Action) => { if (actions.scan.match(action)) { return { ...state, @@ -27,7 +20,7 @@ const scanReducer = (state: LogonoffState, action: Action) => { return state } -const loginReducer = (state: LogonoffState, action: Action) => { +const loginReducer = (state: types.LogonoffState, action: Action) => { if (actions.login.match(action)) { return { ...state, @@ -39,7 +32,7 @@ const loginReducer = (state: LogonoffState, action: Action) => { return state } -const logoutReducer = (state: LogonoffState, action: Action) => { +const logoutReducer = (state: types.LogonoffState, action: Action) => { if (actions.logout.match(action)) { return { ...state, diff --git a/src/ducks/logonoff/selectors.ts b/src/ducks/logonoff/selectors.ts index 0098e10..45c2c9f 100644 --- a/src/ducks/logonoff/selectors.ts +++ b/src/ducks/logonoff/selectors.ts @@ -1,9 +1,11 @@ +import * as types from './types' -export function online (duck: any) { - return Object.keys(duck.logonoff).filter( - id => { - const info = duck.logonoff[id] - return info.qrcode === undefined && info.userName !== undefined - } - ) +export function status ( + state: types.LogonoffState, + wechatyId: string, +) { + if (wechatyId in state) { + return state[wechatyId] + } + return {} } diff --git a/src/ducks/logonoff/types.ts b/src/ducks/logonoff/types.ts index 2f81db8..31d8d94 100644 --- a/src/ducks/logonoff/types.ts +++ b/src/ducks/logonoff/types.ts @@ -1,3 +1,10 @@ -export const SCAN = 'SCAN' -export const LOGIN = 'LOGIN' -export const LOGOUT = 'LOGOUT' +export const SCAN = 'osschat/wechaty/event/scan' +export const LOGIN = 'osschat/wechaty/event/login' +export const LOGOUT = 'osschat/wechaty/event/logout' + +export interface LogonoffState { + [k: string]: { + qrcode? : string, + userName? : string, + } +} diff --git a/src/handlers/on-login.ts b/src/handlers/on-login.ts index d4dce2a..5bb9631 100644 --- a/src/handlers/on-login.ts +++ b/src/handlers/on-login.ts @@ -6,12 +6,25 @@ import { } from 'wechaty' import { debug } from '../config' +import { + store, + logonoffActions, +} from '../ducks/' + export default async function onLogin ( this : Wechaty, user : Contact, ): Promise { const msg = `${user.name()} Heroku Wechaty Getting Started v${VERSION} logined` log.info('startBot', 'onLogin(%s) %s', user, msg) + + store.dispatch( + logonoffActions.login( + this.id, + user.name(), + ) + ) + if (debug()) { await user.say(msg) } diff --git a/src/handlers/on-logout.ts b/src/handlers/on-logout.ts index 9a98a7b..75e1bfe 100644 --- a/src/handlers/on-logout.ts +++ b/src/handlers/on-logout.ts @@ -4,9 +4,21 @@ import { Wechaty, } from 'wechaty' +import { + store, + logonoffActions, +} from '../ducks/' + export default async function onLogout ( this : Wechaty, user : Contact, ): Promise { log.info('on-logout', `onLogout(%s)`, user) + + store.dispatch( + logonoffActions.logout( + this.id, + ) + ) + } diff --git a/src/handlers/on-scan.ts b/src/handlers/on-scan.ts index 9fc51f4..9a3fc11 100644 --- a/src/handlers/on-scan.ts +++ b/src/handlers/on-scan.ts @@ -5,6 +5,11 @@ import { import { generate } from 'qrcode-terminal' +import { + store, + logonoffActions, +} from '../ducks/' + export default async function onScan ( this : Wechaty, qrcode : string, @@ -15,6 +20,13 @@ export default async function onScan ( qrcodeValueToUrl(qrcode), ) + store.dispatch( + logonoffActions.scan( + this.id, + qrcode, + ) + ) + generate(qrcode) } diff --git a/src/routers.ts b/src/routers.ts index c87cb0e..12f9544 100644 --- a/src/routers.ts +++ b/src/routers.ts @@ -7,8 +7,6 @@ import { import { Room, - ScanStatus, - Contact, Wechaty, } from 'wechaty' @@ -19,52 +17,12 @@ import { import { Chatops } from './chatops' import { getHAWechaty } from './get-wechaty' -const haBot = getHAWechaty() - -interface BotInfo { - qrcode?: string - userName?: string -} +import { + store, + logonoffSelectors, +} from './ducks/' -const botInfo = new WeakMap() - -haBot.on( - 'scan', - function ( - this: Wechaty, - qrcode: string, - status: ScanStatus, - ) { - void status - const info = { ...botInfo.get(this) } as BotInfo - info.qrcode = qrcode - info.userName = undefined - botInfo.set(this, info) - }, -) -haBot.on( - 'login', - function ( - this: Wechaty, - user: Contact, - ) { - const info = { ...botInfo.get(this) } as BotInfo - info.qrcode = undefined - info.userName = user.name() - botInfo.set(this, info) - }, -) -haBot.on( - 'logout', - function ( - this: Wechaty, - ) { - const info = { ...botInfo.get(this) } as BotInfo - info.qrcode = undefined - info.userName = undefined - botInfo.set(this, info) - }, -) +const haBot = getHAWechaty() const FORM_HTML = ` @@ -132,7 +90,12 @@ async function rootHandler ( ) { let html = '' for (const wechaty of haBot.wechatyList) { - const info = botInfo.get(wechaty) + + const info = logonoffSelectors.status( + store.getState().logonoff, + wechaty.id, + ) + html += [ '
\n', await rootHtml(wechaty, info), @@ -148,11 +111,9 @@ async function rootHandler ( ` - const htmlFoot = ` ` - res.end( [ htmlHead, @@ -165,8 +126,9 @@ async function rootHandler ( async function rootHtml ( wechaty: Wechaty, - info: BotInfo = {}, + info: ReturnType, ) { + let html if (info.qrcode) { From 1d5ee90d97b1bef2ad04d4f97823a5eff368b0e2 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Wed, 1 Apr 2020 00:25:45 +0800 Subject: [PATCH 16/17] HA supported! --- src/ducks/counter/index.ts | 2 ++ src/ducks/counter/reducers.ts | 6 +++--- src/ducks/counter/selectors.ts | 20 ++++++++++++-------- src/ducks/counter/types.ts | 6 +++--- src/ducks/index.ts | 7 +++++++ src/ducks/logonoff/reducers.ts | 8 ++++---- src/ducks/logonoff/selectors.ts | 2 +- src/ducks/logonoff/types.ts | 2 +- src/handlers/on-message.ts | 12 ++++++++++++ src/redux.ts | 23 ----------------------- src/routers.ts | 14 ++++++++++++++ 11 files changed, 59 insertions(+), 43 deletions(-) delete mode 100644 src/redux.ts diff --git a/src/ducks/counter/index.ts b/src/ducks/counter/index.ts index b65c5b4..26ff7fd 100644 --- a/src/ducks/counter/index.ts +++ b/src/ducks/counter/index.ts @@ -5,9 +5,11 @@ import reducer from './reducers' import * as counterActions from './actions' import * as counterTypes from './types' +import * as counterSelectors from './selectors' export { counterActions, + counterSelectors, counterTypes, } diff --git a/src/ducks/counter/reducers.ts b/src/ducks/counter/reducers.ts index e6d62a5..462fe6e 100644 --- a/src/ducks/counter/reducers.ts +++ b/src/ducks/counter/reducers.ts @@ -8,14 +8,14 @@ import * as types from './types' const initialState = { mo: 0, mt: 0, -} as types.CounterState +} as types.State -const moReducer = (state: types.CounterState) => ({ +const moReducer = (state: types.State) => ({ ...state, mo: state.mo + 1, }) -const mtReducer = (state: types.CounterState) => ({ +const mtReducer = (state: types.State) => ({ ...state, mt: state.mt + 1, }) diff --git a/src/ducks/counter/selectors.ts b/src/ducks/counter/selectors.ts index 1f9d889..9022bcd 100644 --- a/src/ducks/counter/selectors.ts +++ b/src/ducks/counter/selectors.ts @@ -1,9 +1,13 @@ +import * as types from './types' -// export function online (duck: any) { -// return Object.keys(duck.logonoff).filter( -// id => { -// const info = duck.logonoff[id] -// return info.qrcode === undefined && info.userName !== undefined -// } -// ) -// } +export function mo ( + state: types.State, +) { + return state.mo +} + +export function mt ( + state: types.State, +) { + return state.mt +} diff --git a/src/ducks/counter/types.ts b/src/ducks/counter/types.ts index 3dfadcf..12039fd 100644 --- a/src/ducks/counter/types.ts +++ b/src/ducks/counter/types.ts @@ -1,7 +1,7 @@ -export const MT = 'osschat/wechaty/message/mt' export const MO = 'osschat/wechaty/message/mo' +export const MT = 'osschat/wechaty/message/mt' -export interface CounterState { - mt: number, +export interface State { mo: number, + mt: number, } diff --git a/src/ducks/index.ts b/src/ducks/index.ts index c0d1654..86fd2dd 100644 --- a/src/ducks/index.ts +++ b/src/ducks/index.ts @@ -17,12 +17,15 @@ import logonoff, { } from './logonoff' import counter, { counterActions, + counterSelectors, } from './counter' export { logonoffActions, logonoffSelectors, + counterActions, + counterSelectors, } const reducer = combineReducers({ @@ -34,5 +37,9 @@ export const store = configureStore({ reducer, }) +store.subscribe(() => { + console.info('state:', store.getState()) +}) + export type RootState = ReturnType export type AppDispatch = typeof store.dispatch diff --git a/src/ducks/logonoff/reducers.ts b/src/ducks/logonoff/reducers.ts index d2a0ecb..45ce8fc 100644 --- a/src/ducks/logonoff/reducers.ts +++ b/src/ducks/logonoff/reducers.ts @@ -6,9 +6,9 @@ import { import * as types from './types' import * as actions from './actions' -const initialState: types.LogonoffState = {} +const initialState: types.State = {} -const scanReducer = (state: types.LogonoffState, action: Action) => { +const scanReducer = (state: types.State, action: Action) => { if (actions.scan.match(action)) { return { ...state, @@ -20,7 +20,7 @@ const scanReducer = (state: types.LogonoffState, action: Action) => { return state } -const loginReducer = (state: types.LogonoffState, action: Action) => { +const loginReducer = (state: types.State, action: Action) => { if (actions.login.match(action)) { return { ...state, @@ -32,7 +32,7 @@ const loginReducer = (state: types.LogonoffState, action: Action) => { return state } -const logoutReducer = (state: types.LogonoffState, action: Action) => { +const logoutReducer = (state: types.State, action: Action) => { if (actions.logout.match(action)) { return { ...state, diff --git a/src/ducks/logonoff/selectors.ts b/src/ducks/logonoff/selectors.ts index 45c2c9f..e071c63 100644 --- a/src/ducks/logonoff/selectors.ts +++ b/src/ducks/logonoff/selectors.ts @@ -1,7 +1,7 @@ import * as types from './types' export function status ( - state: types.LogonoffState, + state: types.State, wechatyId: string, ) { if (wechatyId in state) { diff --git a/src/ducks/logonoff/types.ts b/src/ducks/logonoff/types.ts index 31d8d94..23c52ac 100644 --- a/src/ducks/logonoff/types.ts +++ b/src/ducks/logonoff/types.ts @@ -2,7 +2,7 @@ export const SCAN = 'osschat/wechaty/event/scan' export const LOGIN = 'osschat/wechaty/event/login' export const LOGOUT = 'osschat/wechaty/event/logout' -export interface LogonoffState { +export interface State { [k: string]: { qrcode? : string, userName? : string, diff --git a/src/handlers/on-message.ts b/src/handlers/on-message.ts index a83000e..7ae05aa 100644 --- a/src/handlers/on-message.ts +++ b/src/handlers/on-message.ts @@ -12,12 +12,24 @@ import { import { VoteManager } from '../managers/vote-manager' import { Chatops } from '../chatops' +import { + store, + counterActions, +} from '../ducks/' + const BORN_TIME = Date.now() export default async function onMessage ( this : Wechaty, message : Message, ): Promise { + + if (message.self()) { + store.dispatch(counterActions.mo()) + } else { + store.dispatch(counterActions.mt()) + } + const text = message.text() const contact = message.from() if (!contact) { diff --git a/src/redux.ts b/src/redux.ts deleted file mode 100644 index ca74d26..0000000 --- a/src/redux.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit' -// import { -// createStore, -// } from 'redux' - -// import reducer from './ducks/' - -import reducer, { - logonoffActions, - counterActions, -} from './ducks/' - -const store = configureStore({ - reducer, -}) - -store.subscribe(() => console.info(store.getState())) - -store.dispatch(counterActions.mo()) -store.dispatch(logonoffActions.scan('1', '2')) - -export type RootState = ReturnType -export type AppDispatch = typeof store.dispatch diff --git a/src/routers.ts b/src/routers.ts index 12f9544..c2daeef 100644 --- a/src/routers.ts +++ b/src/routers.ts @@ -20,6 +20,7 @@ import { getHAWechaty } from './get-wechaty' import { store, logonoffSelectors, + counterSelectors, } from './ducks/' const haBot = getHAWechaty() @@ -111,13 +112,26 @@ async function rootHandler ( ` + + const mt = counterSelectors.mt(store.getState().counter) + const mo = counterSelectors.mo(store.getState().counter) + + const htmlCounter = ` +
+
    +
  • Message Received: ${mt}
  • +
  • Message Sent: ${mo}
  • +
+ ` const htmlFoot = ` + ` res.end( [ htmlHead, html, + htmlCounter, htmlFoot, ].join('\n') ) From bc76bcfb8dc1895f6c727a7c6b58e25ba1945285 Mon Sep 17 00:00:00 2001 From: Huan LI Date: Wed, 1 Apr 2020 00:26:01 +0800 Subject: [PATCH 17/17] 0.3.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81d2026..ee96e7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osschat", - "version": "0.3.5", + "version": "0.3.6", "description": "Apache OSSChat", "main": "index.js", "engines": {