You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

412 lines
14 KiB
JavaScript

const config = require('../config')
const rqt = require('../node_modules/rqt')
const MCDSession = new rqt.Session({
host: config.mcdEndpoint,
headers: config.mcdHeaders,
})
const Telegraf = require('telegraf')
const Markup = require('telegraf/markup')
const Extra = require('telegraf/extra')
const Stage = require('telegraf/stage')
const { leave } = Stage
const Scene = require('telegraf/scenes/base')
const rateLimit = require('telegraf-ratelimit')
// MongoDB
const { mongoose, Models } = require('../database')
const bot = new Telegraf(config.telegramBot.token)
/* Redis Stuff */
const Redis = require('redisng')
const redis = new Redis()
redis.connect(config.redisHost, config.redisPort)
const memorysession = require('telegraf/session')
const RedisSession = require('telegraf-session-redis')
const session = new RedisSession({
store: {
host: config.redisHost,
port: config.redisPort,
},
getSessionKey: (ctx) => {
if (!ctx.from) return console.log('fail redis key', ctx);
return ctx.from.id.toString();
}
})
bot.use(memorysession())
//bot.use(session)
bot.use(rateLimit({
window: 3000,
limit: 10,
onLimitExceeded: (ctx, next) => ctx.reply('Sie haben zuviele Anfragen geschickt, bitte warten Sie')
}))
bot.use(async (ctx, next) => {
const start = new Date()
//console.log(ctx)
await next()
const ms = new Date() - start
console.log('Response time: %sms', ms)
})
// bot.use(Telegraf.log())
const cancelText = '\n\nZum abbrechen /cancel schicken!'
const restartText = '\n\nZum neustarten /start schicken!'
const errorTextSessionManager = 'Tut mir leid, der SessionManager ist gerade nicht erreichbar. Bitte kontaktiere den Entwickler'
const errorTextRenderService = 'Tut mir leid, der RenderService ist gerade nicht erreichbar. Bitte kontaktiere den Entwickler'
const errorTextStoreFinder = 'Tut mir leid, der StoreFinder ist gerade nicht erreichbar. Bitte kontaktiere den Entwickler'
// Setup 1 Stage
const setup0Stage = new Scene('setup')
setup0Stage.enter(async (ctx) => {
await ctx.reply('Willkommen! Bevor wir loslegen können, setze bitte einige Einstellungen.',
Markup
.keyboard([
['Ich nutze Android', 'Ich nutze ein iPhone'],
['❌ Überspringen'],
])
.resize()
.extra()
)
})
setup0Stage.hears('Ich nutze Android', async (ctx) => {
console.log('android')
await ctx.replyWithChatAction('typing')
await ctx.reply('Du hast dich für ein Android entschieden', Markup.removeKeyboard())
await Models.User.create({
id: ctx.from.id,
ios: false,
screenWidth: 591,
screenHeight: 1280,
})
let userExists = await Models.User.count({ id: ctx.from.id })
if (userExists === 0) {
await Models.User.create({
id: ctx.from.id,
ios: false,
screenWidth: 1080,
screenHeight: 1920,
})
} else {
await Models.User.findOneAndUpdate({
id: ctx.from.id
}, {
ios: false,
screenWidth: 1080,
screenHeight: 1920,
})
}
await ctx.scene.enter('setup1')
})
setup0Stage.hears('Ich nutze ein iPhone', async (ctx) => {
await ctx.replyWithChatAction('typing')
await ctx.reply('Du hast dich für ein iPhone entschieden', Markup.removeKeyboard())
let userExists = await Models.User.count({ id: ctx.from.id })
if (userExists === 0) {
await Models.User.create({
id: ctx.from.id,
ios: true,
screenWidth: 591,
screenHeight: 1280,
})
} else {
await Models.User.findOneAndUpdate({
id: ctx.from.id
}, {
ios: true,
screenWidth: 591,
screenHeight: 1280,
})
}
await ctx.scene.enter('setup1')
})
setup0Stage.hears('❌ Überspringen', async (ctx) => {
await ctx.replyWithChatAction('typing')
await ctx.reply('Du hast dich für ein Android-Smartphone mit 1080x1920px entschieden', Markup.removeKeyboard())
let userExists = await Models.User.count({ id: ctx.from.id })
if (userExists === 0) {
await Models.User.create({
id: ctx.from.id,
ios: false,
screenWidth: 1080,
screenHeight: 1920,
})
} else {
await Models.User.findOneAndUpdate({
id: ctx.from.id
}, {
id: ctx.from.id,
ios: false,
screenWidth: 1080,
screenHeight: 1920,
})
}
return ctx.scene.enter('main')
})
// Setup 2 Stage
const setup1Stage = new Scene('setup1')
setup1Stage.enter(
(ctx) => ctx.reply('Um ein perfekten Code-Screenshot zu erzeugen benötige ich deine Bildschirmgröße. Dafür musst du mir einfach nur einen Screenshot schicken.',
Markup
.keyboard([
['❌ Überspringen'],
])
.forceReply()
.resize()
.oneTime()
.extra()
)
)
setup1Stage.hears('❌ Überspringen', async (ctx) => {
return ctx.scene.enter('main')
})
setup1Stage.on('message', async (ctx) => {
await ctx.replyWithChatAction('typing')
if (!!ctx.message && !!ctx.message.photo) {
let resPhoto = ctx.message.photo.reverse()[0]
await Models.User.findOneAndUpdate({
id: ctx.from.id
}, {
screenWidth: resPhoto.width,
screenHeight: resPhoto.height,
})
ctx.reply('Deine Bildschirmauflösung wurde gesetzt.')
} else {
return ctx.reply('Bitte schick mir einen Screenshot als Bild oder wähle "Überspringen"!')
}
return ctx.scene.enter('main')
})
// Main Stage
const mainStage = new Scene('main')
mainStage.enter(
(ctx) => ctx.reply('Willkommen! Schicke mir einen Standort über 📎 oder einen Ort(z.B. Berlin)' + cancelText, Extra.markup((markup) => {
return markup.resize()
.keyboard([
markup.locationRequestButton('Aktuellen Standort senden')
])
}))
)
mainStage.on('message', async (ctx) => {
await ctx.replyWithChatAction('typing')
let results = []
let url = `http://${ config.storeFinder.host }:${ config.storeFinder.port }/fillialsuche`
try {
if (!!ctx.message.location) {
results = await rqt.jqt(url, {
method: 'POST',
type: 'json',
data: {
lon: ctx.message.location.longitude,
lat: ctx.message.location.latitude,
},
timeout: 15000,
})
} else if (!!ctx.message.text) {
results = await rqt.jqt(url, {
method: 'POST',
type: 'json',
data: {
query: ctx.message.text,
},
timeout: 15000,
})
} else {
return ctx.reply('Schicke mir einen Standort über 📎 oder einen Ort(z.B. Berlin)' + cancelText)
}
} catch (e) {
console.error(e)
return ctx.reply(errorTextStoreFinder + cancelText)
}
if (typeof(results) == 'object' && (!!results || results.length > 0)) {
await ctx.reply('Bitte wähle eine Filliale aus' + cancelText,
Markup.inlineKeyboard(
results.map(restaurant => {
return [Markup.callbackButton(`${ restaurant.street } ${ restaurant.address }`, restaurant._id)]
})
).oneTime().extra()
)
} else {
return ctx.reply('Ort nicht gefunden')
}
})
mainStage.action(/([0-9a-z]{12,24})/, async (ctx) => {
await ctx.replyWithChatAction('typing')
let dbId = mongoose.Types.ObjectId(ctx.match[1])
ctx.deleteMessage()
ctx.session.fillialSelection = dbId
try {
ctx.answerCbQuery('Lade Angebote...')
let url = `http://${ config.sessionManager.host }:${ config.sessionManager.port }/sitzung/${ ctx.from.id }`
let session = await rqt.jqt(url, { method: 'POST', timeout: 15000 })
ctx.session.id = session._id
ctx.scene.enter('angebotSelect')
} catch (e) {
console.error(e)
return ctx.reply(errorTextSessionManager + cancelText)
}
})
// Offer Selection
const angebotSelectStage = new Scene('angebotSelect')
angebotSelectStage.enter(async (ctx) => {
let loadMsg = await ctx.reply('Lade Angebote...', Markup.removeKeyboard().extra())
let offers = []
await ctx.replyWithChatAction('upload_photo')
try {
let url = `http://${ config.sessionManager.host }:${ config.sessionManager.port }/sitzung/${ ctx.from.id }/restaurant/${ ctx.session.fillialSelection }`
offers = await rqt.jqt(url, { method: 'POST', timeout: 15000 })
offers = offers.filter((offer) => offer.OfferType !== 9)
} catch (e) {
console.error(e)
return ctx.reply(errorTextSessionManager + restartText)
}
await ctx.deleteMessage(loadMsg.message_id)
let overviewImage, cacheId
try {
let url = `http://${ config.renderService.host }:${ config.renderService.port }/overview`
let response = await rqt.aqt(url, {
data: offers.map(offer => offer.ImageBaseName),
type: 'json', method: 'POST', timeout: 15000, binary: true
})
cacheId = response.headers['cache-id']
overviewImage = response.body
} catch (e) {
console.error(e)
return ctx.reply(errorTextRenderService + restartText)
}
let fileId = await redis.get('render-' + cacheId)
let selectMsg
let selectMsgKeyboard = Markup.inlineKeyboard(
offers.map(offer => {
return [Markup.callbackButton(`[${ offer.Id }] ${ offer.Name }`, offer.Id)]
})
.concat([
[ Markup.callbackButton('Abbrechen', 'abort') ]
])
).extra()
if (!fileId) {
selectMsg = await ctx.replyWithPhoto({ source: overviewImage }, selectMsgKeyboard)
await redis.set('render-' + cacheId, selectMsg.photo.reverse()[0].file_id)
} else {
console.log('using cached file id')
selectMsg = await ctx.replyWithPhoto(fileId, selectMsgKeyboard)
}
await redis.set('msg-' + ctx.from.id, selectMsg.message_id)
// ctx.scene.state.selectMsg = selectMsg.message_id // doesnt work because of #reason
})
angebotSelectStage.leave(async (ctx) => {
let msgToDelete = await redis.get('msg-' + ctx.from.id)
if (!!msgToDelete)
try {
await ctx.deleteMessage(msgToDelete)
} catch (e) {}
})
angebotSelectStage.action('abort', (ctx) => ctx.scene.enter('main'))
angebotSelectStage.action('back', (ctx) => ctx.scene.leave())
angebotSelectStage.action(/(.*)/, async (ctx) => {
const offerId = parseInt(ctx.match[1])
try {
ctx.answerCbQuery('Lade Angebot...')
let url = `http://${ config.sessionManager.host }:${ config.sessionManager.port }/sitzung/${ ctx.from.id }/angebot/${ offerId }`
let code = await rqt.jqt(url, { method: 'GET', timeout: 15000, type: 'json' })
if (!code) throw 'Kein Code vorhanden'
ctx.session.code = code
console.log('entering code stage', !!code)
ctx.scene.enter('angebotCode')
} catch (e) {
console.error(e)
return ctx.reply(errorTextSessionManager + cancelText)
}
})
// Offer Code
const angebotCodeStage = new Scene('angebotCode')
angebotCodeStage.enter(async (ctx) => {
await ctx.replyWithChatAction('upload_photo')
//let loadMsg = await ctx.reply('Lade Code...' + cancelText)
let codeImage
try {
let code = ctx.session.code
let user = await Models.User.findOne({ id: ctx.from.id })
code.ios = !!user.ios
code.screenWidth = user.screenWidth
code.screenHeight = user.screenHeight
let url = `http://${ config.renderService.host }:${ config.renderService.port }/qrcode`
codeImage = await rqt.bqt(url, { data: code, type: 'json', method: 'POST', timeout: 6000 })
} catch (e) {
console.error(e)
return ctx.reply(errorTextRenderService + cancelText)
}
//await ctx.deleteMessage(loadMsg.message_id)
let codeMsg = await ctx.replyWithPhoto({ source: codeImage }, Markup.inlineKeyboard([
[
Markup.callbackButton('Zurück', 'back'),
Markup.callbackButton('Nächster Code', 'next')
],
[
Markup.callbackButton('Sitzung beenden', 'abort')
]
]).extra())
ctx.session.codeMsg = codeMsg.message_id
})
angebotCodeStage.leave(async (ctx) => {
// if (!!ctx.session.codeMsg)
// await ctx.deleteMessage(ctx.session.codeMsg)
})
angebotCodeStage.action('abort', async (ctx) => {
await ctx.replyWithChatAction('typing')
try {
//TODO: call SessionManager to delete the session
} catch(e) {
}
leaveFunc(ctx)
ctx.scene.enter('main')
})
angebotCodeStage.action('back', (ctx) => {
ctx.answerCbQuery('')
ctx.scene.enter('angebotSelect')
})
angebotCodeStage.action('next', async (ctx) => {
ctx.answerCbQuery('')
ctx.scene.enter('angebotCode')
})
angebotCodeStage.action(/(.*)/, async (ctx) => {
ctx.answerCbQuery('')
console.log(ctx.match[1])
})
const startFunc = async (ctx) => {
let userExists = await Models.User.count({ id: ctx.from.id })
if (userExists === 0) {
ctx.scene.enter('setup')
} else {
ctx.scene.enter('main')
}
}
// Create scene manager
const stage = new Stage()
const leaveFunc = leave()
stage.command('cancel', (ctx) => {
leaveFunc(ctx)
ctx.reply('Aktion erfolgreich abgebrochen, nutze /start um nochmal neu anzufangen')
})
stage.command('start', (ctx) => {
leaveFunc(ctx)
startFunc(ctx)
})
// Scene registration
stage.register(setup0Stage)
stage.register(setup1Stage)
stage.register(mainStage)
stage.register(angebotSelectStage)
stage.register(angebotCodeStage)
bot.use(stage.middleware())
bot.command('start', startFunc)
bot.command('settings', (ctx) => ctx.scene.enter('setup'))
bot.launch()