├── .gitignore ├── LICENSE ├── README.md ├── cache ├── followlist.csv └── unfollowlist.csv ├── config.json ├── input ├── friends.csv ├── tags.csv └── tags_to_avoid.csv ├── requirements.txt ├── run.py └── src ├── __init__.py ├── instabot.py ├── instafunctions.py ├── instaprofile.py ├── logger.py └── miscellaneous.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # Profile 92 | cache/*.json 93 | 94 | # log 95 | cache/log 96 | 97 | # Mac 98 | .DS_Store 99 | *.DS_Store 100 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This code is no longer maintained. [Bots are no longer safe](https://likeup.me/bots-are-dead/), you could easily get banned. 2 | 3 | Big thanks to everyone who got involved in this project, namely @erleiuat. 4 | 5 | Instabot logo 6 | 7 | # InstaBot 1.3.0 8 | 9 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/4c7690e086f54631b169eaa2375d9c31)](https://www.codacy.com/app/nickpettican/InstaBot?utm_source=github.com&utm_medium=referral&utm_content=nickpettican/InstaBot&utm_campaign=Badge_Grade) 10 | 11 | - [InstaBot 1.3.0](#instabot-130) 12 | - [Requirements](#requirements) 13 | 14 | - [In a nutshell](#in-a-nutshell) 15 | - [InstaBot will](#instabot-will) 16 | - [Instabot runs in two modes](#instabot-runs-in-two-modes) 17 | 18 | - [How to install](#how-to-install) 19 | 20 | - [Your input](#your-input) 21 | - [Set likes](#set-likes) 22 | - [Set comments](#set-comments) 23 | - [Follow and unfollow](#follow-and-unfollow) 24 | 25 | - [Set Instabot to run during the day](#set-instabot-to-run-during-the-day) 26 | 27 | - [Instabot is smart](#instabot-is-smart) 28 | 29 | - [Motivation](#motivation) 30 | 31 | - [Server](#server) 32 | 33 | - [Future of InstaBot](#future-of-instabot) 34 | 35 | - [Warnings](#warnings) 36 | 37 | Automate your Instagram activity with InstaBot - a customisable bot that likes, follows and comments 38 | 39 | InstaBot is a Python-based automated Instagram bot made for **Social Media Marketing Campaigns** in order to reach the desired audience while saving time and money. 40 | 41 | All you need is your username and password and other parameters - no API signup is needed. 42 | 43 | ## Requirements 44 | 45 | `Python 2.7` and a server (a Raspberry Pi works great). See installation bellow to install libraries. 46 | 47 | ## In a nutshell 48 | 49 | ### InstaBot will 50 | 51 | - [x] **Like** posts of users you want to reach 52 | - [x] **Comment** on these posts 53 | - [x] **Follow** users you want to reach 54 | - [x] **Unfollow** users who don't follow you back within x hours 55 | - [x] **Like** posts in your news feed - only those you want to 56 | - [x] **Build a `profile` object** with all your followers and users you follow 57 | 58 | ### Instabot runs in two modes 59 | 60 | - From XX:XX to YY:YY 61 | - All day (24/7) 62 | 63 | _The bot works on 24-hour military time_, so you should input 09:00 to have it start at 9am. 64 | 65 | ## How to install 66 | 67 | 1. Download and install Python on your computer (a Raspberry Pi will have it pre-installed) 68 | 2. Install dependencies running `pip install -r requirements.txt`. 69 | 3. [Install lxml](http://lxml.de/installation.html) 70 | 4. `Git clone` this repo or download as a ZIP and extract 71 | 5. Add your input (see bellow) 72 | 6. Run `python run.py` 73 | 74 | ## Your input 75 | 76 | Open the `config` file and insert your credentials and preferences: 77 | 78 | { 79 | "username": "username", 80 | "password": "password", 81 | "timezone": 0, 82 | "tags": "input/tags.csv", 83 | "tags_to_avoid": "input/tags_to_avoid.csv", 84 | "friends": "input/friends.csv", 85 | "like_news_feed": true, 86 | "likes_in_day": 500, 87 | "media_max_likes": 50, 88 | "media_min_likes": 0, 89 | "follow_in_day": 50, 90 | "unfollow": true, 91 | "follow_time_hours": 6, 92 | "comments_in_day": 0, 93 | "comments_list": [["Cool", "Sweet", "Awesome", "Great"], 94 | ["😄", "🙌", "👍", "👌", "😊"], 95 | [".", "!", "!!", "!!!"]], 96 | "bot_start_at": "07:00", 97 | "bot_stop_at": "23:00" 98 | } 99 | 100 | | Variables | Defaults | Explanation | 101 | | :---------------: | :-----------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------- | 102 | | username | username | Your Instagram username | 103 | | password | password | Your Instagram password | 104 | | timezone | 0 | Your timezone code, in order to run it at your local time | 105 | | tags | input/tags.csv | Tags file - alternatively you can use \['tag1', 'tag2', 'tag3',...] | 106 | | tags_to_avoid | input/tags_to_avoid.csv | Tags to avoid file - you can also use \['tagx', 'tagy',...] | 107 | | friends | input/friends.csv | Friends file, containing usernames all in one column - alternatively you can use \['friend1', 'friend2',...] | 108 | | like_news_feed | true | `true` or `false` whether you want to like your friends' posts | 109 | | likes_in_day | 500 | Number of likes InstaBot will do in a day | 110 | | media_max_likes | 50 | Maximum number of likes a post will have for InstaBot to like it | 111 | | media_min_likes | 0 | Minimum number of likes a post will have for InstaBot to like it | 112 | | follow_in_day | 50 | Number of users it will follow per day | 113 | | unfollow | true | `true` or `false` whether you want to unfollow users | 114 | | follow_time_hours | 6 | Time (in hours) it will follow these users for | 115 | | comments_in_day | 0 | Number of comments it will leave per day | 116 | | comments_list | \[\["Cool", "Awesome", "Great"], \["😄", "🙌", "👍", "😊"], \[".", "!", "!!", "!!!"]] | What words you want shuffled to post comments | 117 | | bot_start_at | 07:00 | Time when InstaBot will start | 118 | | bot_stop_at | 23:00 | Time when InstaBot will stop | 119 | 120 | ### Set likes 121 | 122 | Tell Instabot how **many posts you want to like per day**. The **maximum** you will get to do before you get **banned** is **1000**, but the default is set at 500 to keep it safe, especially if you're doing a daytime run (7am to 11pm). Instabot will take random time breaks between likes, to make it look more like a real person and not an automated bot. 123 | 124 | "likes_in_day": 500 125 | 126 | Set Instabot to **like your friends posts** so you don't have to. Only the friends usernames you included in the friends list will have their posts liked. 127 | 128 | "friends": "input/friends.csv" 129 | "like_news_feed": true 130 | 131 | You can tell Instabot not to like posts with less than a **minimum like number** or more than a **maximum like number**. This property is useful because you will get **more feedback** from posts with a lower number of likes. 132 | 133 | "media_max_likes": 50 134 | "media_min_likes": 0 135 | 136 | Give Instabot a list of **hashtags** to look for posts to like. I recommend you read [this article](http://www.socialmediaexaminer.com/how-to-use-hashtags-on-instagram-to-grow-your-reach/) for tips on using hashtags. You can either put the tags into `input/tags.csv`, as it is currently the case in the default values: 137 | 138 | "tags": "input/tags.csv" 139 | 140 | Or you can insert the tags straight into the config file like this: 141 | 142 | "tags": ['tag1', 'tag2'] 143 | 144 | You can do the same with your friends list: either leave it as the default and include your friends' usernames in the `friends.csv` file or insert the usernames straight into the config file. 145 | 146 | "friends": "input/friends.csv" 147 | 148 | And finally, **negative keywords** are very important. These will help you avoid liking posts you don't want to like. These can be inserted the same way as the tags. The default is for them to be in a file. 149 | 150 | "tags_to_avoid": "input/tags_to_avoid.csv" 151 | 152 | ### Set comments 153 | 154 | Instabot will comment randomly on posts that it liked. The comments are generated through a list of lists that gets shuffled to make a comment. You can add extra lists to increase the comment, or just reduce it to a single list of list e.g. `[['I just want this comment']]`. 155 | 156 | "comments_in_day": 0 157 | "comments_list": [["Cool", "Sweet", "Awesome", "Great"], 158 | ["😄", "🙌", "👍", "👌", "😊"], 159 | [".", "!", "!!", "!!!"]] 160 | 161 | ### Follow and unfollow 162 | 163 | Follow users of posts you liked: 164 | 165 | "follow_in_day": 50 166 | 167 | Set the amount of time you want to follow users: 168 | 169 | "follow_time_hours": 6 170 | 171 | Set Instabot to unfollow them if they haven't followed you back: 172 | 173 | "unfollow": true 174 | 175 | ## Set Instabot to run during the day 176 | 177 | To make Instabot's activities look more real, you can set the bot to run between set times such as the default 7am to 11pm. Between those times the bot will either sleep or carry on unfollowing users it followed during the day. 178 | 179 | "bot_start_at": "07:00" 180 | "bot_stop_at": "23:00" 181 | 182 | Make sure you use military time. 183 | 184 | ## Instabot is smart 185 | 186 | - **Before unfollowing** a user, Instabot will **check if that user followed you back**. If that person followed you back, Instabot won't unfollow them. I mean it's only fair. It will also check if users are potentially fake accounts and will unfollow them. If a user was unfollowed recently but followed you back, he/she will be followed back again. 187 | - **Internet connection breaks?** No problem. InstaBot will wait until your internet connection is back in order to continue. 188 | - **Need to shut it down quickly but you're still following lots of people?** No problem. Instabot will save a list of the users it followed and will unfollow them next time it's turned on. 189 | 190 | ## Motivation 191 | 192 | Digital Marketing is becoming more and more complicated, with new platforms coming out and newer and better competitors appearing left right and center. InstaBot was created in order to save the time of having to reach out to the potential audience by liking posts and following those that will be interested in your service. Not everyone on Instagram is actively seeking out interesting profiles on the 'explore' tab, so in order to reach them, a like and a follow might be enough to spark that extra interest for them to visit your profile. 193 | 194 | ## Server 195 | 196 | If you haven't got a server I find a Raspberry Pi works just fine. All you need to do is follow the installation guide above and run the bot. 197 | 198 | ## Future of InstaBot 199 | 200 | Instabot is written in Python2, meaning it would need to be translated to Python3. Feel free to fork it and do this yourself. 201 | 202 | The stats functionality is something I would also like to work on, but feel free to collaborate to make it happen sooner. 203 | 204 | ## Warnings 205 | 206 | - Instagram does not like spam or bulk liking and following, so: 207 | 208 | > _Keep the likes per day bellow 1000 in a 24 hour period_, meaning if you use the bot for 12 hours a day, I would suggest only setting the likes to 500 or you might risk a ban. 209 | 210 | - Don't bulk unfollow: 211 | 212 | > You only get 15 unfollows in a short time period, so if you do a bulk unfollowing on your own, you risk InstaBot not being able to unfollow for a couple of hours. 213 | 214 | - Choose your tags wisely, and monitor the posts you are liking: 215 | 216 | > In case InstaBot likes unappropriate posts. 217 | 218 | - Don't rely on InstaBot as your only Social Media Digital Marketeer, Social Media is a two-way conversation, it's a way to gain an audience and talk to them, not a means to sell. 219 | -------------------------------------------------------------------------------- /cache/followlist.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickpettican/InstaBot/a2eb7d5016b319f919e8102db15a0448512db84a/cache/followlist.csv -------------------------------------------------------------------------------- /cache/unfollowlist.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickpettican/InstaBot/a2eb7d5016b319f919e8102db15a0448512db84a/cache/unfollowlist.csv -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "username", 3 | "password": "password", 4 | "timezone": 0, 5 | "friends": "input/friends.csv", 6 | "tags": "input/tags.csv", 7 | "tags_to_avoid": "input/tags_to_avoid.csv", 8 | "like_news_feed": false, 9 | "likes_in_day": 500, 10 | "media_max_likes": 50, 11 | "media_min_likes": 0, 12 | "follow_in_day": 50, 13 | "unfollow": true, 14 | "follow_time_hours": 6, 15 | "comments_in_day": 0, 16 | "comments_list": [ 17 | [ 18 | "Cool", "Awesome", "Great", "Amazing", "Fantastic", "Excellent", "Perfect", "Nice", 19 | "cool", "awesome", "great", "amazing", "fantastic","excellent", "perfect", "nice" 20 | ], 21 | 22 | [ 23 | "pic!", "pic!!", "pic!!!", 24 | "pic :thumbsup:", "pic :raised_hands:", "pic :smile:", "pic :smiley:", "pic :ok_hand:", 25 | "pic :clap:", "pic :muscle:", "pic :top:", "pic :star:", "pic :star2:", "pic :v:", "pic :open_mouth:", 26 | 27 | "picture!", "picture!!", "picture!!!", 28 | "picture :thumbsup:", "picture :raised_hands:", "picture :smile:", "picture :smiley:", "picture :ok_hand:", 29 | "picture :clap:", "picture :muscle:", "picture :top:", "picture :star:", "picture :star2:", "picture :v:", "picture :open_mouth:", 30 | 31 | "composition!", "composition!!", "composition!!!", 32 | "composition :thumbsup:", "composition :raised_hands:", "composition :smile:", "composition :smiley:", "composition :ok_hand:", 33 | "composition :clap:", "composition :muscle:", "composition :top:", "composition :star:", "composition :star2:", "composition :v:", "composition :open_mouth:", 34 | 35 | "!", "!!", "!!!", 36 | ":thumbsup:", ":raised_hands:", ":smile:", ":smiley:", ":ok_hand:", 37 | ":clap:", ":muscle:", ":top:", ":star:", ":star2:", ":v:", ":open_mouth:" 38 | ], 39 | 40 | [ 41 | ":thumbsup:", ":raised_hands:", ":smile:", ":smiley:", ":ok_hand:", 42 | ":clap:", ":muscle:", ":top:", ":star:", ":star2:", ":v:", ":open_mouth:", 43 | 44 | "And gorgeous collection!", "And gorgeous collection!!", "And gorgeous collection!!!", 45 | "And gorgeous collection :thumbsup:", "And gorgeous collection :raised_hands:", "And gorgeous collection :smile:", "And gorgeous collection :smiley:", "And gorgeous collection :ok_hand:", 46 | "And gorgeous collection :clap:", "And gorgeous collection :muscle:", "And gorgeous collection :top:", "And gorgeous collection :star:", "And gorgeous collection :star2:", "And gorgeous collection :v:", "And gorgeous collection :open_mouth:", 47 | 48 | " " 49 | ], 50 | 51 | [ 52 | ":thumbsup:", ":raised_hands:", ":smile:", ":smiley:", ":ok_hand:", 53 | ":clap:", ":muscle:", ":top:", ":star:", ":star2:", ":v:", ":open_mouth:", 54 | 55 | " " 56 | ], 57 | 58 | [ 59 | "Keep it up!", "Keep it up!!", "Keep it up!!!", 60 | "Keep it up :thumbsup:", "Keep it up :raised_hands:", "Keep it up :smile:", "Keep it up :smiley:", "Keep it up :ok_hand:", 61 | "Keep it up :clap:", "Keep it up :muscle:", "Keep it up :top:", "Keep it up :star:", "Keep it up :star2:", "Keep it up :v:", "Keep it up :open_mouth:", 62 | 63 | "Keep up the good work!", "Keep up the good work!!", "Keep up the good work!!!", 64 | "Keep up the good work :thumbsup:", "Keep up the good work :raised_hands:", "Keep up the good work :smile:", "Keep up the good work :smiley:", "Keep up the good work :ok_hand:", 65 | "Keep up the good work :clap:", "Keep up the good work :muscle:", "Keep up the good work :top:", "Keep up the good work :star:", "Keep up the good work :star2:", "Keep up the good work :v:", "Keep up the good work :open_mouth:", 66 | 67 | " " 68 | ] 69 | ], 70 | "bot_start_at": "07:00", 71 | "bot_stop_at": "23:00" 72 | } 73 | -------------------------------------------------------------------------------- /input/friends.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /input/tags.csv: -------------------------------------------------------------------------------- 1 | love 2 | happy 3 | photography 4 | science 5 | coding 6 | instagood 7 | instapassport 8 | travel 9 | programming 10 | holiday 11 | me 12 | coffee 13 | throwback 14 | -------------------------------------------------------------------------------- /input/tags_to_avoid.csv: -------------------------------------------------------------------------------- 1 | alone 2 | horny 3 | fff 4 | f4f 5 | l4l 6 | fashion 7 | kpop 8 | kpopmemes 9 | kpopstar 10 | buy 11 | guns 12 | followmeplease 13 | anime 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | asn1crypto==0.24.0 3 | astroid==1.6.5 4 | backports-abc==0.5 5 | backports.functools-lru-cache==1.5 6 | backports.shutil-get-terminal-size==1.0.0 7 | bleach==2.1.3 8 | certifi==2018.4.16 9 | cffi==1.11.5 10 | chardet==3.0.4 11 | configparser==3.5.0 12 | cryptography==2.3 13 | cryptography-vectors==2.3 14 | cycler==0.10.0 15 | decorator==4.3.0 16 | emoji==0.5.1 17 | entrypoints==0.2.3 18 | enum34==1.1.6 19 | functools32==3.2.3.post2 20 | futures==3.2.0 21 | html5lib==1.0.1 22 | idna==2.7 23 | ipaddress==1.0.22 24 | ipykernel==4.8.2 25 | ipython==5.8.0 26 | ipython-genutils==0.2.0 27 | ipywidgets==7.4.0 28 | isort==4.3.4 29 | Jinja2==2.10 30 | jsonschema==2.6.0 31 | jupyter==1.0.0 32 | jupyter-client==5.2.3 33 | jupyter-console==5.2.0 34 | jupyter-core==4.4.0 35 | jupyterthemes==0.19.6 36 | kiwisolver==1.0.1 37 | lazy-object-proxy==1.3.1 38 | lesscpy==0.13.0 39 | lxml==4.2.4 40 | MarkupSafe==1.0 41 | matplotlib==2.2.3 42 | mccabe==0.6.1 43 | mistune==0.8.3 44 | nbconvert==5.3.1 45 | nbformat==4.4.0 46 | notebook==5.6.0 47 | numpy==1.15.0 48 | pandocfilters==1.4.2 49 | pathlib2==2.3.2 50 | pexpect==4.6.0 51 | pickleshare==0.7.4 52 | ply==3.11 53 | prometheus-client==0.3.1 54 | prompt-toolkit==1.0.15 55 | ptyprocess==0.6.0 56 | pycparser==2.18 57 | Pygments==2.2.0 58 | pylint==1.9.3 59 | pyOpenSSL==18.0.0 60 | pyparsing==2.2.0 61 | PySocks==1.6.8 62 | python-dateutil==2.7.3 63 | pytz==2018.5 64 | pyzmq==17.1.2 65 | qtconsole==4.3.1 66 | requests==2.20.0 67 | scandir==1.9.0 68 | selenium==3.13.0 69 | Send2Trash==1.5.0 70 | simplegeneric==0.8.1 71 | singledispatch==3.4.0.3 72 | six==1.11.0 73 | subprocess32==3.5.2 74 | terminado==0.8.1 75 | testpath==0.3.1 76 | tornado==5.1 77 | traitlets==4.3.2 78 | urllib3==1.23 79 | wcwidth==0.1.7 80 | webencodings==0.5.1 81 | widgetsnbextension==3.4.0 82 | wrapt==1.10.11 83 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ___ InstaBot V 1.2.0 by nickpettican ___ 5 | # ___ Automate your Instagram activity ___ 6 | 7 | # ___ Copyright 2017 Nicolas Pettican ___ 8 | 9 | # ___ This software is licensed under the Apache 2 ___ 10 | # ___ license. You may not use this file except in ___ 11 | # ___ compliance with the License. ___ 12 | 13 | # ___ DISCLAIMER ___ 14 | 15 | # ___ InstaBot was created for educational purposes and ___ 16 | # ___ the end-user assumes sole responsibility of any ___ 17 | # ___ consequences of it's misuse. Please be advised of ___ 18 | # ___ Instagram's monitoring, 1000 likes a day is the ___ 19 | # ___ maximum you will get or you risk a temporary ban. ___ 20 | # ___ Choose your tags wisely or you may risk liking ___ 21 | # ___ and commenting on undesirable media or spam. ___ 22 | 23 | from src.instabot import InstaBot 24 | from src.instaprofile import InstaProfile 25 | import json 26 | 27 | 28 | def parse_config(path): 29 | # parses config file to load parameters 30 | 31 | try: 32 | raw = [line.strip() for line in open(path, 'r')] 33 | return json.loads(''.join(raw)) 34 | except BaseException: 35 | print 'Could not open config file, check parameters.' 36 | 37 | 38 | def main(): 39 | # main operations 40 | 41 | data = parse_config('config.json') 42 | profile = InstaProfile(path='cache/', params=data) 43 | instabot = InstaBot(profile, data=data) 44 | instabot.main() 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickpettican/InstaBot/a2eb7d5016b319f919e8102db15a0448512db84a/src/__init__.py -------------------------------------------------------------------------------- /src/instabot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ___ InstaBot V 1.2.0 by nickpettican ___ 5 | # ___ Automate your Instagram activity ___ 6 | 7 | # ___ Copyright 2017 Nicolas Pettican ___ 8 | 9 | # ___ This software is licensed under the Apache 2 ___ 10 | # ___ license. You may not use this file except in ___ 11 | # ___ compliance with the License. ___ 12 | 13 | # ___ DISCLAIMER ___ 14 | 15 | # ___ InstaBot was created for educational purposes and ___ 16 | # ___ the end-user assumes sole responsibility of any ___ 17 | # ___ consequences of it's misuse. Please be advised of ___ 18 | # ___ Instagram's monitoring, 1000 likes a day is the ___ 19 | # ___ maximum you will get or you risk a temporary ban. ___ 20 | # ___ Choose your tags wisely or you may risk liking ___ 21 | # ___ and commenting on undesirable media or spam. ___ 22 | 23 | from datetime import datetime, timedelta 24 | from json import loads as toJSON 25 | from emoji import emojize 26 | from requests import Session 27 | from os import path, makedirs 28 | from time import sleep, time, mktime 29 | from random import random as aleatory, randint, choice 30 | from requests.exceptions import ConnectionError 31 | from operator import itemgetter 32 | import traceback, re 33 | 34 | from logger import Logger 35 | from src.miscellaneous import * 36 | from src.instafunctions import * 37 | 38 | # === INSTABOT === 39 | 40 | # after doing some web development I kinda liked having the code more packed together 41 | # which is why the code is a bit more minimised now 42 | # also I thought of leaving more comments for my (and your) convenience 43 | # and I also got rid of some "else"s to make it cleaner 44 | 45 | 46 | class InstaBot: 47 | 48 | def __init__(self, profile, data={ 49 | 'username': 'user', 50 | 'password': 'pwd', 51 | 'timezone': 0, 52 | 'friends': 'input/friends.csv', 53 | 'tags': 'input/tags.csv', 54 | 'tags_to_avoid': 'input/tags_to_avoid.csv', 55 | 'like_news_feed': True, 56 | 'likes_in_day': 500, 57 | 'media_max_likes': 50, 58 | 'media_min_likes': 0, 59 | 'follow_in_day': 0, 60 | 'unfollow': True, 61 | 'follow_time_hours': 5, 62 | 'comments_in_day': 0, 63 | "comments_list": [["Cool", "Sweet", "Awesome", "Great"], 64 | ["😄", "🙌", "👍", "👌", "😊"], 65 | [".", "!", "!!", "!!!"]], 66 | 'bot_start_at': '07:00', 67 | 'bot_stop_at': '23:00' 68 | }): 69 | # default values 70 | self.today = datetime.today().strftime('%d-%m-%Y') 71 | self.users_checked_today = [] 72 | self.header = '\n\tInstaBot 1.3.0 by nickpettican\ 73 | \n\tAutomate your Instagram activity\n\ 74 | \n\tNew features:\ 75 | \n\t- Added timezone as config parameter\ 76 | \n\t- Removed arrow as dependency' 77 | 78 | # INITIALISE VARIABLES 79 | 80 | self.params = data 81 | self.profile = profile 82 | self.params['total_operations'] = data['likes_in_day'] + \ 83 | (data['follow_in_day'] * 2 if data['unfollow'] else data['follow_in_day']) + data['comments_in_day'] 84 | self.params['follow_time'] = data['follow_time_hours'] * 60 * 60 85 | self.ERROR = {'mkdir': False, 'importing': False, 'cache': False} 86 | self.op_tmp = {'like': False, 'comment': False} 87 | self.banned = {'banned': False, '400': 0} 88 | self.cache = {} 89 | # define the bucket 90 | self.bucket = { 91 | 'explore': { 92 | 'follow': set(), 93 | 'unfollow': [], 94 | 'like': set(), 95 | 'unlike': set(), 96 | 'comment': set(), 97 | 'done': { 98 | 'follow': set(), 99 | 'unfollow': [], 100 | 'like': set(), 101 | 'comment': set() 102 | }, 103 | }, 104 | 'feed': { 105 | 'like': [], 106 | 'media_ids': [], 107 | 'done': [] 108 | }, 109 | 'codes': {}, 110 | 'user_ids': {} 111 | } 112 | # initialise the logger 113 | self.logger = Logger( 114 | self.header, 115 | self.profile.save_unfollow_list, 116 | self.bucket['explore']['unfollow']) 117 | # instagram urls 118 | url = 'https://www.instagram.com/' 119 | self.insta_urls = { 120 | 'domain': url, 121 | 'user': url + '%s/', 122 | 'login': url + 'accounts/login/ajax/', 123 | 'logout': url + 'accounts/logout/', 124 | 'explore': url + 'explore/tags/%s/', 125 | 'like': url + 'web/likes/%s/like/', 126 | 'unlike': url + 'web/likes/%s/unlike/', 127 | 'comment': url + 'web/comments/%s/add/', 128 | 'follow': url + 'web/friendships/%s/follow/', 129 | 'unfollow': url + 'web/friendships/%s/unfollow/', 130 | 'media': url + 'p/%s/' 131 | } 132 | # counters 133 | self.total_counters = { 134 | 'like_feed': 0, 135 | 'like': 0, 136 | 'follow': 0, 137 | 'unfollow': 0, 138 | 'comment': 0} 139 | self.day_counters = { 140 | 'all': 0, 141 | 'like_feed': 0, 142 | 'like': 0, 143 | 'follow': 0, 144 | 'unfollow': 0, 145 | 'comment': 0} 146 | # initialise links and other parameters 147 | self.mkdir_cache() 148 | self.sort_enabling() 149 | self.starting_operations() 150 | self.catch_up_operations() 151 | self.init_requests() 152 | self.init_profile() 153 | 154 | # === INITIAL OPERATIONS === 155 | 156 | def starting_operations(self): 157 | # starting operations 158 | 159 | self.user_input() 160 | self.create_delays() 161 | self.set_operation_sequence() 162 | 163 | def mkdir_cache(self): 164 | # creates the cache folder 165 | 166 | if not path.isdir('cache'): 167 | try: 168 | makedirs('cache') 169 | except BaseException: 170 | self.logger.log('\nERROR creating cache.\n') 171 | self.ERROR['mkdir'] = True 172 | 173 | def user_input(self): 174 | # imports friend list so that the bot likes their new media 175 | 176 | try: 177 | for key in ['friends', 'tags', 'tags_to_avoid']: 178 | if isinstance(self.params[key], list): 179 | self.cache[key] = self.params[key] 180 | continue 181 | self.cache[key] = [ 182 | line.strip() for line in open( 183 | self.params[key], 'r')] 184 | except BaseException: 185 | self.ERROR['importing'] = True 186 | self.logger.log('\nERROR importing cached lists') 187 | 188 | self.cache['unfollow'] = False 189 | 190 | try: 191 | if path.isfile('cache/followlist.csv'): 192 | self.cache['unfollow'] = [line.strip().split(',') 193 | for line in open('cache/followlist.csv', 'r')] 194 | except BaseException: 195 | self.logger.log('\nERROR importing cached follow list') 196 | 197 | def sort_enabling(self): 198 | # define which operations are enabled 199 | 200 | self.enabled = { 201 | 'like_feed': False, 202 | 'like': False, 203 | 'follow': False, 204 | 'comment': False, 205 | 'unfollow': False 206 | } 207 | try: 208 | if self.params['like_news_feed']: 209 | self.enabled['like_feed'] = True 210 | 211 | if self.params['likes_in_day'] > 0: 212 | self.enabled['like'] = True 213 | 214 | if self.params['follow_in_day'] > 0: 215 | self.enabled['follow'] = True 216 | 217 | if self.params['comments_in_day'] > 0: 218 | self.enabled['comment'] = True 219 | 220 | if self.enabled['follow']: 221 | self.enabled['unfollow'] = self.params['unfollow'] 222 | except BaseException: 223 | self.logger.log( 224 | 'ERROR while sorting parameters. Check you entered the right formats.') 225 | exit() 226 | 227 | def init_profile(self): 228 | # returns data for InstaProfile 229 | 230 | self.profile.import_profile( 231 | check_user( 232 | self.browser, 233 | self.insta_urls['user'], 234 | self.params['username'])) 235 | 236 | # === TIME OPERATIONS === 237 | 238 | def time_now(self): 239 | # returns current timestamp 240 | 241 | try: 242 | return float(time() + (self.params["timezone"] * 60 * 60)) 243 | except BaseException: 244 | return float(time()) 245 | 246 | def create_delays(self): 247 | # creates the necessary times 248 | 249 | if self.params['bot_start_at'] == self.params['bot_stop_at']: 250 | self.run_all_day = True 251 | self.params['time_in_day'] = 24 * 60 * 60 252 | else: 253 | self.run_all_day = False 254 | self.times = self.today_times() 255 | self.params['time_in_day'] = int( 256 | self.times['stop_bot'] - self.times['start_bot']) 257 | self.params['total_operations'] += self.params['time_in_day'] / 60 / 60 258 | follow_delays = return_random_sequence( 259 | self.params['follow_in_day'], self.params['time_in_day']) 260 | self.delays = { 261 | 'like': return_random_sequence( 262 | self.params['likes_in_day'], 263 | self.params['time_in_day']), 264 | 'follow': follow_delays, 265 | 'comment': return_random_sequence( 266 | self.params['comments_in_day'], 267 | self.params['time_in_day']), 268 | 'unfollow': follow_delays, 269 | 'like_feed': [ 270 | 60 * 271 | 60 for i in range( 272 | 0, 273 | int( 274 | self.params['time_in_day'] / 275 | 60 / 276 | 60))]} 277 | 278 | def today_times(self): 279 | # creates the times for the day 280 | 281 | if not self.run_all_day: 282 | if float(self.params['bot_stop_at'].replace( 283 | ':', '.')) - float(self.params['bot_start_at'].replace(':', '.')) < 0: 284 | self.params['bot_stop_at'] = str( 285 | float( 286 | self.params['bot_stop_at'].replace( 287 | ':', 288 | '.')) + 289 | 12).replace( 290 | '.', 291 | ':') 292 | time_tomorrow = float( 293 | mktime( 294 | datetime.strptime( 295 | (datetime.now() + timedelta( 296 | days=1)).strftime("%Y-%m-%d ") + str( 297 | self.params['bot_start_at']).replace( 298 | '.', 299 | ':'), 300 | '%Y-%m-%d %H:%M').timetuple())) + ( 301 | self.params["timezone"] * 60 * 60) 302 | return { 303 | 'start_bot': float(mktime( 304 | datetime.strptime( 305 | datetime.today().strftime("%Y-%m-%d ") + str(self.params['bot_start_at']).replace('.', ':'), '%Y-%m-%d %H:%M' 306 | ).timetuple())) + (self.params["timezone"] * 60 * 60), 307 | 'stop_bot': float(mktime( 308 | datetime.strptime( 309 | datetime.today().strftime("%Y-%m-%d ") + str(self.params['bot_stop_at']).replace('.', ':'), '%Y-%m-%d %H:%M' 310 | ).timetuple())) + (self.params["timezone"] * 60 * 60), 311 | 'tomorrow_start': time_tomorrow 312 | } 313 | 314 | def set_operation_sequence(self): 315 | # set the operation times to iterate 316 | 317 | self.next_operation = { 318 | 'like': ( 319 | datetime.now() - 320 | datetime( 321 | 1970, 322 | 1, 323 | 1)).total_seconds() + 324 | 40, 325 | 'follow': ( 326 | datetime.now() - 327 | datetime( 328 | 1970, 329 | 1, 330 | 1)).total_seconds() + 331 | 60, 332 | 'comment': ( 333 | datetime.now() - 334 | datetime( 335 | 1970, 336 | 1, 337 | 1)).total_seconds() + 338 | 40, 339 | 'unfollow': ( 340 | datetime.now() - 341 | datetime( 342 | 1970, 343 | 1, 344 | 1)).total_seconds() + 345 | self.params['follow_time'], 346 | 'like_feed': ( 347 | datetime.now() - 348 | datetime( 349 | 1970, 350 | 1, 351 | 1)).total_seconds()} 352 | if self.bucket['explore']['unfollow']: 353 | self.next_operation['unfollow'] = min( 354 | i[1] for i in self.bucket['explore']['unfollow']) 355 | self.next_check_feed = ( 356 | datetime.now() - 357 | datetime( 358 | 1970, 359 | 1, 360 | 1)).total_seconds() 361 | self.max_operation = { 362 | 'like': self.params['likes_in_day'], 363 | 'follow': self.params['follow_in_day'], 364 | 'comment': self.params['comments_in_day'], 365 | 'unfollow': self.params['follow_in_day'], 366 | 'like_feed': self.params['time_in_day'] / 60 / 60, 367 | 'all': self.params['total_operations'] 368 | } 369 | 370 | def reset_day_counters(self): 371 | # resets the day counters 372 | 373 | unfollow_counter_tmp = self.day_counters['unfollow'] 374 | self.day_counters = { 375 | 'all': 0, 376 | 'like_feed': 0, 377 | 'like': 0, 378 | 'follow': 0, 379 | 'comment': 0, 380 | 'unfollow': unfollow_counter_tmp} 381 | 382 | def catch_up_operations(self): 383 | # organises the operations for the day 384 | 385 | if not self.run_all_day: 386 | if self.times['start_bot'] < ( 387 | datetime.now() - 388 | datetime( 389 | 1970, 390 | 1, 391 | 1)).total_seconds() < self.times['stop_bot']: 392 | for action, enabled in self.enabled.items(): 393 | if enabled: 394 | tmp_time = self.times['start_bot'] 395 | while tmp_time < ( 396 | datetime.now() - 397 | datetime( 398 | 1970, 399 | 1, 400 | 1)).total_seconds(): 401 | if self.day_counters[action] < self.max_operation[action]: 402 | tmp_time = tmp_time + \ 403 | self.delays[action][self.day_counters[action]] 404 | self.day_counters[action] += 1 405 | self.day_counters['all'] += 1 406 | continue 407 | break 408 | 409 | self.day_counters['unfollow'] = self.day_counters['follow'] 410 | 411 | def banned_sleep(self): 412 | # sleep for x minutes and try again 413 | 414 | if self.banned['400'] < 3: 415 | sleep_time = 5 * 60 416 | 417 | else: 418 | sleep_time = 60 * 60 419 | self.banned['banned'] = True 420 | self.banned['400'] = 0 421 | self.logger.log( 422 | '\n\t--- Sleeping for %s minutes ---' % 423 | (sleep_time / 60)) 424 | self.next_operation['like'] += sleep_time 425 | self.next_operation['follow'] += sleep_time 426 | self.next_operation['comment'] += sleep_time 427 | self.next_operation['like_feed'] += sleep_time 428 | self.next_check_feed += sleep_time 429 | sleep(sleep_time) 430 | 431 | # === MAIN REQUESTS OPERATIONS === 432 | 433 | def init_requests(self): 434 | # starting requests operations 435 | 436 | self.start_requests() 437 | self.log_in() 438 | 439 | def start_requests(self): 440 | # starts requests session 441 | 442 | user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36' 443 | 444 | self.browser = Session() 445 | self.browser.cookies.update({ 446 | 'sessionid': '', 'mid': '', 'ig_pr': '1', 'ig_vw': '1920', 447 | 'csrftoken': '', 's_network': '', 'ds_user_id': '' 448 | }) 449 | self.browser.headers.update({ 450 | 'Accept-Encoding': 'gzip, deflate', 451 | 'Accept-Language': 'en-US;q=0.6,en;q=0.4', 452 | 'Connection': 'keep-alive', 453 | 'Content-Length': '0', 454 | 'Host': 'www.instagram.com', 455 | 'Origin': 'https://www.instagram.com', 456 | 'Referer': 'https://www.instagram.com/', 457 | 'User-Agent': user_agent, 458 | 'X-Instagram-AJAX': '1', 459 | 'X-Requested-With': 'XMLHttpRequest' 460 | 461 | }) 462 | 463 | def log_in(self): 464 | # signs the user into instagram 465 | 466 | self.logged_in = False 467 | try: 468 | self.logger.log( 469 | '\nTrying to sign in as %s: \,' % 470 | (self.params['username'])) 471 | # extract cookies from Instagram main page 472 | extract = self.browser.get(self.insta_urls['domain']) 473 | csrftoken = re.search( 474 | '(?<=\"csrf_token\":\")\w+', extract.text).group(0) 475 | self.browser.headers.update( 476 | {'X-CSRFToken': csrftoken}) 477 | sleep(2) 478 | # extract csrf token from login requests post 479 | extract_login = self.browser.post( 480 | self.insta_urls['login'], 481 | data={ 482 | 'username': self.params['username'], 483 | 'password': self.params['password']}, 484 | allow_redirects=True) 485 | self.csrf_token = extract_login.cookies['csrftoken'] 486 | self.browser.headers.update({'X-CSRFToken': self.csrf_token}) 487 | self.browser.cookies['ig_vw'] = '1536' 488 | self.browser.cookies['ig_pr'] = '1.25' 489 | self.browser.cookies['ig_vh'] = '772' 490 | self.browser.cookies['ig_or'] = 'landscape-primary' 491 | sleep(2) 492 | # check if login successful 493 | if extract_login.ok: 494 | if self.check_login(): 495 | self.logger.log('Success') 496 | self.logged_in = True 497 | sleep(2) 498 | else: 499 | 500 | self.logger.log('\nError signing in.\,') 501 | self.logger.log( 502 | 'Request returns %s but it seems it didn\'t work.' % 503 | (extract_login.status_code)) 504 | 505 | response_data = toJSON(extract_login.text) 506 | self.logger.log( 507 | '\nInstagram error message: %s' % 508 | (response_data.get('message'))) 509 | if response_data.get('error_type'): 510 | self.logger.log( 511 | '\nError type: %s' % 512 | (response_data.get('error_type'))) 513 | 514 | exit('\nCheck you entered the correct details!\n') 515 | 516 | else: 517 | 518 | self.logger.log('\nError signing in.\,') 519 | self.logger.log( 520 | 'Request returns %s error.' % 521 | (extract_login.status_code)) 522 | 523 | response_data = toJSON(extract_login.text) 524 | self.logger.log( 525 | '\nInstagram error message: %s' % 526 | (response_data.get('message'))) 527 | if (response_data.get('message') == 'checkpoint_required'): 528 | self.logger.log( 529 | '\nCheckpoint_url: %s' % 530 | (response_data.get('checkpoint_url'))) 531 | self.logger.log( 532 | '\nOpen Instagram on your desktop browser and confirm that it is you trying to log in!') 533 | self.logger.log('You have 30 seconds...') 534 | sleep(30) 535 | elif response_data.get('error_type'): 536 | self.logger.log( 537 | '\nError type: %s' % 538 | (response_data.get('error_type'))) 539 | exit('\nCheck you entered the correct details!\n') 540 | else: 541 | exit('\nCheck you entered the correct details!\n') 542 | 543 | except KeyError as e: 544 | self.logger.log('\nERROR signing in, InstaBot probably failed to obtain Instagram\'s cookies') 545 | self.logger.log(traceback.format_exc()) 546 | exit('\nApologies, I could not sign you in.\n') 547 | 548 | except Exception as e: 549 | self.logger.log('\nERROR while attempting sign in: %s\n' % e) 550 | self.logger.log(traceback.format_exc()) 551 | exit('\nApologies, I could not sign you in.\n') 552 | 553 | def check_login(self): 554 | # checks if user is logged in 555 | 556 | try: 557 | main_page = self.browser.get(self.insta_urls['domain']) 558 | if self.params['username'] in main_page.text: 559 | return True 560 | except Exception as e: 561 | self.logger.log('ERROR while checking if logged in: %s' % (e)) 562 | return False 563 | 564 | def log_out(self): 565 | # logs the user out 566 | 567 | try: 568 | self.browser.post( 569 | self.insta_urls['logout'], data={ 570 | 'csrfmiddlewaretoken': self.csrf_token}) 571 | self.browser.get(self.insta_urls['domain']) 572 | self.logger.log('\nSuccessfully logged out!') 573 | self.logged_in = False 574 | except Exception as err: 575 | self.logger.log('\nError while attempting logout: %s' % err) 576 | 577 | self.logger.backup() 578 | exit('\nThank you for using InstaBot!\n') 579 | 580 | def clean_up(self, on_exit, statement): 581 | # unfollows from list if user wants to log out or if day is over 582 | 583 | self.logger.log(statement) 584 | try: 585 | if on_exit: 586 | # unfollows between 30-60 second breaks 587 | while len(self.bucket['explore']['unfollow']) > 0: 588 | self.clean_up_loop_count += 1 589 | # sort the users by the time they were followed 590 | for user in sorted( 591 | self.bucket['explore']['unfollow'], 592 | key=itemgetter(1)): 593 | user_id = user[0] 594 | # try to find the username 595 | try: 596 | username = self.bucket['user_ids'][user_id] 597 | except BaseException: 598 | username = False 599 | # if available, print the username 600 | if username: 601 | check = self.user_following_back(username) 602 | if check[0]: 603 | self.logger.log( 604 | '\n * "%s" followed you back' % (username)) 605 | self.bucket['explore']['unfollow'].remove(user) 606 | self.profile.add_follower(check[1]) 607 | continue 608 | else: 609 | username = user_id 610 | self.logger.log( 611 | '\n * Trying to unfollow "%s": \,' % 612 | (username)) 613 | # unfollow 614 | response = self.explore_operation('unfollow', user) 615 | if not response[0]: 616 | if response[1].status_code == 400: 617 | self.banned_sleep() 618 | if response[0]: 619 | self.profile.remove_follow(user_id) 620 | self.banned['400'] = 0 621 | # small break 622 | sleep_time = randint(20, 50) 623 | print '\n\t--- Delaying %s seconds ---' % (sleep_time) 624 | sleep(sleep_time) 625 | # in case there's an error and it keeps looping 626 | if self.clean_up_loop_count > 1000: 627 | self.clean_up_loop_count = 0 628 | self.logger.log( 629 | '\nThe clean_up function is stuck in a loop. Breaking it...') 630 | break 631 | return 632 | 633 | # normal unfollow 634 | while self.max_operation['unfollow'] > self.day_counters['unfollow']: 635 | self.clean_up_loop_count += 1 636 | self.next_operation['unfollow'] += self.delays['unfollow'][self.day_counters['unfollow']] 637 | self.day_counters['all'] += 1 638 | self.day_counters['unfollow'] += 1 639 | # normal unfollow operation 640 | response = self.insta_operation('unfollow') 641 | if not response[0]: 642 | if response[1].status_code == 400: 643 | self.banned_sleep() 644 | if response[0]: 645 | self.banned['400'] = 0 646 | if (datetime.now() - datetime(1970, 1, 1) 647 | ).total_seconds() > self.times['tomorrow_start']: 648 | break 649 | # just to log the time 650 | sleep_time = self.delays['unfollow'][self.day_counters['unfollow']] 651 | print '\n\t--- Delaying %s seconds ---' % (sleep_time) 652 | sleep(sleep_time) 653 | # in case there's an error and it keeps looping 654 | if self.clean_up_loop_count > 1000: 655 | self.clean_up_loop_count = 0 656 | self.logger.log( 657 | '\nThe clean_up function is stuck in a loop. Breaking it...') 658 | break 659 | 660 | except KeyboardInterrupt: 661 | if len(self.bucket['explore']['unfollow']): 662 | self.logger.log( 663 | '\nList of followers will be created and unfollowed when InstaBot is next started.') 664 | self.log_out() 665 | 666 | except Exception as e: 667 | self.logger.log('\nError cleaning up: %s' % (e)) 668 | self.logger.log(traceback.format_exc()) 669 | if on_exit: 670 | self.log_out() 671 | 672 | # === MAIN LOOP === 673 | 674 | def main(self): 675 | 676 | # --- main --- 677 | 678 | self.loop_count = 0 679 | self.clean_up_loop_count = 0 680 | # unfollow previous session's follows 681 | if self.cache['unfollow']: 682 | if len(self.cache['unfollow']) >= 1: 683 | [self.bucket['explore']['unfollow'].append( 684 | [user[0], i]) for i, user in enumerate(self.cache['unfollow'])] 685 | self.clean_up( 686 | on_exit=True, 687 | statement='\nCleaning up last sessions follows...') 688 | # start 689 | self.logger.log('\n\tStarting Bot at %s' % 690 | (datetime.today().strftime('%H:%m %Y-%m-%d'))) 691 | sleep(1 * aleatory()) 692 | # check if out of hours 693 | if not self.run_all_day: 694 | if self.times['start_bot'] > ( 695 | datetime.now() - 696 | datetime( 697 | 1970, 698 | 1, 699 | 1)).total_seconds(): 700 | self.logger.log( 701 | '\n%s => Stopping time reached. Pausing Bot until %s' % 702 | (self.params['bot_stop_at'], self.params['bot_start_at'])) 703 | # print self.times['start_bot'], (datetime.now() - 704 | # datetime(1970, 1, 1)).total_seconds(), 705 | # self.times['tomorrow_start'] 706 | sleep(int(self.times['start_bot'] - 707 | (datetime.now() - 708 | datetime(1970, 1, 1)).total_seconds())) 709 | # main while loop 710 | while True: 711 | dc = self.day_counters 712 | self.logger.backup() 713 | self.logger.log( 714 | '\n\tCurrent daily count:\n\t - All operations: %s\n\t - Like news feed: %s\n\t - Likes: %s\n\t - Follows: %s\n\t - Unfollows: %s\n\t - Comments: %s' % 715 | (dc['all'], dc['like_feed'], dc['like'], dc['follow'], dc['unfollow'], dc['comment'])) 716 | # here we go 717 | try: 718 | if self.run_all_day: 719 | # all day mode 720 | while internet_connection(): 721 | self.main_loop() 722 | if all(self.max_operation[op] == self.day_counters[op] 723 | for op in self.day_counters if op != 'unfollow' if op != 'all'): 724 | self.reset_day_counters() 725 | self.starting_operations() 726 | continue 727 | # hour to hour mode 728 | while self.times['start_bot'] < ( 729 | datetime.now() - 730 | datetime( 731 | 1970, 732 | 1, 733 | 1)).total_seconds() < self.times['stop_bot']: 734 | # where the magic happens 735 | self.main_loop() 736 | # if the loop is broken, finish up 737 | self.clean_up(on_exit=False, 738 | statement='\nFinishing operations...') 739 | sleep(10) 740 | self.logger.log( 741 | '\nSleeping until %s' % 742 | (self.params['bot_start_at'])) 743 | sleep_time = int( 744 | self.times['tomorrow_start'] - (datetime.now() - datetime(1970, 1, 1)).total_seconds()) 745 | if sleep_time > 0: 746 | sleep(sleep_time) 747 | # check if it's the next day 748 | if (datetime.now() - datetime(1970, 1, 1) 749 | ).total_seconds() > self.times['tomorrow_start']: 750 | self.reset_day_counters() 751 | self.starting_operations() 752 | 753 | except ConnectionError: 754 | self.logger.log('Connection error!') 755 | if not internet_connection(): 756 | while not internet_connection(): 757 | self.logger.log( 758 | 'NO INTERNET - re-establish connection to continue') 759 | sleep(60) 760 | self.reset_day_counters() 761 | self.starting_operations() 762 | self.catch_up_operations() 763 | if not self.check_login(): 764 | self.init_requests() 765 | 766 | except Exception as e: 767 | self.logger.log('Error: %s' % (e)) 768 | self.logger.log(traceback.format_exc()) 769 | 770 | except KeyboardInterrupt: 771 | self.clean_up( 772 | on_exit=True, 773 | statement='\n\nStopping Bot. Please wait for cleaning follows...') 774 | self.logger.log('\nCleanup Done. Logging out...') 775 | self.log_out() 776 | 777 | exit('\nBye!\n') 778 | 779 | def main_loop(self): 780 | # main loop of operations 781 | 782 | self.refill_bucket() 783 | self.loop_count += 1 784 | # run operations 785 | for operation, enabled in self.enabled.items(): 786 | if enabled: 787 | if self.next_operation[operation] < (datetime.now() - datetime(1970, 1, 1)).total_seconds() \ 788 | and self.max_operation[operation] > self.day_counters[operation] \ 789 | and self.max_operation['all'] > self.day_counters['all']: 790 | # if unfollow, pick the user that was followed the soonest 791 | if operation == 'unfollow': 792 | if self.bucket['explore']['unfollow']: 793 | self.next_operation[operation] = min( 794 | i[1] for i in self.bucket['explore']['unfollow']) 795 | # add to counter and create delay 796 | self.next_operation[operation] += self.delays[operation][self.day_counters[operation]] 797 | self.day_counters['all'] += 1 798 | self.day_counters[operation] += 1 799 | if self.day_counters['unfollow'] == self.max_operation['unfollow']: 800 | self.day_counters['unfollow'] = 0 801 | # normal operation 802 | response = self.insta_operation(operation) 803 | # handle error 804 | if not response[0]: 805 | if response[1].status_code == 400: 806 | self.banned_sleep() 807 | else: 808 | self.banned['400'] = 0 809 | # add sleep time 810 | minim = min( 811 | value for key, 812 | value in self.next_operation.items() if self.enabled[key]) 813 | sleep_time = minim - \ 814 | (datetime.now() - datetime(1970, 1, 1)).total_seconds() 815 | if sleep_time > 0: 816 | print '\n\t--- Delaying %s seconds ---' % (sleep_time) 817 | sleep(sleep_time) 818 | 819 | if not internet_connection(): 820 | raise ConnectionError 821 | # in case there's an error and it keeps looping 822 | if self.loop_count > 100: 823 | print '\tDelaying 60 seconds' 824 | sleep(60) 825 | 826 | def refill_bucket(self): 827 | # refill bucket with media and user ids 828 | 829 | if self.logged_in: 830 | tag = choice(self.cache['tags']) 831 | if (len(self.bucket['explore']['like']) < 5 and self.enabled['like']) or ( 832 | len(self.bucket['explore']['follow']) < 5 and self.enabled['follow']): 833 | self.logger.log( 834 | '\nRefilling bucket - looking for "%s" posts: \,' % 835 | (tag)) 836 | bucket_len = len(self.bucket['explore']['like']) 837 | # refill the bucket 838 | try: 839 | self.bucket = refill( 840 | self.profile.profile['user']['user_id'], 841 | media_by_tag( 842 | self.browser, 843 | self.insta_urls['explore'], 844 | self.insta_urls['media'], 845 | tag, 846 | self.params['media_max_likes'], 847 | self.params['media_min_likes']), 848 | self.bucket, 849 | self.cache['friends'], 850 | self.cache['tags_to_avoid'], 851 | self.enabled, 852 | 'explore') 853 | 854 | except Exception as e: 855 | tag = choice(self.cache['tags']) 856 | self.logger.log( 857 | '\nError: %s. Trying again with %s: \,' % 858 | (e, tag)) 859 | self.bucket = refill( 860 | self.profile.profile['user']['user_id'], 861 | media_by_tag( 862 | self.browser, 863 | self.insta_urls['explore'], 864 | self.insta_urls['media'], 865 | tag, 866 | self.params['media_max_likes'], 867 | self.params['media_min_likes']), 868 | self.bucket, 869 | self.cache['friends'], 870 | self.cache['tags_to_avoid'], 871 | self.enabled, 872 | 'explore') 873 | 874 | self.logger.log('found %s new.' % 875 | (len(self.bucket['explore']['like']) - bucket_len)) 876 | # refill if the bucket is low 877 | if len(self.bucket['feed']['like']) < 5: 878 | if (datetime.now() - datetime(1970, 1, 1) 879 | ).total_seconds() > self.next_check_feed: 880 | self.next_check_feed += 5 * 60 881 | self.logger.log( 882 | "\nScrolling through feed for friend's posts: \,") 883 | # new feed posts 884 | bucket_feed_len = len(self.bucket['feed']['like']) 885 | feed_data = news_feed_media( 886 | self.browser, 887 | self.insta_urls['domain'], 888 | self.profile.profile['user']['user_id']) 889 | self.bucket = refill( 890 | self.profile.profile['user']['user_id'], 891 | feed_data, 892 | self.bucket, 893 | self.cache['friends'], 894 | self.cache['tags_to_avoid'], 895 | self.enabled, 896 | 'feed') 897 | self.logger.log( 898 | 'found %s, %s from friends. \n' % 899 | (len(feed_data), len( 900 | self.bucket['feed']['like']) - bucket_feed_len)) 901 | # organise this data please 902 | self.organise_profile(feed_data) 903 | # give it some sleep time 904 | minim = min( 905 | value for key, 906 | value in self.next_operation.items() if self.enabled[key]) 907 | sleep_time = minim - \ 908 | (datetime.now() - datetime(1970, 1, 1)).total_seconds() 909 | if sleep_time > 0: 910 | print '\n\t--- Delaying %s seconds ---' % (sleep_time) 911 | sleep(sleep_time) 912 | return 913 | 914 | self.logger.log("\nYou're not logged in!\n") 915 | raise ConnectionError 916 | 917 | def insta_operation(self, op): 918 | # runs the instagram operation 919 | 920 | self.loop_count = 0 921 | if op == 'like_feed': 922 | return self.like_feed() 923 | elif op == 'like': 924 | return self.like_media(False) 925 | elif op == 'comment': 926 | return self.comment_media(False) 927 | elif op == 'follow': 928 | return self.follow_user() 929 | elif op == 'unfollow': 930 | return self.unfollow_user() 931 | # there are no other options 932 | 933 | def follow_user(self): 934 | # follows random user 935 | 936 | user_id = choice(list(self.bucket['explore']['follow'])) 937 | username = self.bucket['user_ids'][user_id] 938 | if not username: 939 | username = user_id 940 | self.logger.log('\n * Trying to follow "%s": \,' % (username)) 941 | if not any( 942 | user_id == user for user in self.profile.master_unfollow_list): 943 | return self.explore_operation('follow', user_id) 944 | self.logger.log('\nuser has already been unfollowed before!') 945 | return [True] 946 | 947 | def unfollow_user(self): 948 | # unfollows user 949 | 950 | for user in self.bucket['explore']['unfollow']: 951 | if (datetime.now() - datetime(1970, 1, 1) 952 | ).total_seconds() > user[1]: 953 | user_id = user[0] 954 | username = self.bucket['user_ids'][user[0]] 955 | if username: 956 | check = self.user_following_back(username) 957 | if check[0]: 958 | self.logger.log( 959 | '\n * "%s" followed you back' % 960 | (username)) 961 | self.bucket['explore']['unfollow'].remove(user) 962 | self.profile.add_follower(check[1]) 963 | return [True] 964 | else: 965 | username = user_id 966 | self.logger.log( 967 | '\n * Trying to unfollow "%s": \,' % 968 | (username)) 969 | self.profile.remove_follow(user_id) 970 | return self.explore_operation('unfollow', user) 971 | return [True] 972 | 973 | def user_following_back(self, username): 974 | # check if the user is following back 975 | 976 | user_data = check_user(self.browser, self.insta_urls['user'], username) 977 | if user_data['follower']: 978 | return [True, user_data['data']] 979 | return [False] 980 | 981 | def like_media(self, media): 982 | # likes media, or does it? 983 | # yeah, it does... 984 | 985 | media_id = media if media else choice( 986 | list(self.bucket['explore']['like'])) 987 | self.logger.log( 988 | '\n * Trying to like "%s": \,' % 989 | (self.insta_urls['media'] % 990 | (self.bucket['codes'][media_id]))) 991 | try: 992 | response = self.explore_operation('like', media_id) 993 | del self.bucket['codes'][media_id] 994 | if self.enabled['comment'] and (datetime.now() - datetime(1970, 1, 1)).total_seconds( 995 | ) > self.next_operation['comment'] and self.max_operation['comment'] > self.day_counters['comment']: 996 | self.next_operation['comment'] = self.next_operation['comment'] + \ 997 | self.delays['comment'][self.day_counters['comment']] 998 | self.day_counters['all'] += 1 999 | self.day_counters['comment'] += 1 1000 | self.comment_media(media_id) 1001 | else: 1002 | self.bucket['explore']['comment'].discard(media_id) 1003 | return response 1004 | except Exception as e: 1005 | raise Exception(' while liking - debugging needed: %s' % (e)) 1006 | return [True] 1007 | 1008 | def comment_media(self, media): 1009 | # comments media 1010 | 1011 | media_id = media if media else choice( 1012 | list(self.bucket['explore']['like'])) 1013 | self.logger.log( 1014 | '\n * Trying to comment "%s": \,' % 1015 | (self.insta_urls['media'] % 1016 | (self.bucket['codes'][media_id]))) 1017 | try: 1018 | response = self.explore_operation('comment', media_id) 1019 | if self.enabled['like'] and self.max_operation['like'] > self.day_counters['like']: 1020 | self.next_operation['like'] = self.next_operation['like'] + \ 1021 | self.delays['like'][self.day_counters['like']] 1022 | self.day_counters['all'] += 1 1023 | self.day_counters['like'] += 1 1024 | self.like_media(media_id) 1025 | else: 1026 | self.bucket['explore']['like'].discard(media_id) 1027 | return response 1028 | except Exception as e: 1029 | raise Exception('\nwhile commenting - debugging needed: %s' % (e)) 1030 | return [True] 1031 | 1032 | def explore_operation(self, operation, identifier): 1033 | # sends requests post and checks response 1034 | 1035 | self.clean_up_loop_count = 0 1036 | result = [True] 1037 | try: 1038 | comment = False 1039 | # comment 1040 | if operation == 'comment': 1041 | commentString = generate_comment(self.params['comments_list']) 1042 | self.logger.log('\n\n\tComment: "' + commentString + '" \n') 1043 | comment = emojize((commentString), use_aliases=True) 1044 | # unfollow 1045 | if operation == 'unfollow': 1046 | self.profile.master_unfollow_list.append(identifier[0]) 1047 | response = post_data( 1048 | self.browser, 1049 | self.insta_urls[operation], 1050 | identifier[0], 1051 | comment) 1052 | else: 1053 | response = post_data( 1054 | self.browser, 1055 | self.insta_urls[operation], 1056 | identifier, 1057 | comment) 1058 | # check if it worked 1059 | if response['response'].ok: 1060 | self.total_counters[operation] += 1 1061 | self.logger.log('Success') 1062 | # error 1063 | elif response['response'].status_code == 400: 1064 | self.logger.log('Error 400 - failed') 1065 | self.banned['400'] += 1 1066 | result = [False, response['response']] 1067 | else: 1068 | self.logger.log( 1069 | 'Error %s - failed' % 1070 | (response['response'].status_code)) 1071 | # remove from bucket 1072 | if not operation == 'unfollow': 1073 | self.bucket['explore'][operation].discard(identifier) 1074 | self.bucket['explore']['done'][operation].add(identifier) 1075 | else: 1076 | self.bucket['explore'][operation].remove(identifier) 1077 | self.bucket['explore']['done'][operation].append(identifier) 1078 | if operation == 'follow': 1079 | # add with time to unfollow 1080 | self.bucket['explore']['unfollow'].append([identifier, (datetime.now( 1081 | ) - datetime(1970, 1, 1)).total_seconds() + self.params['follow_time']]) 1082 | except Exception as e: 1083 | raise Exception( 1084 | '\nin explore operation: %s - debugging needed: %s' % 1085 | (e, traceback.format_exc())) 1086 | return result 1087 | 1088 | def like_feed(self): 1089 | # like news feed 1090 | 1091 | post_ids = self.bucket['feed']['like'] 1092 | if len(post_ids) == 0: 1093 | self.logger.log('\nNo new posts from your friends to like') 1094 | return [True] 1095 | elif len(post_ids) > 5: 1096 | until = 5 1097 | else: 1098 | until = len(post_ids) 1099 | self.logger.log('\nLiking news feed...') 1100 | ban_counter = 0 1101 | for i in range(0, until): 1102 | try: 1103 | post = self.bucket['feed']['like'][0] 1104 | liked = post_data( 1105 | self.browser, 1106 | self.insta_urls['like'], 1107 | post[0], 1108 | False) 1109 | if liked['response'].ok: 1110 | self.logger.log(" * Liked %s's post %s" % 1111 | (post[1], self.insta_urls['media'] % 1112 | (self.bucket['codes'][post[0]]))) 1113 | elif liked['response'].status_code == 400: 1114 | ban_counter += 1 1115 | if ban_counter > 2: 1116 | self.bucket['feed']['done'].append(post) 1117 | self.bucket['feed']['like'].remove(post) 1118 | return [False, liked['response']] 1119 | else: 1120 | raise Exception( 1121 | "%s could not like %s's media" % 1122 | (liked['response'].status_code, post_ids[i][2])) 1123 | except Exception as e: 1124 | self.logger.log(' * Error: %s' % (e)) 1125 | self.bucket['feed']['done'].append(post) 1126 | self.bucket['feed']['like'].remove(post) 1127 | sleep(10 * aleatory()) 1128 | self.total_counters['like_feed'] += 1 1129 | return [True] 1130 | 1131 | def organise_profile(self, data): 1132 | # checks news feed for users and adds them to profile object 1133 | 1134 | self.logger.log( 1135 | 'Checking the profiles of those you follow:\nChecked users:\,') 1136 | user_names = list(set( 1137 | [post['username'] for post in data if post['username'] not in self.users_checked_today])) 1138 | if len(user_names) > 1: 1139 | users_data = [ 1140 | check_user( 1141 | self.browser, 1142 | self.insta_urls['user'], 1143 | user) for user in user_names] 1144 | for user in users_data[:-1]: 1145 | self.logger.log('%s, \,' % (user['data']['username'])) 1146 | self.logger.log('%s.' % (users_data[-1]['data']['username'])) 1147 | for user in users_data: 1148 | self.users_checked_today.append(user['data']['username']) 1149 | if user['follower'] and not user['fake']: 1150 | # add user to your followers list in profile 1151 | if not any(user['data']['user_id'] == node['user_id'] 1152 | for node in self.profile.profile['followers']): 1153 | self.profile.add_follower(user['data']) 1154 | else: 1155 | self.profile.update_user(user['data'], 'followers') 1156 | # remove from bucket if they are going to be unfollowed 1157 | if any(user['data']['user_id'] == user_id[0] 1158 | for user_id in self.bucket['explore']['unfollow']): 1159 | for i, user_id in enumerate( 1160 | self.bucket['explore']['unfollow']): 1161 | if user['data']['user_id'] == user_id[0]: 1162 | self.logger.log( 1163 | ' - %s followed you back - \,' % 1164 | (user['data']['username'])) 1165 | del self.bucket['explore']['unfollow'][i] 1166 | self.logger.log('removed from unfollow list') 1167 | break 1168 | # if user has been unfollowed, follow back 1169 | if any(user['data']['user_id'] == user_id[0] 1170 | for user_id in self.bucket['explore']['done']['unfollow']): 1171 | for i, user_id in enumerate( 1172 | self.bucket['explore']['done']['unfollow']): 1173 | if user['data']['user_id'] == user_id[0]: 1174 | self.logger.log( 1175 | ' - %s followed you back but was unfollowed - \,' % 1176 | (user['data']['username'])) 1177 | del self.bucket['explore']['done']['unfollow'][i] 1178 | try: 1179 | response = post_data( 1180 | self.browser, self.insta_urls['follow'], user['data']['user_id'], False) 1181 | if response['response'].ok: 1182 | self.logger.log('followed back!') 1183 | else: 1184 | self.logger.log( 1185 | 'Error %s while following back' % 1186 | (response['response'].status_code)) 1187 | except BaseException: 1188 | self.logger.log( 1189 | 'Error while following back') 1190 | break 1191 | # unfollow if user is fake 1192 | elif user['fake']: 1193 | self.logger.log( 1194 | ' - %s is a fake account - \,' % 1195 | (user['data']['username'])) 1196 | for i, user_id in enumerate( 1197 | self.bucket['explore']['unfollow']): 1198 | if user['data']['user_id'] == user_id[0]: 1199 | response = self.explore_operation( 1200 | 'unfollow', user_id) 1201 | if response[0]: 1202 | self.logger.log('unfollowed!') 1203 | 1204 | else: 1205 | self.logger.log( 1206 | '\nError %s while unfollowing' % (response[1])) 1207 | break 1208 | # add user to follow list in profile 1209 | if not any(user['data']['user_id'] == node['user_id'] 1210 | for node in self.profile.profile['follows']): 1211 | self.logger.log( 1212 | ' - %s will be added to follow list in profile' % 1213 | (user['data']['username'])) 1214 | self.profile.add_follow(user['data']) 1215 | 1216 | else: 1217 | self.profile.update_user(user['data'], 'follows') 1218 | else: 1219 | self.logger.log('all users have already been checked for today.') 1220 | -------------------------------------------------------------------------------- /src/instafunctions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ___ InstaBot V 1.2.0 by nickpettican ___ 5 | # ___ Automate your Instagram activity ___ 6 | 7 | # ___ Copyright 2017 Nicolas Pettican ___ 8 | 9 | # ___ This software is licensed under the Apache 2 ___ 10 | # ___ license. You may not use this file except in ___ 11 | # ___ compliance with the License. ___ 12 | 13 | # ___ DISCLAIMER ___ 14 | 15 | # ___ InstaBot was created for educational purposes and ___ 16 | # ___ the end-user assumes sole responsibility of any ___ 17 | # ___ consequences of it's misuse. Please be advised of ___ 18 | # ___ Instagram's monitoring, 1000 likes a day is the ___ 19 | # ___ maximum you will get or you risk a temporary ban. ___ 20 | # ___ Choose your tags wisely or you may risk liking ___ 21 | # ___ and commenting on undesirable media or spam. ___ 22 | 23 | from lxml.etree import HTML 24 | from json import loads as toJSON 25 | from random import random, choice 26 | from time import sleep 27 | import itertools 28 | 29 | # === INSTAGRAM FUNCTIONS === 30 | 31 | 32 | def refill(user_id, data, bucket, friends, tags_to_avoid, enabled, mode): 33 | # refill the bucket - returns dict with modified bucket 34 | 35 | if mode == 'feed' and data: 36 | for _post in data: 37 | bucket['codes'][_post['media_id']] = _post['url_code'] 38 | # add feed posts to bucket 39 | if enabled['like_feed']: 40 | bucket['feed']['like'].extend([[_post['media_id'], _post['username']] for _post in data 41 | if any(n.lower() == _post['username'].lower() for n in friends) 42 | if not user_id == _post['user_id'] 43 | if not any(n == _post['media_id'] for n in bucket['feed']['media_ids']) 44 | if not any(n[0] == _post['media_id'] for n in bucket['feed']['done']) 45 | if not any(n in _post['caption'] for n in tags_to_avoid)]) 46 | bucket['feed']['media_ids'].extend([_post['media_id'] for _post in data]) 47 | # add explored posts to bucket 48 | if mode == 'explore' and data['posts']: 49 | for _post in data['posts']: 50 | bucket['codes'][_post['media_id']] = _post['url_code'] 51 | bucket['user_ids'][_post['user_id']] = _post['username'] 52 | # just to get the keys right 53 | tmp = [['like', 'media_id'], [ 54 | 'follow', 'user_id'], ['comment', 'media_id']] 55 | params = [param for param in tmp if enabled[param[0]]] 56 | # check if posts obbey the rules 57 | for param in params: 58 | if param: 59 | bucket[mode][param[0]].update([_post[param[1]] for _post in data['posts'] if not user_id == _post['user_id'] 60 | if not any(_post[param[1]] in n for n in bucket[mode]['done'][param[0]]) 61 | if not any(n in _post['caption'] for n in tags_to_avoid)]) 62 | elif mode == 'explore' and not data['posts']: 63 | raise Exception('No posts found') 64 | return bucket 65 | 66 | 67 | def get_node_post_data( 68 | node, 69 | media_min_likes, 70 | media_max_likes, 71 | browser, 72 | media_url): 73 | try: 74 | if media_min_likes <= node['edge_liked_by']['count'] <= media_max_likes and not node['comments_disabled']: 75 | return dict( 76 | user_id=node['owner']['id'], 77 | username=return_username( 78 | browser, 79 | media_url, 80 | node['shortcode']), 81 | likes=node['edge_liked_by']['count'], 82 | caption=node['edge_media_to_caption']['edges'][0]['node']['text'], 83 | media_id=node['id'], 84 | url_code=node['shortcode']) 85 | except BaseException: 86 | return None 87 | 88 | 89 | def media_by_tag( 90 | browser, 91 | tag_url, 92 | media_url, 93 | tag, 94 | media_max_likes, 95 | media_min_likes): 96 | # returns list with the 14 'nodes' (posts) for the tag page 97 | 98 | result = {'posts': False, 'tag': tag} 99 | try: 100 | explore_site = browser.get(tag_url % (tag)) 101 | tree = HTML(explore_site.text) 102 | data = return_sharedData(tree) 103 | if data: 104 | nodes = data['entry_data']['TagPage'][0]['graphql']['hashtag']['edge_hashtag_to_media']['edges'] 105 | posts = [get_node_post_data( 106 | n['node'], media_min_likes, media_max_likes, browser, media_url) for n in nodes] 107 | result['posts'] = [p for p in posts if p is not None] 108 | except Exception as e: 109 | print '\nError in obtaining media by tag: %s' % (e) 110 | return result 111 | 112 | 113 | def return_sharedData(tree): 114 | # returns the sharedData JSON object 115 | 116 | identifier = 'window._sharedData = ' 117 | for a in tree.findall('.//script'): 118 | try: 119 | if a.text.startswith(identifier): 120 | try: 121 | return toJSON(a.text.replace(identifier, '')[:-1]) 122 | except Exception as e: 123 | print '\nError returning sharedData JSON: %s' % (e) 124 | except Exception as e: 125 | continue 126 | return None 127 | 128 | 129 | def return_username(browser, media_url, code): 130 | # returns the username from an image 131 | 132 | try: 133 | media_page = browser.get(media_url % (code)) 134 | tree = HTML(media_page.text) 135 | data = return_sharedData(tree) 136 | if data is not None: 137 | return data['entry_data']['PostPage'][0]['graphql']['shortcode_media']['owner']['username'] 138 | except Exception as e: 139 | print '\nError obtaining username: %s' % (e) 140 | return None 141 | 142 | 143 | def get_feed_node_post_data(node, user_id): 144 | 145 | try: 146 | if not node['owner']['id'] == user_id and not node['viewer_has_liked']: 147 | return dict( 148 | user_id=node['owner']['id'], 149 | username=node['owner']['username'], 150 | likes=node['edge_media_preview_like']['count'], 151 | caption=node['edge_media_to_caption']['edges'][0]['node']['text'], 152 | media_id=node['id'], 153 | url_code=node['shortcode']) 154 | except BaseException: 155 | return None 156 | 157 | 158 | def return_feedData(tree): 159 | 160 | identifier = "window.__additionalDataLoaded('feed'," 161 | for a in tree.findall('.//script'): 162 | try: 163 | if a.text.startswith(identifier): 164 | try: 165 | return toJSON(a.text.replace(identifier, '')[:-2]) 166 | except Exception as e: 167 | print '\nError returning sharedData JSON: %s' % (e) 168 | except Exception as e: 169 | continue 170 | return None 171 | 172 | 173 | def news_feed_media(browser, url, user_id): 174 | # returns the latest media on the news feed 175 | 176 | try: 177 | news_feed = browser.get(url) 178 | tree = HTML(news_feed.text) 179 | data = return_feedData(tree) 180 | if data: 181 | nodes = data['user']['edge_web_feed_timeline']['edges'] 182 | if nodes: 183 | posts = [get_feed_node_post_data(n['node'], user_id) for n in nodes] 184 | return [p for p in posts if p is not None] 185 | except Exception as e: 186 | print '\nError getting new feed data: %s.' % (e) 187 | return [] 188 | 189 | 190 | def check_user(browser, url, user): 191 | # checks the users profile to assess if it's fake 192 | 193 | result = { 194 | 'fake': False, 195 | 'active': False, 196 | 'follower': False, 197 | 'data': { 198 | 'username': '', 199 | 'user_id': '', 200 | 'media': '', 201 | 'follows': 0, 202 | 'followers': 0}} 203 | try: 204 | site = browser.get(url % (user)) 205 | tree = HTML(site.text) 206 | data = return_sharedData(tree) 207 | user_data = data['entry_data']['ProfilePage'][0]['graphql']['user'] 208 | if user_data: 209 | if user_data['follows_viewer'] or user_data['has_requested_viewer']: 210 | result['follower'] = True 211 | if user_data['edge_followed_by']['count'] > 0: 212 | try: 213 | if user_data['edge_follow']['count'] / user_data['edge_followed_by']['count'] > 2 \ 214 | and user_data['edge_followed_by']['count'] < 10: 215 | result['fake'] = True 216 | except ZeroDivisionError: 217 | result['fake'] = True 218 | try: 219 | if user_data['edge_follow']['count'] / user_data['edge_owner_to_timeline_media']['count'] < 10 \ 220 | and user_data['edge_followed_by']['count'] / user_data['edge_owner_to_timeline_media']['count'] < 10: 221 | result['active'] = True 222 | except ZeroDivisionError: 223 | pass 224 | else: 225 | result['fake'] = True 226 | result['data'] = { 227 | 'username': user_data['username'], 228 | 'user_id': user_data['id'], 229 | 'media': user_data['edge_owner_to_timeline_media']['count'], 230 | 'follows': user_data['edge_follow']['count'], 231 | 'followers': user_data['edge_followed_by']['count'] 232 | } 233 | 234 | except Exception as e: 235 | print '\nError checking user: %s.' % (e) 236 | 237 | sleep(5 * random()) 238 | return result 239 | 240 | 241 | def generate_comment(comments_list): 242 | # returns a randomly generated generic comment 243 | 244 | batch = list(itertools.product(*comments_list)) 245 | return ' '.join(choice(batch)) 246 | 247 | 248 | def post_data(browser, url, identifier, comment): 249 | # sends post request 250 | 251 | result = {'response': False, 'identifier': identifier} 252 | 253 | try: 254 | if comment: 255 | response = browser.post( 256 | url % 257 | (identifier), data={ 258 | 'comment_text': comment}) 259 | else: 260 | response = browser.post(url % (identifier)) 261 | result['response'] = response 262 | except BaseException: 263 | pass 264 | return result 265 | -------------------------------------------------------------------------------- /src/instaprofile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ___ InstaBot V 1.2.0 by nickpettican ___ 5 | # ___ Automate your Instagram activity ___ 6 | 7 | # ___ Copyright 2017 Nicolas Pettican ___ 8 | 9 | # ___ This software is licensed under the Apache 2 ___ 10 | # ___ license. You may not use this file except in ___ 11 | # ___ compliance with the License. ___ 12 | 13 | # ___ DISCLAIMER ___ 14 | 15 | # ___ InstaBot was created for educational purposes and ___ 16 | # ___ the end-user assumes sole responsibility of any ___ 17 | # ___ consequences of it's misuse. Please be advised of ___ 18 | # ___ Instagram's monitoring, 1000 likes a day is the ___ 19 | # ___ maximum you will get or you risk a temporary ban. ___ 20 | # ___ Choose your tags wisely or you may risk liking ___ 21 | # ___ and commenting on undesirable media or spam. ___ 22 | 23 | import json 24 | import os 25 | import csv 26 | 27 | # === Instagram Profile === 28 | 29 | def profile_template(): 30 | # returns template of profile object 31 | 32 | return { 33 | 'user': { 34 | 'username': '', 35 | 'user_id': '', 36 | 'media': 0, 37 | 'follows': 0, 38 | 'followers': 0 39 | }, 40 | 'followers': [], 41 | 'follows': [] 42 | } 43 | 44 | class InstaProfile: 45 | # This object will contain the followers and followed users that 46 | # InstaBot picks up along the way in order to operate efficiently 47 | 48 | def __init__(self, path='cache/', params=''): 49 | 50 | self.prof_path = path + params['username'] + '.json' 51 | self.unf_list_path = path + 'unfollowlist.csv' 52 | self.params = params 53 | self.import_unfollow_list() 54 | 55 | def import_unfollow_list(self): 56 | # imports the master unfollow list 57 | 58 | self.master_unfollow_list = [ 59 | line for line in open( 60 | self.unf_list_path, 'r')] 61 | 62 | def save_unfollow_list(self): 63 | # saves the master unfollow list 64 | 65 | with open(self.unf_list_path, 'wb') as outfile: 66 | w = csv.writer(outfile) 67 | w.writerows([[user] for user in self.master_unfollow_list]) 68 | 69 | def import_profile(self, user): 70 | # imports the profile file with all the data 71 | 72 | if os.path.isfile(self.prof_path): 73 | with open(self.prof_path) as data_file: 74 | self.profile = json.load(data_file) 75 | 76 | else: 77 | self.profile = profile_template() 78 | self.populate_profile(user) 79 | 80 | def save_profile(self): 81 | # saves the profile to file 82 | 83 | with open(self.prof_path, 'wb') as outfile: 84 | json.dump(self.profile, outfile) 85 | 86 | def populate_profile(self, user): 87 | # builds the profile if it's the first time 88 | 89 | self.profile['user'] = { 90 | 'username': self.params['username'], 91 | 'user_id': user['data']['user_id'], 92 | 'media': user['data']['media'], 93 | 'follows': user['data']['follows'], 94 | 'followers': user['data']['followers'] 95 | } 96 | 97 | def add_follower(self, data): 98 | # adds user to the follower list 99 | 100 | self.profile['followers'].append(data) 101 | self.save_profile() 102 | 103 | def add_follow(self, data): 104 | # adds user to the follows list 105 | 106 | self.profile['follows'].append(data) 107 | self.save_profile() 108 | 109 | def remove_follow(self, user_id): 110 | # removes follow from follows list 111 | 112 | for i, node in enumerate(self.profile['follows']): 113 | if node['user_id'] == user_id: 114 | del self.profile['follows'][i] 115 | break 116 | 117 | def update_user(self, data, op): 118 | # updates current follower data 119 | 120 | for i, node in enumerate(self.profile[op]): 121 | if node['user_id'] == data['user_id']: 122 | self.profile[op][i] = data 123 | break 124 | 125 | self.save_profile() 126 | -------------------------------------------------------------------------------- /src/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ___ InstaBot V 1.2.0 by nickpettican ___ 5 | # ___ Automate your Instagram activity ___ 6 | 7 | # ___ Copyright 2017 Nicolas Pettican ___ 8 | 9 | # ___ This software is licensed under the Apache 2 ___ 10 | # ___ license. You may not use this file except in ___ 11 | # ___ compliance with the License. ___ 12 | 13 | # ___ DISCLAIMER ___ 14 | 15 | # ___ InstaBot was created for educational purposes and ___ 16 | # ___ the end-user assumes sole responsibility of any ___ 17 | # ___ consequences of it's misuse. Please be advised of ___ 18 | # ___ Instagram's monitoring, 1000 likes a day is the ___ 19 | # ___ maximum you will get or you risk a temporary ban. ___ 20 | # ___ Choose your tags wisely or you may risk liking ___ 21 | # ___ and commenting on undesirable media or spam. ___ 22 | 23 | import datetime 24 | from csv import writer 25 | from os import makedirs, path 26 | 27 | 28 | class Logger: 29 | 30 | def __init__(self, header, backupUnfollows, bucketUnfollow): 31 | # initialise the logger variables 32 | 33 | self.path = 'cache/log/' 34 | self.log_temp = '' 35 | self.new_line = True 36 | self.backupUnfollows = backupUnfollows 37 | self.bucketUnfollow = bucketUnfollow 38 | self.today = datetime.datetime.today().strftime('%d-%m-%Y') 39 | 40 | if not path.isdir(self.path): 41 | makedirs(self.path) 42 | 43 | self.init_log_name() 44 | 45 | print header 46 | 47 | def init_log_name(self): 48 | # change log file name 49 | 50 | self.today = datetime.datetime.today().strftime('%d-%m-%Y') 51 | self.log_main = [] 52 | self.log_file = self.path + 'activity_log_' + self.today + '.txt' 53 | 54 | def log(self, string): 55 | # write to log file 56 | 57 | try: 58 | if self.today != datetime.datetime.today().strftime('%d-%m-%Y'): 59 | self.init_log_name() 60 | 61 | self.backup() 62 | 63 | if string.endswith('\,'): 64 | log = string.replace('\,', '') 65 | 66 | if self.new_line or log.startswith('\n'): 67 | if log.startswith('\n'): 68 | log = log.replace('\n', '') 69 | print '\n', 70 | log = datetime.datetime.today().strftime( 71 | '[ %Y-%m-%d %H:%m:%S ] ') + log 72 | print log, 73 | 74 | self.new_line = False 75 | if self.log_temp: 76 | try: 77 | self.log_temp += log 78 | except BaseException: 79 | pass 80 | else: 81 | self.log_temp = log 82 | return 83 | 84 | log = string 85 | print log 86 | if self.log_temp: 87 | string = self.log_temp + string 88 | self.log_temp = '' 89 | self.new_line = True 90 | 91 | self.log_main.append([string.strip()]) 92 | 93 | except Exception as e: 94 | print 'Error while logging: %s' % (e) 95 | 96 | def backup(self): 97 | # backs up the log 98 | 99 | self.backupUnfollows() 100 | 101 | try: 102 | with open(self.log_file, 'w') as log: 103 | for line in self.log_main: 104 | log.writelines(line if isinstance(line, list) else [line]) 105 | log.write('\n') 106 | 107 | except Exception as e: 108 | print 'Error backing up: %s' % (e) 109 | 110 | try: 111 | with open('cache/followlist.csv', 'wb') as backup: 112 | w = writer(backup) 113 | w.writerows(self.bucketUnfollow) 114 | except Exception as e: 115 | print 'Error while saving backup follow list: %s' % (e) 116 | -------------------------------------------------------------------------------- /src/miscellaneous.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ___ InstaBot V 1.2.0 by nickpettican ___ 5 | # ___ Automate your Instagram activity ___ 6 | 7 | # ___ Copyright 2017 Nicolas Pettican ___ 8 | 9 | # ___ This software is licensed under the Apache 2 ___ 10 | # ___ license. You may not use this file except in ___ 11 | # ___ compliance with the License. ___ 12 | 13 | # ___ DISCLAIMER ___ 14 | 15 | # ___ InstaBot was created for educational purposes and ___ 16 | # ___ the end-user assumes sole responsibility of any ___ 17 | # ___ consequences of it's misuse. Please be advised of ___ 18 | # ___ Instagram's monitoring, 1000 likes a day is the ___ 19 | # ___ maximum you will get or you risk a temporary ban. ___ 20 | # ___ Choose your tags wisely or you may risk liking ___ 21 | # ___ and commenting on undesirable media or spam. ___ 22 | 23 | import socket 24 | import numpy as np 25 | 26 | 27 | def internet_connection(host='8.8.8.8', port=53, timeout=3): 28 | # check for internet connection 29 | 30 | try: 31 | socket.setdefaulttimeout(timeout) 32 | socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) 33 | return True 34 | except BaseException: 35 | return False 36 | 37 | 38 | def return_random_sequence(data, time_in_day): 39 | # generates random delay values that add up to time in day 40 | 41 | if data == 0: 42 | return [0] 43 | random_values = np.random.random(data) 44 | random_values /= random_values.sum() 45 | return [int(i * time_in_day) for i in random_values] 46 | --------------------------------------------------------------------------------