├── .gitignore ├── LICENSE ├── README.md ├── autoinsta ├── cookies │ └── cookies.json ├── profiles │ ├── example.json │ └── example │ │ └── login.pass └── src │ ├── bot.py │ ├── brain.py │ ├── driver.py │ ├── instagram.py │ ├── mail.py │ ├── profile.py │ ├── tiktok.py │ ├── utils.py │ └── video.py ├── dockerfile └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Branny 2 | map.json 3 | *.pass 4 | *.exe 5 | 6 | autoinsta/profiles/* 7 | !autoinsta/profiles/example/ 8 | !autoinsta/profiles/example/*.pass 9 | videos/tikTokTrending.mp4 10 | autoinsta/videos 11 | 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | MANIFEST 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .nox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | *.py,cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | local_settings.py 73 | db.sqlite3 74 | db.sqlite3-journal 75 | 76 | # Flask stuff: 77 | instance/ 78 | .webassets-cache 79 | 80 | # Scrapy stuff: 81 | .scrapy 82 | 83 | # Sphinx documentation 84 | docs/_build/ 85 | 86 | # PyBuilder 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 107 | __pypackages__/ 108 | 109 | # Celery stuff 110 | celerybeat-schedule 111 | celerybeat.pid 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Brannydonowt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autoinsta 2 | ## An Automated Instagram Page Manager 3 | ### Post Trending Tiktoks, Interact with relevant hashtags, Follow relevant pages and grow your instagram automatically. 4 | 5 | This is an in-development automated social media tool, built on Selenium, that I am developing for fun. 6 | 7 | I am currently also using the TikTokAPI for video retrieval. 8 | 9 | For an example of an instagram account that uses the bot, check out [@trendmeisters](https://www.instagram.com/trendmeisters/?hl=en) 10 | 11 | ## Recently Added Features 12 | - [X] Account Creation Process 13 | 14 | *You can define any profile that you'd like in "autoinsta/profiles/map.json" and an account will be generated if one doesn't already exist.* 15 | - [X] Better Profile Management 16 | 17 | *Profiles can now be defined in "autoinsta/profiles/map.json" and the bot will behave according to the details provided in there.* 18 | *Bot will like, comment and follow posts/users with the correct topic (Food, Memes, Funny, etc...)* 19 | *Bot will place relevant hashtags on posts based on profile information* 20 | - [X] Multiple Profile Support 21 | 22 | *Bot will now cycle through all profiles defined in map.json file* 23 | - [X] ffmpeg auto-padded videos 24 | 25 | *Automatically pads any TikTok content to fit the 4:3 Instagram Aspect Ratio* 26 | - [X] Docker Support 27 | 28 | *Added a basic Dockerfile that runs the bot via docker* 29 | 30 | ## Getting Started 31 | 32 | Clone the repo 33 | ``` 34 | git clone https://github.com/Brannydonowt/autoinsta.git 35 | ``` 36 | ### Running Via Docker 37 | I have dockerized the project so that you can simply create a deployable docker image to run. 38 | ``` 39 | docker build -t autoinsta . 40 | ``` 41 | You **MUST** be running in headless mode for Docker to run succesfully. 42 | 43 | I'm new to using Docker, so if there are any problems please raise an issue and I'll jump on it! 44 | 45 | ### Running Locally 46 | install the required pip packages (requirements.txt file included) 47 | ``` 48 | pip install virtualenv (if you don't already have virtualenv installed) 49 | virtualenv venv 50 | source venv/bin/activate 51 | pip install -r requirements.txt 52 | ``` 53 | 54 | TikTokApi uses the Playwright library. Therefore with a fresh install, you need to ensure that you run 55 | *(due to this, you may have some problems running this bot on linux. I recommend using Docker if running locally doesn't work for you.)* 56 | ``` 57 | playwright install 58 | ``` 59 | 60 | Make sure you have a valid web driver installed and placed into your PATH for Selenium. 61 | [Follow this link for more information on how to do that](https://pypi.org/project/selenium/) 62 | 63 | Look at the `autoinsta/profiles` folder. Inside is a `example.json` file. Duplicate this file and rename it to `map.json` 64 | Open `map.json` and fill out the relevant information for your account profile. 65 | 66 | ### Using an existing account 67 | Create a folder in `autoinsta/profiles` named after your account username. For example: `autoinsta/profiles/example/` 68 | 69 | In this folder create a file called `login.pass`, and write your login details inside as like so: 70 | ``` 71 | exampleaccount@example.com 72 | examplepassword123 73 | ``` 74 | path should look like: `autoinsta/profiles/username/login.pass` 75 | 76 | *.pass files are in the .gitignore, so will not be picked up when commiting, you can also remove the requirement for this file by manually entering your login details in the instagram.py.* 77 | *To do this, go to instagram.py. Delete line 107 and enter your details in the place of usr and pwd on line 109* 78 | 79 | ### Creating new accounts 80 | To run the bot and create new accounts automatically, simply define the profile you would like to be created in the `map.json` file. Ensure your username is unique beforehand, as you could run into issues otherwise. 81 | 82 | **Important** - The account will be created using a temporary email address. If you intend to use the account for a long period of time - migrate the account to a new email address manually. Login details are stored in a login.pass file. 83 | 84 | ### Next Steps 85 | 86 | `brain.py` contains example code that shows how to carry out the most basic tasks that the bot is capable of. 87 | 88 | Use the schedule class to set intervals for how often tasks should be carried out. 89 | 90 | You can also adjust and run `brain.py` to schedule posts every x minutes or at certain times throughout the day. 91 | [Follow this link for more information on how to do that](https://pypi.org/project/schedule/) 92 | 93 | Feel free to read through the project code and offer up any suggestions, tweaks or improvements. 94 | 95 | This is my first public, intermediate python project, and so any feedback is always appreciated. 96 | 97 | ### Cookies 98 | 99 | [There are some issues that can occur related to the TikTokAPI](https://github.com/davidteather/TikTok-Api/issues/891). Most of these issues can be circumvented by logging into tik tok manually, scrolling through for a short time and then saving all of your cookies to a json file (at cookies/cookies.json) 100 | 101 | This can be done using a browser extension [like this one.](https://add0n.com/cookie-editor.html) 102 | 103 | #### This is a hobby project, and so further development is not guaranteed, however below are some of the features I'd like to add. 104 | - [ ] Mutliple Instance Deployment 105 | - [ ] Better Error Handling/Checking 106 | - [ ] Automatic Profile Setup (Bio's, Profile Pictures) 107 | - [ ] Profile Analytics (Growth, Likes, posts, etc...) 108 | - [ ] **Better Video Targeting (Find videos based on #)** 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /autoinsta/cookies/cookies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "domain": ".tiktok.com", 4 | "expirationDate": 1689357887, 5 | "hostOnly": false, 6 | "httpOnly": false, 7 | "name": "cookie-consent", 8 | "path": "/", 9 | "sameSite": "no_restriction", 10 | "secure": true, 11 | "session": false, 12 | "storeId": "0", 13 | "value": "{%22ga%22:false%2C%22af%22:false%2C%22fbp%22:false%2C%22lip%22:false%2C%22bing%22:false%2C%22ttads%22:false%2C%22reddit%22:false%2C%22version%22:%22v8%22}", 14 | "origin": "https://www.tiktok.com" 15 | }, 16 | { 17 | "domain": ".tiktok.com", 18 | "expirationDate": 1660845912.087672, 19 | "hostOnly": false, 20 | "httpOnly": false, 21 | "name": "passport_csrf_token", 22 | "path": "/", 23 | "sameSite": "no_restriction", 24 | "secure": true, 25 | "session": false, 26 | "storeId": "0", 27 | "value": "904ca32f49abb1d2008be8de831d868c", 28 | "origin": "https://www.tiktok.com" 29 | }, 30 | { 31 | "domain": ".tiktok.com", 32 | "expirationDate": 1660845912.087713, 33 | "hostOnly": false, 34 | "httpOnly": false, 35 | "name": "passport_csrf_token_default", 36 | "path": "/", 37 | "sameSite": "unspecified", 38 | "secure": false, 39 | "session": false, 40 | "storeId": "0", 41 | "value": "904ca32f49abb1d2008be8de831d868c", 42 | "origin": "https://www.tiktok.com" 43 | }, 44 | { 45 | "domain": ".tiktok.com", 46 | "expirationDate": 1660845913.860889, 47 | "hostOnly": false, 48 | "httpOnly": true, 49 | "name": "cmpl_token", 50 | "path": "/", 51 | "sameSite": "unspecified", 52 | "secure": true, 53 | "session": false, 54 | "storeId": "0", 55 | "value": "AgQQAPOYF-RO0rKR-XDVPt0_-M78EpSdf4cTYMaWiQ", 56 | "origin": "https://www.tiktok.com" 57 | }, 58 | { 59 | "domain": ".tiktok.com", 60 | "expirationDate": 1686765913.860901, 61 | "hostOnly": false, 62 | "httpOnly": true, 63 | "name": "sid_guard", 64 | "path": "/", 65 | "sameSite": "unspecified", 66 | "secure": false, 67 | "session": false, 68 | "storeId": "0", 69 | "value": "27059c86d2646d2668d80c8c6dc19597%7C1655661913%7C5184000%7CThu%2C+18-Aug-2022+18%3A05%3A13+GMT", 70 | "origin": "https://www.tiktok.com" 71 | }, 72 | { 73 | "domain": ".tiktok.com", 74 | "expirationDate": 1660845913.860911, 75 | "hostOnly": false, 76 | "httpOnly": true, 77 | "name": "uid_tt", 78 | "path": "/", 79 | "sameSite": "unspecified", 80 | "secure": false, 81 | "session": false, 82 | "storeId": "0", 83 | "value": "e8fb6b1949ced1140c190780780ed30e94066c932df4fa9323cfc6f3bb28407a", 84 | "origin": "https://www.tiktok.com" 85 | }, 86 | { 87 | "domain": ".tiktok.com", 88 | "expirationDate": 1660845913.860932, 89 | "hostOnly": false, 90 | "httpOnly": true, 91 | "name": "uid_tt_ss", 92 | "path": "/", 93 | "sameSite": "no_restriction", 94 | "secure": true, 95 | "session": false, 96 | "storeId": "0", 97 | "value": "e8fb6b1949ced1140c190780780ed30e94066c932df4fa9323cfc6f3bb28407a", 98 | "origin": "https://www.tiktok.com" 99 | }, 100 | { 101 | "domain": ".tiktok.com", 102 | "expirationDate": 1660845913.860957, 103 | "hostOnly": false, 104 | "httpOnly": true, 105 | "name": "sid_tt", 106 | "path": "/", 107 | "sameSite": "unspecified", 108 | "secure": false, 109 | "session": false, 110 | "storeId": "0", 111 | "value": "27059c86d2646d2668d80c8c6dc19597", 112 | "origin": "https://www.tiktok.com" 113 | }, 114 | { 115 | "domain": ".tiktok.com", 116 | "expirationDate": 1660845913.860964, 117 | "hostOnly": false, 118 | "httpOnly": true, 119 | "name": "sessionid", 120 | "path": "/", 121 | "sameSite": "unspecified", 122 | "secure": false, 123 | "session": false, 124 | "storeId": "0", 125 | "value": "27059c86d2646d2668d80c8c6dc19597", 126 | "origin": "https://www.tiktok.com" 127 | }, 128 | { 129 | "domain": ".tiktok.com", 130 | "expirationDate": 1660845913.860972, 131 | "hostOnly": false, 132 | "httpOnly": true, 133 | "name": "sessionid_ss", 134 | "path": "/", 135 | "sameSite": "no_restriction", 136 | "secure": true, 137 | "session": false, 138 | "storeId": "0", 139 | "value": "27059c86d2646d2668d80c8c6dc19597", 140 | "origin": "https://www.tiktok.com" 141 | }, 142 | { 143 | "domain": ".tiktok.com", 144 | "expirationDate": 1660845913.86098, 145 | "hostOnly": false, 146 | "httpOnly": true, 147 | "name": "sid_ucp_v1", 148 | "path": "/", 149 | "sameSite": "unspecified", 150 | "secure": true, 151 | "session": false, 152 | "storeId": "0", 153 | "value": "1.0.0-KGRhMjMzNDM1N2ZiMmMxMmJlMWI5MGMyZmIyMzkyMDUwYmI2YWUwNmQKIAiGiLDo9frF12IQ2cq9lQYYswsgDDDw1LyVBjgBQOoHEAMaBm1hbGl2YSIgMjcwNTljODZkMjY0NmQyNjY4ZDgwYzhjNmRjMTk1OTc", 154 | "origin": "https://www.tiktok.com" 155 | }, 156 | { 157 | "domain": ".tiktok.com", 158 | "expirationDate": 1660845913.860989, 159 | "hostOnly": false, 160 | "httpOnly": true, 161 | "name": "ssid_ucp_v1", 162 | "path": "/", 163 | "sameSite": "no_restriction", 164 | "secure": true, 165 | "session": false, 166 | "storeId": "0", 167 | "value": "1.0.0-KGRhMjMzNDM1N2ZiMmMxMmJlMWI5MGMyZmIyMzkyMDUwYmI2YWUwNmQKIAiGiLDo9frF12IQ2cq9lQYYswsgDDDw1LyVBjgBQOoHEAMaBm1hbGl2YSIgMjcwNTljODZkMjY0NmQyNjY4ZDgwYzhjNmRjMTk1OTc", 168 | "origin": "https://www.tiktok.com" 169 | }, 170 | { 171 | "domain": ".tiktok.com", 172 | "expirationDate": 1660845913.568195, 173 | "hostOnly": false, 174 | "httpOnly": true, 175 | "name": "store-idc", 176 | "path": "/", 177 | "sameSite": "unspecified", 178 | "secure": false, 179 | "session": false, 180 | "storeId": "0", 181 | "value": "maliva", 182 | "origin": "https://www.tiktok.com" 183 | }, 184 | { 185 | "domain": ".tiktok.com", 186 | "expirationDate": 1660845913.56828, 187 | "hostOnly": false, 188 | "httpOnly": true, 189 | "name": "store-country-code", 190 | "path": "/", 191 | "sameSite": "unspecified", 192 | "secure": false, 193 | "session": false, 194 | "storeId": "0", 195 | "value": "gb", 196 | "origin": "https://www.tiktok.com" 197 | }, 198 | { 199 | "domain": ".tiktok.com", 200 | "expirationDate": 1660845913.568311, 201 | "hostOnly": false, 202 | "httpOnly": true, 203 | "name": "tt-target-idc", 204 | "path": "/", 205 | "sameSite": "unspecified", 206 | "secure": false, 207 | "session": false, 208 | "storeId": "0", 209 | "value": "useast1a", 210 | "origin": "https://www.tiktok.com" 211 | }, 212 | { 213 | "domain": ".tiktok.com", 214 | "hostOnly": false, 215 | "httpOnly": true, 216 | "name": "tt_csrf_token", 217 | "path": "/", 218 | "sameSite": "lax", 219 | "secure": true, 220 | "session": true, 221 | "storeId": "0", 222 | "value": "HAj6g1tm-ZTOb49w9bdgr_AjB_3juFY_IY-4", 223 | "origin": "https://www.tiktok.com" 224 | }, 225 | { 226 | "domain": ".tiktok.com", 227 | "expirationDate": 1657314157.125257, 228 | "hostOnly": false, 229 | "httpOnly": false, 230 | "name": "bm_sz", 231 | "path": "/", 232 | "sameSite": "unspecified", 233 | "secure": false, 234 | "session": false, 235 | "storeId": "0", 236 | "value": "436F39A4E590C4296DDD44090F850D13~YAAQJpr0WokGfqmBAQAA/W/D3hCybNIPVnl0AU/ixfnjcgNzRQJLGHmmfX+XLLj6u2cMh1czHngFSa4nMa63hAmTfTw4eLM0cBP+t30StyHxbZmh1xohGUW3xmTdUyV6beG1ylx+hGOPo7mbl7F7FFpKa2sJKrR1bYxIN9sdQCxy+7lcdcyjyQGgxHWrmiu/BpoGfEnPuwl8pgsa4KesIWQZNiXCq8xVidtuFqI4o+Q2BqOizhjQnk3iNnPxcLIx15QbjYO8SrC2wzNlGsCVI4SAxjUYOeBbrIw4Qo+oS2Sx1no=~3223601~3491383", 237 | "origin": "https://www.tiktok.com" 238 | }, 239 | { 240 | "domain": ".www.tiktok.com", 241 | "expirationDate": 1657905075, 242 | "hostOnly": false, 243 | "httpOnly": false, 244 | "name": "__tea_cache_tokens_1988", 245 | "path": "/", 246 | "sameSite": "unspecified", 247 | "secure": false, 248 | "session": false, 249 | "storeId": "0", 250 | "value": "{%22_type_%22:%22default%22}", 251 | "origin": "https://www.tiktok.com" 252 | }, 253 | { 254 | "domain": ".tiktok.com", 255 | "expirationDate": 1657306988.953582, 256 | "hostOnly": false, 257 | "httpOnly": true, 258 | "name": "ak_bmsc", 259 | "path": "/", 260 | "sameSite": "unspecified", 261 | "secure": false, 262 | "session": false, 263 | "storeId": "0", 264 | "value": "D09C7BD9ECBC8E58E818E4DA7EB3D1C2~000000000000000000000000000000~YAAQJpr0Wp0GfqmBAQAAm+PD3hAGXspFXz1XuFwLF/mwKvO+KRzzIZD704M0ZZOk4YhBkNhzZOtwVlNCdvZPD2KOFgS8NYZRfe3/xeckvNHlplC6AdBwFXdnzlnJJSOX/SgdCEj2J1LMmI5x5yR7GYH37uq1HPL/YrQXpUAplIOIcHjbQiq+L3tb5pSU51Lm3LSuZ07hDLHE47N4gwGToU2Cpnly2ISknrqSpMEmtgKtQkYkPQq93TxH89pGe7+6iFsCcROHIWMWg3vfM+qdhQ56XToZQsKKDoDT8wVeV7T8LR5EJ/13hemqYS5hFnn8t5JhrAPdKXoHEPTpi2Y/W3Lxnd2jo4RwVA7LCiOi3nsfgxbubbbaB2TSTHIdkp+ZNtQgSfrE2dq4t+l9", 265 | "origin": "https://www.tiktok.com" 266 | }, 267 | { 268 | "domain": "www.tiktok.com", 269 | "hostOnly": true, 270 | "httpOnly": false, 271 | "name": "csrf_session_id", 272 | "path": "/", 273 | "sameSite": "no_restriction", 274 | "secure": true, 275 | "session": true, 276 | "storeId": "0", 277 | "value": "7e79cf972104fccc37cf27889a32a209", 278 | "origin": "https://www.tiktok.com" 279 | }, 280 | { 281 | "domain": ".tiktok.com", 282 | "expirationDate": 1688836257.640048, 283 | "hostOnly": false, 284 | "httpOnly": false, 285 | "name": "_abck", 286 | "path": "/", 287 | "sameSite": "unspecified", 288 | "secure": true, 289 | "session": false, 290 | "storeId": "0", 291 | "value": "45AE3A156386040D8BB4E6E73BCA6773~-1~YAAQRpr0WoZnd6mBAQAAdQrL3ggKwl7vkWZYG8XTjPHrDcScogeZIUCi0BgXDy6kT2pFAvkgyTn4Yyt/W3pA1m0mlq5zG98X+Nl0bBHjqyn3pAsgJm027yE/5MKYTJy7YiuOVSfLw/OrP6rlQsZ9iP8V82ltyNMNjexAu63R5RdN+U41x4ZQew7CgX6MQY8N2aZalRWn12JIu8m/122TzNEt9H579a1Vs0b2q3DWA7YYlaoFmfD/D4Tf7UunZhsuXIDfQpCrZ/xyFx/LQrLpKHwdxnVS/M1z65ofL3X0t5uk1/9QtmKCCCf/mThQ6MnaqG2TkxunIt6DHzDVXceQRZaKEYLWdlcWo/v+DuegXujqz8fFCoK+MXpeb0sSN3zFrVkKzqiLFU3DFA==~-1~-1~-1", 292 | "origin": "https://www.tiktok.com" 293 | }, 294 | { 295 | "domain": ".tiktok.com", 296 | "expirationDate": 1657306989.030783, 297 | "hostOnly": false, 298 | "httpOnly": false, 299 | "name": "bm_mi", 300 | "path": "/", 301 | "sameSite": "unspecified", 302 | "secure": true, 303 | "session": false, 304 | "storeId": "0", 305 | "value": "6151AFA9BB91984769F7A29911FBA40B~YAAQRpr0Wohnd6mBAQAAKCTL3hBR01yK39KfGOOeVlH2O199l6LQExqZ1m1xaaw01r5FDMIVwFpWIS1KoWKcSH5R41YyusnxIwJZBUjjHahIfwkP+gZ090dmA1pkwgNDSk+WsOEAYFEjkGeQI9bXRvw4S6ddBRrEIMNn7lw+2Ya/zAhjfaGLgguJVqbuhVce72T2dm4AJUmbG6IzkBs429nxIx+BvjlEIao83OyJdQ5IFQaWjIJ9zvEZne0mAHOWDf5Ngv41XO2pphXKL7Xh7QiDnEqu3y+2QdSKgZ7sd7BF+BqIe/xI9eeQOMj7ccE0RdoCJdY7/sn48w==~1", 306 | "origin": "https://www.tiktok.com" 307 | }, 308 | { 309 | "domain": ".tiktok.com", 310 | "expirationDate": 1657306998.701572, 311 | "hostOnly": false, 312 | "httpOnly": false, 313 | "name": "bm_sv", 314 | "path": "/", 315 | "sameSite": "unspecified", 316 | "secure": true, 317 | "session": false, 318 | "storeId": "0", 319 | "value": "7A92DE83B8DDE7BB22EDF38271B817E4~YAAQRpr0Wotnd6mBAQAA50nL3hDf2VY8Ac3I6VTImRxRUpznpH9mauO8ukFd87owUP27VwscH4OBZz0WsPYDakG2NDVP4zHH0tXziBJt28vfiFaUq0zjt9Deuhlm1w3FHDcgRl9HsK0WgsAh+j0mvhVR7Aj8ATCCHRdLoMjOZ+9hsQ51m1wom3G3vgpbdhc005Lh0pXbShpx9t8gZv3Vq9YlxM1Xcn+mcWL9SW4vsWMCOtEzjT09meFntujMRPahyw==~1", 320 | "origin": "https://www.tiktok.com" 321 | }, 322 | { 323 | "domain": ".www.tiktok.com", 324 | "hostOnly": false, 325 | "httpOnly": false, 326 | "name": "passport_fe_beating_status", 327 | "path": "/", 328 | "sameSite": "unspecified", 329 | "secure": false, 330 | "session": true, 331 | "storeId": "0", 332 | "value": "true", 333 | "origin": "https://www.tiktok.com" 334 | }, 335 | { 336 | "domain": ".tiktok.com", 337 | "expirationDate": 1688836276.933306, 338 | "hostOnly": false, 339 | "httpOnly": true, 340 | "name": "ttwid", 341 | "path": "/", 342 | "sameSite": "no_restriction", 343 | "secure": true, 344 | "session": false, 345 | "storeId": "0", 346 | "value": "1%7CJka1j325pAAdnnfE5wWdR2uQcUhXeVwzs3eGWA4tt4Q%7C1657300276%7C1fde4afd62427272113d9a4a525e86067b394af9002c69953fd570ff996f1409", 347 | "origin": "https://www.tiktok.com" 348 | }, 349 | { 350 | "domain": ".tiktok.com", 351 | "expirationDate": 1688836277.725202, 352 | "hostOnly": false, 353 | "httpOnly": true, 354 | "name": "odin_tt", 355 | "path": "/", 356 | "sameSite": "unspecified", 357 | "secure": false, 358 | "session": false, 359 | "storeId": "0", 360 | "value": "d66593ea6b7ea2b653aaebe374d134fbedef45ab9af891eb9f6f9a5cd2bb198d73fda09eb7eeff738f17628f7cee6ffaad52b971f2e834299b4a9cf0957d61f2c9adf70f61508328391e23caa41f7bfd", 361 | "origin": "https://www.tiktok.com" 362 | }, 363 | { 364 | "domain": ".tiktok.com", 365 | "expirationDate": 1658164279.380163, 366 | "hostOnly": false, 367 | "httpOnly": false, 368 | "name": "msToken", 369 | "path": "/", 370 | "sameSite": "no_restriction", 371 | "secure": true, 372 | "session": false, 373 | "storeId": "0", 374 | "value": "19SDfYwggRJuxJDq6ehExy_cf9bQaHbah-TTMe8oJUF2M7UpUzRSdCsg35ThN7OPZkgo0kU4b-tXllORACU2vCxfueYOp3OjTQF_OM27x2-aFWdikQHOZPCOT5Yn1abAPJmshQ==", 375 | "origin": "https://www.tiktok.com" 376 | }, 377 | { 378 | "domain": "www.tiktok.com", 379 | "expirationDate": 1665076279, 380 | "hostOnly": true, 381 | "httpOnly": false, 382 | "name": "msToken", 383 | "path": "/", 384 | "sameSite": "unspecified", 385 | "secure": false, 386 | "session": false, 387 | "storeId": "0", 388 | "value": "19SDfYwggRJuxJDq6ehExy_cf9bQaHbah-TTMe8oJUF2M7UpUzRSdCsg35ThN7OPZkgo0kU4b-tXllORACU2vCxfueYOp3OjTQF_OM27x2-aFWdikQHOZPCOT5Yn1abAPJmshQ==", 389 | "origin": "https://www.tiktok.com" 390 | } 391 | ] -------------------------------------------------------------------------------- /autoinsta/profiles/example.json: -------------------------------------------------------------------------------- 1 | // This is the JSON Format for running multiple instagram accounts. 2 | // Create a file named map.json and place it in the same directory (autoinsta/profiles/) in order to run the bot. 3 | 4 | // If you want to use an account that already exists, create a folder with the correct username in this directory, with a .pass file inside. 5 | // E.G - autoinsta/profiles/example/login.pass 6 | [ 7 | { 8 | "username" : "pizzapageultimate9001", 9 | "bio" : "Pizza is simply the best!", 10 | "topic" : "pizza", 11 | "tags" : 12 | [ 13 | "food", 14 | "pizza", 15 | "hungry", 16 | "foodporn", 17 | "bestfood", 18 | "chef", 19 | "italy", 20 | "cheese", 21 | "foodlover", 22 | "melting", 23 | "pepperoni", 24 | "amazing" 25 | ], 26 | "comments": 27 | [ 28 | "This pizza looks great!!", 29 | "THIS LOOKS SO GOOD!!!!", 30 | "Wowsers", 31 | "Ahahaha!", 32 | "Love this!", 33 | "My heart is melting", 34 | "I need this", 35 | "HOLY MOLY!", 36 | "Now this is good", 37 | "Yaaaaas", 38 | "Need it!", 39 | "Yes please" 40 | ] 41 | }, 42 | { 43 | "username" : "exampleaccount", 44 | "bio" : "Enter a bio here", 45 | "topic" : "demonstration", 46 | "tags" : 47 | [ 48 | "you", 49 | "can", 50 | "have", 51 | "any", 52 | "number", 53 | "of", 54 | "hashtags", 55 | "that", 56 | "you", 57 | "want", 58 | "to" 59 | ], 60 | "comments": 61 | [ 62 | "When", 63 | "Commenting", 64 | "a random", 65 | "one!", 66 | "of", 67 | "these", 68 | "strings", 69 | "will", 70 | "be", 71 | "selected" 72 | ] 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /autoinsta/profiles/example/login.pass: -------------------------------------------------------------------------------- 1 | exampleusername 2 | ThIsIsAnExAmPlEpAsSwOrD -------------------------------------------------------------------------------- /autoinsta/src/bot.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from browsers import browsers 4 | from profile import ProfileManager 5 | 6 | import instagram 7 | import tiktok 8 | import driver 9 | import video 10 | import utils 11 | import mail 12 | 13 | def Upload_Trending_TikTok(user, VIDEO_EDIT=False, HEADLESS=True): 14 | tik_tok, video_path = tiktok.get_tiktok_video() 15 | if video_path == False: 16 | utils.error_log("No video path was returned") 17 | Upload_Trending_TikTok(user, VIDEO_EDIT, HEADLESS) 18 | return False 19 | 20 | if VIDEO_EDIT: 21 | utils.clean_log("Padding Video Content") 22 | video_path = video.add_video_pad(video_path) 23 | 24 | browser = driver.GetBrowser(HEADLESS=HEADLESS) 25 | 26 | sleep(3) 27 | 28 | if instagram.sign_in_to_account(browser, user): 29 | if instagram.upload_video(browser, video_path): 30 | if instagram.finalise_upload(browser, tik_tok, user): 31 | utils.clean_log("Video Uploaded Succesfully") 32 | browser.close() 33 | return True 34 | 35 | browser.close() 36 | return 37 | 38 | def Like_Relevant_Posts(user, num, HEADLESS=False): 39 | 40 | browser = driver.GetBrowser(HEADLESS=HEADLESS) 41 | 42 | sleep(3) 43 | 44 | result, explore = instagram.navigate_to_explore(user, browser, user.topic) 45 | if not result: 46 | utils.error_log("Failed to navigate to explore page.") 47 | utils.error_log("This usually happens when a user failed to sign in.\n1) Try Running Again\n2) Check your Login details\n3)check the account exists\nCheck") 48 | return False 49 | 50 | recent_posts = explore.get_recent_posts(num) 51 | print(recent_posts) 52 | 53 | sleep(3) 54 | 55 | for x in range(len(recent_posts)): 56 | p = instagram.load_post(browser, recent_posts[x]) 57 | sleep(5) 58 | p.like_post() 59 | p.comment_post(user.get_random_comment()) 60 | p.follow_poster() 61 | 62 | sleep(3) 63 | browser.close() 64 | utils.clean_log("Finished liking posts") 65 | return True 66 | 67 | def Test_Email(): 68 | browser = driver.GetBrowser(HEADLESS=False) 69 | mail.get_email_address(browser) 70 | 71 | def Test_VideoEdit(): 72 | tik_tok, video_path = tiktok.get_tiktok_video() 73 | if video_path == False: 74 | utils.error_log("No video path was returned") 75 | #return False 76 | 77 | video.add_video_pad('autoinsta/videos/tikTokTrending.mp4') -------------------------------------------------------------------------------- /autoinsta/src/brain.py: -------------------------------------------------------------------------------- 1 | import schedule 2 | import time 3 | 4 | import bot 5 | import utils 6 | from profile import ProfileManager 7 | 8 | def job(): 9 | p_mgr = ProfileManager() 10 | p_mgr.parse_json() 11 | for p in p_mgr.profiles: 12 | utils.clean_log(f"Running Bot for Profile: {p.username}") 13 | utils.clean_log(f"Liking relevant posts with Profile: {p.username}") 14 | bot.Like_Relevant_Posts(p, 10, HEADLESS=True) 15 | utils.clean_log(f"Posting Trending Tiktok with Profile: {p.username}") 16 | bot.Upload_Trending_TikTok(p, VIDEO_EDIT=False, HEADLESS=True) 17 | utils.clean_log(f"Finished Running Bot for Profile: {p.username}") 18 | print("bot finished running!") 19 | 20 | print("--- STARTING BOT ---") 21 | job() 22 | schedule.every(5).minutes.do(job) 23 | schedule.every().hour.do(job) 24 | 25 | while 1: 26 | schedule.run_pending() 27 | time.sleep(1) 28 | -------------------------------------------------------------------------------- /autoinsta/src/driver.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.firefox.options import Options 3 | from webdriver_manager.firefox import GeckoDriverManager 4 | 5 | 6 | def GetBrowser(HEADLESS=False): 7 | options = Options() 8 | options.headless = HEADLESS 9 | 10 | browser = webdriver.Firefox(executable_path=GeckoDriverManager().install(), options=options) 11 | browser.implicitly_wait(5) 12 | return browser -------------------------------------------------------------------------------- /autoinsta/src/instagram.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from selenium import webdriver 3 | from selenium.webdriver.common.by import By 4 | from selenium.webdriver.support.ui import Select 5 | import os 6 | from mail import Email 7 | import utils 8 | import driver 9 | 10 | class LandingPage: 11 | def __init__(self, browser): 12 | self.browser = browser 13 | 14 | def save_login_information(self): 15 | # TODO - Update to By.CSS_SELECTOR 16 | return utils.try_click_element(self.browser, By.XPATH, "/html/body/div[1]/section/main/div/div/div/div/button") 17 | 18 | def turn_on_notifications(self): 19 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, "button._a9--:nth-child(2)") 20 | 21 | def upload_page(self): 22 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, '._acub > button:nth-child(1)') 23 | 24 | def upload_from_computer(self): 25 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, '._ab9x > button:nth-child(1)') 26 | 27 | def upload_from_computer_cp(self, video_path): 28 | utils.clean_log("Trying to send keys") 29 | # This is still temperamental, sometimes visibile - sometimes not. 30 | # xpath is - "/html/body/div[1]/div/div[1]/div/div[2]/div/div/div[1]/div/div[3]/div/div/div/div/div/div/div/div/div[2]/div[1]/form/input" 31 | # css selector is - ._ac2t > form:nth-child(2) > input:nth-child(1) 32 | # css path is - html._9dls.js-focus-visible._aa4c body._a3wf.system-fonts--body.segoe div#mount_0_0_TM div div div.rq0escxv.l9j0dhe7.du4w35lb div div div.hwddc3l5 div.rq0escxv.l9j0dhe7.du4w35lb div.j83agx80.cbu4d94t.h3gjbzrl.l9j0dhe7.dza99gun div.iqfcb0g7.tojvnm2t.a6sixzi8.k5wvi7nf.q3lfd5jv.pk4s997a.bipmatt0.cebpdrjk.qowsmv63.owwhemhu.dp1hu0rb.dhp61c6y.l9j0dhe7.iyyx5f41.a8s20v7p div.gs1a9yip.rq0escxv.j83agx80.cbu4d94t.buofh1pr.taijpn5t div.ll8tlv6m.rq0escxv.j83agx80.taijpn5t.tgvbjcpo.hpfvmrgz.hzruof5a div.du4w35lb.cjfnh4rs.lzcic4wl.ni8dbmo4.stjgntxs.oqq733wu.futnfnd5.mudwbb97.fg7vo5n6.q0p5rdf8.li38xygf div.ryzhgsaw.q0p5rdf8.nxkddm9p.d6zs4f6z.dopw56fx.lcf4bpt0.kbli7zfr.fg7vo5n6.mpyj2j6a.d2uofw50.h1gfnr7q.mgim66vq div.qg4pu3sx.flebnqrf.kzt5xp73.h98he7qt.e793r6ar.pi61vmqs.od1n8kyl.h6an9nv3.j4yusqav div._a3gq._ab-1 div div._ab8w._ab94._ab99._ab9f._ab9m._ab9o._ab9s div._ac2r div._ac2t form input._ac69 33 | if self.select_upload_file(): 34 | print("Clicked upload button") 35 | 36 | result, element = utils.try_find_element(self.browser, By.CSS_SELECTOR, "._ac2t > form:nth-child(2) > input:nth-child(1)") 37 | if result == True: 38 | element.send_keys(os.path.abspath(video_path)) 39 | sleep(3) 40 | 41 | return result 42 | 43 | def select_upload_file(self): 44 | sleep(3) 45 | 46 | if utils.try_click_element(self.browser, By.CSS_SELECTOR, '._ab9x > button:nth-child(1)'): 47 | return True 48 | else: 49 | return False 50 | sleep(3) 51 | 52 | autoit.control_send("File Upload","Edit1", os.path.abspath(video_path)) 53 | autoit.control_send("File Upload","Edit1","{ENTER}") 54 | sleep(2) 55 | if autoit.win_exists("File Upload"): 56 | return False 57 | else: 58 | return True 59 | 60 | 61 | class UploadPage: 62 | def __init__(self, browser): 63 | self.browser = browser 64 | 65 | def nextpage(self): 66 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, "._abaa > button:nth-child(1)") 67 | 68 | def write_caption(self, caption): 69 | return utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, 'textarea._ablz:nth-child(1)', caption) 70 | 71 | def verify_upload(self): 72 | try: 73 | self.browser.find_element(By.CSS_SELECTOR, "h2._aacl") 74 | return True 75 | except: 76 | return False 77 | 78 | class LoginPage: 79 | def __init__(self, browser): 80 | self.browser = browser 81 | 82 | def login(self, username, password): 83 | usr = utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, "input[name='username']", username) 84 | if not usr: 85 | utils.error_log("Failed to send text to username field") 86 | return False 87 | 88 | pwd = utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, "input[name='password']", password) 89 | 90 | if not pwd: 91 | utils.error_log("Failed to send text to password field") 92 | return False 93 | 94 | return utils.try_click_element(self.browser, By.XPATH, "//button[@type='submit']") 95 | 96 | class HomePage: 97 | def __init__(self, browser): 98 | self.browser = browser 99 | self.browser.get('https://www.instagram.com/') 100 | 101 | def accept_cookies(self): 102 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, 'button.aOOlW:nth-child(2)') 103 | 104 | def go_to_login_page(self): 105 | sleep(2) 106 | return LoginPage(self.browser) 107 | 108 | def go_to_new_account_page(self): 109 | utils.clean_log("Trying to move to sign up page.") 110 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, '.izU2O > a:nth-child(1)') 111 | 112 | class CreateAccountPage: 113 | def __init__(self, browser, p): 114 | self.browser = browser 115 | 116 | ebrowser = driver.GetBrowser(HEADLESS=True) 117 | self.mail = Email(ebrowser) 118 | 119 | email = self.mail.get_email() 120 | 121 | self.set_email_address(email) 122 | self.set_fullname("Micheal Watts") 123 | self.set_username(p.username) 124 | self.set_password(p.create_account_password()) 125 | 126 | self.submit_user_details() 127 | 128 | def set_email_address(self, email): 129 | utils.clean_log("Setting Email Address") 130 | utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, "div.WZdjL:nth-child(4) > div:nth-child(1) > label:nth-child(1) > input:nth-child(2)", email) 131 | 132 | def set_fullname(self, fullname): 133 | utils.clean_log("Setting Fullname") 134 | utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, "div.WZdjL:nth-child(5) > div:nth-child(1) > label:nth-child(1) > input:nth-child(2)", fullname) 135 | 136 | def set_username(self, username): 137 | utils.clean_log("Setting Username") 138 | utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, "div.WZdjL:nth-child(6) > div:nth-child(1) > label:nth-child(1) > input:nth-child(2)", username) 139 | 140 | def set_password(self, password): 141 | utils.clean_log("Setting Password") 142 | utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, "div.WZdjL:nth-child(7) > div:nth-child(1) > label:nth-child(1) > input:nth-child(2)", password) 143 | 144 | def set_birth_date(self): 145 | res1, month = utils.try_find_element(self.browser, By.CSS_SELECTOR, 'span.O15Fw:nth-child(1) > select:nth-child(2)') 146 | if not res1: 147 | utils.error_log("Failed to find month select element") 148 | return False 149 | 150 | res2, day = utils.try_find_element(self.browser, By.CSS_SELECTOR, 'span.O15Fw:nth-child(2) > select:nth-child(2)') 151 | if not res2: 152 | utils.error_log("Failed to find day select element") 153 | return False 154 | 155 | res3, year = utils.try_find_element(self.browser, By.CSS_SELECTOR, 'span.O15Fw:nth-child(3) > select:nth-child(2)') 156 | if not res3: 157 | utils.error_log("Failed to find year select element") 158 | return False 159 | 160 | # select by value 161 | Month = Select(month) 162 | Month.select_by_value('8') 163 | Day = Select(day) 164 | Day.select_by_value('12') 165 | Year = Select(year) 166 | Year.select_by_value('1986') 167 | 168 | sleep(3) 169 | 170 | self.submit_birth_details() 171 | 172 | def submit_user_details(self): 173 | utils.clean_log("Submitting account details") 174 | if utils.try_click_element(self.browser, By.CSS_SELECTOR, 'div.bkEs3:nth-child(1) > button:nth-child(1)'): 175 | utils.clean_log("Details submitted succesfully") 176 | sleep(5) 177 | self.set_birth_date() 178 | 179 | def submit_birth_details(self): 180 | utils.clean_log("Submitting Birthdate Details") 181 | if utils.try_click_element(self.browser, By.CSS_SELECTOR, '.L3NKy'): 182 | utils.clean_log("Birthdate submitted succesfully") 183 | verif = self.mail.get_verification_code() 184 | self.enter_verification_code(verif) 185 | # Do something about verifying email 186 | 187 | def enter_verification_code(self, code): 188 | utils.clean_log("Entering email verification") 189 | if utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, '.j_2Hd', code): 190 | utils.clean_log("Email Submitted Succesfully") 191 | if utils.try_click_element(self.browser, By.CSS_SELECTOR, '.L3NKy'): 192 | utils.clean_log("Clicked next step succesfully") 193 | sleep(100) 194 | 195 | 196 | class ExplorePage: 197 | def __init__(self, browser, hashtag): 198 | self.browser = browser 199 | self.browser.get('https://www.instagram.com/explore/tags/' + hashtag) 200 | 201 | def get_recent_post_id(self): 202 | utils.clean_log("Getting recent post") 203 | 204 | def get_recent_posts(self, num): 205 | # ._aao7 > div:nth-child(3) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) 206 | # ._aao7 > div:nth-child(3) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) 207 | # ._aao7 > div:nth-child(3) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > a:nth-child(1) 208 | # ._aao7 > div:nth-child(3) > div:nth-child(1) > div:nth-child(2) > div:nth-child(2) 209 | recent = self.browser.find_element(By.CSS_SELECTOR, 'h2._aanc:nth-child(2)') 210 | self.browser.execute_script("arguments[0].scrollIntoView(true);", recent) 211 | sleep(5) 212 | 213 | ids = list() 214 | row = 0 215 | for x in range(num): 216 | column = x % 3 217 | if column == 0: 218 | row += 1 219 | 220 | elem = self.browser.find_element(By.CSS_SELECTOR, f'._aao7 > div:nth-child(3) > div:nth-child(1) > div:nth-child({row}) > div:nth-child({column + 1})') 221 | a = elem.find_elements(By.TAG_NAME, 'a') 222 | for l in a: 223 | url = l.get_attribute("href") 224 | id = utils.get_part_from_url(url, 2) 225 | utils.clean_log("id: " + id) 226 | ids.append(id) 227 | 228 | return ids 229 | 230 | class Post: 231 | def __init__(self, browser, id): 232 | self.browser = browser 233 | self.id = id 234 | utils.clean_log("Loading Post: " + id) 235 | self.browser.get('https://www.instagram.com/p/' + id + '/') 236 | 237 | def like_post(self): 238 | utils.clean_log("Liking current post") 239 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, '._aamw > button:nth-child(1)') 240 | 241 | def comment_post(self, comment): 242 | utils.clean_log("Commenting on post") 243 | result = utils.try_sendtext_element(self.browser, By.CSS_SELECTOR, '._ablz', comment) 244 | if not result: 245 | return False 246 | 247 | sleep(3) 248 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, 'button._acan:nth-child(3)') 249 | 250 | def follow_poster(self): 251 | utils.clean_log("Following Poster") 252 | return utils.try_click_element(self.browser, By.CSS_SELECTOR, 'button._acan:nth-child(2)') 253 | 254 | def sign_in_to_account(browser, profile): 255 | home_page = HomePage(browser) 256 | login_page = LoginPage(home_page.browser) 257 | 258 | if profile.get_login_details() == 'MAKE_ACCOUNT': 259 | utils.clean_log("New account is being created.") 260 | if home_page.accept_cookies(): 261 | utils.clean_log("STEP - Accept Cookies, complete") 262 | if home_page.go_to_new_account_page(): 263 | acc = CreateAccountPage(browser, profile) 264 | browser.close() 265 | sign_in_to_account(browser, profile) 266 | return True 267 | else: 268 | usr, pwd = profile.get_login_details() 269 | if home_page.accept_cookies(): 270 | utils.clean_log("STEP - Accept Cookies, complete") 271 | if login_page.login(usr, pwd): 272 | utils.clean_log("STEP - Login, Complete") 273 | landing_page = LandingPage(login_page.browser) 274 | if landing_page.save_login_information(): 275 | utils.clean_log("STEP - Save Login Information, complete") 276 | if landing_page.turn_on_notifications(): 277 | utils.clean_log("STEP - Notification Settings, complete") 278 | return True 279 | 280 | print("Failed to navigate to home page.") 281 | return False 282 | 283 | def navigate_to_explore(profile, browser, hashtag): 284 | if sign_in_to_account(browser, profile): 285 | sleep(3) 286 | 287 | explore = ExplorePage(browser, hashtag) 288 | sleep(5) 289 | return True, explore 290 | else: 291 | utils.error_log("Failed getting to explore page. Try running without headless mode to see what went wrong.") 292 | return False, False 293 | 294 | def load_post(browser, post_id): 295 | post = Post(browser, post_id) 296 | utils.clean_log("Loaded Post") 297 | return post 298 | 299 | def upload_video(browser, video_path): 300 | landing_page = LandingPage(browser) 301 | if landing_page.upload_page(): 302 | utils.clean_log("STEP - Upload Page, complete") 303 | if landing_page.upload_from_computer_cp(video_path): 304 | utils.clean_log("STEP - Select Upload, complete") 305 | return True 306 | 307 | return False 308 | 309 | def finalise_upload(browser, tik_tok, profile): 310 | result = True 311 | 312 | my_hashtags = profile.get_hashtag_string() 313 | 314 | upload_page = UploadPage(browser) 315 | if upload_page.nextpage(): 316 | utils.clean_log("UPLOAD 1/4, Complete") 317 | if upload_page.nextpage(): 318 | utils.clean_log("UPLOAD 2/4, Complete") 319 | if upload_page.write_caption(tik_tok.desc + "\nCredit: " + tik_tok.video_author + "\n\n\n\n " + tik_tok.hashtag_string + "\n" + my_hashtags): 320 | utils.clean_log("UPLOAD 3/4, Complete") 321 | if upload_page.nextpage(): 322 | utils.clean_log("UPLOAD 4/4, Complete") 323 | upload_complete = False 324 | while not upload_complete: 325 | utils.clean_log("UPLOADING...") 326 | sleep(30) 327 | upload_complete = upload_page.verify_upload() 328 | utils.clean_log("UPLOAD COMPLETE") 329 | return True 330 | 331 | return False 332 | -------------------------------------------------------------------------------- /autoinsta/src/mail.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from selenium import webdriver 3 | from selenium.webdriver.common.by import By 4 | import utils 5 | import pyperclip 6 | 7 | #https://temp-mail.org/en/ 8 | 9 | class Email: 10 | def __init__(self, browser): 11 | utils.clean_log('Initializing Email Retriever') 12 | self.browser = browser 13 | self.browser.get('https://temp-mail.org/en/') 14 | # sleep for a while, as the site has a fake delay for displaying emails 15 | sleep(10) 16 | 17 | def get_email(self): 18 | utils.clean_log('Getting Email Address') 19 | 20 | if utils.try_click_element(self.browser, By.CSS_SELECTOR, 'button.btn-rds:nth-child(1)'): 21 | utils.clean_log("copied email to clipboard") 22 | sleep(3) 23 | return pyperclip.paste() 24 | 25 | def get_verification_code(self): 26 | utils.clean_log("Getting verification code.") 27 | utils.clean_log("Sleeping 30 seconds before trying to retrieve") 28 | sleep(30) 29 | 30 | res, element = utils.try_find_element(self.browser, By.CSS_SELECTOR, '.inbox-dataList > ul:nth-child(1) > li:nth-child(2) > div:nth-child(2) > span:nth-child(1) > a:nth-child(1)') 31 | if not res: 32 | utils.error_log("Failed to retrieve verification email") 33 | return False 34 | 35 | return self.parse_verification_code(element) 36 | 37 | def parse_verification_code(self, elem): 38 | utils.clean_log("Parsing verification code") 39 | str = elem.text.split(' ') 40 | utils.clean_log(f'Parsed Verification code is: {str[0]}') 41 | return str[0] 42 | 43 | def get_email_address(browser): 44 | e = Email(browser) 45 | return e.get_email() 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /autoinsta/src/profile.py: -------------------------------------------------------------------------------- 1 | # Responsible for creating/managing profiles and what they can post/target 2 | 3 | import json 4 | from random import randint 5 | import utils 6 | import os 7 | from bing_image_downloader import downloader 8 | 9 | class Profile(): 10 | def __init__(self, j): 11 | self.j = j 12 | self.parse_profile() 13 | 14 | def parse_profile(self): 15 | self.username = self.j['username'] 16 | self.bio = self.j['bio'] 17 | self.topic = self.j['topic'] 18 | self.tags = self.j['tags'] 19 | self.comments = self.j['comments'] 20 | print("Profile:", self.username, "parsed succesfully") 21 | #self.get_profile_picture() 22 | 23 | def get_login_details(self): 24 | utils.clean_log(f"Retrieving login information for account: {self.username}") 25 | f = f'autoinsta/profiles/{self.username}/login.pass' 26 | if os.path.isdir(f'autoinsta/profiles/{self.username}'): 27 | utils.clean_log("user already has information stored.") 28 | if utils.get_account_details(f) == False: 29 | utils.clean_log("User exists, but account has not been created.") 30 | return 'MAKE_ACCOUNT' 31 | else: 32 | return utils.get_account_details(f) 33 | else: 34 | utils.clean_log("no user information stored for this user") 35 | return 'MAKE_ACCOUNT' 36 | 37 | def get_account_password(self): 38 | path = f'autoinsta/profiles/{self.username}/login.pass' 39 | try: 40 | return utils.get_account_details(path) 41 | except: 42 | return False 43 | 44 | def create_account_password(self): 45 | password = utils.generate_password(12) 46 | self.save_login_details(password) 47 | return password 48 | 49 | def save_login_details(self, password): 50 | root = f'autoinsta/profiles/{self.username}/' 51 | if not os.path.exists(root): 52 | os.makedirs(root) 53 | 54 | f = open(root + 'login.pass', "w+") 55 | f.write(f'{self.username}\n') 56 | f.write(f'{password}') 57 | f.close() 58 | 59 | def get_hashtag_string(self): 60 | res = "" 61 | for h in self.tags: 62 | res += '#' + h + ' ' 63 | return res 64 | 65 | def get_random_comment(self): 66 | i = randint(0, len(self.comments) - 1) 67 | return self.comments[i] 68 | 69 | def get_profile_picture(self): 70 | downloader.download(self.topic, limit=1, output_dir=f'autoinsta/profiles/{self.username}/', adult_filter_off=True, force_replace=False, timeout=60, verbose=True) 71 | 72 | 73 | class ProfileManager(): 74 | def __init__(self): 75 | self.profiles = list() 76 | 77 | def parse_json(self): 78 | filepath = 'autoinsta/profiles/map.json' 79 | with open(filepath) as f: 80 | data = json.load(f) 81 | 82 | for profile in data: 83 | prof = Profile(profile) 84 | self.profiles.append(prof) 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /autoinsta/src/tiktok.py: -------------------------------------------------------------------------------- 1 | from TikTokApi import TikTokApi 2 | 3 | import json 4 | 5 | from random import randint 6 | 7 | from time import sleep 8 | 9 | # TikTok Section 10 | class TikTokVideoGet: 11 | def __init__(self): 12 | self.api = TikTokApi() 13 | self.cookies = self.get_cookies_from_file() 14 | 15 | self.api._get_cookies = self.get_cookies 16 | print("initialized tiktok video helper") 17 | 18 | def get_cookies_from_file(self): 19 | with open('autoinsta/cookies/cookies.json') as f: 20 | self.cookies = json.load(f) 21 | 22 | cookies_kv = {} 23 | for cookie in self.cookies: 24 | cookies_kv[cookie['name']] = cookie['value'] 25 | 26 | return cookies_kv 27 | 28 | def get_cookies(self, **kwargs): 29 | return self.cookies 30 | 31 | def get_hashtag_video(self, hashtag): 32 | desiredVideo = randint(0, 10) 33 | step = 0 34 | 35 | h = self.api.hashtag(name=hashtag) 36 | 37 | videos = h.videos(count=10) 38 | 39 | for v in videos: 40 | step += 1 41 | 42 | if (step == desiredVideo): 43 | video_id = v.id 44 | self.download_video_by_id(video_id) 45 | 46 | return "autoinsta/videos/tikTokTrending.mp4" 47 | 48 | def get_trending_video(self): 49 | try: 50 | desiredVideo = randint(0, 3) 51 | step = 0 52 | print("Desired ID: ", desiredVideo) 53 | 54 | for trending_video in self.api.trending.videos(count=10): 55 | step += 1 56 | # Prints the author's username of the trending video. 57 | if (step == desiredVideo): 58 | print("Found a match for our desired video.") 59 | video_id = trending_video.id 60 | 61 | self.video_author = "" 62 | self.desc = "" 63 | 64 | try: 65 | video_data = trending_video.info() 66 | self.desc = video_data['desc'] 67 | print (self.desc) 68 | 69 | self.video_author = trending_video.author.username 70 | print("video author: ", self.video_author) 71 | self.video_hashtags = trending_video.hashtags 72 | self.hashtag_string = "" 73 | for h in self.video_hashtags: 74 | self.hashtag_string += (" #" + h.name) 75 | print("hashtag string:", self.hashtag_string) 76 | except: 77 | print("Failed to pull video data") 78 | 79 | print (self.hashtag_string) 80 | 81 | self.download_video_by_id(video_id) 82 | 83 | return "autoinsta/videos/tikTokTrending.mp4" 84 | return False 85 | except: 86 | print ("Failed to pull from TikTok...") 87 | return False 88 | 89 | 90 | def download_video_by_id(self, video_id): 91 | with TikTokApi() as api: 92 | video_bytes = api.video(id=video_id).bytes() 93 | 94 | # Saving The Video 95 | with open('autoinsta/videos/tikTokTrending.mp4', 'wb') as output: 96 | output.write(video_bytes) 97 | 98 | sleep(10) 99 | 100 | def get_tiktok_video(): 101 | tik_tok = TikTokVideoGet() 102 | #video_path = tik_tok.get_hashtag_video("food") 103 | return tik_tok, tik_tok.get_trending_video() -------------------------------------------------------------------------------- /autoinsta/src/utils.py: -------------------------------------------------------------------------------- 1 | from distutils.log import error 2 | import os 3 | from os import path 4 | import json 5 | import random 6 | import string 7 | from time import sleep 8 | from selenium import webdriver 9 | 10 | from selenium.webdriver.common.by import By 11 | from urllib.parse import unquote, urlparse 12 | from pathlib import PurePosixPath 13 | 14 | def generate_password(length): 15 | # get random password pf length 8 with letters, digits, and symbols 16 | characters = string.ascii_letters + string.digits + string.punctuation 17 | password = ''.join(random.choice(characters) for i in range(length)) 18 | return password 19 | 20 | def get_part_from_url(url, part): 21 | return PurePosixPath( 22 | unquote( 23 | urlparse( 24 | url 25 | ).path 26 | ) 27 | ).parts[part] 28 | 29 | 30 | def abs_path(local_path): 31 | return os.path.abspath(local_path) 32 | 33 | def get_account_details(file): 34 | if path.isfile(file): 35 | f = open(file) 36 | details = f.readlines() 37 | return details[0], details[1] 38 | else: 39 | return False 40 | 41 | def clean_log(message): 42 | print("*****", message, "*****") 43 | 44 | def error_log(message): 45 | print("***** ERROR", message, "*****") 46 | 47 | def get_json_value(j, key): 48 | valid = json.dumps(j) 49 | data = json.loads(valid) 50 | return data[key] 51 | 52 | def try_find_element(browser, bySelector, path): 53 | sleep(5) 54 | try: 55 | target_element = browser.find_element(bySelector, path) 56 | return True, target_element 57 | except: 58 | error_log(f"Failed to find element: {path}") 59 | return False, None 60 | 61 | def try_click_element(browser, bySelector, path): 62 | result, element = try_find_element(browser, bySelector, path) 63 | if result == True: 64 | try: 65 | element.click() 66 | except: 67 | error_log("Failed to click on error, has the page loaded correctly? Is the button obscured?") 68 | return result 69 | 70 | return result 71 | 72 | def try_sendtext_element(browser, bySelector, path, text): 73 | result, target = try_find_element(browser, bySelector, path) 74 | if result: 75 | target.send_keys(text) 76 | return result -------------------------------------------------------------------------------- /autoinsta/src/video.py: -------------------------------------------------------------------------------- 1 | import ffmpeg 2 | import sys 3 | import utils 4 | 5 | def get_video_resolution(video_path): 6 | probe = ffmpeg.probe(video_path) 7 | video_streams = [stream for stream in probe["streams"] if stream["codec_type"] == "video"] 8 | h = utils.get_json_value(video_streams[0], "height") 9 | w = utils.get_json_value(video_streams[0], "width") 10 | 11 | print("height", h) 12 | print("width", w) 13 | return w, h 14 | 15 | def get_optimal_padding(w, h): 16 | pad_w = 0 17 | pad_h = 0 18 | if h > w: 19 | pad_w = h - w 20 | else: 21 | pad_h = w - h 22 | 23 | return pad_w, pad_h 24 | 25 | def add_video_pad(video_path): 26 | utils.clean_log(f"Processing Video at path: {video_path}") 27 | 28 | input = ffmpeg.input(utils.abs_path(video_path)) 29 | #Padding - 'pad=ih*4/3:ih:(ow-iw)/2:(oh-ih)/2:color=white' 30 | output = ffmpeg.output(input, utils.abs_path('autoinsta/videos/final.mp4'), vcodec='libx264', vf=f'pad=ih*4/3:ih:(ow-iw)/2:(oh-ih)/2:color=white') 31 | try: 32 | output.run(overwrite_output=True) 33 | return 'autoinsta/videos/final.mp4' 34 | except: 35 | utils.error_log("Failed to produce edited video") 36 | utils.clean_log("Ensure you have correctly installed ffmpeg and that it is included in your system path.") 37 | utils.clean_log("You can skip the video edit by setting VIDEO_EDIT to false in bot.py") 38 | utils.clean_log("Continuing with un-edited video.") 39 | return video_path -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/playwright:focal 2 | 3 | ENV FIREFOX_VER 87.0 4 | 5 | RUN apt-get update && apt-get install -y python3-pip 6 | 7 | WORKDIR / 8 | 9 | 10 | # Copy Project 11 | COPY autoinsta/src /autoinsta/src/ 12 | COPY autoinsta/cookies /autoinsta/cookies/ 13 | COPY autoinsta/profiles /autoinsta/profiles/ 14 | COPY autoinsta/videos /autoinsta/videos/ 15 | 16 | RUN apt-get install firefox -y 17 | 18 | RUN apt install python3.8-venv 19 | 20 | ENV VIRTUAL_ENV=/opt/venv 21 | RUN python3 -m venv $VIRTUAL_ENV 22 | 23 | ENV PATH="$VIRTUAL_ENV/bin:$PATH" 24 | 25 | # Install dependencies: 26 | COPY requirements.txt . 27 | RUN pip3 install -r requirements.txt 28 | RUN python3 -m playwright install 29 | 30 | CMD exec python autoinsta/src/bot.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | instapy 3 | TikTokApi==5.1.1 4 | schedule 5 | webdriver-manager 6 | ffmpeg-python 7 | pyperclip 8 | bing-image-downloader --------------------------------------------------------------------------------