├── Procfile ├── .gitignore ├── src ├── handlers │ ├── callback.js │ ├── notImplemented.js │ ├── admin │ │ ├── statistics.js │ │ ├── beta.js │ │ └── check.js │ ├── start.js │ ├── text.js │ ├── fileId.js │ ├── index.js │ ├── toSticker.js │ ├── settings.js │ ├── reset.js │ ├── language.js │ ├── historyElement.js │ ├── history.js │ ├── service.js │ ├── photo.js │ ├── document.js │ └── back.js ├── scripts │ ├── setLanguage.js │ ├── checkSubscription.js │ ├── replyWithSticker.js │ ├── replyWithFile.js │ ├── convertToSticker.js │ ├── replyWithError.js │ ├── createStatsObject.js │ └── removeBackground.js ├── database │ ├── models │ │ ├── Bot.js │ │ └── User.js │ ├── findFileById.js │ ├── findOrCreateUser.js │ ├── deleteUser.js │ ├── findFileByTimestamp.js │ ├── connect.js │ ├── updateTokens.js │ ├── resetTokens.js │ └── assignBetaTester.js ├── middlewares │ ├── ignoreOldMessages.js │ └── attachUser.js ├── config.js ├── locales │ ├── te.yaml │ ├── it.yaml │ ├── es.yaml │ ├── pt-br.yaml │ ├── ml.yaml │ ├── ru.yaml │ ├── de.yaml │ └── en.yaml └── bot.js ├── index.js ├── package.json ├── LICENSE └── README.md /Procfile: -------------------------------------------------------------------------------- 1 | Worker: node --max-old-space-size=4096 index.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.prettierrc.json 2 | /package-lock.json 3 | /node_modules -------------------------------------------------------------------------------- /src/handlers/callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = () => (ctx) => { 4 | try { 5 | return ctx.answerCbQuery(); 6 | } catch (err) { 7 | console.error(err); 8 | } 9 | }; -------------------------------------------------------------------------------- /src/handlers/notImplemented.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = () => (ctx) => { 4 | try { 5 | return ctx.answerCbQuery(ctx.i18n.t('error.not_implemented'), true); 6 | } catch (err) { 7 | console.error(err); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The source code of Burn Background Telegram bot 3 | * 4 | * With this bot you can remove background 5 | * from any picture. Enough of manual work, let the bot 6 | * do it for you 7 | * 8 | * Copyright (c) 2021-2022 Vyacheslav 9 | */ 10 | require('./src/bot'); 11 | -------------------------------------------------------------------------------- /src/scripts/setLanguage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | module.exports = (language) => { 6 | try { 7 | const folder = fs.readdirSync('./src/locales/'); 8 | const ISO = folder.map(e => e.replace('.yaml', '')); 9 | 10 | return ISO.includes(language) ? language : 'en'; 11 | } catch (err) { 12 | console.error(err); 13 | } 14 | }; -------------------------------------------------------------------------------- /src/database/models/Bot.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const botSchema = new mongoose.Schema({ 4 | id: Number, 5 | active_token: String, 6 | number: Number, 7 | inactive_tokens: { 8 | type: Array, 9 | required: false, 10 | default: [], 11 | }, 12 | type: String, 13 | }); 14 | 15 | module.exports = mongoose.model('Bot', botSchema); 16 | -------------------------------------------------------------------------------- /src/database/findFileById.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('./models/User'); 4 | 5 | module.exports = async (id, fileId) => { 6 | try { 7 | const user = await User.findOne({ id, 'files.file_id': fileId }); 8 | const file = user.files.filter((e) => e.file_id == fileId); 9 | 10 | return file ? file[0] : false; 11 | } catch (err) { 12 | console.error(err); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/database/findOrCreateUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('./models/User'); 4 | 5 | module.exports = async (data) => { 6 | try { 7 | const user = await User.findOne({ id: data.id }); 8 | 9 | if (!user) { 10 | return await new User({ ...data }).save(); 11 | } else { 12 | return user; 13 | } 14 | } catch (err) { 15 | console.error(err); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/database/deleteUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('./models/User'); 4 | 5 | module.exports = async (id) => { 6 | try { 7 | return User.deleteOne({ id }) 8 | .then(() => { 9 | return { ok: true }; 10 | }) 11 | .catch((err) => { 12 | return { ok: false, message: err.message }; 13 | }); 14 | } catch (err) { 15 | console.error(err); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/database/findFileByTimestamp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('./models/User'); 4 | 5 | module.exports = async (id, timestamp) => { 6 | try { 7 | const user = await User.findOne({ id, 'files.timestamp': timestamp }); 8 | const file = user.files.filter((e) => new Date(e.timestamp).getTime() === new Date(timestamp).getTime()); 9 | 10 | return file ? JSON.parse(JSON.stringify(file[0])) : false; 11 | } catch (err) { 12 | console.error(err); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/middlewares/ignoreOldMessages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = () => { 4 | return async (ctx, next) => { 5 | try { 6 | if (new Date().getTime() / 1000 - (ctx?.message?.date || ctx?.update?.callback_query?.message?.date) < (1400 * 60)) { 7 | return next(); 8 | } else { 9 | console.info( 10 | `[${ctx.chat?.id}] Ignoring update (${ctx.updateType})` 11 | ); 12 | } 13 | } catch (err) { 14 | console.error(err) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/database/connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const config = require('../config'); 5 | 6 | module.exports = async () => { 7 | try { 8 | mongoose 9 | .connect(config.database, { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true, 12 | useCreateIndex: true, 13 | useFindAndModify: false, 14 | }) 15 | .then(() => console.log('[Database] Successfully connected.')) 16 | .catch((err) => console.error('[Database] Failed to connect.\n\n' + err)); 17 | } catch (err) { 18 | console.error(err); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/database/updateTokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Bot = require('./models/Bot'); 4 | 5 | module.exports = async (bot) => { 6 | try { 7 | Bot.updateOne( 8 | { id: 1 }, 9 | { 10 | $set: { 11 | active_token: bot.active_token, 12 | inactive_tokens: bot.inactive_tokens, 13 | number: bot.number, 14 | type: bot.type, 15 | }, 16 | }, 17 | () => {} 18 | ).catch((err) => console.error('[Database] Failed to update tokens\n\n' + err)); 19 | } catch (err) { 20 | console.error(err); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/database/resetTokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Bot = require('./models/Bot'); 4 | const config = require('../config'); 5 | 6 | module.exports = async () => { 7 | try { 8 | Bot.updateOne( 9 | { id: 1 }, 10 | { 11 | $set: { 12 | active_token: config.host_token, 13 | inactive_tokens: [], 14 | number: 1, 15 | type: '5', 16 | }, 17 | }, 18 | () => {} 19 | ) 20 | .then(() => console.log('[Database] Successfully reseted tokens')) 21 | .catch((err) => console.error('[Database] Failed to reset tokens\n\n' + err)); 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/handlers/admin/statistics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('../../database/models/User'); 4 | const config = require('../../config'); 5 | 6 | module.exports = () => async (ctx) => { 7 | try { 8 | if (ctx.from.id != config.admin) return; 9 | 10 | const usersLength = await User.find().countDocuments(); 11 | const lastTimeUsersLength = ctx.session?.usersLength; 12 | const difference = lastTimeUsersLength ? usersLength - lastTimeUsersLength : 0; 13 | 14 | ctx.session.usersLength = usersLength; 15 | 16 | return ctx.replyWithHTML( 17 | `Number of users: ${usersLength} (${(difference <= 0 ? '' : '+') + difference})` 18 | ); 19 | } catch (err) { 20 | console.error(err); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/handlers/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const assignBetaTester = require('../database/assignBetaTester'); 5 | const replyWithError = require('../scripts/replyWithError'); 6 | 7 | module.exports = () => (ctx) => { 8 | try { 9 | if (ctx.startPayload.length > 0) return assignBetaTester(ctx); 10 | 11 | return ctx 12 | .replyWithHTML( 13 | ctx.i18n.t('service.greeting', { name: ctx.from.first_name.replace(/[<>]/g, '') }), 14 | Markup.keyboard([[ctx.i18n.t('button.settings')]]) 15 | .resize() 16 | .extra() 17 | ) 18 | .catch((err) => { 19 | console.error(err); 20 | return replyWithError(ctx, 'METHOD_FAILED'); 21 | }); 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/scripts/checkSubscription.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('../config'); 4 | const replyWithError = require('./replyWithError'); 5 | 6 | module.exports = (ctx) => { 7 | try { 8 | return ctx.telegram 9 | .getChatMember(config.channel, ctx.from.id) 10 | .then(async (response) => { 11 | const roles = ['member', 'creator', 'administrator']; 12 | const isSubscriber = roles.includes(response.status) ? true : false; 13 | 14 | ctx.user.channel_member = isSubscriber; 15 | await ctx.user.save(); 16 | 17 | return isSubscriber; 18 | }) 19 | .catch((err) => { 20 | console.error(err); 21 | return replyWithError(ctx, 'METHOD_FAILED'); 22 | }); 23 | } catch (err) { 24 | console.error(err); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/handlers/text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const replyWithError = require('../scripts/replyWithError'); 4 | 5 | module.exports = () => (ctx) => { 6 | try { 7 | ctx.session.sent_text = ctx.session.sent_text === undefined ? 1 : ctx.session.sent_text + 1; 8 | 9 | if (ctx.session.sent_text === 20) { 10 | ctx.session.sent_text = 0; 11 | return ctx.replyWithHTML(ctx.i18n.t('error.no_text_messages_egg')).catch((err) => { 12 | console.error(err); 13 | return replyWithError(ctx, 'METHOD_FAILED'); 14 | }); 15 | } else { 16 | return ctx.replyWithHTML(ctx.i18n.t('error.no_text_messages')).catch((err) => { 17 | console.error(err); 18 | return replyWithError(ctx, 'METHOD_FAILED'); 19 | }); 20 | } 21 | } catch (err) { 22 | console.error(err); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/scripts/replyWithSticker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const convertToSticker = require('./convertToSticker'); 4 | const replyWithError = require('./replyWithError'); 5 | 6 | module.exports = async (ctx, result) => { 7 | try { 8 | const sticker = await convertToSticker(result.buffer, result.text); 9 | 10 | await ctx.deleteMessage(result.standby_message_id).catch((err) => { 11 | console.error(err); 12 | }); 13 | 14 | await ctx 15 | .replyWithSticker( 16 | { source: sticker }, 17 | { 18 | reply_to_message_id: ctx.message.message_id, 19 | } 20 | ) 21 | .catch((err) => { 22 | console.error(err); 23 | return replyWithError(ctx, 'METHOD_FAILED'); 24 | }); 25 | } catch (err) { 26 | console.error(err); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "burngbg-bot", 3 | "version": "2.0.12", 4 | "description": "With this bot you can remove background from any picture", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js" 9 | }, 10 | "keywords": [ 11 | "telegram", 12 | "bot" 13 | ], 14 | "author": "Vyacheslav ", 15 | "license": "MIT", 16 | "dependencies": { 17 | "axios": "^0.24.0", 18 | "byte-size": "^8.1.0", 19 | "form-data": "^4.0.0", 20 | "moment": "^2.29.1", 21 | "mongoose": "^5.13.4", 22 | "plural-ru": "^2.0.2", 23 | "sharp": "^0.29.3", 24 | "telegraf": "^3.38.0", 25 | "telegraf-i18n": "^6.6.0", 26 | "telegraf-ratelimit": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "nodemon": "^2.0.15" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/middlewares/attachUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findOrCreateUser = require('../database/findOrCreateUser'); 4 | const setLanguage = require('../scripts/setLanguage'); 5 | 6 | module.exports = () => { 7 | return async (ctx, next) => { 8 | try { 9 | const data = { 10 | id: ctx.from.id, 11 | first_name: ctx.from.first_name.replace(/[<>]/g, ''), 12 | last_name: ctx.from.last_name === undefined ? null : ctx.from.last_name, 13 | username: ctx.from.username === undefined ? null : ctx.from.username, 14 | language: setLanguage(ctx.from.language_code) 15 | }; 16 | 17 | const user = await findOrCreateUser(data); 18 | ctx.user = user; 19 | ctx.i18n.locale(ctx.user.language); 20 | 21 | return next(); 22 | } catch (err) { 23 | console.error(err) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/scripts/replyWithFile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const replyWithError = require('./replyWithError'); 4 | 5 | module.exports = async (ctx, result) => { 6 | try { 7 | await ctx.replyWithChatAction('upload_document'); 8 | 9 | await ctx.deleteMessage(result.standby_message_id).catch((err) => { 10 | console.error(err); 11 | }); 12 | 13 | await ctx 14 | .replyWithDocument( 15 | { 16 | source: result.buffer, 17 | filename: result.message?.file_name ? `${result.message.file_name}.png` : '@burnbgbot.png', 18 | }, 19 | { 20 | reply_to_message_id: ctx.message.message_id, 21 | } 22 | ) 23 | .catch((err) => { 24 | console.error(err); 25 | return replyWithError(ctx, 'METHOD_FAILED'); 26 | }); 27 | } catch (err) { 28 | console.error(err); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/handlers/fileId.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findFileById = require('../database/findFileById'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | 6 | module.exports = () => async (ctx) => { 7 | try { 8 | const file_id = ctx.match.input.replace('file_id::', ''); 9 | 10 | const file = await findFileById(ctx.from.id, file_id); 11 | 12 | if (!file) 13 | return ctx.replyWithHTML(ctx.i18n.t('error.not_owner_of_image')).catch((err) => { 14 | console.error(err); 15 | return replyWithError(ctx, 'METHOD_FAILED'); 16 | }); 17 | 18 | return ctx.replyWithPhoto(file_id).catch(() => { 19 | // if the source was a document 20 | return ctx.replyWithDocument(file_id).catch((err) => { 21 | console.error(err); 22 | return replyWithError(ctx, 'METHOD_FAILED'); 23 | }); 24 | }); 25 | } catch (err) { 26 | console.error(err); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/scripts/convertToSticker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sharp = require('sharp'); 4 | 5 | module.exports = async (image) => { 6 | try { 7 | sharp.concurrency(0); 8 | 9 | const config = { 10 | width: 512, 11 | height: 512, 12 | channels: 4, 13 | background: { r: 0, g: 0, b: 0, alpha: 0 }, 14 | }; 15 | 16 | const background = await sharp({ create: config }).webp().toBuffer(); 17 | 18 | const buffer = await sharp(image).trim().webp().toBuffer(); 19 | 20 | const image_over_background = await sharp(buffer) 21 | .resize({ 22 | width: 512, 23 | height: 512, 24 | fit: sharp.fit.inside, 25 | }) 26 | .webp() 27 | .toBuffer(); 28 | 29 | return sharp(background) 30 | .composite([{ input: image_over_background }]) 31 | .webp() 32 | .toBuffer(); 33 | } catch (err) { 34 | console.error(err); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/handlers/index.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | module.exports = { 3 | handleStart : require('./start'), 4 | handlePhoto : require('./photo'), 5 | handleDocument : require('./document'), 6 | handleSettings : require('./settings'), 7 | handleHistory : require('./history'), 8 | handleHistoryElement : require('./historyElement'), 9 | handleToSticker : require('./toSticker'), 10 | handleService : require('./service'), 11 | handleLanguage : require('./language'), 12 | handleText : require('./text'), 13 | handleReset : require('./reset'), 14 | handleNotImplemented : require('./notImplemented'), 15 | handleFileId : require('./fileId'), 16 | handleStatistics : require('./admin/statistics'), 17 | handleBeta : require('./admin/beta'), 18 | handleCheck : require('./admin/check'), 19 | handleBack : require('./back'), 20 | handleCallback : require('./callback'), 21 | }; 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Vyacheslav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | token: process.env.TOKEN, 3 | database: process.env.DATABASE, 4 | admin: process.env.ADMIN, 5 | host: process.env.HOST, 6 | host2: process.env.HOST2, 7 | host_token: process.env.HOST_TOKEN, 8 | host_token2: process.env.HOST_TOKEN2, 9 | host_token3: process.env.HOST_TOKEN3, 10 | host_token4: process.env.HOST_TOKEN4, 11 | host_token5: process.env.HOST_TOKEN5, 12 | host_token6: process.env.HOST_TOKEN6, 13 | host_token7: process.env.HOST_TOKEN7, 14 | host_token8: process.env.HOST_TOKEN8, 15 | host_token9: process.env.HOST_TOKEN9, 16 | host_token10: process.env.HOST_TOKEN10, 17 | host2_token: process.env.HOST2_TOKEN, 18 | channel: process.env.CHANNEL, 19 | handler_timeout: 100, 20 | buttons: [ 21 | 'Settings', 22 | 'Настройки', 23 | 'Impostazioni', 24 | 'Configuraciones', 25 | 'సెట్టింగులు', 26 | 'ക്രമീകരണങ്ങൾ', 27 | 'Configuração', 28 | 'Einstellungen', 29 | ], 30 | limit: { 31 | window: 1000, 32 | limit: 1, 33 | onLimitExceeded: (ctx) => require('./scripts/replyWithError')(ctx, 'LIMIT_EXCEEDED'), 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/database/assignBetaTester.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('./models/User'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | 6 | module.exports = async (ctx) => { 7 | const testers = await User.find({ beta: true }); 8 | const isAlreadyInList = testers.find((e) => e.id === ctx.from.id); 9 | 10 | if (ctx.startPayload !== 'beta') 11 | return ctx.replyWithHTML(ctx.i18n.t('service.wrong_payload')).catch((err) => { 12 | console.error(err); 13 | return replyWithError(ctx, 'METHOD_FAILED'); 14 | }); 15 | 16 | if (isAlreadyInList) 17 | return ctx.replyWithHTML(ctx.i18n.t('service.already_joined_beta')).catch((err) => { 18 | console.error(err); 19 | return replyWithError(ctx, 'METHOD_FAILED'); 20 | }); 21 | 22 | if (testers.length >= 50) { 23 | await ctx.replyWithHTML(ctx.i18n.t('service.beta_full')).catch((err) => { 24 | console.error(err); 25 | return replyWithError(ctx, 'METHOD_FAILED'); 26 | }); 27 | } else { 28 | await User.updateOne({ id: ctx.from.id }, { $set: { beta: true } }, () => {}); 29 | await ctx.replyWithHTML(ctx.i18n.t('service.joined_beta')).catch((err) => { 30 | console.error(err); 31 | return replyWithError(ctx, 'METHOD_FAILED'); 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/handlers/toSticker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | 6 | module.exports = () => async (ctx) => { 7 | try { 8 | await ctx.answerCbQuery(); 9 | 10 | ctx.user.to_sticker = ctx.user.to_sticker ? false : true; 11 | 12 | await ctx 13 | .editMessageReplyMarkup( 14 | Markup.inlineKeyboard([ 15 | [ 16 | Markup.callbackButton(ctx.i18n.t('button.language'), 'language'), 17 | Markup.callbackButton(ctx.i18n.t('button.service'), 'service'), 18 | ], 19 | [ 20 | Markup.callbackButton( 21 | ctx.i18n.t('button.to_sticker', { 22 | state: ctx.user.to_sticker ? ctx.i18n.t('action.a_on') : ctx.i18n.t('action.a_off'), 23 | }), 24 | 'to_sticker' 25 | ), 26 | ], 27 | ]) 28 | ) 29 | .catch((err) => { 30 | console.error(err); 31 | return replyWithError(ctx, 'METHOD_FAILED'); 32 | }); 33 | 34 | await ctx.user.save(); 35 | } catch (err) { 36 | console.error(err); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/handlers/admin/beta.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('../../database/models/User'); 4 | const config = require('../../config'); 5 | 6 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 7 | 8 | module.exports = () => async (ctx) => { 9 | try { 10 | if (ctx.from.id != config.admin) return; 11 | 12 | const betaTesters = await User.find({ beta: true }); 13 | const message = ctx.message.text.replace(/\/beta\s+/g, ''); 14 | 15 | if (betaTesters.length <= 0) return ctx.replyWithHTML('No beta testers.'); 16 | 17 | await ctx.replyWithHTML('Started sending messages...'); 18 | 19 | let i = 0; 20 | let received = 0; 21 | let lost = 0; 22 | 23 | for (const tester of betaTesters) { 24 | if (i === 29) { 25 | await sleep(1000); 26 | i = 0; 27 | } 28 | 29 | await ctx.telegram 30 | .sendMessage(tester.id, message) 31 | .then(() => { 32 | ++received; 33 | }) 34 | .catch(() => { 35 | ++lost; 36 | }); 37 | 38 | ++i; 39 | } 40 | 41 | await ctx.deleteMessage(ctx.message.message_id + 1); 42 | 43 | return ctx.replyWithHTML( 44 | `Done! Messages were sent. Well, at least I've tried.\n\nReceived: ${received}\nLost: ${lost}\nTotal: ${betaTesters.length}` 45 | ); 46 | } catch (err) { 47 | console.error(err); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/handlers/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const createStatsObject = require('../scripts/createStatsObject'); 5 | const replyWithError = require('../scripts/replyWithError'); 6 | 7 | module.exports = () => (ctx) => { 8 | try { 9 | return ctx 10 | .replyWithHTML( 11 | ctx.i18n.t(ctx.user.usage <= 0 ? 'service.settings_new' : 'service.settings', { 12 | beta_sign: ctx.user.beta ? '(beta)' : '', 13 | ...createStatsObject(ctx), 14 | }), 15 | { 16 | reply_markup: Markup.inlineKeyboard([ 17 | [ 18 | Markup.callbackButton(ctx.i18n.t('button.language'), 'language'), 19 | Markup.callbackButton(ctx.i18n.t('button.service'), 'service'), 20 | ], 21 | [ 22 | Markup.callbackButton( 23 | ctx.i18n.t('button.to_sticker', { 24 | state: ctx.user.to_sticker ? ctx.i18n.t('action.a_on') : ctx.i18n.t('action.a_off'), 25 | }), 26 | 'to_sticker' 27 | ), 28 | ], 29 | ]), 30 | disable_web_page_preview: true, 31 | } 32 | ) 33 | .catch((err) => { 34 | console.error(err); 35 | return replyWithError(ctx, 'METHOD_FAILED'); 36 | }); 37 | } catch (err) { 38 | console.error(err); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/database/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | id: { 5 | type: Number, 6 | unique: true, 7 | required: true, 8 | }, 9 | first_name: String, 10 | last_name: String, 11 | username: String, 12 | language: String, 13 | role: { 14 | type: String, 15 | required: false, 16 | default: 'user', 17 | }, 18 | to_sticker: { 19 | type: Boolean, 20 | required: false, 21 | default: false, 22 | }, 23 | add_text: { 24 | type: Boolean, 25 | required: false, 26 | default: false, 27 | }, 28 | service: { 29 | type: Number, 30 | required: false, 31 | default: 0, 32 | }, 33 | usage: { 34 | type: Number, 35 | required: false, 36 | default: 0, 37 | }, 38 | converted_to_file: { 39 | type: Number, 40 | required: false, 41 | default: 0, 42 | }, 43 | converted_to_sticker: { 44 | type: Number, 45 | required: false, 46 | default: 0, 47 | }, 48 | last_time_used: { 49 | type: Date, 50 | required: false, 51 | default: 0, 52 | }, 53 | channel_member: { 54 | type: Boolean, 55 | required: false, 56 | default: null, 57 | }, 58 | beta: { 59 | type: Boolean, 60 | required: false, 61 | default: false, 62 | }, 63 | files: [ 64 | { 65 | type: Object, 66 | required: false, 67 | }, 68 | ], 69 | timestamp: { 70 | type: Date, 71 | required: false, 72 | default: new Date(), 73 | }, 74 | }); 75 | 76 | module.exports = mongoose.model('User', userSchema); 77 | -------------------------------------------------------------------------------- /src/handlers/admin/check.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const User = require('../../database/models/User'); 4 | const config = require('../../config'); 5 | 6 | module.exports = () => async (ctx) => { 7 | try { 8 | if (ctx.from.id != config.admin) return; 9 | 10 | const usersLength = await User.find().countDocuments(); 11 | const pages = Math.ceil(usersLength / 1000); 12 | 13 | if (usersLength <= 0) return ctx.replyWithHTML('No users.'); 14 | 15 | await ctx.replyWithHTML('Started checking users...'); 16 | 17 | let counter = 0; 18 | let alive = 0; 19 | let dead = 0; 20 | let skipNumber = 0; 21 | 22 | for (let i = 0; i < pages; i++) { 23 | let users = await User.find({}, null, { 24 | skip: skipNumber, 25 | limit: 1000, 26 | }).then((response) => response); 27 | 28 | for (const user of users) { 29 | console.log(`[${counter} / ${usersLength}] Checking ${user.id}...`); 30 | 31 | await ctx.telegram 32 | .sendChatAction(user.id, 'typing') 33 | .then(() => { 34 | ++alive; 35 | }) 36 | .catch(() => { 37 | ++dead; 38 | }); 39 | 40 | ++counter; 41 | } 42 | 43 | skipNumber += 1000; 44 | users = []; 45 | } 46 | 47 | await ctx.deleteMessage(ctx.message.message_id + 1); 48 | 49 | return ctx.replyWithHTML( 50 | `Done! All the users were checked.\n\nAlive: ${alive}\nDead: ${dead}\nTotal: ${usersLength}` 51 | ); 52 | } catch (err) { 53 | console.error(err); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/handlers/reset.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | const deleteUser = require('../database/deleteUser'); 6 | 7 | module.exports = () => async (ctx) => { 8 | try { 9 | if (ctx.updateType === 'message') { 10 | return ctx 11 | .replyWithHTML(ctx.i18n.t('service.pre_reset'), { 12 | reply_markup: Markup.inlineKeyboard([ 13 | [ 14 | Markup.callbackButton(ctx.i18n.t('action.a_yes'), 'yes'), 15 | Markup.callbackButton(ctx.i18n.t('action.a_no'), 'no'), 16 | ], 17 | ]), 18 | disable_web_page_preview: true, 19 | }) 20 | .catch((err) => { 21 | console.error(err); 22 | return replyWithError(ctx, 'METHOD_FAILED'); 23 | }); 24 | } else { 25 | await ctx.answerCbQuery(); 26 | 27 | const action = ctx.match; 28 | 29 | if (action === 'yes') { 30 | const isDeleted = await deleteUser(ctx.from.id); 31 | 32 | if (!isDeleted.ok) { 33 | return replyWithError(ctx, 'NO_USER_TO_DELETE'); 34 | } else { 35 | await ctx.deleteMessage(); 36 | 37 | return ctx 38 | .replyWithHTML(ctx.i18n.t('service.reset'), { 39 | parse_mode: 'HTML', 40 | disable_web_page_preview: true, 41 | reply_markup: { remove_keyboard: true }, 42 | }) 43 | .catch((err) => { 44 | console.error(err); 45 | return replyWithError(ctx, 'METHOD_FAILED'); 46 | }); 47 | } 48 | } else { 49 | return ctx.deleteMessage().catch((err) => { 50 | console.error(err); 51 | return replyWithError(ctx, 'METHOD_FAILED'); 52 | }); 53 | } 54 | } 55 | } catch (err) { 56 | console.error(err); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Burn Background Bot 2 | 3 | [![release](https://img.shields.io/badge/release-v2.0.12-green.svg?style=flat)]() 4 | [![license](https://img.shields.io/github/license/s0ftik3/burnbg-bot)]() 5 | [![bot](https://img.shields.io/badge/Bot-Telegram-blue)](https://t.me/burnbgbot) 6 | 7 | ![preview](https://i.ibb.co/dKxZf5G/preview.png) 8 | 9 | ## About 10 | 11 | This bot can remove image backgrounds automatically in a few seconds. No need to spend hours manually picking pixels. Just send a picture or file to the bot and see the magic. You can also convert your image to a sticker, activate this option in settings. You have 5 free attempts, after that you will be asked to subscribe to my channel and you will be granted unlimited access to the bot. The bot is fully free and your subscription is your gratitude to me. 12 | 13 | ## Translators 14 | 15 | - Russian by [Vyacheslav](https://t.me/vychs) 16 | - Italian by [Seba](https://t.me/probably_dead) 17 | - Spanish by [Angel Erazo](https://t.me/aerazo) 18 | - Telugu by [Hello](https://t.me/Udaycab) 19 | - Malayalam by [Shabin-k](https://github.com/SHABIN-K) 20 | - Portuguese by [Douglas Gusson](https://t.me/gussond) 21 | - German by [Supportiii2](https://github.com/Supportiii2) 22 | 23 | Don't see your language on the list? Add it! 24 | Text to be translated is found in `src/locales/en.yaml` 25 | 26 | Fork this [repository](https://github.com/s0ftik3/burnbg-bot), add your translations, and submit a pull request. 27 | 28 | > NOTE: Please, do not change any other files except locale ones. 29 | 30 | ## Additional information 31 | 32 | - [Telegram channel](https://t.me/softik) with the bot's updates and more. 33 | - From [1.4.8](https://github.com/s0ftik3/burnbg-bot/commit/38927527e873f2b9640387f4ff7703ca7a070175) update the bot stores all the pictures you send to it (meaning that if you keep using the bot from September 23rd to this day, your pictures are being saved). They're stored as [file_ids](https://core.telegram.org/bots/api#file) which are provided by Telegram itself. For moderating puprposes only, so just keep it in mind. 34 | - The developer isn't responsible for pictures that are being sent by users and processed by the bot. 35 | - [An article](https://vc.ru/tribuna/309559-lyubitelyam-avtomatizacii-post) about the bot on [vc.ru](https://vc.ru) 36 | -------------------------------------------------------------------------------- /src/handlers/language.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const TelegrafI18n = require('telegraf-i18n'); 8 | const i18n = new TelegrafI18n({ 9 | directory: path.resolve(__dirname, '../locales'), 10 | defaultLanguage: 'en', 11 | defaultLanguageOnMissing: true, 12 | }); 13 | 14 | module.exports = () => async (ctx) => { 15 | try { 16 | await ctx.answerCbQuery(); 17 | 18 | const action = ctx.match; 19 | 20 | if (action === 'language') { 21 | const buttons = []; 22 | 23 | const locales_folder = fs.readdirSync('./src/locales/'); 24 | 25 | locales_folder.forEach((file) => { 26 | const localization = file.split('.')[0]; 27 | buttons.push(Markup.callbackButton(i18n.t(localization, 'language'), `set_language:${localization}`)); 28 | }); 29 | 30 | buttons.push(Markup.callbackButton(ctx.i18n.t('button.back'), 'back:settings')); 31 | 32 | const keyboard = buttons.filter((e) => e.callback_data != `set_language:${ctx.user.language}`); 33 | 34 | await ctx 35 | .editMessageText(ctx.i18n.t('service.change_language'), { 36 | reply_markup: Markup.inlineKeyboard(keyboard, { columns: 2 }), 37 | }) 38 | .catch((err) => { 39 | console.error(err); 40 | return replyWithError(ctx, 'METHOD_FAILED'); 41 | }); 42 | } else { 43 | const language = ctx.match[0].split(':')[1]; 44 | 45 | ctx.i18n.locale(language); 46 | 47 | ctx.user.language = language; 48 | 49 | await ctx.deleteMessage().catch((err) => { 50 | console.error(err); 51 | return replyWithError(ctx, 'METHOD_FAILED'); 52 | }); 53 | 54 | await ctx 55 | .replyWithHTML( 56 | ctx.i18n.t('service.language_changed'), 57 | Markup.keyboard([[ctx.i18n.t('button.settings')]]) 58 | .resize() 59 | .extra() 60 | ) 61 | .catch((err) => { 62 | console.error(err); 63 | return replyWithError(ctx, 'METHOD_FAILED'); 64 | }); 65 | 66 | await ctx.user.save(); 67 | } 68 | } catch (err) { 69 | console.error(err); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /src/locales/te.yaml: -------------------------------------------------------------------------------- 1 | language: 🇮🇳 తెలుగు 2 | 3 | service: 4 | greeting: | 5 | 🖐 హాయ్ , ${name}! మీరు నాకు పంపే ఏదైనా ఫోటో నేపథ్యాన్ని (Background) నేను తీసివేయగలను. నాకు ఫైల్ లేదా ఫోటో పంపి ప్రయత్నించి మరియు మ్యాజిక్ చూడండి. 6 | 7 | ⭐️ నేను మీ ఫోటోలను స్టిక్కర్‌గా కూడా మార్చగలను. ఇది చేయడానికి మీరు మీ సెట్టింగ్స్కి వెళ్ళండి 8 | settings: ${beta_sign} ${range} పాటు మీరు ${usage} ఇమేజ్‌లను ప్రాసెస్ చేసారు, వాటిలో ${converted_to_file} ఫైల్‌గా మార్చబడ్డాయి మరియు ${converted_to_sticker} స్టిక్కర్‌గా మార్చబడ్డాయి. 9 | standby: 🕓 దయచేసి, చిత్రాన్ని ప్రాసెస్ చేస్తున్నప్పుడు వేచి ఉండండి ... 10 | change_language: ఇంటర్‌ఫేస్ భాషను ఎంచుకోండి. 11 | language_changed: 🇮🇳 భాష మార్చబడింది. 12 | warning: (బీటా) మీరు ఎంచుకున్న రెండవ సేవ నెమ్మదిగా మరియు అస్థిరంగా పనిచేస్తుంది. 13 | service_no_change: మీరు ఇప్పుడు సేవల మధ్య మారలేరు. 14 | image_downloaded: ${type} (${size}) చిత్రం డౌన్‌లోడ్ చేయబడింది. ప్రాసెస్ చేస్తోంది ... 15 | 16 | error: 17 | common: 😥 ఎక్కడో తేడ జరిగింది. దయచేసి మళ్ళీ /start ప్రెస్ చేయండి 18 | common_important: 😥 సేవ స్పందించలేదు. సమస్యను పరిష్కరించడానికి దయచేసి @vychs ని సంప్రదించండి. 19 | limit_exceeded: 👮 తొందరపడకండి! 20 | wrong_file_extension: 📄 తప్పు ఫైల్ ఫార్మటు జోడించారు . JPG/JPEG/PNG కి మాత్రమే పని చేస్తుంది. 21 | no_text_messages: 😕 దీని బదులు నాకు ఇమేజ్ లేదా ఇమేజ్ ఫైల్ పంపండి 22 | no_text_messages_egg: 😠 మీకు I-M-A-G-E పంపమని చెప్పబడింది! 23 | file_too_big: ✋ మీ ఫైల్ చాలా పెద్దది దయచేసి నాకు 20 MB లోపు ఫైల్‌లను పంపండి. 24 | switched_tokens: | 25 | 😞 నన్ను క్షమించండి, దయచేసి అదే ఫోటోని మళ్లీ పంపి ప్రయత్నించండి. ఇప్పుడు బోట్‌లో చాలా ఎక్కువ లోడ్ ఉంది. మీరు బహుశా నాకు చాలాసార్లు ఫోటోని పంపవలసి ఉంటుంది. 26 | 27 | ఏమీ మారకపోతే మరియు నేను మీకు ఈ మెసేజ్ నీ పంపుతూనే ఉంటే, @vychs ని సంప్రదించండి. 28 | failed_download_file: 😞 ఈ ఫోటోను డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు. దయచేసి, ఇంకొక ఫోటోను ప్రయత్నించండి. 29 | not_a_member: ✋ బాట్ ఉపయోగించడం కొనసాగించడానికి, దయచేసి ఛానెల్‌కు సభ్యత్వాన్ని పొందండి. ఇది డెవలపర్ ఛానెల్ మరియు మీకు అక్కడ స్పామ్ కనిపించదు. బోట్ ఉచితం మరియు ఇది మీ మద్దతు :) 30 | api_error: 😥 ఎక్కడో తేడ జరిగింది. దయచేసి వేరే ఫోటోను ప్రయత్నించండి లేదా సర్వీస్‌ని /settings లో మార్చండి. 31 | 32 | button: 33 | language: భాషను మార్చు 34 | settings: సెట్టింగులు 35 | to_sticker: 'స్టిక్కర్‌గా మార్చండి: ${state}' 36 | service: సర్వీస్ 37 | add_text: 'టెక్స్ట్‌తో స్టిక్కర్: ${state}' 38 | channel: ఛానల్ 39 | support: సర్వీస్ 40 | subscribe: సబ్స్క్రయిబ్ 41 | back: ‹ బ్యాక్ 42 | 43 | action: 44 | a_on: సహా 45 | a_off: ఆఫ్ 46 | -------------------------------------------------------------------------------- /src/handlers/historyElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const moment = require('moment'); 5 | const findFileByTimestamp = require('../database/findFileByTimestamp'); 6 | const replyWithError = require('../scripts/replyWithError'); 7 | 8 | module.exports = () => async (ctx) => { 9 | try { 10 | await ctx.answerCbQuery(); 11 | 12 | moment.locale(ctx.user.language); 13 | 14 | const userId = ctx.from.id; 15 | const cache = ctx.session?.historyCache; 16 | const timestamp = new Date(Number(ctx.match[0].split(':')[1])); 17 | 18 | if (cache) { 19 | const file = cache.content.filter( 20 | (e) => new Date(e.timestamp).getTime() == new Date(timestamp).getTime() 21 | )[0]; 22 | 23 | return ctx 24 | .editMessageText( 25 | ctx.i18n.t('service.history_element', { 26 | file_id: 'file_id::' + file.file_id, 27 | output: file.output === 'file' ? ctx.i18n.t('other.file') : ctx.i18n.t('other.sticker'), 28 | date: moment(file.timestamp).format('LLL'), 29 | }), 30 | { 31 | parse_mode: 'HTML', 32 | reply_markup: Markup.inlineKeyboard([ 33 | Markup.callbackButton(ctx.i18n.t('button.back'), 'back:history'), 34 | ]), 35 | } 36 | ) 37 | .catch((err) => { 38 | console.error(err); 39 | return replyWithError(ctx, 'METHOD_FAILED'); 40 | }); 41 | } 42 | 43 | const file = await findFileByTimestamp(userId, timestamp); 44 | 45 | return ctx 46 | .editMessageText( 47 | ctx.i18n.t('service.history_element', { 48 | file_id: 'file_id::' + file.file_id, 49 | output: file.output === 'file' ? ctx.i18n.t('other.file') : ctx.i18n.t('other.sticker'), 50 | date: moment(file.timestamp).format('LLL'), 51 | }), 52 | { 53 | parse_mode: 'HTML', 54 | reply_markup: Markup.inlineKeyboard([ 55 | Markup.callbackButton(ctx.i18n.t('button.back'), 'back:history'), 56 | ]), 57 | } 58 | ) 59 | .catch((err) => { 60 | console.error(err); 61 | return replyWithError(ctx, 'METHOD_FAILED'); 62 | }); 63 | } catch (err) { 64 | console.error(err); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/locales/it.yaml: -------------------------------------------------------------------------------- 1 | language: 🇮🇹 Italiano 2 | 3 | service: 4 | greeting: | 5 | 🖐 Ciao, ${name}! Posso rimuovere lo sfondo di ogni foto che mi mandi. Prova a mandarmi un file o una foto e osserva la magia. 6 | 7 | ⭐️ Posso anche convertire la tua immagine in uno sticker. Utilizzare il comando /settings per attivare questa opzione. 8 | settings: ${beta_sign} Per ${range} che hai processato ${usage} image${plural} dei quali ${converted_to_file} convertiti a file e ${converted_to_sticker} convertiti a sticker. 9 | standby: 🕓 Per favore, standby mentre l'immagine è in elaborazione... 10 | change_language: Scegli la lingua dell'interfaccia. 11 | language_changed: 🇮🇹 Lingua cambiata. 12 | warning: (beta) Il secondo servizio che hai appena scelto funziona in modo molto più lento e instabile. 13 | service_no_change: Non puoi passare da un servizio all'altro ora. 14 | image_downloaded: ${type} (${size}) L'immagine è stata scaricata. In lavorazione, potrebbero volerci alcuni secondi... 15 | 16 | error: 17 | common: 😥 Qualcosa è andato storto. Prova /start ancora. 18 | common_important: 😥 Il servizio non ha risposto. Per favore contatta @vychs per risolvere il problema. 19 | limit_exceeded: 👮🏻 Non avere fretta! 20 | wrong_file_extension: 📄 Estensione del file errata. Sono supportati solo JPG/JPEG/PNG. 21 | no_text_messages: 😕 Inviami invece un'immagine o un'immagine. 22 | no_text_messages_egg: 😠 Ti è stato detto di inviare un' I-M-M-A-G-I-N-E! 23 | file_too_big: ✋ Il file è troppo grosso. Per favore mandami un file fino a 20 MB. 24 | switched_tokens: | 25 | 😞 Mi dispiace, prova di nuovo con la stessa immagine. C'è un carico enorme per il bot ora. Probabilmente dovrai inviarmi la foto più volte. 26 | 27 | Se non cambia nulla e continuo ad inviarti questo messaggio, contatta @vychs. 28 | failed_download_file: 😞 Impossibile scaricare questa foto. Per favore, prova con qualche altra foto. 29 | not_a_member: ✋ Per continuare a utilizzare il bot, iscriviti al canale. È il canale degli sviluppatori e lì non vedrai spam. Il bot è gratuito e questo è il tuo supporto :) 30 | api_error: 😥 Qualcosa è andato storto. Prova con qualche altra foto o cambia servizio in /settings. 31 | 32 | button: 33 | language: Cambia Lingua 34 | settings: Impostazioni 35 | to_sticker: 'Converti a Sticker: ${state}' 36 | service: Cambia Servizio 37 | add_text: 'Add text to Sticker: ${state}' 38 | channel: Canale 39 | support: Supporto 40 | subscribe: Sottoscrivi 41 | back: ‹ Indietro 42 | 43 | action: 44 | a_on: 'ON' 45 | a_off: 'OFF' 46 | -------------------------------------------------------------------------------- /src/locales/es.yaml: -------------------------------------------------------------------------------- 1 | language: 🇪🇸 Español 2 | 3 | service: 4 | greeting: | 5 | 🖐 Hola, ${name}! Puedo remover el fondo de cualquier imagen que me envíes. Prueba enviándome un archivo o una foto y verás la magia. 6 | 7 | ⭐️ También puedo convertir tu imagen en un stiker. Utilice el comando /settings para activar esta opción. 8 | settings: ${beta_sign} Por ${range} has procesado ${usage} image${plural}, de la cuales, ${converted_to_file} fueron convertidas a archivos, y ${converted_to_sticker} fueron convertidas a sticker. 9 | standby: 🕓 Por favor, espera mientras la imagen se está convirtiendo... 10 | change_language: Escoge el idioma de la interface. 11 | language_changed: 🇪🇸 Idioma cambiado. 12 | warning: (beta) El segundo servicio trabaja más lento y es inestable. 13 | service_no_change: No tienes permitido cambiar entre servicios por ahora. 14 | image_downloaded: ${type} (${size}) La imagen ha sido descargada. Procesando, puede tardar unos segundos... 15 | 16 | error: 17 | common: 😥 Algo falló. Por favor intenta /start de nuevo. 18 | common_important: 😥 El servicio no respondió. Por favor contacta a @vychs para notificar sobre el problema. 19 | limit_exceeded: 👮🏻 No te apresures! 20 | wrong_file_extension: 📄 La extensión del archivo es errónea. Solo JPG/JPEG/PNG son soportados. 21 | no_text_messages: 😕 Envíame una imagen o archivo de imagen en su lugar. 22 | no_text_messages_egg: 😠 Tu no me enviaste una I-M-A-G-E-N! 23 | file_too_big: ✋ El archivo es demasiado grande. Por favor, envía archivos de hasta 20 MB. 24 | switched_tokens: | 25 | 😞 Lo siento, por favor intenta otra vez con la misma imagen. Es difícil que el bot cargue ahora. Probablemente necesitarás enviarme una imagen varias veces. 26 | 27 | Si no hay cambios y te está enviando este mensaje, contacta a @vychs. 28 | failed_download_file: 😞 No se pudo descargar esta foto. Por favor, intenta con otra. 29 | not_a_member: ✋ Para continuar usando el bot, por favor suscríbete al canal. El desarrollador del canal y tu no verán ningún span allí. El bot es gratis y esta es tu ayuda para nosotros :) 30 | api_error: 😥 Algo salió mal. Por favor intenta con otra foto o cambia de servicio en /settings. 31 | 32 | button: 33 | language: Cambiar idioma 34 | settings: Configuraciones 35 | to_sticker: 'Convertir a sticker: ${state}' 36 | service: Cambiar servicio 37 | add_text: 'Add text to Sticker: ${state}' 38 | channel: Canal 39 | support: Soporte 40 | subscribe: Suscribirse 41 | back: ‹ Regresar 42 | 43 | action: 44 | a_on: 'ON' 45 | a_off: 'OFF' 46 | -------------------------------------------------------------------------------- /src/locales/pt-br.yaml: -------------------------------------------------------------------------------- 1 | language: 🇧🇷 Português 2 | 3 | service: 4 | greeting: | 5 | 🖐 Olá, ${name}! Eu posso remover o fundo de qualquer foto que você me enviar. Experimente, me envie um arquivo ou uma foto e veja a magia! 6 | 7 | ⭐️ Eu também posso converter sua imagem em um sticker. Use o comando /settings para ativar esta opção. 8 | settings: ${beta_sign} Por ${range} você processou ${usage} image${plural}, das quais ${converted_to_file} covertidas para arquivo e ${converted_to_sticker} convertidas para sticker. 9 | standby: 🕓 Por favor, aguarde enquanto a imagem está sendo processada... 10 | change_language: Escolha o idioma da interface. 11 | language_changed: 🇧🇷 Idioma alterado. 12 | warning: (beta) O segundo serviço que você acabou de escolher funciona de forma mais lenta e instável. 13 | service_no_change: Você não pode alternar entre os serviços agora. 14 | image_downloaded: ${type} (${size}) A imagem foi baixada. O processamento pode demorar alguns segundos... 15 | 16 | error: 17 | common: 😥 Algo deu errado. Por favor, tente usar o comando /start novamente. 18 | common_important: 😥 O serviço não respondeu. Por favor, entre em contato com @vychs para resolver o problema. 19 | limit_exceeded: 👮🏻 Fique calmo! 20 | wrong_file_extension: 📄 Extensão de arquivo errada. Apenas JPG/JPEG/PNG são suportados. 21 | no_text_messages: 😕 Envie-me uma imagem ou um arquivo de imagem. 22 | no_text_messages_egg: 😠 Você deve enviar uma I-M-A-G-E-M! 23 | file_too_big: ✋ Seu arquivo é muito grande. Por favor, me envie arquivos de até 20 MB. 24 | switched_tokens: | 25 | 😞 Lamento, tente enviar a mesma imagem novamente. Há uma carga muito grande no bot agora. Você provavelmente precisará me enviar uma foto várias vezes. 26 | 27 | Se o problema continuar e eu continuar enviando esta mensagem para você, entre em contato com @vychs. 28 | failed_download_file: 😞 Não foi possível baixar esta foto. Por favor, tente outra foto. 29 | not_a_member: ✋ Para continuar usando o bot, por favor se inscreva no canal. É o canal do desenvolvedor e você não verá nenhum spam lá. O bot é gratuito e este é o seu apoio :) 30 | api_error: 😥 Algo deu errado. Tente alguma outra foto ou altere o serviço em /settings. 31 | 32 | button: 33 | language: Mudar idioma 34 | settings: Configuração 35 | to_sticker: 'Converter para Sticker: ${state}' 36 | service: Mudar serviço 37 | add_text: 'Sticker com Texto: ${state}' 38 | channel: Canal 39 | support: Suporte 40 | subscribe: Se inscrever 41 | back: ‹ Voltar 42 | 43 | action: 44 | a_on: 'SIM' 45 | a_off: 'NAO' 46 | -------------------------------------------------------------------------------- /src/locales/ml.yaml: -------------------------------------------------------------------------------- 1 | language: 🇮🇳 മലയാളം 2 | 3 | service: 4 | greeting: | 5 | 🖐 ഹായ്, ${name}! നിങ്ങൾ എനിക്ക് അയയ്ക്കുന്ന ഏത് ഫോട്ടോയുടെയും പശ്ചാത്തലം എനിക്ക് നീക്കം ചെയ്യാൻ കഴിയും. ഒരു ഫയലോ ഫോട്ടോയോ എനിക്ക് അയച്ച് മാജിക് കാണുക. 6 | 7 | ⭐️ എനിക്ക് നിങ്ങളുടെ ചിത്രം ഒരു സ്റ്റിക്കറാക്കി മാറ്റാനും കഴിയും. ഈ ഓപ്ഷൻ സജീവമാക്കാൻ /settings കമാൻഡ് ഉപയോഗിക്കുക. 8 | settings: ${beta_sign} ബോട്ട് പ്രവർത്തിക്കുന്ന രീതി നിങ്ങൾക്ക് ഇവിടെ സജ്ജമാക്കാൻ കഴിയും..${plug} 9 | standby: 🕓 ദയവായി കാത്തിരിക്കുക .ചിത്രം പ്രോസസ്സ് ചയ്തുകൊണ്ടിരിക്കുന്നു... 10 | change_language: ഭാഷ തിരഞ്ഞെടുക്കുക. 11 | language_changed: 🇮🇳 ഭാഷ മാറ്റി. 12 | warning: (beta) നിങ്ങൾ തിരഞ്ഞെടുത്ത രണ്ടാമത്തെ സേവനം മന്ദഗതിയിലും അസ്ഥിരമായും പ്രവർത്തിക്കുന്നു. 13 | service_no_change: സേവനങ്ങൾക്കിടയിൽ നിങ്ങൾക്ക് ഇപ്പോൾ മാറാൻ കഴിയില്ല. 14 | image_downloaded: ${type} (${size}) ചിത്രം ഡൗൺലോഡ് ചെയ്തു. പ്രോസസ് ചെയ്യുന്നു, ഇതിന് കുറച്ച് നിമിഷങ്ങൾ എടുത്തേക്കാം... 15 | 16 | error: 17 | common: 😥 എന്തോ കുഴപ്പം സംഭവിച്ചു.ദയവായി വീണ്ടും ശ്രമിക്കുക.അതിന് ആയി /start കമാൻഡ് ഉപയോഗിക്കുക. 18 | common_important: 😥 ഈ സേവനം പ്രതികരിച്ചില്ല.പ്രശ്നം പരിഹരിക്കാൻ @vychs-നെ ബന്ധപ്പെടുക. 19 | limit_exceeded: 👮🏻 തിരക്കുകൂട്ടരുത്! 20 | wrong_file_extension: 📄 തെറ്റായ ഫയൽ വിപുലീകരണം.JPG/JPEG/PNG മാത്രമേ പിന്തുണയ്ക്കൂ. 21 | no_text_messages: 😕 പകരം ഒരു ചിത്രമോ ചിത്രത്തിന്റെ ഒരു ഫയലോ എനിക്ക് അയയ്ക്കുക. 22 | no_text_messages_egg: 😠 നിങ്ങളോട് ഒരു ചിത്രം അയ്ക്കാൻ ആണ് പറഞ്ഞത് ! 23 | file_too_big: ✋ നിങ്ങളുടെ ഫയൽ വളരെ വലുതാണ്. ദയവായി, എനിക്ക് 20 MB വരെയുള്ള ഫയലുകൾ അയയ്ക്കുക.. 24 | switched_tokens: | 25 | 😞 ക്ഷമിക്കണം,ഇതേ ചിത്രം വീണ്ടും ശ്രമിക്കുക. ഇപ്പോൾ ബോട്ടിൽ വളരെ വലിയ ലോഡ് ആണ്. നിങ്ങൾ ഒരുപക്ഷേ എനിക്ക് നിരവധി തവണ ഒരു ചിത്രം അയയ്‌ക്കേണ്ടി വരും. 26 | 27 | ഒന്നും മാറുന്നില്ലെങ്കിൽ ഞാൻ നിങ്ങൾക്ക് ഈ സന്ദേശം അയച്ചുകൊണ്ടിരിക്കുകയാണെങ്കിൽ, @vychs- നെ ബന്ധപ്പെടുക. 28 | failed_download_file: 😞 ഈ ഫോട്ടോ ഡൗൺലോഡ് ചെയ്യാനായില്ല. ദയവായി, മറ്റേതെങ്കിലും ഫോട്ടോ പരീക്ഷിക്കൂ. 29 | not_a_member: ✋ഈ ബോട്ട് ഉപയോഗിക്കുന്നത് തുടരാൻ, ദയവായി ചാനൽ സബ്സ്ക്രൈബ് ചെയ്യുക. ഇത് ഡവലപ്പറുടെ ചാനലാണ്, നിങ്ങൾ അവിടെ സ്പാം ഒന്നും കാണില്ല. ബോട്ട് സൗജന്യമാണ്, ഇത് നിങ്ങൾ നങ്ങൾക് തരുന്ന പിന്തുണയാണ് :) 30 | api_error: 😥 എന്തോ കുഴപ്പം സംഭവിച്ചു.ദയവായി മറ്റേതെങ്കിലും ചിത്രം ശ്രമിക്കുക അല്ലെങ്കിൽ സേവനം മാറ്റുക അതിന് ആയി /settings കമാൻഡ് ഉപയോഗിക്കുക. 31 | 32 | button: 33 | language: ഭാഷ മാറ്റുക 34 | settings: ക്രമീകരണങ്ങൾ 35 | to_sticker: 'സ്റ്റിക്കറിലേക്ക് പരിവർത്തനം ചെയ്യുക: ${state}' 36 | service: സേവനം 37 | add_text: 'Sticker with Text: ${state}' 38 | channel: ചാനൽ 39 | support: സപ്പോർട്ട് 40 | subscribe: സബ്സ്ക്രൈബ് 41 | back: ‹ തിരികെ 42 | 43 | action: 44 | a_on: 'ON' 45 | a_off: 'OFF' 46 | -------------------------------------------------------------------------------- /src/bot.js: -------------------------------------------------------------------------------- 1 | const Telegraf = require('telegraf'); 2 | const config = require('./config'); 3 | const bot = new Telegraf(config.token, { handlerTimeout: config.handler_timeout }); 4 | 5 | const rateLimit = require('telegraf-ratelimit'); 6 | const session = require('telegraf/session'); 7 | 8 | const path = require('path'); 9 | const TelegrafI18n = require('telegraf-i18n'); 10 | const i18n = new TelegrafI18n({ 11 | directory: path.resolve(__dirname, './locales'), 12 | defaultLanguage: 'en', 13 | defaultLanguageOnMissing: true, 14 | }); 15 | 16 | const attachUser = require('./middlewares/attachUser'); 17 | const ignoreOldMessages = require('./middlewares/ignoreOldMessages'); 18 | 19 | const connect = require('./database/connect'); 20 | const resetTokens = require('./database/resetTokens'); 21 | 22 | const { 23 | handleStart, 24 | handlePhoto, 25 | handleDocument, 26 | handleSettings, 27 | handleHistory, 28 | handleHistoryElement, 29 | handleToSticker, 30 | handleService, 31 | handleLanguage, 32 | handleText, 33 | handleReset, 34 | handleNotImplemented, 35 | handleFileId, 36 | handleStatistics, 37 | handleBeta, 38 | handleCheck, 39 | handleBack, 40 | handleCallback, 41 | } = require('./handlers'); 42 | 43 | bot.catch((err, ctx) => { 44 | console.log(`Ooops, encountered an error for ${ctx.updateType}`, err); 45 | }); 46 | 47 | bot.use(i18n.middleware()); 48 | bot.use(session()); 49 | bot.use(ignoreOldMessages()); 50 | bot.use(attachUser()); 51 | bot.use(rateLimit(config.limit)); 52 | 53 | bot.start(handleStart()); 54 | 55 | bot.command('settings', handleSettings()); 56 | bot.command('history', handleHistory()); 57 | bot.command('reset', handleReset()); 58 | 59 | bot.command(['statistics', 's'], handleStatistics()); 60 | bot.command('beta', handleBeta()); 61 | bot.command('check', handleCheck()); 62 | 63 | bot.action('to_sticker', handleToSticker()); 64 | bot.action('service', handleService()); 65 | bot.action('language', handleLanguage()); 66 | bot.action('yes', handleReset()); 67 | bot.action('no', handleReset()); 68 | bot.action('not_implemented_yet', handleNotImplemented()); 69 | bot.action(/set_language:(.*)/, handleLanguage()); 70 | bot.action(/change_service:(.*)/, handleService()); 71 | bot.action(/open:(.*)/, handleHistoryElement()); 72 | bot.action(/back:(.*)/, handleBack()); 73 | 74 | bot.hears(config.buttons, handleSettings()); 75 | bot.hears(/file_id::/, handleFileId()); 76 | 77 | bot.on('text', handleText()); 78 | bot.on('photo', handlePhoto()); 79 | bot.on('document', handleDocument()); 80 | bot.on('callback_query', handleCallback()); 81 | 82 | bot.launch().then(async () => { 83 | await connect(); 84 | await resetTokens(); 85 | console.log( 86 | `[${bot.context.botInfo.first_name}] The bot has been started --> https://t.me/${bot.context.botInfo.username}` 87 | ); 88 | }); 89 | -------------------------------------------------------------------------------- /src/handlers/history.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | 6 | module.exports = () => (ctx) => { 7 | try { 8 | if (!ctx.user.beta) return replyWithError(ctx, 'BETA_ONLY'); 9 | 10 | const userFiles = ctx.user.files; 11 | const cache = ctx.session?.historyCache; 12 | 13 | if (userFiles.length <= 0) { 14 | return ctx.replyWithHTML(ctx.i18n.t('error.history_empty')).catch((err) => { 15 | console.error(err); 16 | return replyWithError(ctx, 'METHOD_FAILED'); 17 | }); 18 | } 19 | 20 | if (cache && userFiles.length === cache.files && ctx.user.language === cache.languageLayout) { 21 | return ctx 22 | .replyWithHTML(ctx.i18n.t('service.history', { total: userFiles.length }), { 23 | reply_markup: Markup.inlineKeyboard(cache.buttons), 24 | }) 25 | .catch((err) => { 26 | console.error(err); 27 | return replyWithError(ctx, 'METHOD_FAILED'); 28 | }); 29 | } 30 | 31 | const lastFiveFiles = userFiles.reverse().slice(0, 5); 32 | 33 | ctx.session.historyCache = { content: lastFiveFiles }; 34 | 35 | const buttons = lastFiveFiles.map((e) => { 36 | const year = new Date(e.timestamp).getFullYear().toString(); 37 | const month = (new Date(e.timestamp).getMonth() + 1).toString().padStart(2, '0'); 38 | const day = new Date(e.timestamp).getDate().toString().padStart(2, '0'); 39 | const hour = new Date(e.timestamp).getHours().toString().padStart(2, '0'); 40 | const minute = new Date(e.timestamp).getMinutes().toString().padStart(2, '0'); 41 | const second = new Date(e.timestamp).getSeconds().toString().padStart(2, '0'); 42 | 43 | const buttonName = `${year}-${month}-${day} ${hour}:${minute}:${second}`; 44 | 45 | return [Markup.callbackButton(buttonName, `open:${new Date(e.timestamp).getTime()}`)]; 46 | }); 47 | 48 | if (userFiles.length > 5) { 49 | buttons.push([ 50 | Markup.callbackButton( 51 | ctx.i18n.t('button.more', { number: userFiles.length - 5 }), 52 | 'not_implemented_yet' 53 | ), 54 | ]); 55 | } 56 | 57 | ctx.session.historyCache = { 58 | ...JSON.parse(JSON.stringify(ctx.session.historyCache)), 59 | files: userFiles.length, 60 | languageLayout: ctx.user.language, 61 | buttons, 62 | }; 63 | 64 | return ctx 65 | .replyWithHTML(ctx.i18n.t('service.history', { total: userFiles.length }), { 66 | reply_markup: Markup.inlineKeyboard(buttons), 67 | }) 68 | .catch((err) => { 69 | console.error(err); 70 | return replyWithError(ctx, 'METHOD_FAILED'); 71 | }); 72 | } catch (err) { 73 | console.error(err); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/scripts/replyWithError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | 5 | module.exports = (ctx, code) => { 6 | try { 7 | switch (code) { 8 | case 'COMMON_ERROR': 9 | ctx.replyWithHTML(ctx.i18n.t('error.common')); 10 | console.error({ 11 | code: 0, 12 | type: 'error', 13 | message: `User ${ctx.from.id} isn't recorded in the database or there are some incorrect lines.`, 14 | }); 15 | break; 16 | case 'LIMIT_EXCEEDED': 17 | const a = () => ctx.answerCbQuery(ctx.i18n.t('error.limit_exceeded'), true); 18 | const b = () => ctx.replyWithHTML(ctx.i18n.t('error.limit_exceeded')); 19 | ctx.updateType === 'callback_query' ? a() : b(); 20 | break; 21 | case 'METHOD_FAILED': 22 | ctx.replyWithHTML(ctx.i18n.t('error.common')); 23 | break; 24 | case 'NOT_SUBSCRIBED': 25 | ctx.replyWithHTML(ctx.i18n.t('error.not_a_member'), { 26 | reply_markup: Markup.inlineKeyboard([ 27 | [Markup.urlButton(ctx.i18n.t('button.subscribe'), 'https://t.me/softik')], 28 | ]), 29 | }); 30 | break; 31 | case 'NO_USER_TO_DELETE': 32 | ctx.replyWithHTML(ctx.i18n.t('error.common')); 33 | break; 34 | case 'PROCESSING_ERROR': 35 | ctx.replyWithHTML(ctx.i18n.t('error.api_error')); 36 | break; 37 | case 'UNKNOWN_SERVICE': 38 | ctx.replyWithHTML(ctx.i18n.t('error.common')); 39 | break; 40 | case 'TOO_BIG_FILE': 41 | ctx.replyWithHTML(ctx.i18n.t('error.file_too_big')); 42 | break; 43 | case 'WRONG_FILE_EXTENSION': 44 | ctx.replyWithHTML(ctx.i18n.t('error.wrong_file_extension')); 45 | break; 46 | case 'FAILED_TO_DOWNLOAD': 47 | ctx.replyWithHTML(ctx.i18n.t('error.failed_download_file')); 48 | break; 49 | case 'CHANGED_SERVICE': 50 | ctx.replyWithHTML(ctx.i18n.t('error.changed_service')); 51 | break; 52 | case 'NO_ACTIVE_TOKENS': 53 | ctx.replyWithHTML(ctx.i18n.t('error.common_important')); 54 | break; 55 | case 'CHANGED_TOKEN': 56 | ctx.replyWithHTML(ctx.i18n.t('error.switched_tokens')); 57 | break; 58 | case 'API_ERROR': 59 | ctx.replyWithHTML(ctx.i18n.t('error.api_error')); 60 | break; 61 | case 'BETA_ONLY': 62 | ctx.replyWithHTML(ctx.i18n.t('error.beta_only')); 63 | break; 64 | default: 65 | ctx.replyWithHTML(ctx.i18n.t('error.common')); 66 | console.error({ 67 | code: 'default', 68 | type: 'error', 69 | message: `Something happed with ${ctx.from.id} user.`, 70 | }); 71 | break; 72 | } 73 | } catch (err) { 74 | console.error(err); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/handlers/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | 6 | module.exports = () => async (ctx) => { 7 | try { 8 | const user = ctx.user; 9 | const action = ctx.match; 10 | 11 | // For backwards compatibility 12 | if (user.service > 1) { 13 | user.service = 0; 14 | await user.save(); 15 | } 16 | 17 | if (action === 'service') { 18 | await ctx.answerCbQuery(); 19 | 20 | await ctx 21 | .editMessageText( 22 | ctx.i18n.t('service.change_service', { 23 | service_1: user.service === 0 ? '✅ cutout.pro' : 'cutout.pro', 24 | service_2: user.service === 1 ? '✅ benzin.io' : 'benzin.io', 25 | }), 26 | { 27 | parse_mode: 'HTML', 28 | disable_web_page_preview: true, 29 | reply_markup: Markup.inlineKeyboard([ 30 | [ 31 | Markup.callbackButton(user.service === 0 ? '· Cutout ·' : 'Cutout', 'change_service:0'), 32 | Markup.callbackButton(user.service === 1 ? '· Benzin ·' : 'Benzin', 'change_service:1'), 33 | ], 34 | [Markup.callbackButton(ctx.i18n.t('button.back'), 'back:settings')], 35 | ]), 36 | } 37 | ) 38 | .catch((err) => { 39 | console.error(err); 40 | return replyWithError(ctx, 'METHOD_FAILED'); 41 | }); 42 | } else { 43 | const old_service = user.service; 44 | const new_service = Number(ctx.match[1]); 45 | 46 | if (new_service === old_service) { 47 | return ctx.answerCbQuery(ctx.i18n.t('error.the_same_service'), true); 48 | } else { 49 | await ctx.answerCbQuery(); 50 | } 51 | 52 | ctx.user.service = new_service; 53 | 54 | ctx.editMessageText( 55 | ctx.i18n.t('service.change_service', { 56 | service_1: user.service === 0 ? '✅ cutout.pro' : 'cutout.pro', 57 | service_2: user.service === 1 ? '✅ benzin.io' : 'benzin.io', 58 | }), 59 | { 60 | parse_mode: 'HTML', 61 | disable_web_page_preview: true, 62 | reply_markup: Markup.inlineKeyboard([ 63 | [ 64 | Markup.callbackButton(user.service === 0 ? '· Cutout ·' : 'Cutout', 'change_service:0'), 65 | Markup.callbackButton(user.service === 1 ? '· Benzin ·' : 'Benzin', 'change_service:1'), 66 | ], 67 | [Markup.callbackButton(ctx.i18n.t('button.back'), 'back:settings')], 68 | ]), 69 | } 70 | ).catch((err) => { 71 | console.error(err); 72 | return replyWithError(ctx, 'METHOD_FAILED'); 73 | }); 74 | 75 | await ctx.user.save(); 76 | } 77 | } catch (err) { 78 | console.error(err); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/handlers/photo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RemoveBackground = require('../scripts/removeBackground'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | const checkSubscription = require('../scripts/checkSubscription'); 6 | const updateTokens = require('../database/updateTokens'); 7 | const replyWithFile = require('../scripts/replyWithFile'); 8 | const replyWithSticker = require('../scripts/replyWithSticker'); 9 | 10 | module.exports = () => async (ctx) => { 11 | try { 12 | const isSubscriber = await checkSubscription(ctx); 13 | if (ctx.user.usage >= 5 && !isSubscriber) return replyWithError(ctx, 'NOT_SUBSCRIBED'); 14 | 15 | const data = { 16 | user: ctx.user, 17 | userId: ctx.from.id, 18 | session: ctx.session, 19 | editMessageText: async (userId, message_id, text) => 20 | ctx.telegram.editMessageText(userId, message_id, null, text, { parse_mode: 'HTML' }), 21 | i18n: ctx.i18n, 22 | language: ctx.user.language, 23 | message: { type: 'photo', file_id: ctx.update.message.photo.reverse()[0].file_id }, 24 | service: ctx.user.service, 25 | output: ctx.user.to_sticker ? 'sticker' : 'file', 26 | }; 27 | 28 | await ctx 29 | .replyWithHTML(ctx.i18n.t('service.standby')) 30 | .then((response) => { 31 | Object.assign(data, { standby_message_id: response.message_id }); 32 | }) 33 | .catch((err) => { 34 | console.error(err); 35 | return replyWithError(ctx, 'METHOD_FAILED'); 36 | }); 37 | 38 | const removeBackground = new RemoveBackground(data); 39 | const result = await removeBackground 40 | .remove() 41 | .then((response) => response) 42 | .catch((err) => { 43 | console.error(err.message); 44 | }); 45 | 46 | if (data.service === 0) { 47 | await updateTokens(ctx.session.bot); 48 | } 49 | 50 | if (result?.code === 'PROCESSING_ERROR') return replyWithError(ctx, result.code); 51 | if (result?.code === 'UNKNOWN_SERVICE') return replyWithError(ctx, result.code); 52 | if (result?.code === 'TOO_BIG_FILE') return replyWithError(ctx, result.code); 53 | if (result?.code === 'FAILED_TO_DOWNLOAD') return replyWithError(ctx, result.code); 54 | if (result?.code === 'CHANGED_SERVICE') return replyWithError(ctx, result.code); 55 | if (result?.code === 'NO_ACTIVE_TOKENS') return replyWithError(ctx, result.code); 56 | if (result?.code === 'CHANGED_TOKEN') return replyWithError(ctx, result.code); 57 | if (result?.code === 'API_ERROR') return replyWithError(ctx, result.code); 58 | 59 | if (data.output === 'file') { 60 | await replyWithFile(ctx, { ...result, ...data }).catch((err) => { 61 | console.error(err); 62 | return replyWithError(ctx, 'METHOD_FAILED'); 63 | }); 64 | } else { 65 | await replyWithSticker(ctx, { ...result, ...data }).catch((err) => { 66 | console.error(err); 67 | return replyWithError(ctx, 'METHOD_FAILED'); 68 | }); 69 | } 70 | 71 | ctx.user.usage += 1; 72 | ctx.user.converted_to_sticker += ctx.user.to_sticker ? 1 : 0; 73 | ctx.user.converted_to_file += ctx.user.to_sticker ? 0 : 1; 74 | ctx.user.last_time_used = new Date(); 75 | ctx.user.files.push({ file_id: data.message.file_id, output: data.output, timestamp: new Date() }); 76 | 77 | await ctx.user.save(); 78 | } catch (err) { 79 | console.error(err); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /src/handlers/document.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RemoveBackground = require('../scripts/removeBackground'); 4 | const replyWithError = require('../scripts/replyWithError'); 5 | const checkSubscription = require('../scripts/checkSubscription'); 6 | const updateTokens = require('../database/updateTokens'); 7 | const replyWithFile = require('../scripts/replyWithFile'); 8 | const replyWithSticker = require('../scripts/replyWithSticker'); 9 | 10 | module.exports = () => async (ctx) => { 11 | try { 12 | const isSubscriber = await checkSubscription(ctx); 13 | if (ctx.user.usage >= 5 && !isSubscriber) return replyWithError(ctx, 'NOT_SUBSCRIBED'); 14 | 15 | const data = { 16 | user: ctx.user, 17 | userId: ctx.from.id, 18 | session: ctx.session, 19 | editMessageText: async (userId, message_id, text) => 20 | ctx.telegram.editMessageText(userId, message_id, null, text, { parse_mode: 'HTML' }), 21 | i18n: ctx.i18n, 22 | language: ctx.user.language, 23 | message: { 24 | type: 'document', 25 | mime: ctx.message.document.mime_type, 26 | file_id: ctx.message.document.file_id, 27 | file_name: ctx.message.document.file_name.split('.')[0], 28 | }, 29 | service: ctx.user.service, 30 | output: ctx.user.to_sticker ? 'sticker' : 'file', 31 | }; 32 | 33 | if ( 34 | data.message?.mime !== undefined && 35 | data.message?.mime !== 'image/jpeg' && 36 | data.message?.mime !== 'image/png' 37 | ) 38 | return replyWithError(ctx, 'WRONG_FILE_EXTENSION'); 39 | 40 | await ctx 41 | .replyWithHTML(ctx.i18n.t('service.standby')) 42 | .then((response) => { 43 | Object.assign(data, { standby_message_id: response.message_id }); 44 | }) 45 | .catch((err) => { 46 | console.error(err); 47 | return replyWithError(ctx, 'METHOD_FAILED'); 48 | }); 49 | 50 | const removeBackground = new RemoveBackground(data); 51 | const result = await removeBackground 52 | .remove() 53 | .then((response) => response) 54 | .catch((err) => { 55 | console.error(err.message); 56 | }); 57 | 58 | if (data.service === 0) { 59 | await updateTokens(ctx.session.bot); 60 | } 61 | 62 | if (result?.code === 'PROCESSING_ERROR') return replyWithError(ctx, result.code); 63 | if (result?.code === 'UNKNOWN_SERVICE') return replyWithError(ctx, result.code); 64 | if (result?.code === 'TOO_BIG_FILE') return replyWithError(ctx, result.code); 65 | if (result?.code === 'FAILED_TO_DOWNLOAD') return replyWithError(ctx, result.code); 66 | if (result?.code === 'CHANGED_SERVICE') return replyWithError(ctx, result.code); 67 | if (result?.code === 'NO_ACTIVE_TOKENS') return replyWithError(ctx, result.code); 68 | if (result?.code === 'CHANGED_TOKEN') return replyWithError(ctx, result.code); 69 | if (result?.code === 'API_ERROR') return replyWithError(ctx, result.code); 70 | 71 | if (data.output === 'file') { 72 | await replyWithFile(ctx, { ...result, ...data }).catch((err) => { 73 | console.error(err); 74 | return replyWithError(ctx, 'METHOD_FAILED'); 75 | }); 76 | } else { 77 | await replyWithSticker(ctx, { ...result, ...data }).catch((err) => { 78 | console.error(err); 79 | return replyWithError(ctx, 'METHOD_FAILED'); 80 | }); 81 | } 82 | 83 | ctx.user.usage += 1; 84 | ctx.user.converted_to_sticker += ctx.user.to_sticker ? 1 : 0; 85 | ctx.user.converted_to_file += ctx.user.to_sticker ? 0 : 1; 86 | ctx.user.last_time_used = new Date(); 87 | ctx.user.files.push({ file_id: data.message.file_id, output: data.output, timestamp: new Date() }); 88 | 89 | await ctx.user.save(); 90 | } catch (err) { 91 | console.error(err); 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /src/locales/ru.yaml: -------------------------------------------------------------------------------- 1 | language: 🇷🇺 Русский 2 | 3 | service: 4 | greeting: | 5 | 🖐 Привет, ${name}! Я могу убрать фон у любой фотографии, которую Вы отправите мне. Попробуйте, отправив мне файл или фото. 6 | 7 | ⭐️ Я также могу конвертировать Вашу фотографию в стикер. Используйте команду /settings и активируйте эту опцию. 8 | settings: ${beta_sign} За ${range} Вы обработали ${images}, из которых ${files} в файл и ${stickers} в стикер. 9 | settings_new: ${beta_sign} Здесь будет отображаться статистика Вашего использования. Отправьте своё первое изображение боту и возвращайтесь сюда обратно. 10 | standby: 🕓 Пожалуйста, подождите, пока фотография загрузится... 11 | change_language: Выберите язык интерфейса. 12 | language_changed: 🇷🇺 Язык изменён. 13 | warning: (beta) Этот сервис работает медленнее и нестабилен. 14 | service_no_change: На данный момент смена сервиса недоступна. 15 | image_downloaded: ${type} (${size}) Фотография загружена. Обработка может занять несколько секунд... 16 | pre_reset: Вы действительно хотите сбросить свой аккаунт? 17 | reset: 🚀 Вы успешно удалили всю сохранённую информацию о Вас. Воспользуйтесь командой /start, чтобы начать сначала. 18 | history: | 19 | Файлов в истории: ${total} 20 | 21 | Воспользуйтесь командой /reset, чтобы очистить историю. 22 | history_element: | 23 | Файл: ${file_id} 24 | Выходной формат: ${output} 25 | Дата: ${date} 26 | 27 | Чтобы посмотреть фотографию, скопируйте идентификатор файла и отправьте его боту. 28 | change_service: | 29 | Переключайтесь между сервисами, чтобы найти подходящий именно Вам. 30 | 31 | ${service_1} — Быстрый и качественный. Из минусов — сервис нестабилен и часто недоступен. 32 | ${service_2} — Среднее качество обработки. Довольно стабильный и почти всегда доступен. 33 | beta_full: 😞 Вы опоздали. Попробуйте присоединиться к программе бета-тестирования в следующий раз. 34 | already_joined_beta: 👍 Всё хорошо. Вы уже присоединились к программе бета-тестирования. Ожидайте сообщения с новыми функциями бота, которые можно будет протестировать одним из первых. 35 | joined_beta: 🎉 Вы успешно присоединились к программе бета-тестирования! Вас оповестят, как только будут доступны новые функции для тестирования. Спасибо! 36 | wrong_payload: 👮‍♂️ Вы думали, что изменив пэйлоад, вы найдёте что-нибудь новенькое? Что-ж, вы нашли. 37 | 38 | error: 39 | common: 😥 Что-то пошло не так. Пожалуйста, попробуйте /start снова. 40 | common_important: 😥 Нет ответа от сервиса. Воспользуйтесь командой /settings, чтобы сменить его. 41 | limit_exceeded: 👮🏻 Не спешите! 42 | wrong_file_extension: 📄 Неверное расширение файла. Поддерживаются только JPG/JPEG/PNG расширения. 43 | no_text_messages: 😕 Отправьте мне фотографию или файл. 44 | no_text_messages_egg: 😠 Тебе по-русски сказано, отправь Ф-О-Т-О-Г-Р-А-Ф-И-Ю! 45 | file_too_big: ✋ Ваш файл слишком большой. Пожалуйста, используйте файлы размером до 20 МB. 46 | switched_tokens: | 47 | 😞 Простите, попробуйте отправить эту же фотографию снова. Бот столкнулся с лимитами. Возможно, Вам придётся отправить эту фотографию не один раз. Вы можете попробовать сменить сервис в настройках /settings. 48 | 49 | Если с ботом возникли какие-то проблемы, о них будет написано на канале @softik. 50 | failed_download_file: 😞 Фотография не была скачана. Пожалуйста, попробуйте другое фото. 51 | not_a_member: ✋ Чтобы продолжить пользоваться ботом, пожалуйста, подпишитесь на канал. Это канал разработчика, Вы не увидите там никакого спама. Бот бесплатный, а это Ваша поддержка :) 52 | api_error: 😥 Что-то пошло не так. Пожалуйста, попробуйте отправить другое фото или сменить сервис в /settings. 53 | changed_service: 🔄 Попробуйте прислать это изображение снова. Сервис был изменён. 54 | the_same_service: 👮🏻 Вы уже выбрали этот сервис. 55 | history_empty: Файлов в истории не обнаружено. Как только вы обработаете фотографию, она появится здесь. 56 | not_implemented: Функция ещё не реализована. 57 | not_owner_of_image: 👮🏻 Это не Ваша фотография. 58 | beta_only: 🙂 Эта функция доступна только бета-тестировщикам. 59 | 60 | button: 61 | language: Сменить язык 62 | settings: Настройки 63 | to_sticker: 'Конвертировать в стикер: ${state}' 64 | service: Сменить сервис 65 | add_text: 'Стикеры с текстом: ${state}' 66 | channel: Канал 67 | support: Пожертвовать 68 | subscribe: Подписаться 69 | more: '...ещё ${number}' 70 | back: ‹ Назад 71 | 72 | action: 73 | a_on: ВКЛ. 74 | a_off: ВЫКЛ. 75 | a_yes: Да 76 | a_no: Нет 77 | 78 | other: 79 | file: документ 80 | sticker: стикер 81 | -------------------------------------------------------------------------------- /src/locales/de.yaml: -------------------------------------------------------------------------------- 1 | language: 🇩🇪 Deutsch 2 | 3 | service: 4 | greeting: | 5 | 🖐 Hi, ${name}! Ich kann den Hintergrund von jedem Foto entfernen, das du mir schickst. Probiere es aus und sende mir eine Datei oder ein Foto, um die Magie zu sehen. 6 | 7 | ⭐️ Ich kann dein Bild auch in einen Sticker umwandeln. Verwende den Befehl /settings, um diese Option zu aktivieren. 8 | settings: ${beta_sign} In ${range} hast du ${usage} image${plural} wovon ${converted_to_file} in eine Datei und ${converted_to_sticker} in einen Sticker umgewandelt wurden, erhalten. 9 | settings_new: ${beta_sign} Auf dieser Seite siehst du die Statistiken über deine Nutzung. Sende dein erstes Bild an den Bot und komm dann hierher zurück. 10 | standby: 🕓 Bitte warte, während das Bild bearbeitet wird... 11 | change_language: Wähle die Sprache der Benutzeroberfläche. 12 | language_changed: 🇩🇪 Sprache gewechselt. 13 | warning: (Beta) Dieser Dienst arbeitet langsamer und instabil. 14 | service_no_change: Du kannst jetzt nicht mehr zwischen den Diensten wechseln. 15 | image_downloaded: ${type} (${size}) Das Bild wurde heruntergeladen. Die Verarbeitung kann ein paar Sekunden dauern... 16 | no_translation: Es scheint, als gäbe es keine Übersetzung für diesen String. Wenn du in der Lage bist, diesen String zu übersetzen, kannst du das gerne auf der GitHub-Seite des Bots tun. 17 | pre_reset: Willst du dein Konto wirklich zurücksetzen? 18 | reset: 🚀 Du hast alle über dich gespeicherten Informationen erfolgreich gelöscht. Um neu zu beginnen, verwende den Befehl /start. 19 | change_service: | 20 | Wechsle zwischen den Diensten und finde den besten für dich. 21 | 22 | ${service_1} — Schnell. Gut trainiertes neuronales Netzwerk. Beste Qualität. Bewältigt alle Fälle wie Menschen, Tiere, harte Hintergründe und andere Dinge. Allerdings unstabil und nicht immer verfügbar. 23 | ${service_2} — Mäßige Qualität. Ziemlich stabil und fast immer verfügbar. 24 | beta_full: 😞 Du bist zu spät dran. Nächstes Mal versuchst du, am Beta-Testprogramm teilzunehmen. 25 | already_joined_beta: 👍 Alles gut. Du hast dich bereits für das Beta-Testprogramm angemeldet. Warte jetzt auf eine Nachricht, in der neue Funktionen beschrieben werden, die nur für Beta-Tester/innen wie dich verfügbar sind! 26 | joined_beta: 🎉 Du hast dich erfolgreich für das Beta-Testprogramm angemeldet! Du wirst benachrichtigt, sobald neue Funktionen zum Testen verfügbar sind. Vielen Dank für deine Teilnahme! 27 | wrong_payload: 👮‍♂️ Dachtest du, dass du etwas finden würdest, wenn du die Nutzlast änderst? Nun, das hast du. 28 | 29 | error: 30 | common: 😥 Etwas ist schief gelaufen. Bitte versuche /start erneut. 31 | common_important: 😥 Der Dienst antwortet nicht. Gehe zu /settings und wechsle zu einem anderen. 32 | limit_exceeded: 👮🏻 Nur keine Eile! 33 | wrong_file_extension: 📄 Falsche Dateierweiterung. Es werden nur JPG/JPEG/PNG unterstützt. 34 | no_text_messages: 😕 Schicke mir stattdessen ein Bild oder eine Bilddatei. 35 | no_text_messages_egg: 😠 Dir wurde gesagt, du sollst ein B-I-L-D schicken! 36 | file_too_big: ✋ Deine Datei ist zu groß. Bitte sende mir Dateien bis zu 20 MB. 37 | switched_tokens: | 38 | 😞 Es tut mir leid, bitte versuche noch einmal das gleiche Bild. Die Belastung für den Bot ist jetzt sehr groß. Du musst mir wahrscheinlich mehrmals ein Bild schicken. Du kannst mit dem Befehl /settings zwischen den Diensten wechseln. 39 | 40 | Wenn etwas mit dem Bot nicht in Ordnung ist, wird es im @softik-Kanal veröffentlicht. 41 | failed_download_file: 😞 Dieses Foto konnte nicht heruntergeladen werden. Bitte versuche es mit einem anderen Foto. 42 | not_a_member: ✋ Um den Bot weiter zu nutzen, abonniere bitte den Kanal. Es ist der Kanal der Entwickler und du wirst dort keinen Spam sehen. Der Bot ist kostenlos und das ist deine Unterstützung dafür :) 43 | api_error: 😥 Etwas ist schief gelaufen. Bitte probiere ein anderes Foto aus oder ändere den Dienst in /settings. 44 | unable_reset: 🙂 Nichts zum Zurücksetzen. Verwende den Befehl /start, um einen Datensatz zu erstellen. 45 | changed_service: 🔄 Sende dieses Bild erneut. Der Dienst wurde geändert in ${new_service}. 46 | daily_limit: ✋ Du hast ein Tageslimit erreicht. Bitte komme morgen wieder oder kontaktiere @vychs und überzeuge ihn, warum genau du ein höheres Limit brauchst. 47 | the_same_service: 👮🏻 Du hast dich bereits für diesen Service entschieden. 48 | 49 | button: 50 | language: Sprache wechseln 51 | settings: Einstellungen 52 | to_sticker: 'In Sticker umwandeln: ${state}' 53 | service: Service ändern 54 | add_text: 'Sticker mit dem Text: ${state}' 55 | channel: Kanal 56 | support: Spenden 57 | subscribe: Abonieren 58 | back: ‹ Zurück 59 | 60 | action: 61 | a_on: EIN 62 | a_off: AUS 63 | a_yes: Ja 64 | a_no: Nein 65 | -------------------------------------------------------------------------------- /src/locales/en.yaml: -------------------------------------------------------------------------------- 1 | language: 🇬🇧 English 2 | 3 | service: 4 | greeting: | 5 | 🖐 Hi, ${name}! I can remove background of any photo you send me. Try and send me a file or a photo and see the magic. 6 | 7 | ⭐️ I can also convert your image into a sticker. Use /settings command to activate this option. 8 | settings: ${beta_sign} For ${range} you processed ${usage} image${plural} of which ${converted_to_file} ${verb_file} converted to file and ${converted_to_sticker} ${verb_sticker} converted to sticker. 9 | settings_new: ${beta_sign} You will see the statistics of your usage on this page. Send your first image to the bot and come back here. 10 | standby: 🕓 Please, standby while picture is downloading... 11 | change_language: Choose the interface language. 12 | language_changed: 🇬🇧 Language changed. 13 | warning: (beta) This service works slower and unstable. 14 | service_no_change: You're not able to switch between services now. 15 | image_downloaded: ${type} (${size}) The image has been downloaded. Processing may take a few seconds... 16 | no_translation: It seems there's no translation for this string. If you're able to translate this string, feel free to contribute on the bot's GitHub repository. 17 | pre_reset: Do you really want to reset your account? 18 | reset: 🚀 You successfully deleted all the information stored about you. To start over use /start command. 19 | history: | 20 | Files in history: ${total} 21 | 22 | Use /reset command to clear the history. 23 | history_element: | 24 | File: ${file_id} 25 | Output format: ${output} 26 | Date: ${date} 27 | 28 | If you want to see the image, copy the file id and send it to the bot. 29 | change_service: | 30 | Switch between services and find the best one for you. 31 | 32 | ${service_1} — Fast. Well-trained neural network. Best quality. Manages all cases such as people, animals, hard backgrounds and other things. However, unstable and not always availabe. 33 | ${service_2} — Moderate quality. Quite stable and almost always available. 34 | beta_full: 😞 You're late. Try to join beta-testing program next time. 35 | already_joined_beta: 👍 All good. You've already joined the beta-testing program. Now, wait for a message describing new features to test that will be available only for beta-testers, just like you! 36 | joined_beta: 🎉 You've successfully joined the beta-testing program! You will get notified as soon as any new features available to test. Thank you! 37 | wrong_payload: 👮‍♂️ Did you think by changing payload you would find something? Well, you did. 38 | 39 | error: 40 | common: 😥 Something went wrong. Please try /start again. 41 | common_important: 😥 The service is not responding. Go to /settings and switch to another. 42 | limit_exceeded: 👮🏻 Don't be in hurry! 43 | wrong_file_extension: 📄 Wrong file extension. Only JPG/JPEG/PNG are supported. 44 | no_text_messages: 😕 Send me an image or a file of image instead. 45 | no_text_messages_egg: 😠 You were told to send an I-M-A-G-E! 46 | file_too_big: ✋ Your file is too big. Please, send me files up to 20 MB. 47 | switched_tokens: | 48 | 😞 I am sorry, please try again the same picture. It's very huge load on the bot now. You will probably need to send me a picture several times. You can switch between services by /settings command. 49 | 50 | If there's something wrong with the bot, it will be published in @softik channel. 51 | failed_download_file: 😞 Couldn't download this photo. Please, try some other photo. 52 | not_a_member: ✋ To continue using the bot, please subscribe to the channel. It's developer's channel and you won't see any spam there. The bot is free and this is your support :) 53 | api_error: 😥 Something went wrong. Please try some other photo or change service in /settings. 54 | unable_reset: 🙂 Nothing to reset. Use /start command to create a record. 55 | changed_service: 🔄 Send this image again. The service has been changed. 56 | the_same_service: 👮🏻 You have already chosen this service. 57 | history_empty: There are no files in the history. Once you process a picture, it will appear here. 58 | not_implemented: This feature is not implemented yet. 59 | not_owner_of_image: 👮🏻 It's not your image. 60 | beta_only: 🙂 This feature is for beta testers only. 61 | 62 | button: 63 | language: Change Language 64 | settings: Settings 65 | to_sticker: 'Convert to Sticker: ${state}' 66 | service: Change Service 67 | add_text: 'Sticker with Text: ${state}' 68 | channel: Channel 69 | support: Donate 70 | subscribe: Subscribe 71 | more: '...${number} more' 72 | back: ‹ Back 73 | 74 | action: 75 | a_on: 'ON' 76 | a_off: 'OFF' 77 | a_yes: 'Yes' 78 | a_no: 'No' 79 | 80 | other: 81 | file: document 82 | sticker: sticker 83 | -------------------------------------------------------------------------------- /src/handlers/back.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Markup = require('telegraf/markup'); 4 | const createStatsObject = require('../scripts/createStatsObject'); 5 | const replyWithError = require('../scripts/replyWithError'); 6 | 7 | module.exports = () => async (ctx) => { 8 | try { 9 | await ctx.answerCbQuery(); 10 | 11 | const direction = ctx.match[0].split(':')[1]; 12 | 13 | switch (direction) { 14 | case 'settings': 15 | ctx.editMessageText( 16 | ctx.i18n.t(ctx.user.usage <= 0 ? 'service.settings_new' : 'service.settings', { 17 | beta_sign: ctx.user.beta ? '(beta)' : '', 18 | ...createStatsObject(ctx), 19 | }), 20 | { 21 | parse_mode: 'HTML', 22 | reply_markup: Markup.inlineKeyboard([ 23 | [ 24 | Markup.callbackButton(ctx.i18n.t('button.language'), 'language'), 25 | Markup.callbackButton(ctx.i18n.t('button.service'), 'service'), 26 | ], 27 | [ 28 | Markup.callbackButton( 29 | ctx.i18n.t('button.to_sticker', { 30 | state: ctx.user.to_sticker 31 | ? ctx.i18n.t('action.a_on') 32 | : ctx.i18n.t('action.a_off'), 33 | }), 34 | 'to_sticker' 35 | ), 36 | ], 37 | ]), 38 | disable_web_page_preview: true, 39 | } 40 | ).catch((err) => { 41 | console.error(err); 42 | return replyWithError(ctx, 'METHOD_FAILED'); 43 | }); 44 | break; 45 | case 'history': 46 | const userFiles = ctx.user.files; 47 | const cache = ctx.session?.historyCache; 48 | 49 | if (userFiles.length <= 0) { 50 | return ctx 51 | .editMessageText(ctx.i18n.t('error.history_empty'), { 52 | parse_mode: 'HTML', 53 | }) 54 | .catch((err) => { 55 | console.error(err); 56 | return replyWithError(ctx, 'METHOD_FAILED'); 57 | }); 58 | } 59 | 60 | if (cache && userFiles.length === cache.files && ctx.user.language === cache.languageLayout) { 61 | return ctx 62 | .editMessageText(ctx.i18n.t('service.history', { total: userFiles.length }), { 63 | parse_mode: 'HTML', 64 | reply_markup: Markup.inlineKeyboard(cache.buttons), 65 | }) 66 | .catch((err) => { 67 | console.error(err); 68 | return replyWithError(ctx, 'METHOD_FAILED'); 69 | }); 70 | } 71 | 72 | const lastFiveFiles = userFiles.reverse().slice(0, 5); 73 | 74 | ctx.session.historyCache = { content: lastFiveFiles }; 75 | 76 | const buttons = lastFiveFiles.map((e) => { 77 | const year = new Date(e.timestamp).getFullYear().toString(); 78 | const month = (new Date(e.timestamp).getMonth() + 1).toString().padStart(2, '0'); 79 | const day = new Date(e.timestamp).getDate().toString().padStart(2, '0'); 80 | const hour = new Date(e.timestamp).getHours().toString().padStart(2, '0'); 81 | const minute = new Date(e.timestamp).getMinutes().toString().padStart(2, '0'); 82 | const second = new Date(e.timestamp).getSeconds().toString().padStart(2, '0'); 83 | 84 | const buttonName = `${year}-${month}-${day} ${hour}:${minute}:${second}`; 85 | 86 | return [Markup.callbackButton(buttonName, `open:${new Date(e.timestamp).getTime()}`)]; 87 | }); 88 | 89 | if (userFiles.length > 5) { 90 | buttons.push([ 91 | Markup.callbackButton( 92 | ctx.i18n.t('button.more', { number: userFiles.length - 5 }), 93 | 'not_implemented_yet' 94 | ), 95 | ]); 96 | } 97 | 98 | ctx.session.historyCache = { 99 | ...JSON.parse(JSON.stringify(ctx.session.historyCache)), 100 | files: userFiles.length, 101 | languageLayout: ctx.user.language, 102 | buttons, 103 | }; 104 | 105 | ctx.editMessageText(ctx.i18n.t('service.history', { total: userFiles.length }), { 106 | parse_mode: 'HTML', 107 | reply_markup: Markup.inlineKeyboard(buttons), 108 | }).catch((err) => { 109 | console.error(err); 110 | return replyWithError(ctx, 'METHOD_FAILED'); 111 | }); 112 | break; 113 | default: 114 | console.log('No action determined'); 115 | break; 116 | } 117 | } catch (err) { 118 | console.error(err); 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /src/scripts/createStatsObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment'); 4 | const plural = require('plural-ru'); 5 | const replyWithError = require('./replyWithError'); 6 | 7 | module.exports = (ctx) => { 8 | try { 9 | const user = ctx.user; 10 | const language = user.language; 11 | 12 | moment.locale(language); 13 | 14 | switch (language) { 15 | case 'en': 16 | const obj_en = { 17 | range: moment(user.timestamp).from(new Date(), true), 18 | converted_to_sticker: user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker, 19 | converted_to_file: user.converted_to_file === undefined ? 0 : user.converted_to_file, 20 | verb_sticker: user.converted_to_sticker > 1 ? 'were' : 'was', 21 | verb_file: user.converted_to_file > 1 ? 'were' : 'was', 22 | plural: user.usage > 1 ? 's' : '', 23 | usage: user.usage === undefined ? 0 : user.usage, 24 | }; 25 | 26 | return obj_en; 27 | case 'ru': 28 | const usage = user.usage === undefined ? 0 : user.usage; 29 | const converted_to_sticker = user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker; 30 | const converted_to_file = user.converted_to_file === undefined ? 0 : user.converted_to_file; 31 | const images = `${usage} ${plural(usage, 'фотографию', 'фотографии', 'фотографий')}`; // 1 / 23 / 11 / 1.5 32 | const files = `${converted_to_file} ${plural( 33 | converted_to_file, 34 | 'была конвертирована', 35 | 'было конвертировано', 36 | 'было конвертировано' 37 | )}`; 38 | const stickers = `${converted_to_sticker} ${plural( 39 | converted_to_sticker, 40 | 'была конвертирована', 41 | 'было конвертировано', 42 | 'было конвертировано' 43 | )}`; 44 | const obj_ru = { 45 | range: moment(user.timestamp).from(new Date(), true), 46 | images: images, 47 | files: files, 48 | stickers: stickers, 49 | }; 50 | return obj_ru; 51 | case 'it': 52 | const obj_it = { 53 | range: moment(user.timestamp).from(new Date(), true), 54 | converted_to_sticker: user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker, 55 | converted_to_file: user.converted_to_file === undefined ? 0 : user.converted_to_file, 56 | plural: user.usage > 1 ? 's' : '', 57 | usage: user.usage === undefined ? 0 : user.usage, 58 | }; 59 | 60 | return obj_it; 61 | case 'es': 62 | const obj_es = { 63 | range: moment(user.timestamp).from(new Date(), true), 64 | converted_to_sticker: user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker, 65 | converted_to_file: user.converted_to_file === undefined ? 0 : user.converted_to_file, 66 | plural: user.usage > 1 ? 'nes' : '', 67 | usage: user.usage === undefined ? 0 : user.usage, 68 | }; 69 | 70 | return obj_es; 71 | case 'te': 72 | const obj_te = { 73 | range: moment(user.timestamp).from(new Date(), true), 74 | converted_to_sticker: user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker, 75 | converted_to_file: user.converted_to_file === undefined ? 0 : user.converted_to_file, 76 | usage: user.usage === undefined ? 0 : user.usage, 77 | }; 78 | 79 | return obj_te; 80 | case 'ml': 81 | // I don't want to add stats to languages that I can't moderate. 82 | // I will think how to let translators set up this line themselves. 83 | const obj_ml = { plug: '' }; 84 | 85 | return obj_ml; 86 | case 'pt-br': 87 | const obj_ptbr = { 88 | range: moment(user.timestamp).from(new Date(), true), 89 | usage: user.usage === undefined ? 0 : user.usage, 90 | plural: user.usage > 1 ? 's' : '', 91 | converted_to_sticker: user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker, 92 | converted_to_file: user.converted_to_file === undefined ? 0 : user.converted_to_file, 93 | }; 94 | 95 | return obj_ptbr; 96 | case 'de': 97 | const obj_de = { 98 | range: moment(user.timestamp).from(new Date(), true), 99 | converted_to_sticker: user.converted_to_sticker === undefined ? 0 : user.converted_to_sticker, 100 | converted_to_file: user.converted_to_file === undefined ? 0 : user.converted_to_file, 101 | plural: user.usage > 1 ? 's' : '', 102 | usage: user.usage === undefined ? 0 : user.usage, 103 | }; 104 | 105 | return obj_de; 106 | default: 107 | replyWithError(ctx, 'COMMON_ERROR'); 108 | break; 109 | } 110 | } catch (err) { 111 | console.error(err); 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /src/scripts/removeBackground.js: -------------------------------------------------------------------------------- 1 | const Bot = require('../database/models/Bot'); 2 | const axios = require('axios'); 3 | const byteSize = require('byte-size'); 4 | const FormData = require('form-data'); 5 | const resetTokens = require('../database/resetTokens'); 6 | const config = require('../config'); 7 | 8 | module.exports = class RemoveBackground { 9 | constructor(data) { 10 | this.userId = data.userId; 11 | this.session = data.session; 12 | this.editMessageText = data.editMessageText; 13 | this.i18n = data.i18n; 14 | this.language = data.language; 15 | this.standby_message_id = data.standby_message_id; 16 | this.message = data.message; 17 | this.service = data.service; 18 | this.output = data.output; 19 | } 20 | 21 | /** 22 | * Removes background from given image. 23 | * @returns Promise, an object with either data or error and its code. 24 | */ 25 | remove() { 26 | return new Promise(async (resolve, reject) => { 27 | const image = await this.getMedia(); 28 | 29 | switch (this.service) { 30 | case 0: 31 | this.getRequestData().then(() => { 32 | this.processImage(image, 0) 33 | .then((response) => resolve(response)) 34 | .catch((err) => 35 | reject({ code: 'PROCESSING_ERROR', error: 'Main function failure', message: err }) 36 | ); 37 | }); 38 | break; 39 | case 1: 40 | this.processImage(image, 1) 41 | .then((response) => resolve(response)) 42 | .catch((err) => 43 | reject({ code: 'PROCESSING_ERROR', error: 'Main function failure', message: err }) 44 | ); 45 | break; 46 | default: 47 | reject({ 48 | code: 'UNKNOWN_SERVICE', 49 | error: `Unknown service. Expected 0, 1, 2 or 3. Received ${service}`, 50 | }); 51 | break; 52 | } 53 | }); 54 | } 55 | 56 | /** 57 | * Downloads a picture the user sent. 58 | * @returns Promise, an object with either data or error and its code. 59 | */ 60 | getMedia() { 61 | return new Promise((resolve, reject) => { 62 | const file_id = this.message.file_id; 63 | const download_url = `https://api.telegram.org/bot${config.token}/getFile?file_id=${file_id}`; 64 | 65 | axios(download_url) 66 | .then(async (response) => { 67 | const file = response.data.result; 68 | const url = `https://api.telegram.org/file/bot${config.token}/${file.file_path}`; 69 | 70 | if (file.file_size >= 20971520) { 71 | reject({ code: 'TOO_BIG_FILE', error: 'Too big file' }); 72 | return; 73 | } else { 74 | const image_stream = await axios({ 75 | method: 'GET', 76 | url: url, 77 | responseType: 'stream', 78 | }).then((response) => response.data); 79 | 80 | await this.editMessageText( 81 | this.userId, 82 | this.standby_message_id, 83 | this.i18n.t('service.image_downloaded', { 84 | type: this.message.type === 'photo' ? '🖼' : '📄', 85 | size: byteSize(file.file_size), 86 | }), 87 | { parse_mode: 'HTML' } 88 | ).catch((err) => { 89 | console.error(err); 90 | }); 91 | 92 | resolve({ 93 | code: 200, 94 | stream: image_stream, 95 | url: url, 96 | size: file.file_size, 97 | name: file.file_path.replace(/(documents\/|photos\/)/g, ''), 98 | }); 99 | } 100 | }) 101 | .catch(() => { 102 | reject({ code: 'FAILED_TO_DOWNLOAD', error: 'Failed to download' }); 103 | }); 104 | }); 105 | } 106 | 107 | /** 108 | * Requests data from database to make an API call to the first service. 109 | * @returns Promise, an object with either data or error and its code. 110 | */ 111 | getRequestData() { 112 | return new Promise((resolve, reject) => { 113 | Bot.find({ id: 1 }) 114 | .then((response) => { 115 | this.session.bot = response[0]; 116 | resolve(response[0]); 117 | }) 118 | .catch(() => { 119 | reject({ code: 'PROCESSING_ERROR', error: 'Failed to get request data from database' }); 120 | }); 121 | }); 122 | } 123 | 124 | /** 125 | * Processes query image via one of the services. 126 | * @param {Object} image Image data 127 | * @param {Number} service 0 - cutout.pro; 1 - benzin.io; 2 - experte.de; 3 - erase.bg 128 | * @returns Promise, an object with either data or error and its code. 129 | */ 130 | processImage(image, service) { 131 | return new Promise((resolve, reject) => { 132 | switch (service) { 133 | case 0: 134 | this.callFirstService(image) 135 | .then((response) => resolve(response)) 136 | .catch((err) => 137 | reject({ code: 'PROCESSING_ERROR', error: 'Failed to process image', message: err }) 138 | ); 139 | break; 140 | case 1: 141 | this.callSecondService(image) 142 | .then((response) => resolve(response)) 143 | .catch((err) => 144 | reject({ code: 'PROCESSING_ERROR', error: 'Failed to process image', message: err }) 145 | ); 146 | break; 147 | default: 148 | reject({ 149 | code: 'UNKNOWN_SERVICE', 150 | error: `Unknown service. Expected 0, 1, 2 or 3. Received ${service}`, 151 | }); 152 | break; 153 | } 154 | }); 155 | } 156 | 157 | /** 158 | * Calls the first service's API and converts the result to buffer. 159 | * @param {Object} image Image data 160 | * @returns Promise, an object with either data or error and its code. 161 | */ 162 | callFirstService(image) { 163 | return new Promise((resolve, reject) => { 164 | const hosts = { 165 | 1: config.host_token, 166 | 2: config.host_token2, 167 | 3: config.host_token3, 168 | 4: config.host_token4, 169 | 5: config.host_token5, 170 | 6: config.host_token6, 171 | 7: config.host_token7, 172 | 8: config.host_token8, 173 | 9: config.host_token9, 174 | 10: config.host_token10, 175 | }; 176 | const data = new FormData(); 177 | 178 | data.append('file', image.stream); 179 | data.append('mattingType', this.session.bot.type || '5'); 180 | 181 | axios({ 182 | method: 'POST', 183 | url: config.host, 184 | headers: { 185 | ...data.getHeaders(), 186 | token: this.session.bot.active_token, 187 | }, 188 | data: data, 189 | }) 190 | .then(async (response) => { 191 | const codes = [4000, 4001, 4002, 4003, 4004]; 192 | 193 | if (codes.includes(response.data.code)) { 194 | if (this.session.bot?.inactive_tokens.length >= 10) { 195 | if (this.session.bot.type == 6) { 196 | this.session.bot.active_token = config.host_token; 197 | this.session.bot.inactive_tokens = []; 198 | this.session.bot.number = 1; 199 | this.session.bot.type = '5'; 200 | 201 | await resetTokens(); 202 | 203 | const new_service = user.service === 0 ? 1 : 0; 204 | 205 | user.service = new_service; 206 | 207 | reject({ code: 'CHANGED_SERVICE', error: 'Changed service', service: new_service }); 208 | return; 209 | } else { 210 | this.session.bot.active_token = config.host_token; 211 | this.session.bot.inactive_tokens = []; 212 | this.session.bot.number = 1; 213 | this.session.bot.type = '6'; 214 | } 215 | 216 | reject({ code: 'NO_ACTIVE_TOKENS', error: 'No active tokens left' }); 217 | 218 | return; 219 | } else { 220 | this.session.bot?.inactive_tokens.push(this.session.bot.active_token); 221 | 222 | if (this.session.bot?.number === 10) { 223 | this.session.bot.active_token = config.host_token; 224 | this.session.bot.number = 1; 225 | } else { 226 | this.session.bot.active_token = hosts[this.session.bot.number + 1]; 227 | ++this.session.bot.number; 228 | } 229 | 230 | reject({ code: 'CHANGED_TOKEN', error: 'Changed token' }); 231 | 232 | return; 233 | } 234 | } else { 235 | axios({ 236 | method: 'GET', 237 | url: response.data?.data?.bgRemoved, 238 | responseType: 'arraybuffer', 239 | }) 240 | .then((response) => { 241 | resolve({ 242 | buffer: Buffer.from(response.data, 'binary'), 243 | initial_file_size: image.size, 244 | }); 245 | }) 246 | .catch(() => { 247 | reject({ code: 'PROCESSING_ERROR', error: 'Failed to download processed photo' }); 248 | }); 249 | } 250 | }) 251 | .catch((err) => { 252 | reject({ code: 'API_ERROR', error: 'Failed to call API', message: err }); 253 | }); 254 | }); 255 | } 256 | 257 | /** 258 | * Calls the second service's API and converts the result to buffer. 259 | * @param {Object} image Image data 260 | * @returns Promise, an object with either data or error and its code. 261 | */ 262 | callSecondService(image) { 263 | return new Promise((resolve, reject) => { 264 | const data = new FormData(); 265 | 266 | data.append('image_file_url', ''); 267 | data.append('image_file', image.stream); 268 | data.append('bg_color', ''); 269 | data.append('crop', 'false'); 270 | data.append('crop_margin', 'px,px,px,px'); 271 | data.append('bg_image_file_url', ''); 272 | data.append('size', 'full'); 273 | data.append('output_format', 'json'); 274 | 275 | axios({ 276 | method: 'POST', 277 | url: config.host2, 278 | headers: { 279 | 'X-Api-Key': config.host2_token, 280 | ...data.getHeaders(), 281 | }, 282 | data: data, 283 | }) 284 | .then((response) => { 285 | resolve({ 286 | buffer: Buffer.from(response.data.image_raw, 'base64'), 287 | initial_file_size: image.size, 288 | }); 289 | }) 290 | .catch((err) => { 291 | reject({ code: 'API_ERROR', error: 'Failed to call 2nd API', message: err }); 292 | }); 293 | }); 294 | } 295 | }; 296 | --------------------------------------------------------------------------------