├── README.md ├── data.js ├── .gitignore ├── package.json ├── LICENSE.md └── bot.js /README.md: -------------------------------------------------------------------------------- 1 | Code of @OneQRBot in Telegram for scanning and generating QR-code easy-way. 2 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | token: 'token', // token from @BotFather 3 | mongoLink: 'link', // mongo-link from cloud.mongodb.com 4 | dev: 123456789, // developer`s id for sending him/her errors 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | # Removing private data 31 | data.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oneqrbot", 3 | "version": "1.0.0", 4 | "description": "oneqrbot", 5 | "main": "bot.js", 6 | "scripts": { 7 | "test": "nodemon bot.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Khuzha/oneqrbot.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/Khuzha/oneqrbot/issues" 18 | }, 19 | "homepage": "https://github.com/Khuzha/oneqrbot#readme", 20 | "dependencies": { 21 | "axios": "^0.21.1", 22 | "mongodb": "^3.2.6", 23 | "nodemon": "^1.19.1", 24 | "qrcode": "^1.3.3", 25 | "telegraf": "^3.29.0", 26 | "unirest": "^0.6.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | const Telegraf = require('telegraf') 2 | const mongo = require('mongodb').MongoClient 3 | const axios = require('axios') 4 | const fs = require('fs') 5 | const data = require('./data') 6 | const countries = require('./countries') 7 | const session = require('telegraf/session') 8 | const Stage = require('telegraf/stage') 9 | const Scene = require('telegraf/scenes/base') 10 | const { leave } = Stage 11 | const stage = new Stage() 12 | const bot = new Telegraf(data.token) 13 | 14 | 15 | const scanQR = new Scene('scanQR') 16 | stage.register(scanQR) 17 | const generate = new Scene('generate') 18 | stage.register(generate) 19 | const scanBarcode = new Scene('scanBarcode') 20 | stage.register(scanBarcode) 21 | 22 | mongo.connect(data.mongoLink, {useNewUrlParser: true}, (err, client) => { 23 | if (err) { 24 | sendError(err) 25 | } 26 | 27 | db = client.db('oneqrbot') 28 | bot.startPolling() 29 | }) 30 | 31 | 32 | bot.use(session()) 33 | bot.use(stage.middleware()) 34 | 35 | bot.start((ctx) => { 36 | starter(ctx) 37 | }) 38 | 39 | 40 | bot.hears('🔍 Scan QR Code', (ctx) => { 41 | ctx.scene.enter('scanQR') 42 | }) 43 | 44 | bot.hears('🖊 Generate QR Code', (ctx) => { 45 | ctx.scene.enter('generate') 46 | }) 47 | 48 | bot.hears('🔍 Scan Barcode', (ctx) => { 49 | ctx.scene.enter('scanBarcode') 50 | }) 51 | 52 | bot.hears('📁 Source code', (ctx) => { 53 | ctx.reply( 54 | 'You can see code of this bot on GitHub. Thanks for stars!', 55 | { reply_markup: { inline_keyboard: [[{text: '🔗 GitHub', url: 'https://github.com/Khuzha/oneqrbot'}]] } } 56 | ) 57 | }) 58 | 59 | scanBarcode.enter((ctx) => { 60 | ctx.reply( 61 | 'I`m ready. Send a picture!', 62 | { reply_markup: { keyboard: [['⬅️ Back']], resize_keyboard: true } } 63 | ) 64 | }) 65 | 66 | scanBarcode.leave((ctx) => starter(ctx)) 67 | 68 | scanBarcode.on('photo', async (ctx) => { 69 | ctx.replyWithChatAction('typing') 70 | 71 | const imageData = await bot.telegram.getFile(ctx.message.photo[ctx.message.photo.length - 1].file_id) 72 | const writer = fs.createWriteStream(data.imagesFolder + imageData.file_path.substr(7)) 73 | 74 | axios({ 75 | method: 'get', 76 | url: `https://api.telegram.org/file/bot${data.token}/${imageData.file_path}`, 77 | responseType: 'stream' 78 | }) 79 | .then(async (response) => { 80 | await response.data.pipe(writer) 81 | axios({ 82 | method: 'get', 83 | url: `https://zxing.org/w/decode?u=https://khuzha.tk/barcodes/${imageData.file_path.substr(7)}` 84 | }) 85 | .then((barcodeData) => { 86 | const html = barcodeData.data.toString() 87 | const start = html.indexOf('Parsed Result') + 31 88 | const end = html.indexOf('') 89 | 90 | ctx.reply(`Your code is ${html.substring(start, end)}. Soon I'll say you the country of origin of products. Wait please! function is in development still.`) 91 | }) 92 | .catch((err) => { 93 | if (err.response.data.includes('No barcode was found in this image')) { 94 | return ctx.reply('No data found on this photo. Please try again.') 95 | } 96 | console.log(2, err) 97 | sendError(`error when sending zxing: ${err}`, ctx) 98 | }) 99 | }) 100 | .catch((err) => { 101 | console.log(1, err) 102 | ctx.reply('No data found on this photo. Please try again.') 103 | sendError(err, ctx) 104 | }) 105 | }) 106 | 107 | scanBarcode.hears('⬅️ Back', (ctx) => {ctx.scene.leave('scanBarcode')}) 108 | 109 | scanBarcode.leave((ctx) => starter(ctx)) 110 | 111 | 112 | scanQR.enter((ctx) => { 113 | ctx.reply( 114 | 'I`m ready. Send a picture!', 115 | { reply_markup: { keyboard: [['⬅️ Back']], resize_keyboard: true } } 116 | ) 117 | }) 118 | 119 | scanQR.on('photo', async (ctx) => { 120 | ctx.replyWithChatAction('typing') 121 | 122 | const imageData = await bot.telegram.getFile(ctx.message.photo[ctx.message.photo.length - 1].file_id) 123 | 124 | axios({ 125 | url: `https://api.qrserver.com/v1/read-qr-code/?fileurl=https://api.telegram.org/file/bot${data.token}/${imageData.file_path}`, 126 | method: 'GET' 127 | }) 128 | .then(async (response) => { 129 | if (response.data[0].symbol[0].error === null) { 130 | await ctx.reply('Scanned data:') 131 | await ctx.reply(response.data[0].symbol[0].data) 132 | } else { 133 | await ctx.reply('No data found on this picture.') 134 | } 135 | 136 | ctx.reply('You can send me other pictures or tap "⬅️ Back"') 137 | 138 | updateStat('scanning') 139 | updateUser(ctx, true) 140 | }) 141 | .catch((err) => { 142 | ctx.reply('No data found on this picture.') 143 | sendError(err, ctx) 144 | }) 145 | }) 146 | 147 | scanQR.hears('⬅️ Back', (ctx) => { 148 | starter(ctx) 149 | ctx.scene.leave('scanQR') 150 | }) 151 | 152 | 153 | generate.enter((ctx) => { 154 | ctx.reply( 155 | 'I`m ready. Send me text!', 156 | { reply_markup: { keyboard: [['⬅️ Back']], resize_keyboard: true } } 157 | ) 158 | }) 159 | 160 | generate.hears('⬅️ Back', (ctx) => { 161 | starter(ctx) 162 | ctx.scene.leave('generate') 163 | }) 164 | 165 | generate.on('text', async (ctx) => { 166 | if (ctx.message.text.length > 900) { 167 | return ctx.reply('Your text is too long. Please send text that contains not more than 900 symbols.') 168 | } 169 | 170 | ctx.replyWithChatAction('upload_photo') 171 | 172 | axios.get(`http://api.qrserver.com/v1/create-qr-code/?data=${encodeURI(ctx.message.text)}&size=300x300`) 173 | .then(async (response) => { 174 | await ctx.replyWithPhoto(`http://api.qrserver.com/v1/create-qr-code/?data=${encodeURI(ctx.message.text)}&size=300x300`, { caption: 'Generated via @OneQRBot' }) 175 | ctx.reply('You can send me another text or tap "⬅️ Back"') 176 | 177 | updateStat('generating') 178 | updateUser(ctx, true) 179 | }) 180 | .catch(async (err) => { 181 | console.log(err) 182 | await ctx.reply('Data you sent isn`t valid. Please check that and try again.') 183 | ctx.reply('You can send me another text or tap "⬅️ Back"') 184 | 185 | sendError(`Generating error by message ${ctx.message.text}: \n\n ${err.toString()}`, ctx) 186 | }) 187 | }) 188 | 189 | 190 | bot.hears('📈 Statistic', async (ctx) => { 191 | ctx.replyWithChatAction('typing') 192 | 193 | const allUsers = (await db.collection('allUsers').find({}).toArray()).length 194 | const activeUsers = (await db.collection('allUsers').find({status: 'active'}).toArray()).length 195 | const blockedUsers = (await db.collection('allUsers').find({status: 'blocked'}).toArray()).length 196 | const scanned = await db.collection('statistic').find({genAct: 'scanning'}).toArray() 197 | const generated = await db.collection('statistic').find({genAct: 'generating'}).toArray() 198 | const button = (await db.collection('statistic').find({genAct: 'button'}).toArray())[0].count 199 | let todayScans = +(await db.collection('statistic').find({action: 'scanning'}).toArray())[0][makeDate()] 200 | let todayGens = +(await db.collection('statistic').find({action: 'generating'}).toArray())[0][makeDate()] 201 | 202 | !todayScans ? todayScans = 0 : false 203 | !todayGens ? todayGens = 0 : false 204 | 205 | let scansPercent = Math.round((scanned[0].count / (scanned[0].count + generated[0].count)) * 100) 206 | let gensPercent = Math.round((generated[0].count / (scanned[0].count + generated[0].count)) * 100) 207 | let todayScansPercent = Math.round((todayScans / (todayScans + todayGens)) * 100) 208 | let todayGensPercent = Math.round((todayGens / (todayScans + todayGens)) * 100) 209 | 210 | !scansPercent ? scansPercent = 0 : false 211 | !gensPercent ? gensPercent = 0 : false 212 | !todayScansPercent ? todayScansPercent = 0 : false 213 | !todayGensPercent ? todayGensPercent = 0 : false 214 | 215 | ctx.reply( 216 | `👥 Total users: ${allUsers}` + 217 | `\n🤴 Active users: ${activeUsers} - ${Math.round((activeUsers / allUsers) * 100)}%` + 218 | `\n🧛‍♂️ Blocked users: ${blockedUsers} - ${Math.round((blockedUsers / allUsers) * 100)}%` + 219 | 220 | `\n\n🕹 All actions: ${scanned[0].count + generated[0].count}` + 221 | `\n📽 Scanned: ${scanned[0].count} times - ${scansPercent}%` + 222 | `\n📤 Generated: ${generated[0].count} times - ${gensPercent}%` + 223 | 224 | `\n\n📅 Actions today: ${todayScans + todayGens} - ${Math.round((todayScans + todayGens) / (scanned[0].count + generated[0].count) * 100)}% of all` + 225 | `\n📽 Scanned today: ${todayScans} times - ${todayScansPercent}%` + 226 | `\n📤 Generated today: ${todayGens} times - ${todayGensPercent}%` + 227 | 228 | `\n\n⭕️ This button was pressed ${button} times`, 229 | {parse_mode: 'html'} 230 | ) 231 | 232 | updateStat('button') 233 | }) 234 | 235 | 236 | bot.command('users', async (ctx) => { 237 | let allUsers = await db.collection('allUsers').find({}).toArray() 238 | let activeUsers = 0 239 | let blockedUsers = 0 240 | 241 | for (let key of allUsers) { 242 | await bot.telegram.sendChatAction(key.userId, 'typing') 243 | .then((res) => { 244 | activeUsers++ 245 | }) 246 | .catch((err) => { 247 | blockedUsers++ 248 | updateUser(ctx, false) 249 | }) 250 | } 251 | 252 | ctx.reply( 253 | `⭕️ Total users: ${allUsers.length} ` + 254 | `\n✅ Active users: ${activeUsers} - ${Math.round((activeUsers / allUsers.length) * 100)}%` + 255 | `\n❌ Blocked users: ${blockedUsers} - ${Math.round((blockedUsers / allUsers.length) * 100)}%` 256 | ) 257 | }) 258 | 259 | bot.on('message', async (ctx) => { 260 | ctx.scene.leave('scanQR') 261 | ctx.scene.leave('generator') 262 | starter(ctx) 263 | }) 264 | 265 | 266 | function starter (ctx) { 267 | ctx.reply( 268 | 'Hi! What do you want to do?', 269 | { reply_markup: { keyboard: [['🔍 Scan QR Code'], ['🖊 Generate QR Code'], ['🔍 Scan Barcode'], ['📈 Statistic', '📁 Source code']], resize_keyboard: true } } 270 | ) 271 | 272 | updateUser(ctx, true) 273 | } 274 | 275 | function updateUser (ctx, active) { 276 | let jetzt = active ? 'active' : 'blocked' 277 | db.collection('allUsers').updateOne({userId: ctx.from.id}, {$set: {status: jetzt}}, {upsert: true, new: true}) 278 | } 279 | 280 | function updateStat (action) { 281 | if (action == 'button') { 282 | return db.collection('statistic').updateOne({genAct: action}, {$inc: {count: 1}}, {new: true, upsert: true}) 283 | } 284 | 285 | db.collection('statistic').updateOne({action: action}, {$inc: {[makeDate()]: 1}}, {new: true, upsert: true}) 286 | db.collection('statistic').updateOne({genAct: action}, {$inc: {count: 1}}, {new: true, upsert: true}) 287 | } 288 | 289 | function makeDate () { 290 | const today = new Date() 291 | const yyyy = today.getFullYear() 292 | let mm = today.getMonth() + 1 293 | let dd = today.getDate() 294 | 295 | dd < 10 ? dd = '0' + dd : false 296 | mm < 10 ? mm = '0' + mm : false 297 | return `${mm}/${dd}/${yyyy}` 298 | } 299 | 300 | function sendError (err, ctx) { 301 | if (err.toString().includes('message is not modified')) { 302 | return 303 | } 304 | bot.telegram.sendMessage(data.dev, `Ошибка у [${ctx.from.first_name}](tg://user?id=${ctx.from.id}) \n\nОшибка: ${err}`, { parse_mode: 'markdown' }) 305 | } --------------------------------------------------------------------------------