├── .github └── FUNDING.yml ├── .gitignore ├── DmBot.py ├── LICENSE.md ├── README.md ├── __init__.py ├── config └── config.yaml.example ├── data └── .gitignore ├── lib ├── __init__.py └── instadm.py └── requirements.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [hbollon] 4 | ko_fi: hugobollon 5 | custom: ['paypal.me/hugobollon'] 6 | -------------------------------------------------------------------------------- /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | #lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | 126 | # Config file 127 | config/config.yaml -------------------------------------------------------------------------------- /DmBot.py: -------------------------------------------------------------------------------- 1 | from lib.instadm import InstaDM 2 | from time import sleep 3 | from tqdm import tqdm 4 | import os 5 | import confuse 6 | import random 7 | import csv 8 | import time 9 | import datetime 10 | import instaloader 11 | 12 | class ConfuseConfig(confuse.Configuration): 13 | def config_dir(self): 14 | return os.getcwd() + "/config" 15 | 16 | class Config(object): 17 | config = ConfuseConfig('IG_Automation_Bot', __name__) 18 | 19 | def __init__(self): 20 | self.app_name = self.config['app_name'].get(str) 21 | self.username = self.config["account"]["username"].get(str) 22 | self.password = self.config["account"]["password"].get(str) 23 | self.srcAccounts = self.config["users_src"]["src_accounts"].get(list) 24 | self.fetchQuantity = self.config["users_src"]["fetch_quantity"].get(int) 25 | self.autoFollow = self.config["bot"]["auto_follow"].get(bool) 26 | self.autoDm = self.config["bot"]["auto_dm"].get(bool) 27 | self.maxDmQuantity = self.config["bot"]["max_dm_quantity"].get(int) 28 | self.maxFollowQuantity = self.config["bot"]["max_follow_quantity"].get(int) 29 | self.blacklistInteractedUsers = self.config["bot"]["blacklist_interacted_users"].get(bool) 30 | self.headlessBrowser = self.config["bot"]["headless_browser"].get(bool) 31 | self.dmTemplates = self.config["dm_templates"].get(list) 32 | self.greetingTemplate = self.config["greeting"]["template"].get(str) 33 | self.greetingEnabled = self.config["greeting"]["activated"].get(bool) 34 | 35 | self.quotas = Quotas(self.config) 36 | self.scheduler = Scheduler(self.config) 37 | 38 | class Blacklist(object): 39 | def __init__(self): 40 | self.users = [] 41 | if not os.path.exists('data/blacklist.csv'): 42 | os.mknod('data/blacklist.csv') 43 | with open("data/blacklist.csv", 'w') as f: 44 | writer = csv.DictWriter(f, fieldnames=["Username"]) 45 | writer.writeheader() 46 | f.close() 47 | 48 | with open("data/blacklist.csv", 'r') as f: 49 | reader = csv.DictReader(f) 50 | for row in reader: 51 | self.users.append(row) 52 | f.close() 53 | 54 | def addUser(self, u): 55 | with open("data/blacklist.csv", 'a', newline='') as f: 56 | writer = csv.DictWriter(f, fieldnames=["Username"]) 57 | writer.writerow({'Username': u}) 58 | self.users.append({'Username': u}) 59 | f.close() 60 | 61 | def isBlacklisted(self, u): 62 | for user in self.users: 63 | if(user['Username'] == u): 64 | return True 65 | return False 66 | 67 | class Quotas(object): 68 | def __init__(self, config): 69 | self.enabled = config["bot"]["quotas"]["activated"].get(bool) 70 | if self.enabled: 71 | self.dayTime = time.time() 72 | self.totalDmSentDay = 0 73 | self.totalFollowDay = 0 74 | self.dmPerDay = config["bot"]["quotas"]["dm_per_day"].get(int) 75 | self.dmPerHour = config["bot"]["quotas"]["dm_per_hour"].get(int) 76 | self.followPerDay = config["bot"]["quotas"]["follow_per_day"].get(int) 77 | self.followPerHour = config["bot"]["quotas"]["follow_per_hour"].get(int) 78 | self.initTimeQuota() 79 | 80 | def initTimeQuota(self): 81 | self.timeQuota = time.time() 82 | self.dmSent = 0 83 | self.followDone = 0 84 | 85 | def resetDaily(self): 86 | self.dayTime = time.time() 87 | self.totalDmSentDay = 0 88 | self.totalFollowDay = 0 89 | 90 | def checkQuota(self): 91 | if self.dmSent >= self.dmPerHour or self.followDone >= self.followPerHour: 92 | if (time.time() - self.timeQuota) < 3600: 93 | print("Hourly quota reached, sleeping 120 sec...") 94 | sleep(120) 95 | self.checkQuota() 96 | else: 97 | print("Reset hourly quotas!") 98 | self.initTimeQuota() 99 | 100 | if self.totalDmSentDay >= self.dmPerDay or self.totalFollowDay >= self.followPerDay: 101 | if (time.time() - self.dayTime) < 86400: 102 | print("Daily quota reached, sleeping for one hour...") 103 | sleep(3600) 104 | self.checkQuota() 105 | else: 106 | print("Reset daily quotas!") 107 | self.resetDaily() 108 | 109 | def addDm(self): 110 | self.dmSent += 1 111 | self.totalDmSentDay += 1 112 | self.checkQuota() 113 | 114 | def addFollow(self): 115 | self.followDone += 1 116 | self.totalFollowDay += 1 117 | self.checkQuota() 118 | 119 | class Scheduler(object): 120 | def __init__(self, config): 121 | self.enabled = config["bot"]["schedule"]["activated"].get(bool) 122 | if self.enabled: 123 | self.dayTime = time.time() 124 | self.begin = datetime.time(config["bot"]["schedule"]["begin_at"].get(int), 0, 0) 125 | self.end = datetime.time(config["bot"]["schedule"]["end_at"].get(int), 0, 0) 126 | 127 | def isWorkingTime(self, t): 128 | if self.begin <= self.end: 129 | return self.begin <= t <= self.end 130 | else: 131 | return self.begin <= t or t <= self.end 132 | 133 | def checkTime(self): 134 | if(self.isWorkingTime(datetime.datetime.now().time())): 135 | return True 136 | else: 137 | print("Reached end of service. Sleeping for one hour...") 138 | sleep(3600) 139 | self.checkTime() 140 | 141 | 142 | def extractUsersFromCsv(): 143 | csv_file = 'data/users.csv' 144 | usernames = [] 145 | 146 | with open(csv_file, 'r') as f: 147 | reader = csv.DictReader(f) 148 | for row in reader: 149 | usernames.append(row.get('Username')) 150 | return usernames 151 | 152 | def __is_followers_list_valid__(blacklist, users): 153 | blacklistedUsers = 0 154 | for user in users: 155 | if blacklist.isBlacklisted(user): 156 | blacklistedUsers += 1 157 | blacklistedUsers = blacklistedUsers / len(users) 158 | print(f"Blacklisted percentage: {blacklistedUsers*100}%") 159 | if blacklistedUsers > 0.05: return False 160 | return True 161 | 162 | def fetchUsersFromIG(login, password, srcUsernames, blacklist, quantity=1000): 163 | print("Fetching users to dm...") 164 | users = [] 165 | igl = instaloader.Instaloader() 166 | igl.login(login, password) 167 | for user in srcUsernames: 168 | print(f"=> Fetching from '{user}' account...") 169 | profile = instaloader.Profile.from_username(igl.context, user) 170 | with tqdm(total=quantity) as pbar: 171 | for follower in profile.get_followers(): 172 | #print(follower) 173 | users.append(follower.username) 174 | pbar.update(1) 175 | if len(users) == quantity: 176 | if __is_followers_list_valid__(blacklist, users): 177 | break 178 | else: 179 | print("/!\ : Too much blacklisted users in fetched ones! Continue...") 180 | users.clear() 181 | pbar.reset() 182 | 183 | print("Writing fetched data to csv...") 184 | if not os.path.exists('data/users.csv'): 185 | os.mknod('data/users.csv') 186 | with open("data/users.csv", 'w') as f: 187 | writer = csv.DictWriter(f, fieldnames=["Username"]) 188 | writer.writeheader() 189 | for user in users: 190 | #print(user) 191 | writer.writerow({'Username': user}) 192 | f.close() 193 | 194 | print("Users fetching successfully completed!") 195 | 196 | if __name__ == '__main__': 197 | # Get config from config.yml 198 | config = Config() 199 | usersBlacklist = Blacklist() 200 | 201 | # Recover followers list from source accounts 202 | fetchUsersFromIG(config.username, config.password, config.srcAccounts, usersBlacklist, config.fetchQuantity) 203 | followers = extractUsersFromCsv() 204 | 205 | # Auto login 206 | insta = InstaDM(username=config.username, 207 | password=config.password, headless=config.headlessBrowser) 208 | 209 | # Send message 210 | if(config.autoDm): 211 | for user in followers: 212 | if(config.scheduler.checkTime() if config.scheduler.enabled else True): 213 | if usersBlacklist.isBlacklisted(user) == False: 214 | messageSend = insta.sendMessage( 215 | user=user, 216 | message=random.choice(config.dmTemplates), 217 | greeting=config.greetingTemplate if config.greetingEnabled else None) 218 | if messageSend: 219 | print("Dm sent to "+user) 220 | usersBlacklist.addUser(user) 221 | if config.quotas.enabled: 222 | config.quotas.addDm() 223 | sleep(random.randint(30, 45)) 224 | else: 225 | print("Error durring message sending to "+user+". User blacklisted.") 226 | usersBlacklist.addUser(user) 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2020 Hugo Bollon 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Python tool for Instagram direct message automation with scheduler, quota management, user blacklist & autonomous user scrapping. Easily configurable through Yaml config files!
16 | 17 |⚠️ Not maintained anymore in favor of its successor IGopher ⚠️
18 | 19 | --- 20 | 21 | > Disclaimer: This is a research project. I am in no way responsible for the use you made of this tool. In addition, I am not responsible for any sanctions and/or limitations imposed on your account after using this bot. 22 | 23 | --- 24 | 25 | ## Table of Contents 26 | 27 | - [Requirements](#requirements) 28 | - [Features](#features) 29 | - [Installation](#installation) 30 | - [Author](#author) 31 | - [Contributing](#-contributing) 32 | - [License](#-license) 33 | 34 | 35 | --- 36 | 37 | ## Requirements 38 | - [Python 3+](https://www.python.org/downloads/) 39 | - Pip 40 | 41 | ## Features 42 | - DM automation ✨ 43 | 44 | - Easy to configure and set up thanks to Yaml files! ✨ 45 | - Autonomous user recovery from Instagram ✨ 46 | - Blacklist for unique interactions ✨ 47 | - Interaction quotas (hourly / daily) ✨ 48 | - Scheduler to configure the bot's operating hours! ✨ 49 | - Custom greeting with user name ✨ 50 | - Human typing simulation and random delay between interactions ✨ 51 | - Completely customizable ! ✨ 52 | 53 | **Check this [Project](https://github.com/hbollon/IG_Automation_Bot/projects/1) to see all planned features for this tool! Feel free to suggest additional features to implement! 🥳** 54 | 55 | ## Installation 56 | 57 | 1. Clone the project: 58 | ``` 59 | https://github.com/hbollon/IG_Automation_Bot.git 60 | ``` 61 | 62 | 2. Install dependencies: 63 | ``` 64 | cd IG_Automation_Bot 65 | pip install -r requirements.txt 66 | ``` 67 | 68 | 3. Create and setup yaml config file: 69 | - Copy ``` config/config.yaml.example ``` to ``` config/config.yaml ``` 70 | - Fill it with your Instagram credentials and desired settings 71 | 72 | 4. Launch it 🚀 73 | ``` 74 | python DmBot.py 75 | ``` 76 | 77 | **Important**: depending on your system, make sure to use pip3 and python3 instead. 78 | 79 | ## Author 80 | 81 | 👤 **Hugo Bollon** 82 | 83 | * Github: [@hbollon](https://github.com/hbollon) 84 | * LinkedIn: [@Hugo Bollon](https://www.linkedin.com/in/hugobollon/) 85 | * Portfolio: [hugobollon.me](https://www.hugobollon.me) 86 | 87 | ## 🤝 Contributing 88 | 89 | Contributions, issues and feature requests are welcome!