diff --git a/commands/departures.js b/commands/departures.js index a49d5f1..6f73974 100644 --- a/commands/departures.js +++ b/commands/departures.js @@ -1,10 +1,97 @@ 'use strict' -const commandsKeyboard = require('../lib/commands-keyboard') +const searchStations = require('vbb-stations-autocomplete') +const getStations = require('vbb-stations') +const parseTime = require('parse-messy-time') +const hafas = require('vbb-hafas') -const departures = async (ctx, next) => { - await ctx.replyWithMarkdown('`departures` command', commandsKeyboard) +const commandKeys = require('../lib/commands-keyboard') +const whenKeys = require('../lib/when-keyboard') + +const promptWhen = `\ +*When?* +e.g. "now", "in 10 minutes" or "tomorrow 17:20"` +const unknownTime = `\ +Hm, don't understand this format.` + +const promptWhere = `\ +*Which station?* +Enter a station name like "u mehringdamm" or "Kotti".` +const unknownStation = `\ +I don't know about this station, please double-check for typos. +If you're sure it's my fault, please let my creator @derhuerst know.` + +const parseWhere = async (where, ctx) => { + await ctx.replyWithChatAction('typing') + let [station] = searchStations(where, 1, false, false) // non-fuzzy + if (!station) [station] = searchStations(where, 1, true, false) // fuzzy + if (station) [station] = getStations(station.id) // get details + + if (station) await ctx.replyWithMarkdown(`I found ${station.name}.`) + else await ctx.replyWithMarkdown(unknownStation) + return station +} + +const parseWhen = async (when, ctx) => { + when = +parseTime(when, {now: new Date()}) + if (Number.isNaN(when)) { + await ctx.replyWithMarkdown(unknownTime) + return null + } + return when +} + +const renderDeps = (deps, header) => { // todo +} + +const printDeps = async (allDeps, ctx) => { + await ctx.replyWithMarkdown(`todo`, commandKeys) // todo + for (let i = 0; i < allDeps.length; i += 10) { + const deps = allDeps.slice(i, i + 10) + await ctx.replyWithMarkdown(renderDeps(deps), commandKeys) + } +} + +const departures = async (ctx, next) => { + // `/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) + return next() + } + if (ctx.state.cmd) { + await ctx.replyWithMarkdown(promptWhere) // todo: frequent keyboard + return next() // await next message + } + + let where = await ctx.getData('where') + if (!where) { + where = await parseWhere(ctx.message.text, ctx) + if (!where) return next() // await next message + await ctx.putData('where', where) + + await ctx.replyWithMarkdown(promptWhen, whenKeys) + return next() // await next message + } + + let when = await ctx.getData('when') + if (!when) { + when = await parseWhen(ctx.message.text, ctx) + if (!when) return next() // await next message + await ctx.putData('when', when) + } + + // clear session data + await Promise.all([ + ctx.putData('when', null), + ctx.putData('where', null) + ]) + + // fetch & render + await ctx.replyWithChatAction('typing') + const deps = await hafas.departures(where.id, when) + await printDeps(deps, ctx) next() } diff --git a/index.js b/index.js index 2011ecc..294614d 100644 --- a/index.js +++ b/index.js @@ -28,15 +28,13 @@ const bot = new Bot(TOKEN) bot.use(logging) bot.use(command) bot.use((ctx, next) => { - const {cmd, prevCmd} = ctx.state - - let handler = commands.help - if (cmd) { - if (commands[cmd]) handler = commands[cmd] - } else if (prevCmd) { - if (commands[prevCmd]) handler = commands[prevCmd] - } + if (!ctx.message) return next() + const cmd = ctx.state.cmd || ctx.state.prevCmd || 'help' + 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) }) diff --git a/lib/storage.js b/lib/storage.js index 89fc5a5..52765e9 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -10,18 +10,36 @@ const db = level(path.join(__dirname, '..', 'vbb-telegram.ldb'), { valueEncoding: 'json' }) -const getCommand = (user) => { - return db.get(user + ':cmd') +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:' @@ -58,5 +76,6 @@ const incLocation = async (user, id) => { module.exports = { getCommand, putCommand, + createGetData, createPutData, getTopLocations, incLocation } diff --git a/lib/when-keyboard.js b/lib/when-keyboard.js new file mode 100644 index 0000000..faceb6c --- /dev/null +++ b/lib/when-keyboard.js @@ -0,0 +1,9 @@ +'use strict' + +const Markup = require('telegraf/markup') + +const keys = ['now', 'in 10 min', 'in 1 hour'] + +const whenKeyboard = Markup.keyboard(keys).oneTime().extra() + +module.exports = whenKeyboard diff --git a/package.json b/package.json index f44515c..4b17a73 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,10 @@ "hifo": "^1.0.0", "js-string-escape": "^1.0.1", "level": "^3.0.0", - "telegraf": "^3.19.0" + "parse-messy-time": "^2.1.0", + "telegraf": "^3.19.0", + "vbb-hafas": "^4.1.1", + "vbb-stations": "^6.2.1", + "vbb-stations-autocomplete": "^3.2.0" } }