⬆️ ⬇️

Telegram bot development for Instagram





I want to share the result of my work, the development of Telegram bot for massfollowing on Instagram.



Most likely, some of you are not familiar with this term, here is a short description:

Massfollowing - a mass subscription to people according to certain criteria.

In simple language, you subscribe to a person, he sees in the tape that someone has subscribed to him, goes to your page.

')

On this goal, this tool for business is completed.

Services for massfollowing already exist long ago, 2 years ago I noticed the activity of my friends on Instagram, namely, the desire to promote my business through this social network. To do this, they used different mass subscription services, where a monthly subscription costs about 1000 rubles, and according to them, the effect is quite noticeable, especially for restaurants, food delivery services.



It became interesting to me, I registered on one of the popular services, I looked at the functionality, it seemed interesting to me, I used it for a while.

It is worth saying that at that moment Telegram presented the Bot Platform platform , it gained sharply popularity, many developers tried to do something based on it, including me, in particular, I used to send notifications from sites.



So I thought that working through the site was rather inconvenient, for example, I started the task for a subscription, it ended in a week, and I don’t even know that it ended, sometimes I even forgot about it. Also, I constantly had to go to the site, log in, watch tasks and create new tasks.



The solution was simple, why not write a Telegram bot, with which you can add accounts, create tasks for subscriptions and formal replies, and receive an alert directly to the messenger about the completion of the task?



I started to develop a prototype somewhere in a year and a half in March 2017, at that time and even now I just did not find analogues, I decided to write.



The first difficulty was to get access to the instagram API, of course, after my request I didn’t receive it, I had to look for a solution, it was found quickly enough, good people maintain the instagram-private-api repository, this is the private Instagram API, it was possible most required actions.



I started development on Node.js, used MongoDB for data storage.



The main features that I wanted to write were:





Next, create a scheme for the database, you need to keep a list of users, accounts, tasks, list of subscriptions and likes for all the time (not to re-subscribe).



The scheme for storing id telegram users to identify them:



const UserSchema = new db.mongoose.Schema({ id: { type: Number, required: [true, 'idRequired'] }, name: { type: String, required: [true, 'nameRequired'] }, date: { type: Date, default: Date.now } }) 


The scheme for storing Instagram accounts:



 const AccountSchema = new db.mongoose.Schema({ user: { type: Number, required: [true, 'userRequired'] }, login: { type: String, required: [true, 'loginRequired'] }, password: { type: String, required: [true, 'passwordRequired'] }, verified: { type: Boolean, default: false }, date: { type: Date, default: Date.now } }) 


As well as the scheme for the tasks:



 const TaskSchema = new db.mongoose.Schema({ user: { type: Number, required: [true, 'userRequired'] }, login: { type: String, required: [true, 'loginRequired'] }, type: { type: String, required: [true, 'typeRequired'] }, params: { type: Object, required: [true, 'paramsRequired'] }, status: { type: String, default: 'active' }, date: { type: Date, default: Date.now }, start: { type: Number, required: [true, 'startReqiured'] } }) 


In the params property, we store specific data for a specific task, for example, for a subscription:

sourceTypeSource type (user, hashtag, location)
sourcesource name (username, # hashtag, [lat, long])
actionFollowtotal number of subscriptions / formal replies
actionFollowDaynumber of actions per day
actionLikeDaynumber of likes to each user


The remaining schemes are available in the repository of the instalator-telegram project.



Next, it was necessary to process incoming commands from the user, at this moment I had problems, I did not find a ready solution at that moment, I made it quite simple, perhaps many people would not like this approach, made a map of the path:



Project Map
 module.exports = { event: 'home', children: { ' ': { event: 'task:create', children: { '*': { event: 'task:select', children: { ' + ': { event: 'task:select:follow+like', children: { : { event: 'task:select:follow+like:user', children: { '*': { event: 'task:select:follow+like:user:select', children: { '*': { event: 'task:select:follow+like:source:action', children: { '*': { event: 'task:select:follow+like:source:actionPerDay', children: { '*': { event: 'task:select:follow+like:source:like' } } }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'task:select:follow+like:hashtag', children: { '*': { event: 'task:select:follow+like:hashtag:find', children: { '*': { event: 'task:select:follow+like:source:action', children: { '*': { event: 'task:select:follow+like:source:actionPerDay', children: { '*': { event: 'task:select:follow+like:source:like' } } }, : { event: 'location:back' } } }, : { event: 'location:back' } } } } }, : { event: 'task:select:follow+like:source', children: { '*': { event: 'task:select:follow+like:source:select', children: { '*': { event: 'task:select:follow+like:source:action', children: { '*': { event: 'task:select:follow+like:source:actionPerDay', children: { '*': { event: 'task:select:follow+like:source:like' }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'task:select:type', children: { '*': { event: 'task:select:type:unfollow' // await: true }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, ' ': { event: 'account:add', children: { '*': { event: 'account:await', await: true }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'actions', children: { '*': { event: 'actions:account', children: { : { event: 'actions:account:update', children: { '*': { event: 'actions:account:update:one', children: { '*': { event: 'actions:account:update:two', children: { '*': { event: 'actions:account:update:three' } } } } }, : { event: 'location:back' } } }, : { event: 'actions:account:cancel' }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'account:list', children: { '*': { event: 'account:select', children: { : { event: 'account:edit', children: { '*': { event: 'account:edit:await', await: true }, : { event: 'location:back' } } }, : { event: 'account:delete', await: true }, : { event: 'location:back' } } }, ' ': { event: 'account:add', children: { '*': { event: 'account:await', await: true }, : { event: 'location:back' } } }, : { event: 'location:back' } } }, : { event: 'limit:message', children: { : { event: 'location:back' } } } } } 




The map contains “event” objects, it contains an event property, which contains the name of the event EventEmitter , as well as the children property, which contains child “routes”, corresponding in structure.



How it works, the user sends a command, for example, Create a task , having received a message, walk through the tree, if there is a property, call the appropriate EventEmitter event, and then write the user id to the state object, where we will store the current location of the users, for example:



 state = { 23445432: [' ', ' + '], 1345532: [''] } 


Now if the user sends a message under id 23445432 , the task: select: follow + like event will be called in response, and what if the corresponding message is not in the map? or do we need to get some data from the user that is not registered in the map, for example, the number of subscriptions per day? To do this, one of the properties of children must be marked with an asterisk, like this: * , then inside it we describe the event that needs to be called .



Directly "routing" itself:



 const router = msg => { //   if (msg.text) msg.text = emoji.decode(msg.text) // No user status, we give the main menu if (!state[msg.from.id]) { commandEvents.emit('/home', msg) // Adding the user to the state state[msg.from.id] = [] } else { // Go to the desired branch const findBranch = state[msg.from.id].reduce((path, item) => { // If there are no child partitions if (!path.children) { return path } else { if (path.children[item]) { return path.children[item] } else { // If there is no suitable branch, then we try to use a common branch if (path.children['*']) { return path.children['*'] } else { return path } } } }, map) // Call branch method const callBranch = branch => { const action = findBranch.children[branch] // Call action event.emit(action.event, msg, action, (value = msg.text) => { event.emit('location:next', msg, action, value) }) } // We check the existence of the method if (findBranch.children.hasOwnProperty(msg.text)) { callBranch(msg.text) } else if (findBranch.children['*']) { // If there is no suitable branch, then we try to use a common branch callBranch('*') } else { // back event.emit('location:back', msg) } } } 


If there is no property in the map, then we call the event back , return the user one step higher.



Talking in detail about how the handlers are arranged does not make sense, these are the usual callback functions that change certain data in the database, for example, here is the handler for sending the list of accounts to the user:



 event.on('account:list', async (msg, action, next) => { try { const list = await Account.list(msg.from.id) if (list === null) { throw new Error(`There are no accounts for ${msg.from.id}`) } // Sending the list of accounts const elements = list.map(item => item.login) send.keyboard(msg.from.id, ' ', [ ...elements, ' ', '' ]) next && next() } catch (e) { event.emit('account:empty', msg) next && next() } }) 


The next () method takes the user to the next level in the map, for this he adds the active section to the state , and the keyboard method sends the buttons to the user.



Further, if you notice, in the Task scheme, we have a start property, in which the minute in which the task is created is recorded, so it’s time to start the tasks, for this we iterate over all the tasks created in the current minute, the full code of the cron:



cron.js
 cron.schedule(conf.cron, async () => { try { const list = await task.currentList() if (list === null) throw new Error('No active assignments') for (let item of list) { const id = item._id.toString() // Missing running tasks if (activeTask.includes(id)) continue switch (item.type) { case ' + ': activeTask.push(id) actions .followLike(item) .then(res => { // Remove from list const keyActiveTask = activeTask.indexOf(id) delete activeTask[keyActiveTask] if (res.name === 'AuthenticationError') { send.message( item.user, `️    ${item.login}  ,  !` ) throw new Error(' ') } // notify the user when the task is completed if (res) { send.message( item.user, ` ${item.type}    ${item.login}` ) } }) .catch(e => console.log(e)) break case '': activeTask.push(id) actions .unFollow(item) .then(res => { // Remove from list const keyActiveTask = activeTask.indexOf(id) delete activeTask[keyActiveTask] if (res.name === 'AuthenticationError') { send.message( item.user, `️    ${item.login}  ,  !` ) throw new Error(' ') } // notify the user when the task is completed if (res) { send.message( item.user, ` ${item.type}    ${item.login}` ) } }) .catch(e => console.log(e)) break default: // Job type is not defined break } } } catch (e) { // No active assignments return e } }) 




To work with Telegram, I set up a webhook, if you don't need it yet, you can run it in dev mode (npm run dev).



I recommend setting up the webook, so your server will not send polling requests all the time, the configuration is in the conf.json file.



And probably the last thing is working with the Instagram private API, it makes no sense to consider it, the documentation is available with examples from the project repository.



And finally, an example of a working functional:





Conclusion



I would like to say that all the same there are problems, they are related to the fact that Instagram blocks and does not give permission to authorize more accounts from the same IP address of some VPS. So in my case I was able to log in from several accounts, then access was already blocked for adding new ones, but the ones added earlier had no problems, unless of course the daily limits were violated.



Of course, you can get around all this, if you use a proxy for each account or several accounts, but I did not set goals to develop a whole service, it was important for me to make a convenient and free tool and gain additional development experience, as well as a contribution to open source.



It is important to say that if you placed a bot somewhere in a rented virtual server, from which you did not naturally go to Instagram before (unless of course you’re not using openvpn), then you need to confirm that you are trying to access Instagram from the Netherlands or where your server, this message will appear when you log in to Instagram, after which you can re-add your account.



Of course, this is not a complete analogue of the service substitute, but I implemented the basic methods that most of the people I surveyed use.



Be careful using the bot! Especially for you added the “Limits” section, where some information was written that will prevent you from blocking by Instagram.



Important! Passwords are stored in unencrypted form, do not enter your password!

Project repository

Source: https://habr.com/ru/post/355014/



All Articles