Skip to content

Commit

Permalink
clean up middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
derhuerst committed Mar 7, 2018
1 parent b7e4084 commit 138ffec
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 94 deletions.
27 changes: 12 additions & 15 deletions commands/departures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const parseTime = require('parse-messy-time')
const linesAt = require('vbb-lines-at')
const hafas = require('vbb-hafas')

const storage = require('../../lib/storage')
const commandKeys = require('../../lib/commands-keyboard')
const whenKeys = require('../../lib/when-keyboard')
const getFrequentStationsKeys = require('../../lib/frequent-stations-keyboard')
Expand Down Expand Up @@ -53,42 +52,40 @@ const printDeps = async (allDeps, ctx) => {
}

const departures = async (ctx, next) => {
const chat = ctx.message.chat.id

// `/a spichernstr` shorthand
if (ctx.state.args && ctx.state.args[0]) {
const station = await parseWhere(ctx.state.args[0], ctx)
if (station) await ctx.putData('where', station)
if (ctx.args && ctx.args[0]) {
const station = await parseWhere(ctx.args[0], ctx)
if (station) await ctx.storage.putData('where', station)
return next()
}
if (ctx.state.cmd) {
const ids = await storage.getTopLocations(chat)
if (ctx.command) {
const ids = await ctx.storage.getTopLocations()
await ctx.replyWithMarkdown(promptWhere, getFrequentStationsKeys(ids))
return next() // await next message
}

let where = await ctx.getData('where')
let where = await ctx.storage.getData('where')
if (!where) {
where = await parseWhere(ctx.message.text, ctx)
if (!where) return next() // await next message
await ctx.putData('where', where)
await storage.incLocation(chat, where.id)
await ctx.storage.putData('where', where)
await ctx.storage.incLocation(where.id)

await ctx.replyWithMarkdown(promptWhen, whenKeys)
return next() // await next message
}

let when = await ctx.getData('when')
let when = await ctx.storage.getData('when')
if (!when) {
when = await parseWhen(ctx.message.text, ctx)
if (!when) return next() // await next message
await ctx.putData('when', when)
await ctx.storage.putData('when', when)
}

// clear session data
await Promise.all([
ctx.putData('when', null),
ctx.putData('where', null)
ctx.storage.putData('when', null),
ctx.storage.putData('where', null)
])

let lines = linesAt[where.id] || []
Expand Down
14 changes: 9 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict'

const path = require('path')
const Bot = require('telegraf')

const logging = require('./lib/logging')
const session = require('./lib/session')
const storage = require('./lib/storage')
const command = require('./lib/command')

Expand All @@ -24,17 +26,19 @@ if (!TOKEN) {
process.exit(1)
}

const pathToDb = path.join(__dirname, 'vbb-telegram.ldb')

const bot = new Bot(TOKEN)
bot.use(logging)
bot.use(session(pathToDb))
bot.use(command)
bot.use(storage)
bot.use((ctx, next) => {
if (!ctx.message) return next()
const cmd = ctx.state.cmd || ctx.state.prevCmd || 'help'
const cmd = ctx.command || ctx.prevCommand || 'help'
ctx.storage.getData = ctx.storage.createGetData(cmd)
ctx.storage.putData = ctx.storage.createPutData(cmd)
const handler = commands[cmd] || commands.help

const chat = ctx.message.chat.id
ctx.getData = storage.createGetData(chat, cmd)
ctx.putData = storage.createPutData(chat, cmd)
return handler(ctx, next)
})

Expand Down
18 changes: 8 additions & 10 deletions lib/command.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
'use strict'

const storage = require('./storage')

const invisibleSpace = '\u2063'

const command = async (ctx, next) => {
const commandMiddleware = async (ctx, next) => {
if (!ctx.message) return next()
if (!ctx.session) return Promise.reject('ctx.session is missing')

const msg = ctx.message
const prevCmd = await storage.getCommand(msg.chat.id)
ctx.state.prevCmd = prevCmd
ctx.prevCommand = await ctx.session.get('cmd')

if (!Array.isArray(msg.entities)) return next()
const entity = msg.entities.find(e => e.type === 'bot_command' && e.offset === 0)
if (!entity) return next()
const cmd = msg.text.substr(entity.offset, entity.length).slice(1).toLowerCase()
ctx.state.cmd = cmd
ctx.command = cmd

const argsStart = entity.offset + entity.length + 1
if (msg.text[argsStart - 1] === invisibleSpace) ctx.state.args = []
else ctx.state.args = msg.text.slice(argsStart).split(/\s+/)
if (msg.text[argsStart - 1] === invisibleSpace) ctx.args = []
else ctx.args = msg.text.slice(argsStart).split(/\s+/).filter(arg => !!arg)

if (prevCmd !== cmd) await storage.putCommand(msg.chat.id, cmd)
if (ctx.prevCommand !== cmd) await ctx.session.put('cmd', cmd)
next()
}

module.exports = command
module.exports = commandMiddleware
39 changes: 39 additions & 0 deletions lib/session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

const level = require('level')

const last = '\xff'

const createSessionMiddleware = (pathToDb) => {
const db = level(pathToDb, {valueEncoding: 'json'})

const _get = (key) => {
return db.get(key)
.catch((err) => {
if (err.notFound) return null
else throw err
})
}

const createSession = (chat) => {
const prefix = chat + ':'
const get = key => _get(prefix + key)
const put = (key, val) => db.put(prefix + key, val)

const session = {get, put}
Object.defineProperty(session, 'prefix', {value: prefix})
Object.defineProperty(session, 'db', {value: db})
return session
}

const sessionMiddleware = (ctx, next) => {
Object.defineProperty(ctx, 'session', {
value: createSession(ctx.chat.id),
enumerable: true
})
next()
}
return sessionMiddleware
}

module.exports = createSessionMiddleware
107 changes: 43 additions & 64 deletions lib/storage.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,60 @@
'use strict'

const level = require('level')
const path = require('path')
const hifo = require('hifo')

const last = '\xff'

const db = level(path.join(__dirname, '..', 'vbb-telegram.ldb'), {
valueEncoding: 'json'
})

const get = (key) => {
return db.get(key)
.catch((err) => {
if (err.notFound) return null
else throw err
})
}

const getCommand = user => get(user + ':cmd')

const putCommand = (user, cmd) => {
return db.put(user + ':cmd', cmd)
}

const createGetData = (user, cmd) => {
const getData = key => {
console.error('get', user + ':data:' + cmd + ':' + key)
return get(user + ':data:' + cmd + ':' + key)
}
return getData
}

const createPutData = (user, cmd) => {
const putData = (key, val) => {
console.error('put', user + ':data:' + cmd + ':' + key, val)
return db.put(user + ':data:' + cmd + ':' + key, val)
}
return putData
}

// todo: sth more efficient
const getTopLocations = (user) => {
const ns = user + ':locations:'
const top = hifo(hifo.highest('count'), 3)

const onItem = ({key, value}) => {
const id = key.slice(ns.length)
top.add({id, count: value})
}

const items = db.createReadStream({gt: ns, lt: ns + last})
const range = (db, prefix, onItem) => {
const items = db.createReadStream({gt: prefix, lt: prefix + last})
return new Promise((resolve, reject) => {
items.once('error', (err) => {
reject(err)
items.destroy()
})
items.once('end', () => {
resolve(top.data.map(res => res.id))
})
items.once('end', () => resolve())
items.on('data', onItem)
})
}

const incLocation = async (user, id) => {
const key = user + ':locations:' + id
try {
const count = await db.get(key)
await db.put(key, count + 1)
} catch (err) {
if (err.notFound) await db.put(key, 1)
else throw err
const storageMiddleware = (ctx, next) => {
if (!ctx.session) return Promise.reject('ctx.session is missing')

const createGetData = (cmd) => {
const getData = key => ctx.session.get('data:' + cmd + ':' + key)
return getData
}
const createPutData = (cmd) => {
const putData = (key, val) => ctx.session.put('data:' + cmd + ':' + key, val)
return putData
}
}

module.exports = {
getCommand, putCommand,
createGetData, createPutData,
getTopLocations, incLocation
// todo: use sth more efficient
const getTopLocations = async () => {
const prefix = ctx.session.prefix + 'locs:'
const top = hifo(hifo.highest('count'), 3)

const onItem = ({key, value}) => {
const id = key.slice(prefix.length)
top.add({id, count: value})
}
await range(ctx.session.db, prefix, onItem)
return top.data.map(res => res.id)
}
const incLocation = async (id) => {
const key = 'locs:' + id
const count = await ctx.session.get(key)
if (!count) await ctx.session.put(key, 1)
else await ctx.session.put(key, count + 1)
}

const storage = {
createGetData, createPutData,
getTopLocations, incLocation
}
Object.defineProperty(ctx, 'storage', {
value: storage,
enumerable: true
})
next()
}

module.exports = storageMiddleware

0 comments on commit 138ffec

Please sign in to comment.