Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e49c42f

Browse files
committed
tested with self dialog
0 parents  commit e49c42f

File tree

10 files changed

+455
-0
lines changed

10 files changed

+455
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
config.json
2+
.yarnrc
3+
.pki
4+
node_modules/
5+
tickets.json
6+
*.lock
7+
**.swp
8+
.v8flags.*

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
# installation
3+
4+
```
5+
brew install chromedriver
6+
npm install
7+
```
8+
9+
create config.json file:
10+
11+
```
12+
cp config.json.example config.json
13+
vi config.json
14+
```
15+
16+
run:
17+
18+
```
19+
npm start
20+
```

bot.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict'
2+
3+
const { Wechaty } = require('wechaty')
4+
5+
const reload = require('./reloader')
6+
7+
const data = {}
8+
9+
Wechaty.instance() // Singleton
10+
.on('scan', (url, code) => console.log(`Scan QR Code to login: ${code}\n${url}`))
11+
.on('login', user => console.log(`User ${user} logined`))
12+
.on('message', message => { reload('./handler.js')(data, message) })
13+
.init()

config.json.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"prefix": "candra",
3+
"whitelisted": [
4+
"Agora Space #3:bunker",
5+
"Agora - management",
6+
"File Transfer"
7+
],
8+
"management": "Agora - management",
9+
"store": "./tickets.json"
10+
}

handler.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict'
2+
3+
const reload = require('./reloader')
4+
5+
function roomName(message) {
6+
return message.room() ? message.room().topic() : 'self'
7+
}
8+
9+
function userName(message) {
10+
return message.from().name()
11+
}
12+
13+
function content(message) {
14+
return message.content()
15+
}
16+
17+
function cleanContent(prefix, message) {
18+
return content(message).substr(prefix.length).trim()
19+
}
20+
21+
function messageString(message) {
22+
return `${roomName(message)}/${userName(message)}: ${content(message)}`
23+
}
24+
25+
function mine(content, prefix) {
26+
return content.substr(0, prefix.length).toLowerCase().startsWith(prefix)
27+
}
28+
29+
function destination(message) {
30+
return message.room()? message.room() : message.from()
31+
}
32+
33+
function handler(data, message) {
34+
console.log(messageString(message))
35+
try {
36+
var config = reload('./config.json')
37+
if(mine(content(message), config.prefix) && config.whitelisted.indexOf(roomName(message)) != -1) {
38+
console.log(`Captured message: ${message}`)
39+
40+
const processor = reload('./support.js')
41+
if(!data.tickets) {
42+
data.tickets = processor.load(data.store)
43+
}
44+
const reply = processor.process(data.tickets, {
45+
prefix: config.prefix,
46+
roomName: roomName(message),
47+
userName: userName(message),
48+
content: cleanContent(config.prefix, message)
49+
})
50+
processor.store(config.store, data.tickets)
51+
destination(message).say(reply)
52+
}
53+
} catch(e) {
54+
destination(message).say(e)
55+
}
56+
}
57+
58+
module.exports = handler

package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "candra-wechaty-bot",
3+
"version": "1.0.0",
4+
"description": "support tickets bot",
5+
"main": "bot.js",
6+
"dependencies": {
7+
"require-reload": "^0.2.2",
8+
"wechaty": "^0.7.3"
9+
},
10+
"devDependencies": {
11+
"chai": "^3.5.0",
12+
"mocha": "^3.2.0"
13+
},
14+
"scripts": {
15+
"test": "node_modules/.bin/mocha",
16+
"start": "node bot.js"
17+
},
18+
"author": "Ricky Ng-Adam <[email protected]>",
19+
"license": "ISC"
20+
}

reloader.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
const reload = require('require-reload')(require)
4+
const fs = require('fs')
5+
6+
const updates = {}
7+
const modules = {}
8+
9+
function reloadIfUpdated(name) {
10+
const mtime = fs.statSync(name).mtime.getTime()
11+
if(!updates[name] || updates[name] < mtime) {
12+
console.log(`RELOADING ${name}`)
13+
updates[name] = mtime
14+
modules[name] = reload(name)
15+
}
16+
return modules[name]
17+
}
18+
19+
module.exports = reloadIfUpdated

support.js

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
5+
const Sample = {
6+
assignee: null,
7+
status: 'open',
8+
id: null,
9+
content: null,
10+
roomName: null,
11+
requester: null,
12+
created: null,
13+
closed: null
14+
}
15+
16+
function TicketError(message) {
17+
this.name = 'TicketError';
18+
this.message = message || 'Ticketing error';
19+
this.stack = (new Error()).stack;
20+
}
21+
TicketError.prototype = Object.create(Error.prototype);
22+
TicketError.prototype.constructor = TicketError;
23+
24+
function createTicket() {
25+
return JSON.parse(JSON.stringify(Sample))
26+
}
27+
28+
function closeTicket(tickets, id) {
29+
const ticket = findTicket(tickets, id)
30+
ticket.status = 'closed'
31+
ticket.closed = Date.now()
32+
return ticket
33+
}
34+
35+
function openTicket(tickets, message, content) {
36+
const newTicket = createTicket()
37+
tickets.lastId = tickets.lastId + 1
38+
newTicket.id = tickets.lastId
39+
newTicket.created = new Date()
40+
newTicket.content = content
41+
newTicket.roomName = message.roomName
42+
newTicket.requester = message.userName
43+
tickets[newTicket.id] = newTicket
44+
return tickets[newTicket.id]
45+
}
46+
47+
function findTicket(tickets, id) {
48+
if(!id) {
49+
throw new TicketError(`Invalid id`)
50+
}
51+
if(!tickets[id]) {
52+
throw new TicketError(`Not found: #${id}`)
53+
}
54+
return tickets[id]
55+
}
56+
57+
function assignTicket(tickets, id, assignee) {
58+
const ticket = findTicket(tickets, id)
59+
ticket.assignee = message.userName
60+
return ticket
61+
}
62+
63+
function stringifyThis(message) {
64+
return JSON.stringify(this)
65+
}
66+
67+
function showTicket(ticket) {
68+
return `${ticket.content} requested by ${ticket.requester} (${ticket.roomName}) assigned to ${ticket.assignee}`
69+
}
70+
71+
function showTickets(tickets, states) {
72+
var count = 0;
73+
var output = "\n"
74+
for(var i=1; i<tickets.lastId+1; i++) {
75+
if(states.indexOf(tickets[i].status) != -1) {
76+
output += `ticket #${i}: ${showTicket(tickets[i])}\n`
77+
count++
78+
}
79+
}
80+
if(!count) {
81+
return "nothing TODO!"
82+
}
83+
return output
84+
}
85+
86+
function forget(tickets) {
87+
if(!tickets.lastId) {
88+
return "nothing to remove!"
89+
}
90+
const lastId = tickets.lastId
91+
for(var i=1; i<=tickets.lastId; i++) {
92+
delete tickets[i]
93+
}
94+
tickets.lastId = 0
95+
return lastId
96+
}
97+
98+
function assign(tickets, id, assignee) {
99+
const ticket = findTicket(tickets, id)
100+
ticket.assignee = assignee
101+
return ticket
102+
}
103+
104+
function help() {
105+
return actions.map(action => `${action.regexp.toString()}`).join('\n')
106+
}
107+
108+
const actions = [
109+
{
110+
regexp: /^$/gi,
111+
action: (tickets, message, id) => {},
112+
reply: (message, output) => `Yes ${message.userName}?`,
113+
},
114+
{
115+
regexp: /help/gi,
116+
action: (tickets, message, id) => {return help() },
117+
reply: (message, output) => '\n' + output,
118+
},
119+
{
120+
regexp: /close #([0-9]*)/gi,
121+
action: (tickets, message, id) => closeTicket(tickets, id),
122+
reply: (message, output) => `ticket #${output.id} is closed`,
123+
},
124+
{
125+
regexp: /please (.*)/gi,
126+
action: (tickets, message, content) => openTicket(tickets, message, content),
127+
reply: (message, output) => `will ${output.content} (ticket #${output.id})`,
128+
},
129+
{
130+
regexp: /show #([0-9]*)/gi,
131+
action: (tickets, message, id) => findTicket(tickets, id),
132+
reply: (message, output) => showTicket(output),
133+
},
134+
{
135+
regexp: /take #([0-9]*)/gi,
136+
action: (tickets, message, id) => assign(tickets, id, message.userName),
137+
reply: (message, output) => `ticket #${output.id} is assigned to ${output.assignee}`,
138+
},
139+
{
140+
regexp: /assign #([0-9]*) to (\w*)/gi,
141+
action: (tickets, message, id, assignee) => assign(tickets, id, assignee),
142+
reply: (message, output) => `ticket #${output.id} is assigned to ${output.assignee}`,
143+
},
144+
{
145+
regexp: /debug/gi,
146+
action: (tickets, message, id) => tickets,
147+
reply: stringifyThis,
148+
},
149+
{
150+
regexp: /todo/gi,
151+
action: (tickets, message, id) => tickets,
152+
reply: (message, output) => showTickets(output, ['open']),
153+
},
154+
{
155+
regexp: /history/gi,
156+
action: (tickets, message, id) => tickets,
157+
reply: (message, output) => showTickets(output, ['open', 'closed']),
158+
},
159+
{
160+
regexp: /forget it/gi,
161+
action: (tickets, message, id) => forget(tickets),
162+
reply: (message, output) => `deleted ${output} tickets`,
163+
},
164+
]
165+
166+
function searchRegexp(action) {
167+
if(action && action.regexp) {
168+
const exec = action.regexp.exec(this.content)
169+
if(exec) {
170+
this.result = exec.slice(1)
171+
return true
172+
}
173+
}
174+
return false
175+
}
176+
177+
function process(tickets, message) {
178+
const findParams = {content: message.content, result: null}
179+
const action = actions.find(searchRegexp, findParams)
180+
var reply;
181+
if(action) {
182+
var output = action.action(tickets, message, ...findParams.result)
183+
// if(!(output instanceof Object) && !output) {
184+
// reply = `${JSON.stringify(action)} did not return any output`
185+
// } else {
186+
reply = `${action.reply.bind(output)(message, output)}`
187+
// }
188+
} else {
189+
reply = `I don't understand: ${message.content}`
190+
}
191+
return `#${message.prefix}: ${reply}`
192+
}
193+
194+
function createTickets() {
195+
return {
196+
lastId: 0
197+
}
198+
}
199+
200+
function store(store, tickets) {
201+
fs.writeFileSync(store, JSON.stringify(tickets, null, 4))
202+
}
203+
204+
function load(store) {
205+
try {
206+
const datastore = fs.readFileSync(store)
207+
return JSON.parse(datastore)
208+
} catch(e) {
209+
return createTickets()
210+
}
211+
}
212+
213+
module.exports = {
214+
store,
215+
load,
216+
createTickets,
217+
process,
218+
openTicket
219+
}

0 commit comments

Comments
 (0)