├── .gitignore ├── INSTALL PACKAGES.bat ├── LICENSE ├── README.md ├── START BOT.bat ├── config.json ├── device_auths.json ├── fortnite.py ├── partybot ├── __init__.py ├── bot.py ├── client.py ├── cosmetic.py ├── deviceauths.py ├── errors.py ├── generator.py ├── helper.py ├── party.py └── settings.py └── requirements.txt /.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 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | # static files generated from Django application using `collectstatic` 142 | media 143 | static 144 | 145 | 146 | # Windows thumbnail cache files 147 | Thumbs.db 148 | Thumbs.db:encryptable 149 | ehthumbs.db 150 | ehthumbs_vista.db 151 | 152 | # Dump file 153 | *.stackdump 154 | 155 | # Folder config file 156 | [Dd]esktop.ini 157 | 158 | # Recycle Bin used on file shares 159 | $RECYCLE.BIN/ 160 | 161 | # Windows Installer files 162 | *.cab 163 | *.msi 164 | *.msix 165 | *.msm 166 | *.msp 167 | 168 | # Windows shortcuts 169 | *.lnk 170 | 171 | *~ 172 | 173 | # temporary files which can be created if a process still has a handle open of a deleted file 174 | .fuse_hidden* 175 | 176 | # KDE directory preferences 177 | .directory 178 | 179 | # Linux trash folder which might appear on any partition or disk 180 | .Trash-* 181 | 182 | # .nfs files are created when an open file is removed but is still being accessed 183 | .nfs* 184 | 185 | 186 | # General 187 | .DS_Store 188 | .AppleDouble 189 | .LSOverride 190 | 191 | # Icon must end with two \r 192 | Icon 193 | 194 | # Thumbnails 195 | ._* 196 | 197 | # Files that might appear in the root of a volume 198 | .DocumentRevisions-V100 199 | .fseventsd 200 | .Spotlight-V100 201 | .TemporaryItems 202 | .Trashes 203 | .VolumeIcon.icns 204 | .com.apple.timemachine.donotpresent 205 | 206 | # Directories potentially created on remote AFP share 207 | .AppleDB 208 | .AppleDesktop 209 | Network Trash Folder 210 | Temporary Items 211 | .apdisk 212 | 213 | # Pycharm 214 | .idea -------------------------------------------------------------------------------- /INSTALL PACKAGES.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Check for Python Installation 3 | py -3 --version 2>NUL 4 | if errorlevel 1 goto errorNoPython 5 | 6 | :: Reaching here means Python is installed. 7 | IF EXIST "python-3.6.0-amd64.exe" ( 8 | del "python-3.6.0-amd64.exe" 9 | ) 10 | 11 | cls 12 | 13 | ECHO Installing the required packages for the bot! 14 | TIMEOUT 3 15 | 16 | py -3 -m pip install -U -r requirements.txt 17 | 18 | ECHO Done! Now run START BOT.bat 19 | PAUSE 20 | 21 | :: Once done, exit the batch file -- skips executing the errorNoPython section 22 | goto:eof 23 | 24 | :errorNoPython 25 | TITLE PartyBot^: Error 26 | echo Error^: Python not installed or not added to PATH. 27 | :: set mypath=%cd% 28 | :: bitsadmin.exe /transfer "InstallPython" https://www.python.org/ftp/python/3.7.0/python-3.7.0-amd64.exe %mypath%\python-3.7.0-amd64.exe 29 | 30 | IF EXIST "python-3.7.0-amd64.exe" ( 31 | echo Python Installer is already installed, install and/or add Python to PATH 32 | ) ELSE ( 33 | echo Installing Python Installer now, this will take a minute or 2. 34 | powershell -Command "(New-Object Net.WebClient).DownloadFile('https://www.python.org/ftp/python/3.6.0/python-3.6.0-amd64.exe', 'python-3.7.0-amd64.exe')" 35 | powershell -Command "Invoke-WebRequest https://www.python.org/ftp/python/3.7.0/python-3.7.0-amd64.exe -OutFile python-3.7.0-amd64.exe" 36 | echo Python Installer is now installed, install and/or add Python to PATH. 37 | ) 38 | 39 | cmd /k -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | “Commons Clause” License Condition v1.0 2 | Copyright Oli 2019-2024 3 | 4 | The Software is provided to you by the Licensor under the 5 | License, as defined below, subject to the following condition. 6 | 7 | Without limiting other conditions in the License, the grant 8 | of rights under the License will not include, and the License 9 | does not grant to you, the right to Sell the Software. 10 | 11 | For purposes of the foregoing, “Sell” means practicing any or 12 | all of the rights granted to you under the License to provide 13 | to third parties, for a fee or other consideration (including 14 | without limitation fees for hosting or consulting/ support 15 | services related to the Software), a product or service whose 16 | value derives, entirely or substantially, from the functionality 17 | of the Software. Any license notice or attribution required by 18 | the License must also include this Commons Clause License 19 | Condition notice. 20 | 21 | Software: PartyBot (fortnitepy-bot) 22 | 23 | License: Apache 2.0 24 | 25 | --------------------------------------------------------------------- 26 | 27 | 28 | Apache License 29 | Version 2.0, January 2004 30 | http://www.apache.org/licenses/ 31 | 32 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 33 | 34 | 1. Definitions. 35 | 36 | "License" shall mean the terms and conditions for use, reproduction, 37 | and distribution as defined by Sections 1 through 9 of this document. 38 | 39 | "Licensor" shall mean the copyright owner or entity authorized by 40 | the copyright owner that is granting the License. 41 | 42 | "Legal Entity" shall mean the union of the acting entity and all 43 | other entities that control, are controlled by, or are under common 44 | control with that entity. For the purposes of this definition, 45 | "control" means (i) the power, direct or indirect, to cause the 46 | direction or management of such entity, whether by contract or 47 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 48 | outstanding shares, or (iii) beneficial ownership of such entity. 49 | 50 | "You" (or "Your") shall mean an individual or Legal Entity 51 | exercising permissions granted by this License. 52 | 53 | "Source" form shall mean the preferred form for making modifications, 54 | including but not limited to software source code, documentation 55 | source, and configuration files. 56 | 57 | "Object" form shall mean any form resulting from mechanical 58 | transformation or translation of a Source form, including but 59 | not limited to compiled object code, generated documentation, 60 | and conversions to other media types. 61 | 62 | "Work" shall mean the work of authorship, whether in Source or 63 | Object form, made available under the License, as indicated by a 64 | copyright notice that is included in or attached to the work 65 | (an example is provided in the Appendix below). 66 | 67 | "Derivative Works" shall mean any work, whether in Source or Object 68 | form, that is based on (or derived from) the Work and for which the 69 | editorial revisions, annotations, elaborations, or other modifications 70 | represent, as a whole, an original work of authorship. For the purposes 71 | of this License, Derivative Works shall not include works that remain 72 | separable from, or merely link (or bind by name) to the interfaces of, 73 | the Work and Derivative Works thereof. 74 | 75 | "Contribution" shall mean any work of authorship, including 76 | the original version of the Work and any modifications or additions 77 | to that Work or Derivative Works thereof, that is intentionally 78 | submitted to Licensor for inclusion in the Work by the copyright owner 79 | or by an individual or Legal Entity authorized to submit on behalf of 80 | the copyright owner. For the purposes of this definition, "submitted" 81 | means any form of electronic, verbal, or written communication sent 82 | to the Licensor or its representatives, including but not limited to 83 | communication on electronic mailing lists, source code control systems, 84 | and issue tracking systems that are managed by, or on behalf of, the 85 | Licensor for the purpose of discussing and improving the Work, but 86 | excluding communication that is conspicuously marked or otherwise 87 | designated in writing by the copyright owner as "Not a Contribution." 88 | 89 | "Contributor" shall mean Licensor and any individual or Legal Entity 90 | on behalf of whom a Contribution has been received by Licensor and 91 | subsequently incorporated within the Work. 92 | 93 | 2. Grant of Copyright License. Subject to the terms and conditions of 94 | this License, each Contributor hereby grants to You a perpetual, 95 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 96 | copyright license to reproduce, prepare Derivative Works of, 97 | publicly display, publicly perform, sublicense, and distribute the 98 | Work and such Derivative Works in Source or Object form. 99 | 100 | 3. Grant of Patent License. Subject to the terms and conditions of 101 | this License, each Contributor hereby grants to You a perpetual, 102 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 103 | (except as stated in this section) patent license to make, have made, 104 | use, offer to sell, sell, import, and otherwise transfer the Work, 105 | where such license applies only to those patent claims licensable 106 | by such Contributor that are necessarily infringed by their 107 | Contribution(s) alone or by combination of their Contribution(s) 108 | with the Work to which such Contribution(s) was submitted. If You 109 | institute patent litigation against any entity (including a 110 | cross-claim or counterclaim in a lawsuit) alleging that the Work 111 | or a Contribution incorporated within the Work constitutes direct 112 | or contributory patent infringement, then any patent licenses 113 | granted to You under this License for that Work shall terminate 114 | as of the date such litigation is filed. 115 | 116 | 4. Redistribution. You may reproduce and distribute copies of the 117 | Work or Derivative Works thereof in any medium, with or without 118 | modifications, and in Source or Object form, provided that You 119 | meet the following conditions: 120 | 121 | (a) You must give any other recipients of the Work or 122 | Derivative Works a copy of this License; and 123 | 124 | (b) You must cause any modified files to carry prominent notices 125 | stating that You changed the files; and 126 | 127 | (c) You must retain, in the Source form of any Derivative Works 128 | that You distribute, all copyright, patent, trademark, and 129 | attribution notices from the Source form of the Work, 130 | excluding those notices that do not pertain to any part of 131 | the Derivative Works; and 132 | 133 | (d) If the Work includes a "NOTICE" text file as part of its 134 | distribution, then any Derivative Works that You distribute must 135 | include a readable copy of the attribution notices contained 136 | within such NOTICE file, excluding those notices that do not 137 | pertain to any part of the Derivative Works, in at least one 138 | of the following places: within a NOTICE text file distributed 139 | as part of the Derivative Works; within the Source form or 140 | documentation, if provided along with the Derivative Works; or, 141 | within a display generated by the Derivative Works, if and 142 | wherever such third-party notices normally appear. The contents 143 | of the NOTICE file are for informational purposes only and 144 | do not modify the License. You may add Your own attribution 145 | notices within Derivative Works that You distribute, alongside 146 | or as an addendum to the NOTICE text from the Work, provided 147 | that such additional attribution notices cannot be construed 148 | as modifying the License. 149 | 150 | (e) You may not steal the source code or any other content related to 151 | PartyBot w/o providing credit to the original owner/s. Meaning you 152 | cannot redistribute it without providing full credit and you also 153 | cannot market the product as your own w/o explicit permission from 154 | the owner/s. 155 | 156 | You may add Your own copyright statement to Your modifications and 157 | may provide additional or different license terms and conditions 158 | for use, reproduction, or distribution of Your modifications, or 159 | for any such Derivative Works as a whole, provided Your use, 160 | reproduction, and distribution of the Work otherwise complies with 161 | the conditions stated in this License. 162 | 163 | 5. Submission of Contributions. Unless You explicitly state otherwise, 164 | any Contribution intentionally submitted for inclusion in the Work 165 | by You to the Licensor shall be under the terms and conditions of 166 | this License, without any additional terms or conditions. 167 | Notwithstanding the above, nothing herein shall supersede or modify 168 | the terms of any separate license agreement you may have executed 169 | with Licensor regarding such Contributions. 170 | 171 | 6. Trademarks. This License does not grant permission to use the trade 172 | names, trademarks, service marks, or product names of the Licensor, 173 | except as required for reasonable and customary use in describing the 174 | origin of the Work and reproducing the content of the NOTICE file. 175 | 176 | 7. Disclaimer of Warranty. Unless required by applicable law or 177 | agreed to in writing, Licensor provides the Work (and each 178 | Contributor provides its Contributions) on an "AS IS" BASIS, 179 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 180 | implied, including, without limitation, any warranties or conditions 181 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 182 | PARTICULAR PURPOSE. You are solely responsible for determining the 183 | appropriateness of using or redistributing the Work and assume any 184 | risks associated with Your exercise of permissions under this License. 185 | 186 | 8. Limitation of Liability. In no event and under no legal theory, 187 | whether in tort (including negligence), contract, or otherwise, 188 | unless required by applicable law (such as deliberate and grossly 189 | negligent acts) or agreed to in writing, shall any Contributor be 190 | liable to You for damages, including any direct, indirect, special, 191 | incidental, or consequential damages of any character arising as a 192 | result of this License or out of the use or inability to use the 193 | Work (including but not limited to damages for loss of goodwill, 194 | work stoppage, computer failure or malfunction, or any and all 195 | other commercial damages or losses), even if such Contributor 196 | has been advised of the possibility of such damages. 197 | 198 | 9. Accepting Warranty or Additional Liability. While redistributing 199 | the Work or Derivative Works thereof, You may choose to offer, 200 | and charge a fee for, acceptance of support, warranty, indemnity, 201 | or other liability obligations and/or rights consistent with this 202 | License. However, in accepting such obligations, You may act only 203 | on Your own behalf and on Your sole responsibility, not on behalf 204 | of any other Contributor, and only if You agree to indemnify, 205 | defend, and hold each Contributor harmless for any liability 206 | incurred by, or claims asserted against, such Contributor by reason 207 | of your accepting any such warranty or additional liability. 208 | 209 | END OF TERMS AND CONDITIONS 210 | 211 | APPENDIX: How to apply the Apache License to your work. 212 | 213 | To apply the Apache License to your work, attach the following 214 | boilerplate notice, with the fields enclosed by brackets "[]" 215 | replaced with your own identifying information. (Don't include 216 | the brackets!) The text should be enclosed in the appropriate 217 | comment syntax for the file format. We also recommend that a 218 | file or class name and description of purpose be included on the 219 | same "printed page" as the copyright notice for easier 220 | identification within third-party archives. 221 | 222 | Copyright 2019-2020 Oli 223 | 224 | Licensed under the Apache License, Version 2.0 (the "License"); 225 | you may not use this file except in compliance with the License. 226 | You may obtain a copy of the License at 227 | 228 | http://www.apache.org/licenses/LICENSE-2.0 229 | 230 | Unless required by applicable law or agreed to in writing, software 231 | distributed under the License is distributed on an "AS IS" BASIS, 232 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 233 | See the License for the specific language governing permissions and 234 | limitations under the License. 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

PartyBot

2 | 3 |

4 | 5 | Downloads 6 | 7 | 8 | Python 9 | 10 | 11 | PEP8 12 | 13 |

14 | 15 |

A Fortnite HTTP/XMPP bot coded in Python with party capabilities.

16 | 17 | --- 18 | 19 | ## official Website 20 | [PartyBot.net](https://partybot.net) 21 | 22 | ## Discord Support 23 | 24 | 25 | ## Installation 26 | PartyBot requires Python 3.6.1 or greater. If you want Python 3.7 (the recommended version), you can get it from here: [Python 3.7.0 Download](https://www.python.org/ftp/python/3.7.0/python-3.7.0-amd64.exe "Python 3.6.1 Download"). 27 | 28 | 1. Install the required dependencies. 29 | 30 | ``` 31 | pip install -U -r requirements.txt 32 | ``` 33 | 34 | 2. [Register](https://epicgames.com/id/register) a new Epic Games account. 35 | 36 | 3. Configure your bot and enter the new Epic Games account details. 37 | 38 | 3. Launch the fortnite.py file and enjoy. 39 | 40 | 4. ***This step is optional and WILL NOT work on Windows.***
The bot will automatically use uvloop (fastest event loop) if it's installed. To install uvloop, enter this into terminal: 41 | 42 | ``` 43 | pip install -U uvloop 44 | ``` 45 | 46 | ## License 47 | By downloading this, you agree to the Commons Clause license and that you're not allowed to sell this repository or any code from this repository. For more info see https://commonsclause.com/. 48 | -------------------------------------------------------------------------------- /START BOT.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | :: Check for Python Installation 3 | py -3 --version 2>NUL 4 | if errorlevel 1 goto errorNoPython 5 | 6 | :: Reaching here means Python is installed. 7 | IF EXIST "python-3.6.0-amd64.exe" ( 8 | del "python-3.6.0-amd64.exe" 9 | ) 10 | 11 | cls 12 | TITLE PartyBot Official ^| github.com/xMistt/fortnitepy-bot 13 | @ECHO ON 14 | py fortnite.py 15 | cmd /k 16 | 17 | :: Once done, exit the batch file -- skips executing the errorNoPython section 18 | goto:eof 19 | 20 | :errorNoPython 21 | TITLE PartyBot^: Error 22 | ECHO Error^: Python not installed or has not been added to PATH. 23 | 24 | IF EXIST "python-3.7.0-amd64.exe" ( 25 | echo Python Installer is already installed, install and/or add Python to PATH 26 | ) ELSE ( 27 | ECHO Installing Python Installer now, this will take a minute or 2. 28 | powershell -Command "(New-Object Net.WebClient).DownloadFile('https://www.python.org/ftp/python/3.7.0/python-3.7.0-amd64.exe', 'python-3.7.0-amd64.exe')" 29 | powershell -Command "Invoke-WebRequest https://www.python.org/ftp/python/3.7.0/python-3.7.0-amd64.exe -OutFile python-3.7.0-amd64.exe" 30 | ECHO Python Installer is now installed, install and/or add Python to PATH. 31 | ) 32 | cmd /k -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cid": "CID_028_Athena_Commando_F", 3 | "bid": "BID_023_Pinkbear", 4 | "eid": "EID_NoDance", 5 | "pickaxe_id": "Pickaxe_Lockjaw", 6 | "banner": "otherbanner51", 7 | "banner_colour": "defaultcolor15", 8 | "level": 1002, 9 | "bp_tier": 999999999, 10 | "status": "discord.gg/8heARRB", 11 | "platform": "WIN", 12 | "debug": false, 13 | "friend_accept": true 14 | } 15 | -------------------------------------------------------------------------------- /device_auths.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /fortnite.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | “Commons Clause” License Condition v1.0 5 | Copyright Oli 2019-2021 6 | 7 | The Software is provided to you by the Licensor under the 8 | License, as defined below, subject to the following condition. 9 | 10 | Without limiting other conditions in the License, the grant 11 | of rights under the License will not include, and the License 12 | does not grant to you, the right to Sell the Software. 13 | 14 | For purposes of the foregoing, “Sell” means practicing any or 15 | all of the rights granted to you under the License to provide 16 | to third parties, for a fee or other consideration (including 17 | without limitation fees for hosting or consulting/ support 18 | services related to the Software), a product or service whose 19 | value derives, entirely or substantially, from the functionality 20 | of the Software. Any license notice or attribution required by 21 | the License must also include this Commons Clause License 22 | Condition notice. 23 | 24 | Software: PartyBot (fortnitepy-bot) 25 | 26 | License: Apache 2.0 Modified. 27 | """ 28 | 29 | try: 30 | # System imports. 31 | import asyncio 32 | import json 33 | import logging 34 | import sys 35 | import datetime 36 | 37 | # Third party imports. 38 | import partybot 39 | import aiofiles 40 | import rebootpy 41 | import crayons 42 | import aiohttp 43 | except ModuleNotFoundError as e: 44 | print(e) 45 | print('Failed to import 1 or more modules, running "INSTALL PACKAGES.bat" ' 46 | 'might fix the issue, if not please create an issue or join ' 47 | 'the support server.') 48 | sys.exit() 49 | 50 | # Imports uvloop and uses it if installed (Unix only). 51 | try: 52 | import uvloop 53 | except ImportError: 54 | pass 55 | else: 56 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 57 | 58 | if sys.platform == 'win32': 59 | asyncio.set_event_loop(asyncio.ProactorEventLoop()) 60 | 61 | 62 | def enable_debug() -> None: 63 | modules = { 64 | 'fortnitepy.http': 6, 65 | 'fortnitepy.xmpp': 5 66 | } 67 | 68 | for module, colour in module.items(): 69 | logger = logging.getLogger(module) 70 | logger.setLevel(level=logging.DEBUG) 71 | handler = logging.StreamHandler(sys.stdout) 72 | handler.setFormatter(logging.Formatter(f'\u001b[3{colour}m %(asctime)s:%(levelname)s:%(name)s: %(message)s' 73 | ' \u001b[0m')) 74 | logger.addHandler(handler) 75 | 76 | 77 | async def main() -> None: 78 | settings = partybot.BotSettings() 79 | 80 | await settings.load_settings_from_file('config.json') 81 | 82 | if settings.debug: 83 | enable_debug() 84 | 85 | try: 86 | async with aiohttp.ClientSession() as session: 87 | async with session.request( 88 | method="GET", 89 | url="https://partybot.net/api/discord", 90 | timeout=3 91 | ) as r: 92 | invite = (await r.json())['invite'] if r.status == 200 else "8heARRB" 93 | except (asyncio.TimeoutError, aiohttp.client_exceptions.ContenTypeError): 94 | invite = "8heARRB" 95 | 96 | print(crayons.cyan(f"[PartyBot] [{datetime.datetime.now().strftime('%H:%M:%S')}] PartyBot made by xMistt. " 97 | 'Massive credit to Terbau for creating the library.')) 98 | print(crayons.cyan(f"[PartyBot] [{datetime.datetime.now().strftime('%H:%M:%S')}] Discord server: " 99 | f"https://discord.gg/{invite} - For support, questions, etc.")) 100 | 101 | device_auths = partybot.DeviceAuths( 102 | filename='device_auths.json' 103 | ) 104 | 105 | try: 106 | await device_auths.load_device_auths() 107 | except partybot.errors.MissingDeviceAuth: 108 | print(f"[PartyBot] [{datetime.datetime.now().strftime('%H:%M:%S')}] Automatically opening Epic Games login, " 109 | f"please sign in.") 110 | 111 | gen = partybot.EpicGenerator() 112 | new_device_auths = await gen.generate_device_auths() 113 | device_auths.set_device_auth( 114 | **new_device_auths 115 | ) 116 | 117 | await device_auths.save_device_auths() 118 | 119 | client = partybot.PartyBot( 120 | settings=settings, 121 | device_auths=device_auths 122 | ) 123 | 124 | client.add_cog(partybot.CosmeticCommands(client)) 125 | client.add_cog(partybot.PartyCommands(client)) 126 | client.add_cog(partybot.ClientCommands(client)) 127 | 128 | try: 129 | await client.start() 130 | except fortnitepy.errors.AuthException as e: 131 | print(crayons.red(client.message % f"[ERROR] {e}")) 132 | 133 | await client.http.close() 134 | 135 | 136 | loop = asyncio.get_event_loop() 137 | loop.run_until_complete(main()) 138 | -------------------------------------------------------------------------------- /partybot/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | “Commons Clause” License Condition v1.0 5 | Copyright Oli 2019-2020 6 | 7 | The Software is provided to you by the Licensor under the 8 | License, as defined below, subject to the following condition. 9 | 10 | Without limiting other conditions in the License, the grant 11 | of rights under the License will not include, and the License 12 | does not grant to you, the right to Sell the Software. 13 | 14 | For purposes of the foregoing, “Sell” means practicing any or 15 | all of the rights granted to you under the License to provide 16 | to third parties, for a fee or other consideration (including 17 | without limitation fees for hosting or consulting/ support 18 | services related to the Software), a product or service whose 19 | value derives, entirely or substantially, from the functionality 20 | of the Software. Any license notice or attribution required by 21 | the License must also include this Commons Clause License 22 | Condition notice. 23 | 24 | Software: PartyBot (fortnitepy-bot) 25 | 26 | License: Apache 2.0 27 | """ 28 | 29 | from .settings import BotSettings 30 | from .bot import PartyBot 31 | from .deviceauths import DeviceAuth, DeviceAuths 32 | from .cosmetic import CosmeticCommands 33 | from .party import PartyCommands 34 | from .client import ClientCommands 35 | from .errors import MissingDeviceAuth 36 | from .generator import EpicGenerator 37 | -------------------------------------------------------------------------------- /partybot/bot.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # System imports. 28 | from typing import Any 29 | 30 | import uuid 31 | import datetime 32 | import asyncio 33 | 34 | # Third party imports. 35 | from rebootpy.ext import commands 36 | 37 | import rebootpy 38 | import crayons 39 | import FortniteAPIAsync 40 | import pypresence 41 | 42 | # Local imports 43 | from .settings import BotSettings 44 | from .deviceauths import DeviceAuth, DeviceAuths 45 | # from .helper import HelperFunctions 46 | 47 | 48 | class PartyBot(commands.Bot): 49 | def __init__(self, settings: BotSettings, device_auths: DeviceAuths) -> None: 50 | self.device_auths = device_auths.get_device_auth() 51 | self.settings = settings 52 | 53 | self.fortnite_api = FortniteAPIAsync.APIClient() 54 | 55 | super().__init__( 56 | command_prefix='!', 57 | auth=rebootpy.DeviceAuth( 58 | device_id=self.device_auths.device_id, 59 | account_id=self.device_auths.account_id, 60 | secret=self.device_auths.secret 61 | ), 62 | status=self.settings.status, 63 | platform=rebootpy.Platform(self.settings.platform) 64 | ) 65 | 66 | @property 67 | def message(self) -> str: 68 | return f'[PartyBot] [{datetime.datetime.now().strftime("%H:%M:%S")}] %s' 69 | 70 | async def start_discord_rich_presence(self) -> None: 71 | rpc = pypresence.AioPresence( 72 | client_id='717610574837710919', 73 | loop=self.loop 74 | ) 75 | 76 | try: 77 | await rpc.connect() 78 | except Exception as discord_error: 79 | print(f'There was an error: {discord_error}.') 80 | 81 | start_time = datetime.datetime.now().timestamp() 82 | 83 | while True: 84 | try: 85 | outfit = (await self.fortnite_api.cosmetics.get_cosmetic_from_id( 86 | fortnite_id=self.party.me.outfit 87 | )).name 88 | except FortniteAPIAsync.exceptions.NotFound: 89 | outfit = self.party.me.outfit 90 | 91 | await rpc.update( 92 | details=f"Logged in as {self.user.display_name}.", 93 | state=f"{self.party.leader.display_name}'s party.", 94 | large_image="skull_trooper", 95 | large_text="discord.gg/fnpy", 96 | small_image="outfit", 97 | small_text=outfit, 98 | start=int(start_time), 99 | party_id=self.party.id, 100 | party_size=[self.party.member_count, 16], 101 | join=uuid.uuid4().hex 102 | ) 103 | 104 | await asyncio.sleep(20) 105 | 106 | async def set_and_update_member_prop(self, schema_key: str, new_value: Any) -> None: 107 | prop = {schema_key: self.party.me.meta.set_prop(schema_key, new_value)} 108 | 109 | await self.party.me.patch(updated=prop) 110 | 111 | async def set_and_update_party_prop(self, schema_key: str, new_value: Any) -> None: 112 | prop = {schema_key: self.party.me.meta.set_prop(schema_key, new_value)} 113 | 114 | await self.party.patch(updated=prop) 115 | 116 | async def event_device_auth_generate(self, details: dict, email: str) -> None: 117 | device_auth = DeviceAuth( 118 | email=email, 119 | **details 120 | ) 121 | 122 | await self.device_auths.save_device_auth(device_auth) 123 | 124 | async def event_ready(self) -> None: 125 | print(crayons.green(self.message % f'Client ready as {self.user.display_name}.')) 126 | 127 | if self.party.me.leader: 128 | await self.party.set_privacy(rebootpy.PartyPrivacy.PUBLIC) 129 | 130 | # discord_exists = await self.loop.run_in_executor(None, HelperFunctions.check_if_process_running, 'Discord') 131 | 132 | # if discord_exists: 133 | # asyncio.get_event_loop().create_task(self.start_discord_rich_presence()) 134 | 135 | # NOTE: Ignore this commented out code below, I use it to generate the "docs". 136 | # command_names = [] 137 | # 138 | # for commands in self.commands: 139 | # command_names.append(commands.name) 140 | # 141 | # for command in command_names: 142 | # print(command) 143 | 144 | for pending in self.incoming_pending_friends: 145 | try: 146 | epic_friend = await pending.accept() if self.settings.friend_accept else await pending.decline() 147 | if isinstance(epic_friend, rebootpy.Friend): 148 | print(self.message % f"Accepted friend request from: {epic_friend.display_name}.") 149 | else: 150 | print(self.message % f"Declined friend request from: {pending.display_name}.") 151 | except rebootpy.HTTPException as epic_error: 152 | if epic_error.message_code != 'errors.com.epicgames.common.throttled': 153 | raise 154 | 155 | await asyncio.sleep(int(epic_error.message_vars[0] + 1)) 156 | await pending.accept() if self.settings.friend_accept else await pending.decline() 157 | 158 | async def event_party_invite(self, invite: rebootpy.ReceivedPartyInvitation) -> None: 159 | await invite.accept() 160 | print(self.message % f'Accepted party invite from {invite.sender.display_name}.') 161 | 162 | async def event_friend_request(self, request: rebootpy.IncomingPendingFriend) -> None: 163 | if isinstance(request, rebootpy.OutgoingPendingFriend): 164 | return 165 | 166 | print(self.message % f"Received friend request from: {request.display_name}.") 167 | 168 | if self.settings.friend_accept: 169 | await request.accept() 170 | print(self.message % f"Accepted friend request from: {request.display_name}.") 171 | else: 172 | await request.decline() 173 | print(self.message % f"Declined friend request from: {request.display_name}.") 174 | 175 | async def event_party_member_join(self, member: rebootpy.PartyMember) -> None: 176 | await FortniteAPIAsync.set_default_loadout( 177 | self, 178 | self.settings.to_dict(), 179 | member 180 | ) 181 | 182 | async def event_friend_message(self, message: rebootpy.FriendMessage) -> None: 183 | print(self.message % f'{message.author.display_name}: {message.content}') 184 | 185 | async def event_command_error(self, ctx: rebootpy.ext.commands.Context, 186 | error: rebootpy.ext.commands.CommandError) -> None: 187 | if isinstance(error, rebootpy.ext.commands.errors.CommandNotFound): 188 | if isinstance(ctx.message, rebootpy.FriendMessage): 189 | await ctx.send('Command not found, are you sure it exists?') 190 | else: 191 | pass 192 | elif isinstance(error, rebootpy.ext.commands.errors.MissingRequiredArgument): 193 | await ctx.send('Failed to execute commands as there are missing requirements, please check usage.') 194 | elif isinstance(error, rebootpy.ext.commands.errors.PrivateMessageOnly): 195 | pass 196 | else: 197 | await ctx.send(f'When trying to process !{ctx.command.name}, an error occured: "{error}"\n' 198 | f'Please report this on Discord or GitHub.') 199 | raise error 200 | -------------------------------------------------------------------------------- /partybot/client.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # System imports. 28 | import os 29 | import sys 30 | 31 | from typing import Optional, Union 32 | 33 | # Third party imports. 34 | import rebootpy 35 | import aiohttp 36 | import crayons 37 | 38 | from rebootpy.ext import commands 39 | 40 | 41 | class ClientCommands(commands.Cog): 42 | def __init__(self, bot: commands.Bot) -> None: 43 | self.bot = bot 44 | 45 | @commands.dm_only() 46 | @commands.command( 47 | description="[Client] Sends and sets the status.", 48 | help="Sends and sets the status.\n" 49 | "Example: !status Presence Unknown" 50 | ) 51 | async def status(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 52 | await self.bot.set_presence(content) 53 | 54 | await ctx.send(f'Status set to {content}') 55 | print(self.bot.message % f'Status set to {content}.') 56 | 57 | @commands.dm_only() 58 | @commands.command( 59 | aliases=['clear'], 60 | description="[Client] Clears command prompt/terminal.", 61 | help="Clears command prompt/terminal.\n" 62 | "Example: !clean" 63 | ) 64 | async def clean(self, ctx: rebootpy.ext.commands.Context) -> None: 65 | os.system('cls' if 'win' in sys.platform else 'clear') 66 | 67 | print(crayons.cyan(self.bot.message % f'PartyBot made by xMistt. ' 68 | 'Massive credit to Terbau for creating the library.')) 69 | print(crayons.cyan( 70 | self.bot.message % f'Discord server: https://discord.gg/8heARRB - For support, questions, etc.')) 71 | 72 | await ctx.send('Command prompt/terminal cleared.') 73 | print(self.bot.message % f'Command prompt/terminal cleared.') 74 | 75 | @commands.dm_only() 76 | @commands.command( 77 | description="[Client] Sends and sets the status to away.", 78 | help="Sends and sets the status to away.\n" 79 | "Example: !away" 80 | ) 81 | async def away(self, ctx: rebootpy.ext.commands.Context) -> None: 82 | await self.bot.set_presence( 83 | status=self.bot.status, 84 | away=rebootpy.AwayStatus.AWAY 85 | ) 86 | 87 | await ctx.send('Status set to away, you can use !status to revert.') 88 | print(self.bot.message % f'Status set to away.') 89 | 90 | @commands.dm_only() 91 | @commands.command( 92 | aliases=['updates'], 93 | description="[Client] Sends the most recent commit/s.", 94 | help="Sends the most recent commit/s.\n" 95 | "Example: !update" 96 | ) 97 | async def update(self, ctx: rebootpy.ext.commands.Context) -> None: 98 | async with aiohttp.ClientSession() as session: 99 | async with session.request( 100 | method="GET", 101 | url="https://api.github.com/repos/xMistt/rebootpy-bot/commits/master" 102 | ) as request: 103 | data = await request.json() 104 | 105 | date = rebootpy.Client.from_iso(data['commit']['committer']['date']) 106 | pretty_date = f'{date.day}/{date.month}/{date.year} @ {date.hour}:{date.minute}' 107 | commit_title = data['commit']['message'].split('\n')[0] 108 | 109 | await ctx.send(f"Last commit by {data['committer']['login']} made on {pretty_date}:\n" 110 | f"[{data['sha'][0:7]}] {commit_title}") 111 | 112 | print(self.bot.message % f'Sent last commit information.') 113 | 114 | @commands.dm_only() 115 | @commands.command( 116 | description="[Party] Sends the defined user a friend request.", 117 | help="Sends the defined user a friend request.\n" 118 | "Example: !friend Ninja" 119 | ) 120 | async def friend(self, ctx: rebootpy.ext.commands.Context, *, epic_username: str) -> None: 121 | user = await self.bot.fetch_user(epic_username) 122 | 123 | if user is not None: 124 | await self.bot.add_friend(user.id) 125 | await ctx.send(f'Sent/accepted friend request to/from {user.display_name}.') 126 | print(self.bot.message % f'Sent/accepted friend request to/from {user.display_name}.') 127 | else: 128 | await ctx.send(f'Failed to find user with the name: {epic_username}.') 129 | print( 130 | crayons.red(self.bot.message % f"[ERROR] Failed to find a user with the name {epic_username}.")) 131 | -------------------------------------------------------------------------------- /partybot/cosmetic.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # System imports. 28 | import asyncio 29 | import functools 30 | 31 | from typing import Optional, Union, Tuple 32 | 33 | # Third party imports. 34 | import rebootpy 35 | import aiohttp 36 | import FortniteAPIAsync 37 | import random as py_random 38 | 39 | from rebootpy.ext import commands 40 | 41 | 42 | class CosmeticCommands(commands.Cog): 43 | def __init__(self, bot: commands.Bot) -> None: 44 | self.bot = bot 45 | 46 | # async def set_vtid(self, variant_token: str) -> Tuple[str, str, int]: 47 | # async with aiohttp.ClientSession() as session: 48 | # request = await session.request( 49 | # method='GET', 50 | # url='https://benbot.app/api/v1/assetProperties', 51 | # params={ 52 | # 'path': 'FortniteGame/Content/Athena/' 53 | # f'Items/CosmeticVariantTokens/{variant_token}.uasset' 54 | # }) 55 | # 56 | # response = await request.json() 57 | # 58 | # file_location = response['export_properties'][0] 59 | # 60 | # skin_cid = file_location['cosmetic_item'] 61 | # variant_channel_tag = file_location['VariantChanelTag']['TagName'] 62 | # variant_name_tag = file_location['VariantNameTag']['TagName'] 63 | # 64 | # variant_type = variant_channel_tag.split( 65 | # 'Cosmetics.Variant.Channel.' 66 | # )[1].split('.')[0] 67 | # 68 | # variant_int = int("".join(filter( 69 | # lambda x: x.isnumeric(), variant_name_tag 70 | # ))) 71 | # 72 | # return skin_cid, variant_type if variant_type != 'ClothingColor' else 'clothing_color', variant_int 73 | 74 | @commands.dm_only() 75 | @commands.command( 76 | description="[Cosmetic] Sets the outfit of the client using the outfits name.", 77 | help="Sets the outfit of the client using the outfits name.\n" 78 | "Example: !skin Nog Ops" 79 | ) 80 | async def skin(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 81 | try: 82 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 83 | matchMethod="contains", 84 | name=content, 85 | backendType="AthenaCharacter" 86 | ) 87 | except FortniteAPIAsync.exceptions.NotFound: 88 | print(self.bot.message % f"Failed to find a skin with the name: {content}.") 89 | return await ctx.send(f"Failed to find a skin with the name: {content}.") 90 | 91 | await self.bot.party.me.set_outfit(asset=cosmetic.id) 92 | 93 | await ctx.send(f'Skin set to {cosmetic.id}.') 94 | print(self.bot.message % f"Set skin to: {cosmetic.id}.") 95 | 96 | 97 | @commands.dm_only() 98 | @commands.command( 99 | description="[Cosmetic] Sets the backpack of the client using the backpacks name.", 100 | help="Sets the backpack of the client using the backpacks name.\n" 101 | "Example: !backpack Black Shield" 102 | ) 103 | async def backpack(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 104 | try: 105 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 106 | matchMethod="contains", 107 | name=content, 108 | backendType="AthenaBackpack" 109 | ) 110 | except FortniteAPIAsync.exceptions.NotFound: 111 | print(self.bot.message % f"Failed to find a backpack with the name: {content}.") 112 | return await ctx.send(f"Failed to find a backpack with the name: {content}.") 113 | 114 | if "brcosmetics" in cosmetic.path.lower(): 115 | await self.bot.party.me.set_backpack(asset=cosmetic.id) 116 | else: 117 | path = f"/Game/Athena/Items/Cosmetics/Backpacks/{cosmetic.id}.{cosmetic.id}" 118 | await self.bot.party.me.set_backpack(asset=path) 119 | 120 | await ctx.send(f'Backpack set to {cosmetic.id}.') 121 | print(self.bot.message % f"Set backpack to: {cosmetic.id}.") 122 | 123 | @commands.dm_only() 124 | @commands.command( 125 | description="[Cosmetic] Sets the emote of the client using the emotes name.", 126 | help="Sets the emote of the client using the emotes name.\n" 127 | "Example: !emote Windmill Floss" 128 | ) 129 | async def emote(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 130 | try: 131 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 132 | matchMethod="contains", 133 | name=content, 134 | backendType="AthenaDance" 135 | ) 136 | except FortniteAPIAsync.exceptions.NotFound: 137 | print(self.bot.message % f"Failed to find an emote with the name: {content}.") 138 | return await ctx.send(f"Failed to find an emote with the name: {content}.") 139 | 140 | await self.bot.party.me.clear_emote() 141 | if "brcosmetics" in cosmetic.path.lower(): 142 | await self.bot.party.me.set_emote(asset=cosmetic.id) 143 | else: 144 | path = f"/Game/Athena/Items/Cosmetics/Dances/{cosmetic.id}.{cosmetic.id}" 145 | await self.bot.party.me.set_emote(asset=path) 146 | 147 | await ctx.send(f'Emote set to {cosmetic.id}.') 148 | print(self.bot.message % f"Set emote to: {cosmetic.id}.") 149 | 150 | @commands.dm_only() 151 | @commands.command( 152 | description="[Cosmetic] Sets the pickaxe of the client using the pickaxe name.", 153 | help="Sets the pickaxe of the client using the pickaxe name.\n" 154 | "Example: !pickaxe Raider's Revenge" 155 | ) 156 | async def pickaxe(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 157 | try: 158 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 159 | matchMethod="contains", 160 | name=content, 161 | backendType="AthenaPickaxe" 162 | ) 163 | except FortniteAPIAsync.exceptions.NotFound: 164 | print(self.bot.message % f"Failed to find a pickaxe with the name: {content}.") 165 | return await ctx.send(f"Failed to find a pickaxe with the name: {content}.") 166 | 167 | if "brcosmetics" in cosmetic.path.lower(): 168 | await self.bot.party.me.set_pickaxe(asset=cosmetic.id) 169 | else: 170 | path = f"/Game/Athena/Items/Cosmetics/PickAxes/{cosmetic.id}.{cosmetic.id}" 171 | await self.bot.party.me.set_pickaxe(asset=path) 172 | 173 | await ctx.send(f'Pickaxe set to {cosmetic.id}.') 174 | print(self.bot.message % f"Set pickaxe to: {cosmetic.id}.") 175 | 176 | @commands.dm_only() 177 | @commands.command( 178 | description="[Cosmetic] Sets the pet (backpack) of the client using the pets name.", 179 | help="Sets the pet (backpack) of the client using the pets name.\n" 180 | "Example: !pet Bonesy" 181 | ) 182 | async def pet(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 183 | try: 184 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 185 | matchMethod="contains", 186 | name=content, 187 | backendType="AthenaPetCarrier" 188 | ) 189 | except FortniteAPIAsync.exceptions.NotFound: 190 | print(self.bot.message % f"Failed to find a pet with the name: {content}.") 191 | return await ctx.send(f"Failed to find a pet with the name: {content}.") 192 | 193 | if "brcosmetics" in cosmetic.path.lower(): 194 | await self.bot.party.me.set_pet(asset=cosmetic.id) 195 | else: 196 | path = f"/Game/Athena/Items/Cosmetics/PetCarriers/{cosmetic.id}.{cosmetic.id}" 197 | await self.bot.party.me.set_pet(asset=path) 198 | 199 | await ctx.send(f'Pet set to {cosmetic.id}.') 200 | print(self.bot.message % f"Set pet to: {cosmetic.id}.") 201 | 202 | @commands.dm_only() 203 | @commands.command( 204 | description="[Cosmetic] Sets the emoji of the client using the emojis name.", 205 | help="Sets the emoji of the client using the emojis name.\n" 206 | "Example: !emoji Snowball" 207 | ) 208 | async def emoji(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 209 | try: 210 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 211 | matchMethod="contains", 212 | name=content, 213 | backendType="AthenaEmoji" 214 | ) 215 | except FortniteAPIAsync.exceptions.NotFound: 216 | print(self.bot.message % f"Failed to find an emoji with the name: {content}.") 217 | return await ctx.send(f"Failed to find an emoji with the name: {content}.") 218 | 219 | await self.bot.party.me.clear_emote() 220 | if "brcosmetics" in cosmetic.path.lower(): 221 | await self.bot.party.me.set_emoji(asset=cosmetic.id) 222 | else: 223 | path = f"/Game/Athena/Items/Cosmetics/Dances/Emoji/{cosmetic.id}.{cosmetic.id}" 224 | await self.bot.party.me.set_emoji(asset=path) 225 | 226 | await ctx.send(f'Emoji set to {cosmetic.id}.') 227 | print(self.bot.message % f"Set emoji to: {cosmetic.id}.") 228 | 229 | @commands.dm_only() 230 | @commands.command( 231 | description="[Cosmetic] Sets the contrail of the client using the contrail name.", 232 | help="Sets the contrail of the client using the contrail name.\n" 233 | "Example: !contrail Holly And Divey" 234 | ) 235 | async def contrail(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 236 | try: 237 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 238 | matchMethod="contains", 239 | name=content, 240 | backendType="AthenaSkyDiveContrail" 241 | ) 242 | except FortniteAPIAsync.exceptions.NotFound: 243 | print(self.bot.message % f"Failed to find an contrail with the name: {content}.") 244 | return await ctx.send(f"Failed to find a contrail with the name: {content}.") 245 | 246 | if "brcosmetics" in cosmetic.path.lower(): 247 | await self.bot.party.me.set_contrail(asset=cosmetic.id) 248 | else: 249 | path = f"/Game/Athena/Items/Cosmetics/Contrails/{cosmetic.id}.{cosmetic.id}" 250 | await self.bot.party.me.set_contrail(asset=path) 251 | 252 | await ctx.send(f'Contrail set to {cosmetic.id}.') 253 | print(self.bot.message % f"Set contrail to: {cosmetic.id}.") 254 | 255 | @commands.dm_only() 256 | @commands.command( 257 | description="[Cosmetic] Sets the outfit of the client to Purple Skull Trooper.", 258 | help="Sets the outfit of the client to Purple Skull Trooper.\n" 259 | "Example: !purpleskull" 260 | ) 261 | async def purpleskull(self, ctx: rebootpy.ext.commands.Context) -> None: 262 | skin_variants = self.bot.party.me.create_variants( 263 | clothing_color=1 264 | ) 265 | 266 | await self.bot.party.me.set_outfit( 267 | asset='CID_030_Athena_Commando_M_Halloween', 268 | variants=skin_variants 269 | ) 270 | 271 | await ctx.send('Skin set to Purple Skull Trooper!') 272 | print(self.bot.message % f"Skin set to Purple Skull Trooper.") 273 | 274 | @commands.dm_only() 275 | @commands.command( 276 | description="[Cosmetic] Sets the outfit of the client to Pink Ghoul Trooper.", 277 | help="Sets the outfit of the client to Pink Ghoul Trooper.\n" 278 | "Example: !pinkghoul" 279 | ) 280 | async def pinkghoul(self, ctx: rebootpy.ext.commands.Context) -> None: 281 | skin_variants = self.bot.party.me.create_variants( 282 | material=3 283 | ) 284 | 285 | await self.bot.party.me.set_outfit( 286 | asset='CID_029_Athena_Commando_F_Halloween', 287 | variants=skin_variants 288 | ) 289 | 290 | await ctx.send('Skin set to Pink Ghoul Trooper!') 291 | print(self.bot.message % f"Skin set to Pink Ghoul Trooper.") 292 | 293 | @commands.dm_only() 294 | @commands.command( 295 | description="[Cosmetic] Sets the backpack of the client to Purple Ghost Portal.", 296 | help="Sets the backpack of the client to Purple Ghost Portal.\n" 297 | "Example: !purpleportal" 298 | ) 299 | async def purpleportal(self, ctx: rebootpy.ext.commands.Context) -> None: 300 | skin_variants = self.bot.party.me.create_variants( 301 | config_overrides={ 302 | 'particle': 'Particle{}' 303 | }, 304 | particle=1 305 | ) 306 | 307 | await self.bot.party.me.set_backpack( 308 | asset='BID_105_GhostPortal', 309 | variants=skin_variants 310 | ) 311 | 312 | await ctx.send('Backpack set to Purple Ghost Portal!') 313 | print(self.bot.message % f"Backpack set to Purple Ghost Portal.") 314 | 315 | @commands.dm_only() 316 | @commands.command( 317 | description="[Cosmetic] Sets the outfit of the client using CID.", 318 | help="Sets the outfit of the client using CID.\n" 319 | "Example: !cid CID_047_Athena_Commando_F_HolidayReindeer" 320 | ) 321 | async def cid(self, ctx: rebootpy.ext.commands.Context, character_id: str) -> None: 322 | await self.bot.party.me.set_outfit( 323 | asset=character_id, 324 | variants=self.bot.party.me.create_variants(profile_banner='ProfileBanner') 325 | ) 326 | 327 | await ctx.send(f'Skin set to {character_id}.') 328 | print(self.bot.message % f'Skin set to {character_id}.') 329 | 330 | # NOTE: Command is currently not possible due to no APIs allowing you to browse the files, hope to fix eventually. 331 | # @commands.dm_only() 332 | # @commands.command( 333 | # description="[Cosmetic] Creates the variants list by the variants you set using VTID.", 334 | # help="Creates the variants list by the variants you set using VTID.\n" 335 | # "Example: !vtid VTID_052_Skull_Trooper_RedFlames" 336 | # ) 337 | # async def vtid(self, ctx: rebootpy.ext.commands.Context, variant_token: str) -> None: 338 | # variant_id = await self.set_vtid(variant_token) 339 | # 340 | # if variant_id[1].lower() == 'particle': 341 | # skin_variants = self.bot.party.me.create_variants(config_overrides={'particle': 'Particle{}'}, particle=1) 342 | # else: 343 | # skin_variants = self.bot.party.me.create_variants(**{variant_id[1].lower(): int(variant_id[2])}) 344 | # 345 | # await self.bot.party.me.set_outfit(asset=variant_id[0], variants=skin_variants) 346 | # print(self.bot.message % f'Set variants of {variant_id[0]} to {variant_id[1]} {variant_id[2]}.') 347 | # await ctx.send(f'Variants set to {variant_token}.\n' 348 | # '(Warning: This feature is not supported, please use !variants)') 349 | 350 | @commands.dm_only() 351 | @commands.command( 352 | description="[Cosmetic] Creates the variants list by the variants you set.", 353 | help="Creates the variants list by the variants you set.\n" 354 | "Example: !variants CID_030_Athena_Commando_M_Halloween clothing_color 1" 355 | ) 356 | async def variants(self, ctx: rebootpy.ext.commands.Context, cosmetic_id: str, variant_type: str, 357 | variant_int: str) -> None: 358 | if 'cid' in cosmetic_id.lower() and 'jersey_color' not in variant_type.lower(): 359 | skin_variants = self.bot.party.me.create_variants( 360 | **{variant_type: int(variant_int) if variant_int.isdigit() else variant_int} 361 | ) 362 | 363 | await self.bot.party.me.set_outfit( 364 | asset=cosmetic_id, 365 | variants=skin_variants 366 | ) 367 | 368 | elif 'cid' in cosmetic_id.lower() and 'jersey_color' in variant_type.lower(): 369 | cosmetic_variants = self.bot.party.me.create_variants( 370 | pattern=0, 371 | numeric=69, 372 | **{variant_type: int(variant_int) if variant_int.isdigit() else variant_int} 373 | ) 374 | 375 | await self.bot.party.me.set_outfit( 376 | asset=cosmetic_id, 377 | variants=cosmetic_variants 378 | ) 379 | 380 | elif 'bid' in cosmetic_id.lower(): 381 | cosmetic_variants = self.bot.party.me.create_variants( 382 | **{variant_type: int(variant_int) if variant_int.isdigit() else variant_int} 383 | ) 384 | 385 | await self.bot.party.me.set_backpack( 386 | asset=cosmetic_id, 387 | variants=cosmetic_variants 388 | ) 389 | elif 'pickaxe_id' in cosmetic_id.lower(): 390 | cosmetic_variants = self.bot.party.me.create_variants( 391 | **{variant_type: int(variant_int) if variant_int.isdigit() else variant_int} 392 | ) 393 | 394 | await self.bot.party.me.set_pickaxe( 395 | asset=cosmetic_id, 396 | variants=cosmetic_variants 397 | ) 398 | 399 | await ctx.send(f'Set variants of {cosmetic_id} to {variant_type} {variant_int}.') 400 | print(self.bot.message % f'Set variants of {cosmetic_id} to {variant_type} {variant_int}.') 401 | 402 | @commands.dm_only() 403 | @commands.command( 404 | description="[Cosmetic] Sets the outfit of the client to Checkered Renegade.", 405 | help="Sets the outfit of the client to Checkered Renegade.\n" 406 | "Example: !checkeredrenegade" 407 | ) 408 | async def checkeredrenegade(self, ctx: rebootpy.ext.commands.Context) -> None: 409 | skin_variants = self.bot.party.me.create_variants( 410 | material=2 411 | ) 412 | 413 | await self.bot.party.me.set_outfit( 414 | asset='CID_028_Athena_Commando_F', 415 | variants=skin_variants 416 | ) 417 | 418 | await ctx.send('Skin set to Checkered Renegade!') 419 | print(self.bot.message % f'Skin set to Checkered Renegade.') 420 | 421 | @commands.dm_only() 422 | @commands.command( 423 | description="[Cosmetic] Sets the outfit of the client to Minty Elf.", 424 | help="Sets the outfit of the client to Minty Elf.\n" 425 | "Example: !mintyelf" 426 | ) 427 | async def mintyelf(self, ctx: rebootpy.ext.commands.Context) -> None: 428 | skin_variants = self.bot.party.me.create_variants( 429 | material=2 430 | ) 431 | 432 | await self.bot.party.me.set_outfit( 433 | asset='CID_051_Athena_Commando_M_HolidayElf', 434 | variants=skin_variants 435 | ) 436 | 437 | await ctx.send('Skin set to Minty Elf!') 438 | print(self.bot.message % f'Skin set to Minty Elf.') 439 | 440 | @commands.dm_only() 441 | @commands.command( 442 | description="[Cosmetic] Sets the emote of the client using EID.", 443 | help="Sets the emote of the client using EID.\n" 444 | "Example: !eid EID_Floss" 445 | ) 446 | async def eid(self, ctx: rebootpy.ext.commands.Context, emote_id: str) -> None: 447 | await self.bot.party.me.clear_emote() 448 | await self.bot.party.me.set_emote( 449 | asset=emote_id 450 | ) 451 | 452 | await ctx.send(f'Emote set to {emote_id}!') 453 | 454 | @commands.dm_only() 455 | @commands.command( 456 | description="[Cosmetic] Clears/stops the emote currently playing.", 457 | help="Clears/stops the emote currently playing.\n" 458 | "Example: !stop" 459 | ) 460 | async def stop(self, ctx: rebootpy.ext.commands.Context) -> None: 461 | await self.bot.party.me.clear_emote() 462 | await ctx.send('Stopped emoting.') 463 | print(self.bot.message % f'Stopped emoting.') 464 | 465 | @commands.dm_only() 466 | @commands.command( 467 | description="[Cosmetic] Sets the backpack of the client using BID.", 468 | help="Sets the backpack of the client using BID.\n" 469 | "Example: !bid BID_023_Pinkbear" 470 | ) 471 | async def bid(self, ctx: rebootpy.ext.commands.Context, backpack_id: str) -> None: 472 | await self.bot.party.me.set_backpack( 473 | asset=backpack_id 474 | ) 475 | 476 | await ctx.send(f'Backbling set to {backpack_id}!') 477 | print(self.bot.message % f'Backbling set to {backpack_id}!') 478 | 479 | @commands.dm_only() 480 | @commands.command( 481 | aliases=['legacypickaxe'], 482 | description="[Cosmetic] Sets the pickaxe of the client using PICKAXE_ID", 483 | help="Sets the pickaxe of the client using PICKAXE_ID\n" 484 | "Example: !pickaxe_id Pickaxe_ID_073_Balloon" 485 | ) 486 | async def pickaxe_id(self, ctx: rebootpy.ext.commands.Context, pickaxe_id_: str) -> None: 487 | await self.bot.party.me.set_pickaxe( 488 | asset=pickaxe_id_ 489 | ) 490 | 491 | await ctx.send(f'Pickaxe set to {pickaxe_id_}') 492 | print(self.bot.message % f'Pickaxe set to {pickaxe_id_}') 493 | 494 | @commands.dm_only() 495 | @commands.command( 496 | description="[Cosmetic] Sets the pet of the client using PetCarrier_.", 497 | help="Sets the pet of the client using PetCarrier_.\n" 498 | "Example: !pet_carrier PetCarrier_002_Chameleon" 499 | ) 500 | async def pet_carrier(self, ctx: rebootpy.ext.commands.Context, pet_carrier_id: str) -> None: 501 | await self.bot.party.me.set_pet( 502 | asset=pet_carrier_id 503 | ) 504 | 505 | await ctx.send(f'Pet set to {pet_carrier_id}!') 506 | print(self.bot.message % f'Pet set to {pet_carrier_id}!') 507 | 508 | @commands.dm_only() 509 | @commands.command( 510 | description="[Cosmetic] Sets the emoji of the client using Emoji_.", 511 | help="Sets the emoji of the client using Emoji_.\n" 512 | "Example: !emoji_id Emoji_PeaceSign" 513 | ) 514 | async def emoji_id(self, ctx: rebootpy.ext.commands.Context, emoji_: str) -> None: 515 | await self.bot.party.me.clear_emote() 516 | await self.bot.party.me.set_emoji( 517 | asset=emoji_ 518 | ) 519 | 520 | await ctx.send(f'Emoji set to {emoji_}!') 521 | print(self.bot.message % f'Emoji set to {emoji_}!') 522 | 523 | @commands.dm_only() 524 | @commands.command( 525 | description="[Cosmetic] Sets the contrail of the client using Trails_.", 526 | help="Sets the contrail of the client using Trails_.\n" 527 | "Example: !trails Trails_ID_075_Celestial" 528 | ) 529 | async def trails(self, ctx: rebootpy.ext.commands.Context, trails_: str) -> None: 530 | await self.bot.party.me.set_contrail( 531 | asset=trails_ 532 | ) 533 | 534 | await ctx.send(f'Contrail set to {trails}!') 535 | print(self.bot.message % f'Contrail set to {trails}!') 536 | 537 | @commands.dm_only() 538 | @commands.command( 539 | description="[Cosmetic] Sets pickaxe using PICKAXE_ID or display name & does 'Point it Out'. If no pickaxe is " 540 | "specified, only the emote will be played.", 541 | help="Sets pickaxe using PICKAXE_ID or display name & does 'Point it Out'. If no pickaxe is " 542 | "specified, only the emote will be played.\n" 543 | "Example: !point Pickaxe_ID_029_Assassin" 544 | ) 545 | async def point(self, ctx: rebootpy.ext.commands.Context, *, content: Optional[str] = None) -> None: 546 | if content is None: 547 | await self.bot.party.me.set_emote(asset='EID_None') 548 | await self.bot.party.me.set_emote(asset='EID_IceKing') 549 | await ctx.send(f'Point it Out played.') 550 | elif 'pickaxe_id' in content.lower(): 551 | await self.bot.party.me.set_pickaxe(asset=content) 552 | await self.bot.party.me.set_emote(asset='EID_None') 553 | await self.bot.party.me.set_emote(asset='EID_IceKing') 554 | await ctx.send(f'Pickaxe set to {content} & Point it Out played.') 555 | else: 556 | try: 557 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 558 | matchMethod="contains", 559 | name=content, 560 | backendType="AthenaPickaxe" 561 | ) 562 | except FortniteAPIAsync.exceptions.NotFound: 563 | print(self.bot.message % f"Failed to find a pickaxe with the name: {content}.") 564 | return await ctx.send(f"Failed to find a pickaxe with the name: {content}.") 565 | 566 | if "brcosmetics" in cosmetic.path.lower(): 567 | await self.bot.party.me.set_pickaxe(asset=cosmetic.id) 568 | else: 569 | path = f"/Game/Athena/Items/Cosmetics/PickAxes/{cosmetic.id}.{cosmetic.id}" 570 | await self.bot.party.me.set_pickaxe(asset=path) 571 | 572 | await self.bot.party.me.set_emote(asset='EID_None') 573 | await self.bot.party.me.set_emote(asset='EID_IceKing') 574 | 575 | await ctx.send(f'Pickaxe set to {content} & Point it Out played.') 576 | 577 | @commands.dm_only() 578 | @commands.command( 579 | description="[Cosmetic] Copies the cosmetic loadout of the defined user. If user is left blank, " 580 | "the message author will be used.", 581 | help="Copies the cosmetic loadout of the defined user. If user is left blank, " 582 | "the message author will be used.\n" 583 | "Example: !copy Terbau" 584 | ) 585 | async def copy(self, ctx: rebootpy.ext.commands.Context, *, epic_username: Optional[str] = None) -> None: 586 | if epic_username is None: 587 | member = [m for m in self.bot.party.members if m.id == ctx.author.id][0] 588 | else: 589 | user = await self.bot.fetch_user(epic_username) 590 | member = [m for m in self.bot.party.members if m.id == user.id][0] 591 | 592 | await self.bot.party.me.edit( 593 | functools.partial( 594 | rebootpy.ClientPartyMember.set_outfit, 595 | asset=member.outfit, 596 | variants=member.outfit_variants 597 | ), 598 | functools.partial( 599 | rebootpy.ClientPartyMember.set_backpack, 600 | asset=member.backpack, 601 | variants=member.backpack_variants 602 | ), 603 | functools.partial( 604 | rebootpy.ClientPartyMember.set_pickaxe, 605 | asset=member.pickaxe, 606 | variants=member.pickaxe_variants 607 | ), 608 | functools.partial( 609 | rebootpy.ClientPartyMember.set_banner, 610 | icon=member.banner[0], 611 | color=member.banner[1], 612 | season_level=member.banner[2] 613 | ), 614 | functools.partial( 615 | rebootpy.ClientPartyMember.set_battlepass_info, 616 | has_purchased=True, 617 | level=member.battlepass_info[1] 618 | ) 619 | ) 620 | 621 | if member.emote is not None: 622 | await self.bot.party.me.set_emote(asset=member.emote) 623 | 624 | await ctx.send(f'Copied the loadout of {member.display_name}.') 625 | print(self.bot.message % f'Copied the loadout of {member.display_name}.') 626 | 627 | @commands.dm_only() 628 | @commands.command( 629 | description="[Cosmetic] Shortcut for equipping the skin CID_VIP_Athena_Commando_M_GalileoGondola_SG.", 630 | help="Shortcut for equipping the skin CID_VIP_Athena_Commando_M_GalileoGondola_SG.\n" 631 | "Example: !hologram" 632 | ) 633 | async def hologram(self, ctx: rebootpy.ext.commands.Context) -> None: 634 | await self.bot.party.me.set_outfit( 635 | asset='CID_VIP_Athena_Commando_M_GalileoGondola_SG' 636 | ) 637 | 638 | await ctx.send('Skin set to Star Wars Hologram!') 639 | print(self.bot.message % f'Skin set to Star Wars Hologram.') 640 | 641 | @commands.dm_only() 642 | @commands.command( 643 | description="[Cosmetic] Shortcut for equipping the skin CID_VIP_Athena_Commando_M_GalileoGondola_SG.", 644 | help="Shortcut for equipping the skin CID_VIP_Athena_Commando_M_GalileoGondola_SG.\n" 645 | "Example: !gift is a joke command." 646 | ) 647 | async def gift(self, ctx: rebootpy.ext.commands.Context) -> None: 648 | await self.bot.party.me.clear_emote() 649 | 650 | await self.bot.party.me.set_emote( 651 | asset='EID_NeverGonna' 652 | ) 653 | 654 | await ctx.send('What did you think would happen?') 655 | 656 | @commands.dm_only() 657 | @commands.command( 658 | description="[Cosmetic] Shortcut for equipping the emote EID_TourBus.", 659 | help="Shortcut for equipping the emote EID_TourBus.\n" 660 | "Example: !ponpon" 661 | ) 662 | async def ponpon(self, ctx: rebootpy.ext.commands.Context) -> None: 663 | await self.bot.party.me.set_emote( 664 | asset='EID_TourBus' 665 | ) 666 | 667 | await ctx.send('Emote set to Ninja Style!') 668 | 669 | @commands.dm_only() 670 | @commands.command( 671 | description="[Cosmetic] Sets the enlightened value of a skin " 672 | "(used for skins such as glitched Scratch or Golden Peely).", 673 | help="Sets the enlightened value of a skin.\n" 674 | "Example: !enlightened CID_701_Athena_Commando_M_BananaAgent 2 350" 675 | ) 676 | async def enlightened(self, ctx: rebootpy.ext.commands.Context, cosmetic_id: str, br_season: int, 677 | skin_level: int) -> None: 678 | variant_types = { 679 | 1: self.bot.party.me.create_variants(progressive=4), 680 | 2: self.bot.party.me.create_variants(progressive=4), 681 | 3: self.bot.party.me.create_variants(material=2) 682 | } 683 | 684 | if 'cid' in cosmetic_id.lower(): 685 | await self.bot.party.me.set_outfit( 686 | asset=cosmetic_id, 687 | variants=variant_types[br_season] if br_season in variant_types else variant_types[2], 688 | enlightenment=(br_season, skin_level) 689 | ) 690 | 691 | await ctx.send(f'Skin set to {cosmetic_id} at level {skin_level} (for Season 1{br_season}).') 692 | elif 'bid' in cosmetic_id.lower(): 693 | await self.bot.party.me.set_backpack( 694 | asset=cosmetic_id, 695 | variants=self.bot.party.me.create_variants(progressive=2), 696 | enlightenment=(br_season, skin_level) 697 | ) 698 | await ctx.send(f'Backpack set to {cosmetic_id} at level {skin_level} (for Season 1{br_season}).') 699 | 700 | print( 701 | self.bot.message % f'Enlightenment for {cosmetic_id} set to level {skin_level} ' 702 | f'(for Season 1{br_season}).' 703 | ) 704 | 705 | @commands.dm_only() 706 | @commands.command( 707 | description="[Cosmetic] Shortcut for equipping the skin CID_605_Athena_Commando_M_TourBus.", 708 | help="Shortcut for equipping the skin CID_605_Athena_Commando_M_TourBus.\n" 709 | "Example: !ninja" 710 | ) 711 | async def ninja(self, ctx: rebootpy.ext.commands.Context) -> None: 712 | await self.bot.party.me.set_outfit( 713 | asset='CID_605_Athena_Commando_M_TourBus' 714 | ) 715 | 716 | await ctx.send('Skin set to Ninja!') 717 | print(self.bot.message % f'Skin set to Ninja.') 718 | 719 | @commands.dm_only() 720 | @commands.command( 721 | description="[Cosmetic] Equips all very rare skins.", 722 | help="Equips all very rare skins.\n" 723 | "Example: !rareskins" 724 | ) 725 | async def rareskins(self, ctx: rebootpy.ext.commands.Context) -> None: 726 | await ctx.send('Showing all rare skins now.') 727 | 728 | await self.bot.party.me.set_outfit( 729 | asset='CID_030_Athena_Commando_M_Halloween', 730 | variants=self.bot.party.me.create_variants(clothing_color=1) 731 | ) 732 | 733 | await ctx.send('Skin set to Purple Skull Trooper!') 734 | print(self.bot.message % f"Skin set to Purple Skull Trooper.") 735 | await asyncio.sleep(2) 736 | 737 | await self.bot.party.me.set_outfit( 738 | asset='CID_029_Athena_Commando_F_Halloween', 739 | variants=self.bot.party.me.create_variants(material=3) 740 | ) 741 | 742 | await ctx.send('Skin set to Pink Ghoul Trooper!') 743 | print(self.bot.message % f"Skin set to Pink Ghoul Trooper.") 744 | await asyncio.sleep(2) 745 | 746 | for rare_skin in ('CID_028_Athena_Commando_F', 'CID_017_Athena_Commando_M'): 747 | await self.bot.party.me.set_outfit( 748 | asset=rare_skin 749 | ) 750 | 751 | await ctx.send(f'Skin set to {rare_skin}!') 752 | print(self.bot.message % f"Skin set to: {rare_skin}!") 753 | await asyncio.sleep(2) 754 | 755 | @commands.dm_only() 756 | @commands.command( 757 | description="[Cosmetic] Sets the outfit of the client to Golden Peely " 758 | "(shortcut for !enlightened CID_701_Athena_Commando_M_BananaAgent 2 350).", 759 | help="Sets the outfit of the client to Golden Peely.\n" 760 | "Example: !goldenpeely" 761 | ) 762 | async def goldenpeely(self, ctx: rebootpy.ext.commands.Context) -> None: 763 | await self.bot.party.me.set_outfit( 764 | asset='CID_701_Athena_Commando_M_BananaAgent', 765 | variants=self.bot.party.me.create_variants(progressive=4), 766 | enlightenment=(2, 350) 767 | ) 768 | 769 | await ctx.send(f'Skin set to Golden Peely.') 770 | 771 | # to fix 772 | @commands.dm_only() 773 | @commands.command( 774 | description="[Cosmetic] Randomly finds & equips a skin. Types currently include skin, backpack, emote & all. " 775 | "If type is left blank, a random skin will be equipped.", 776 | help="Randomly finds & equips a skin.\n" 777 | "Example: !random skin" 778 | ) 779 | async def random(self, ctx: rebootpy.ext.commands.Context, cosmetic_type: str = 'skin') -> None: 780 | if cosmetic_type == 'skin': 781 | all_outfits = await self.bot.fortnite_api.cosmetics.get_cosmetics( 782 | lang="en", 783 | searchLang="en", 784 | backendType="AthenaCharacter" 785 | ) 786 | 787 | random_skin = py_random.choice(all_outfits).id 788 | 789 | await self.bot.party.me.set_outfit( 790 | asset=random_skin, 791 | variants=self.bot.party.me.create_variants(profile_banner='ProfileBanner') 792 | ) 793 | 794 | await ctx.send(f'Skin randomly set to {random_skin}.') 795 | print(self.bot.message % f"Set skin randomly to: {random_skin}.") 796 | 797 | elif cosmetic_type == 'backpack': 798 | all_backpacks = await self.bot.fortnite_api.cosmetics.get_cosmetics( 799 | lang="en", 800 | searchLang="en", 801 | backendType="AthenaBackpack" 802 | ) 803 | 804 | random_backpack = py_random.choice(all_backpacks).id 805 | 806 | await self.bot.party.me.set_backpack( 807 | asset=random_backpack, 808 | variants=self.bot.party.me.create_variants(profile_banner='ProfileBanner') 809 | ) 810 | 811 | await ctx.send(f'Backpack randomly set to {random_backpack}.') 812 | print(self.bot.message % f"Set backpack randomly to: {random_backpack}.") 813 | 814 | elif cosmetic_type == 'emote': 815 | all_emotes = await self.bot.fortnite_api.cosmetics.get_cosmetics( 816 | lang="en", 817 | searchLang="en", 818 | backendType="AthenaDance" 819 | ) 820 | 821 | random_emote = py_random.choice(all_emotes).id 822 | 823 | await self.bot.party.me.set_emote( 824 | asset=random_emote 825 | ) 826 | 827 | await ctx.send(f'Emote randomly set to {random_emote}.') 828 | print(self.bot.message % f"Set emote randomly to: {random_emote}.") 829 | 830 | elif cosmetic_type == 'all': 831 | all_outfits = await self.bot.fortnite_api.cosmetics.get_cosmetics( 832 | lang="en", 833 | searchLang="en", 834 | backendType="AthenaCharacter" 835 | ) 836 | 837 | all_backpacks = await self.bot.fortnite_api.cosmetics.get_cosmetics( 838 | lang="en", 839 | searchLang="en", 840 | backendType="AthenaBackpack" 841 | ) 842 | 843 | all_emotes = await self.bot.fortnite_api.cosmetics.get_cosmetics( 844 | lang="en", 845 | searchLang="en", 846 | backendType="AthenaDance" 847 | ) 848 | 849 | random_outfit = py_random.choice(all_outfits).id 850 | random_backpack = py_random.choice(all_backpacks).id 851 | random_emote = py_random.choice(all_emotes).id 852 | 853 | await self.bot.party.me.set_outfit( 854 | asset=random_outfit 855 | ) 856 | 857 | await ctx.send(f'Skin randomly set to {random_outfit}.') 858 | print(self.bot.message % f"Set skin randomly to: {random_outfit}.") 859 | 860 | await self.bot.party.me.set_backpack( 861 | asset=random_backpack 862 | ) 863 | 864 | await ctx.send(f'Backpack randomly set to {random_backpack}.') 865 | print(self.bot.message % f"Set backpack randomly to: {random_backpack}.") 866 | 867 | await self.bot.party.me.set_emote( 868 | asset=random_emote 869 | ) 870 | 871 | await ctx.send(f'Emote randomly set to {random_emote}.') 872 | print(self.bot.message % f"Set emote randomly to: {random_emote}.") 873 | 874 | @commands.dm_only() 875 | @commands.command( 876 | description="[Cosmetic] Clears the currently set backpack.", 877 | help="Clears the currently set backpack.\n" 878 | "Example: !nobackpack" 879 | ) 880 | async def nobackpack(self, ctx: rebootpy.ext.commands.Context) -> None: 881 | await self.bot.party.me.clear_backpack() 882 | await ctx.send('Removed backpack.') 883 | 884 | @commands.dm_only() 885 | @commands.command( 886 | description="[Cosmetic] Clears the currently set pet.", 887 | help="Clears the currently set pet.\n" 888 | "Example: !nopet" 889 | ) 890 | async def nopet(self, ctx: rebootpy.ext.commands.Context) -> None: 891 | await self.bot.party.me.clear_pet() 892 | await ctx.send('Removed pet.') 893 | 894 | @commands.dm_only() 895 | @commands.command( 896 | description="[Cosmetic] Clears the currently set contrail.", 897 | help="Clears the currently set contrail.\n" 898 | "Example: !nocontrail" 899 | ) 900 | async def nocontrail(self, ctx: rebootpy.ext.commands.Context) -> None: 901 | await self.bot.party.me.clear_contrail() 902 | await ctx.send('Removed contrail.') 903 | 904 | @commands.dm_only() 905 | @commands.command( 906 | description="[Cosmetic] Sets the outfit of the client using the outfits name with the ghost variant.", 907 | help="Sets the outfit of the client using the outfits name with the ghost variant.\n" 908 | "Example: !ghost Meowscles" 909 | ) 910 | async def ghost(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 911 | try: 912 | skin_variants = self.bot.party.me.create_variants( 913 | progressive=2 914 | ) 915 | 916 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 917 | matchMethod="contains", 918 | name=content, 919 | backendType="AthenaCharacter" 920 | ) 921 | 922 | await self.bot.party.me.set_outfit( 923 | asset=cosmetic.id, 924 | variants=skin_variants 925 | ) 926 | 927 | await ctx.send(f'Skin set to Ghost {cosmetic.name}!') 928 | print(self.bot.message % f'Skin set to Ghost {cosmetic.name}.') 929 | 930 | except FortniteAPIAsync.exceptions.NotFound: 931 | print(self.bot.message % f"Failed to find a skin with the name: {content}.") 932 | return await ctx.send(f"Failed to find a skin with the name: {content}.") 933 | 934 | @commands.dm_only() 935 | @commands.command( 936 | description="[Cosmetic] Sets the outfit of the client using the outfits name with the shadow variant.", 937 | help="Sets the outfit of the client using the outfits name with the shadow variant.\n" 938 | "Example: !shadow Midas" 939 | ) 940 | async def shadow(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 941 | try: 942 | skin_variants = self.bot.party.me.create_variants( 943 | progressive=3 944 | ) 945 | 946 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 947 | matchMethod="contains", 948 | name=content, 949 | backendType="AthenaCharacter" 950 | ) 951 | 952 | await self.bot.party.me.set_outfit( 953 | asset=cosmetic.id, 954 | variants=skin_variants 955 | ) 956 | 957 | await ctx.send(f'Skin set to Shadow {cosmetic.name}!') 958 | print(self.bot.message % f'Skin set to Ghost {cosmetic.name}.') 959 | 960 | except FortniteAPIAsync.exceptions.NotFound: 961 | print(self.bot.message % f"Failed to find a skin with the name: {content}.") 962 | return await ctx.send(f"Failed to find a skin with the name: {content}.") 963 | 964 | # to fix 965 | @commands.dm_only() 966 | @commands.command( 967 | name="set", 968 | description="[Cosmetic] Equips all cosmetics from a set.", 969 | help="Equips all cosmetics from a set.\n" 970 | "Example: !set Fort Knights" 971 | ) 972 | async def _set(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 973 | cosmetic_types = { 974 | "AthenaBackpack": self.bot.party.me.set_backpack, 975 | "AthenaCharacter": self.bot.party.me.set_outfit, 976 | "AthenaEmoji": self.bot.party.me.set_emoji, 977 | "AthenaDance": self.bot.party.me.set_emote 978 | } 979 | 980 | set_items = await self.bot.fortnite_api.cosmetics.get_cosmetics( 981 | matchMethod="contains", 982 | set=content 983 | ) 984 | 985 | await ctx.send(f'Equipping all cosmetics from the {set_items[0].set["value"]} set.') 986 | print(self.bot.message % f'Equipping all cosmetics from the {set_items[0].set["value"]} set.') 987 | 988 | for cosmetic in set_items: 989 | if cosmetic.type['backendValue'] in cosmetic_types: 990 | await cosmetic_types[cosmetic.type['backendValue']](asset=cosmetic.id) 991 | 992 | await ctx.send(f'{cosmetic.type["value"].capitalize()} set to {cosmetic.name}!') 993 | print(self.bot.message % f'{cosmetic.type["value"].capitalize()} set to {cosmetic.name}.') 994 | 995 | await asyncio.sleep(3) 996 | 997 | await ctx.send(f'Finished equipping all cosmetics from the {set_items[0].set["value"]} set.') 998 | print(self.bot.message % f'Fishing equipping all cosmetics from the {set_items[0].set["value"]} set.') 999 | 1000 | @commands.dm_only() 1001 | @commands.command( 1002 | description="[Cosmetic] Creates the variants list by the variants you set from skin name. " 1003 | "If you want to include spaces in the skin name, you need to enclose it in \"'s.", 1004 | help="Creates the variants list by the variants you set from skin name.\n" 1005 | "Example: !style \"Skull Trooper\" clothing_color 1" 1006 | ) 1007 | async def style(self, ctx: rebootpy.ext.commands.Context, cosmetic_name: str, variant_type: str, 1008 | variant_int: str) -> None: 1009 | # cosmetic_types = { 1010 | # "AthenaCharacter": self.bot.party.me.set_outfit, 1011 | # "AthenaBackpack": self.bot.party.me.set_backpack, 1012 | # "AthenaPickaxe": self.bot.party.me.set_pickaxe 1013 | # } 1014 | 1015 | cosmetic = await self.bot.fortnite_api.cosmetics.get_cosmetic( 1016 | matchMethod="contains", 1017 | name=cosmetic_name, 1018 | backendType="AthenaCharacter" 1019 | ) 1020 | 1021 | cosmetic_variants = self.bot.party.me.create_variants( 1022 | # item=cosmetic.backend_type.value, 1023 | **{variant_type: int(variant_int) if variant_int.isdigit() else variant_int} 1024 | ) 1025 | 1026 | # await cosmetic_types[cosmetic.backend_type.value]( 1027 | await self.bot.party.me.set_outfit( 1028 | asset=cosmetic.id, 1029 | variants=cosmetic_variants 1030 | ) 1031 | 1032 | await ctx.send(f'Set variants of {cosmetic.id} to {variant_type} {variant_int}.') 1033 | print(self.bot.message % f'Set variants of {cosmetic.id} to {variant_type} {variant_int}.') 1034 | 1035 | @commands.dm_only() 1036 | @commands.command( 1037 | description="[Cosmetic] Equips all new non encrypted cosmetics.", 1038 | help="Equips all new non encrypted cosmetics.\n" 1039 | "Example: !new" 1040 | ) 1041 | async def new(self, ctx: rebootpy.ext.commands.Context, cosmetic_type: str = 'skins') -> None: 1042 | cosmetic_types = { 1043 | 'skins': { 1044 | 'id': 'AthenaCharacter', 1045 | 'function': self.bot.party.me.set_outfit 1046 | }, 1047 | 'backpacks': { 1048 | 'id': 'AthenaBackpack', 1049 | 'function': self.bot.party.me.set_backpack 1050 | }, 1051 | 'emotes': { 1052 | 'id': 'AthenaDance', 1053 | 'function': self.bot.party.me.set_emote 1054 | }, 1055 | } 1056 | 1057 | if cosmetic_type not in cosmetic_types: 1058 | return await ctx.send('Invalid cosmetic type, valid types include: skins, backpacks & emotes.') 1059 | 1060 | new_cosmetics = await self.bot.fortnite_api.cosmetics.get_new_cosmetics() 1061 | 1062 | for new_id in new_cosmetics: 1063 | print(new_id.type) 1064 | 1065 | for new_cosmetic in [new_id for new_id in new_cosmetics if 1066 | new_id.type['backendValue'] == cosmetic_types[cosmetic_type]['id']]: 1067 | await cosmetic_types[cosmetic_type]['function']( 1068 | asset=new_cosmetic.id 1069 | ) 1070 | 1071 | await ctx.send(f"{cosmetic_type[:-1].capitalize()} set to {new_cosmetic.id}.") 1072 | print(self.bot.message % f"{cosmetic_type[:-1].capitalize()} set to: {new_cosmetic.id}.") 1073 | 1074 | await asyncio.sleep(3) 1075 | 1076 | await ctx.send(f'Finished equipping all new unencrypted {cosmetic_type}.') 1077 | print(self.bot.message % f'Finished equipping all new unencrypted {cosmetic_type}.') 1078 | 1079 | @commands.dm_only() 1080 | @commands.command( 1081 | description="[Cosmetic] Equips all skins currently in the item shop.", 1082 | help="Equips all skins currently in the item shop.\n" 1083 | "Example: !shop" 1084 | ) 1085 | async def shop(self, ctx: rebootpy.ext.commands.Context) -> None: 1086 | return ctx.send('Command is broken due to the new shop catalogs, will replace soon with Fortnite-API.') 1087 | 1088 | store = await self.bot.fetch_item_shop() 1089 | 1090 | await ctx.send(f"Equipping all skins in today's item shop.") 1091 | print(self.bot.message % f"Equipping all skins in today's item shop.") 1092 | 1093 | for item in store.special_featured_items + \ 1094 | store.special_daily_items + \ 1095 | store.special_featured_items + \ 1096 | store.special_daily_items: 1097 | for grant in item.grants: 1098 | if grant['type'] == 'AthenaCharacter': 1099 | await self.bot.party.me.set_outfit( 1100 | asset=grant['asset'] 1101 | ) 1102 | 1103 | await ctx.send(f"Skin set to {item.display_names[0]}!") 1104 | print(self.bot.message % f"Skin set to: {item.display_names[0]}!") 1105 | 1106 | await asyncio.sleep(3) 1107 | 1108 | await ctx.send(f'Finished equipping all skins in the item shop.') 1109 | print(self.bot.message % f'Finished equipping all skins in the item shop.') 1110 | 1111 | @commands.dm_only() 1112 | @commands.command( 1113 | description="[Cosmetic] Equips a random old default skin.", 1114 | help="Equips a random old default skin.\n" 1115 | "Example: !olddefault" 1116 | ) 1117 | async def olddefault(self, ctx: rebootpy.ext.commands.Context) -> None: 1118 | random_default = py_random.choice( 1119 | [cid_ for cid_ in dir(rebootpy.DefaultCharactersChapter1) if not cid_.startswith('_')] 1120 | ) 1121 | 1122 | await self.bot.party.me.set_outfit( 1123 | asset=random_default 1124 | ) 1125 | 1126 | await ctx.send(f'Skin set to {random_default}!') 1127 | print(self.bot.message % f"Skin set to {random_default}.") 1128 | 1129 | @commands.dm_only() 1130 | @commands.command( 1131 | description="[Cosmetic] Sets the outfit of the client to Hatless Recon Expert.", 1132 | help="Sets the outfit of the client to Hatless Recon Expert.\n" 1133 | "Example: !hatlessrecon" 1134 | ) 1135 | async def hatlessrecon(self, ctx: rebootpy.ext.commands.Context) -> None: 1136 | skin_variants = self.bot.party.me.create_variants( 1137 | parts=2 1138 | ) 1139 | 1140 | await self.bot.party.me.set_outfit( 1141 | asset='CID_022_Athena_Commando_F', 1142 | variants=skin_variants 1143 | ) 1144 | 1145 | await ctx.send('Skin set to Hatless Recon Expert!') 1146 | print(self.bot.message % f'Skin set to Hatless Recon Expert.') 1147 | 1148 | @commands.dm_only() 1149 | @commands.command( 1150 | description="[Cosmetic] Sets the outfit of the to the max tier skin in the defined season.", 1151 | help="Sets the outfit of the to the max tier skin in the defined season.\n" 1152 | "Example: !season 2" 1153 | ) 1154 | async def season(self, ctx: rebootpy.ext.commands.Context, br_season: int) -> None: 1155 | max_tier_skins = { 1156 | 1: "CID_028_Athena_Commando_F", 1157 | 2: "CID_035_Athena_Commando_M_Medieval", 1158 | 3: "CID_084_Athena_Commando_M_Assassin", 1159 | 4: "CID_116_Athena_Commando_M_CarbideBlack", 1160 | 5: "CID_165_Athena_Commando_M_DarkViking", 1161 | 6: "CID_230_Athena_Commando_M_Werewolf", 1162 | 7: "CID_288_Athena_Commando_M_IceKing", 1163 | 8: "CID_352_Athena_Commando_F_Shiny", 1164 | 9: "CID_407_Athena_Commando_M_BattleSuit", 1165 | 10: "CID_484_Athena_Commando_M_KnightRemix", 1166 | 11: "CID_572_Athena_Commando_M_Viper", 1167 | 12: "CID_694_Athena_Commando_M_CatBurglar", 1168 | 13: "CID_767_Athena_Commando_F_BlackKnight", 1169 | 14: "CID_843_Athena_Commando_M_HightowerTomato_Casual", 1170 | 15: "CID_967_Athena_Commando_M_AncientGladiator", 1171 | 16: "CID_A_038_Athena_Commando_F_TowerSentinel", 1172 | 17: "CID_A_112_Athena_Commando_M_Ruckus", 1173 | 18: "CID_A_197_Athena_Commando_M_Clash", 1174 | 19: "CID_572_Athena_Commando_M_Viper", 1175 | 20: "CID_A_367_Athena_Commando_M_Mystic", 1176 | 21: "CID_A_422_Athena_Commando_M_Realm", 1177 | 22: "Character_RoseDust", 1178 | 23: "Character_Citadel", 1179 | 24: "Character_NitroFlow", 1180 | 25: "Character_LoudPhoenix", 1181 | 26: "Character_LazarusLens", 1182 | 27: "Character_HornedJudgment_Midgard", 1183 | 28: "Character_ZebraScramble_Bacon", 1184 | 29: "Character_DarkStance_Inferno" 1185 | } 1186 | 1187 | await self.bot.party.me.set_outfit(asset=max_tier_skins[br_season]) 1188 | 1189 | await ctx.send(f'Skin set to {max_tier_skins[br_season]}!') 1190 | print(self.bot.message % f"Skin set to {max_tier_skins[br_season]}.") 1191 | 1192 | @commands.dm_only() 1193 | @commands.command( 1194 | aliases=['henchmen'], 1195 | description="[Cosmetic] Sets the outfit of the client to a random Henchman skin.", 1196 | help="Sets the outfit of the client to a random Henchman skin.\n" 1197 | "Example: !henchman" 1198 | ) 1199 | async def henchman(self, ctx: rebootpy.ext.commands.Context) -> None: 1200 | random_henchman = py_random.choice( 1201 | [ 1202 | "CID_794_Athena_Commando_M_HenchmanBadShorts_D", 1203 | "CID_NPC_Athena_Commando_F_HenchmanSpyDark", 1204 | "CID_791_Athena_Commando_M_HenchmanGoodShorts_D", 1205 | "CID_780_Athena_Commando_M_HenchmanBadShorts", 1206 | "CID_NPC_Athena_Commando_M_HenchmanGood", 1207 | "CID_692_Athena_Commando_M_HenchmanTough", 1208 | "CID_707_Athena_Commando_M_HenchmanGood", 1209 | "CID_792_Athena_Commando_M_HenchmanBadShorts_B", 1210 | "CID_793_Athena_Commando_M_HenchmanBadShorts_C", 1211 | "CID_NPC_Athena_Commando_M_HenchmanBad", 1212 | "CID_790_Athena_Commando_M_HenchmanGoodShorts_C", 1213 | "CID_779_Athena_Commando_M_HenchmanGoodShorts", 1214 | "CID_NPC_Athena_Commando_F_RebirthDefault_Henchman", 1215 | "CID_NPC_Athena_Commando_F_HenchmanSpyGood", 1216 | "CID_706_Athena_Commando_M_HenchmanBad", 1217 | "CID_789_Athena_Commando_M_HenchmanGoodShorts_B" 1218 | ] 1219 | ) 1220 | 1221 | await self.bot.party.me.set_outfit( 1222 | asset=random_henchman 1223 | ) 1224 | 1225 | await ctx.send(f'Skin set to {random_henchman}!') 1226 | print(self.bot.message % f"Skin set to {random_henchman}.") 1227 | 1228 | @commands.dm_only() 1229 | @commands.command( 1230 | description="[Cosmetic] Sets the emote of the client to Floss.", 1231 | help="Sets the emote of the client to Floss.\n" 1232 | "Example: !floss" 1233 | ) 1234 | async def floss(self, ctx: rebootpy.ext.commands.Context) -> None: 1235 | # // You caused this FunGames, you caused this... 1236 | await self.bot.party.me.set_emote( 1237 | asset='EID_Floss' 1238 | ) 1239 | 1240 | await ctx.send('Emote set to Floss!') 1241 | print(self.bot.message % f"Emote set to Floss.") 1242 | 1243 | @commands.dm_only() 1244 | @commands.command( 1245 | description="[Cosmetic] Sets the outfit of the client to a random marauder skin.", 1246 | help="Sets the outfit of the client to a random marauder skin.\n" 1247 | "Example: !marauder" 1248 | ) 1249 | async def marauder(self, ctx: rebootpy.ext.commands.Context) -> None: 1250 | random_marauder = py_random.choice( 1251 | [ 1252 | "CID_NPC_Athena_Commando_M_MarauderHeavy", 1253 | "CID_NPC_Athena_Commando_M_MarauderElite", 1254 | "CID_NPC_Athena_Commando_M_MarauderGrunt" 1255 | ] 1256 | ) 1257 | 1258 | await self.bot.party.me.set_outfit( 1259 | asset=random_marauder 1260 | ) 1261 | 1262 | await ctx.send(f'Skin set to {random_marauder}!') 1263 | print(self.bot.message % f"Skin set to {random_marauder}.") 1264 | 1265 | @commands.dm_only() 1266 | @commands.dm_only() 1267 | @commands.command( 1268 | description="[Cosmetic] Sets the outfit of the client to Golden Brutus " 1269 | "(shortcut for !enlightened CID_692_Athena_Commando_M_HenchmanTough 2 180).", 1270 | help="Sets the outfit of the client to Golden Brutus.\n" 1271 | "Example: !goldenbrutus" 1272 | ) 1273 | async def goldenbrutus(self, ctx: rebootpy.ext.commands.Context) -> None: 1274 | await self.bot.party.me.set_outfit( 1275 | asset='CID_692_Athena_Commando_M_HenchmanTough', 1276 | variants=self.bot.party.me.create_variants(progressive=4), 1277 | enlightenment=(2, 180) 1278 | ) 1279 | 1280 | await ctx.send(f'Skin set to Golden Brutus.') 1281 | 1282 | @commands.dm_only() 1283 | @commands.command( 1284 | description="[Cosmetic] Sets the outfit of the client to Golden Meowscles " 1285 | "(shortcut for !enlightened CID_693_Athena_Commando_M_BuffCat 2 220).", 1286 | help="Sets the outfit of the client to Golden Meowscles.\n" 1287 | "Example: !goldenmeowscles" 1288 | ) 1289 | async def goldenmeowscles(self, ctx: rebootpy.ext.commands.Context) -> None: 1290 | await self.bot.party.me.set_outfit( 1291 | asset='CID_693_Athena_Commando_M_BuffCat', 1292 | variants=self.bot.party.me.create_variants(progressive=4), 1293 | enlightenment=(2, 220) 1294 | ) 1295 | 1296 | await ctx.send(f'Skin set to Golden Meowscles.') 1297 | 1298 | @commands.dm_only() 1299 | @commands.command( 1300 | description="[Cosmetic] Sets the outfit of the client to Golden Midas " 1301 | "(shortcut for !enlightened CID_694_Athena_Commando_M_CatBurglar 2 140).", 1302 | help="Sets the outfit of the client to Golden Peely.\n" 1303 | "Example: !goldenmidas" 1304 | ) 1305 | async def goldenmidas(self, ctx: rebootpy.ext.commands.Context) -> None: 1306 | await self.bot.party.me.set_outfit( 1307 | asset='CID_694_Athena_Commando_M_CatBurglar', 1308 | variants=self.bot.party.me.create_variants(progressive=4), 1309 | enlightenment=(2, 140) 1310 | ) 1311 | 1312 | await ctx.send(f'Skin set to Golden Midas.') 1313 | 1314 | @commands.dm_only() 1315 | @commands.command( 1316 | description="[Cosmetic] Sets the outfit of the client to Golden Skye " 1317 | "(shortcut for !enlightened CID_690_Athena_Commando_F_Photographer 2 300).", 1318 | help="Sets the outfit of the client to Golden Skye.\n" 1319 | "Example: !goldenskye" 1320 | ) 1321 | async def goldenskye(self, ctx: rebootpy.ext.commands.Context) -> None: 1322 | await self.bot.party.me.set_outfit( 1323 | asset='CID_690_Athena_Commando_F_Photographer', 1324 | variants=self.bot.party.me.create_variants(progressive=4), 1325 | enlightenment=(2, 300) 1326 | ) 1327 | 1328 | await ctx.send(f'Skin set to Golden Skye.') 1329 | 1330 | @commands.dm_only() 1331 | @commands.command( 1332 | description="[Cosmetic] Sets the outfit of the client to Golden TNTina " 1333 | "(shortcut for !enlightened CID_691_Athena_Commando_F_TNTina 2 350).", 1334 | help="Sets the outfit of the client to Golden TNTina.\n" 1335 | "Example: !goldentntina" 1336 | ) 1337 | async def goldentntina(self, ctx: rebootpy.ext.commands.Context) -> None: 1338 | await self.bot.party.me.set_outfit( 1339 | asset='CID_691_Athena_Commando_F_TNTina', 1340 | variants=self.bot.party.me.create_variants(progressive=7), 1341 | enlightenment=(2, 260) 1342 | ) 1343 | 1344 | await ctx.send(f'Skin set to Golden TNTina.') 1345 | 1346 | @commands.dm_only() 1347 | @commands.command( 1348 | description="[Cosmetic] Equips a To-Be-Determined outfit.", 1349 | help="Equips a To-Be-Determined outfit.\n" 1350 | "Example: !tbd 2" 1351 | ) 1352 | async def tbd(self, ctx: rebootpy.ext.commands.Context, skin: int = 1) -> None: 1353 | cosmetics = await self.bot.fortnite_api.cosmetics.get_cosmetics( 1354 | matchMethod="full", 1355 | name="TBD", 1356 | backendType="AthenaCharacter" 1357 | ) 1358 | 1359 | if not skin or skin > len(cosmetics): 1360 | return await ctx.send(f'Invalid skin number, there is only {len(cosmetics)} TBD outfits.') 1361 | 1362 | await ctx.send(f'Found {len(cosmetics)} TBD outfits.') 1363 | 1364 | await self.bot.party.me.set_outfit(asset=cosmetics[skin - 1].id) 1365 | 1366 | await ctx.send(f'Skin set to {cosmetics[skin - 1].id}\nUse !tbd <1 to {len(cosmetics)}> to equip another.') 1367 | print(self.bot.message % f"Set skin to: {cosmetics[skin - 1].id}.") -------------------------------------------------------------------------------- /partybot/deviceauths.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # System imports. 28 | import json 29 | 30 | # Third party imports. 31 | import aiofiles 32 | 33 | # Local imports. 34 | from .errors import MissingDeviceAuth 35 | from typing import Optional, Union 36 | 37 | 38 | class DeviceAuth: 39 | def __init__(self, 40 | device_id: Optional[str] = None, 41 | account_id: Optional[str] = None, 42 | secret: Optional[str] = None, 43 | **kwargs 44 | ) -> None: 45 | self.device_id = device_id 46 | self.account_id = account_id 47 | self.secret = secret 48 | 49 | 50 | class DeviceAuths: 51 | def __init__(self, filename: str) -> None: 52 | self.device_auth = None 53 | self.filename = filename 54 | 55 | async def load_device_auths(self) -> None: 56 | try: 57 | async with aiofiles.open(self.filename, mode='r') as fp: 58 | data = await fp.read() 59 | raw_device_auths = json.loads(data) 60 | except (json.decoder.JSONDecodeError, FileNotFoundError): 61 | raw_device_auths = {} 62 | 63 | if 'device_id' not in raw_device_auths or \ 64 | 'account_id' not in raw_device_auths or \ 65 | 'secret' not in raw_device_auths: 66 | raise MissingDeviceAuth('Missing required device auth key.') 67 | 68 | self.device_auth = DeviceAuth( 69 | device_id=raw_device_auths.get('device_id'), 70 | account_id=raw_device_auths.get('account_id'), 71 | secret=raw_device_auths.get('secret') 72 | ) 73 | 74 | async def save_device_auths(self) -> None: 75 | async with aiofiles.open(self.filename, mode='w') as fp: 76 | await fp.write(json.dumps( 77 | { 78 | "account_id": self.device_auth.account_id, 79 | "device_id": self.device_auth.device_id, 80 | "secret": self.device_auth.secret 81 | }, 82 | sort_keys=False, 83 | indent=4 84 | )) 85 | 86 | def set_device_auth(self, **kwargs) -> None: 87 | self.device_auth = DeviceAuth( 88 | **kwargs 89 | ) 90 | 91 | def get_device_auth(self) -> Union[DeviceAuth, None]: 92 | return self.device_auth 93 | -------------------------------------------------------------------------------- /partybot/errors.py: -------------------------------------------------------------------------------- 1 | class PartyBotException(Exception): 2 | pass 3 | 4 | 5 | class MissingDeviceAuth(PartyBotException): 6 | pass -------------------------------------------------------------------------------- /partybot/generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: DeviceAuthGenerator 23 | 24 | License: Apache 2.0 Modified. 25 | """ 26 | 27 | # System imports. 28 | import asyncio 29 | import webbrowser 30 | import json 31 | import platform 32 | import os 33 | import sys 34 | 35 | # Third party imports.s 36 | import aiohttp 37 | 38 | 39 | # "Constants" ? I don't know. 40 | DAUNTLESS_TOKEN = "YjA3MGYyMDcyOWY4NDY5M2I1ZDYyMWM5MDRmYzViYzI6SEdAWEUmVEdDeEVKc2dUIyZfcDJdPWFSbyN+Pj0+K2M2UGhSKXpYUA==" 41 | SWITCH_TOKEN = "OThmN2U0MmMyZTNhNGY4NmE3NGViNDNmYmI0MWVkMzk6MGEyNDQ5YTItMDAxYS00NTFlLWFmZWMtM2U4MTI5MDFjNGQ3" 42 | IOS_TOKEN = "MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=" 43 | 44 | 45 | class EpicUser: 46 | def __init__(self, data: dict = {}): 47 | self.raw = data 48 | 49 | self.access_token = data.get('access_token', '') 50 | self.expires_in = data.get('expires_in', 0) 51 | self.expires_at = data.get('expires_at', '') 52 | self.token_type = data.get('token_type', '') 53 | self.refresh_token = data.get('refresh_token', '') 54 | self.refresh_expires = data.get('refresh_expires', '') 55 | self.refresh_expires_at = data.get('refresh_expires_at', '') 56 | self.account_id = data.get('account_id', '') 57 | self.client_id = data.get('client_id', '') 58 | self.internal_client = data.get('internal_client', False) 59 | self.client_service = data.get('client_service', '') 60 | self.display_name = data.get('displayName', '') 61 | self.app = data.get('app', '') 62 | self.in_app_id = data.get('in_app_id', '') 63 | 64 | 65 | class EpicGenerator: 66 | def __init__(self) -> None: 67 | self.http = None 68 | 69 | self.access_token = "" 70 | self.user_agent = f"DeviceAuthGenerator/1.0.0 {platform.system()}/{platform.version()}" 71 | 72 | async def generate_device_auths(self) -> None: 73 | self.http = aiohttp.ClientSession( 74 | headers={ 75 | 'User-Agent': self.user_agent 76 | } 77 | ) 78 | 79 | self.access_token = await self.get_access_token() 80 | 81 | device_code = await self.create_device_code() 82 | webbrowser.open(f"https://www.epicgames.com/activate?userCode={device_code[0]}", new=1) 83 | user = await self.wait_for_device_code_completion(code=device_code[1]) 84 | device_auths = await self.create_device_auths(user) 85 | 86 | await self.http.close() 87 | 88 | return device_auths 89 | 90 | async def get_access_token(self) -> str: 91 | async with self.http.request( 92 | method="POST", 93 | url="https://account-public-service-prod.ol.epicgames.com/account/api/oauth/token", 94 | headers={ 95 | "Content-Type": "application/x-www-form-urlencoded", 96 | "Authorization": f"basic {SWITCH_TOKEN}" 97 | }, 98 | data={ 99 | "grant_type": "client_credentials", 100 | } 101 | ) as request: 102 | data = await request.json() 103 | 104 | return data['access_token'] 105 | 106 | async def create_device_code(self) -> tuple: 107 | async with self.http.request( 108 | method="POST", 109 | url="https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/deviceAuthorization", 110 | headers={ 111 | "Authorization": f"bearer {self.access_token}", 112 | "Content-Type": "application/x-www-form-urlencoded" 113 | } 114 | ) as request: 115 | data = await request.json() 116 | 117 | return data['user_code'], data['device_code'] 118 | 119 | async def wait_for_device_code_completion(self, code: str) -> EpicUser: 120 | while True: 121 | async with self.http.request( 122 | method="POST", 123 | url="https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token", 124 | headers={ 125 | "Authorization": f"basic {SWITCH_TOKEN}", 126 | "Content-Type": "application/x-www-form-urlencoded" 127 | }, 128 | data={ 129 | "grant_type": "device_code", 130 | "device_code": code 131 | } 132 | ) as request: 133 | token = await request.json() 134 | 135 | if request.status == 200: 136 | break 137 | else: 138 | if token['errorCode'] == 'errors.com.epicgames.account.oauth.authorization_pending': 139 | pass 140 | elif token['errorCode'] == 'errors.com.epicgames.not_found': 141 | pass 142 | else: 143 | print(json.dumps(token, sort_keys=False, indent=4)) 144 | 145 | await asyncio.sleep(5) 146 | 147 | async with self.http.request( 148 | method="GET", 149 | url="https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/exchange", 150 | headers={ 151 | "Authorization": f"bearer {token['access_token']}" 152 | } 153 | ) as request: 154 | exchange = await request.json() 155 | 156 | async with self.http.request( 157 | method="POST", 158 | url="https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token", 159 | headers={ 160 | "Authorization": f"basic {IOS_TOKEN}", 161 | "Content-Type": "application/x-www-form-urlencoded" 162 | }, 163 | data={ 164 | "grant_type": "exchange_code", 165 | "exchange_code": exchange['code'] 166 | } 167 | ) as request: 168 | auth_information = await request.json() 169 | 170 | return EpicUser( 171 | data=auth_information 172 | ) 173 | 174 | async def create_device_auths(self, user: EpicUser) -> dict: 175 | async with self.http.request( 176 | method="POST", 177 | url="https://account-public-service-prod.ol.epicgames.com/" 178 | f"account/api/public/account/{user.account_id}/deviceAuth", 179 | headers={ 180 | "Authorization": f"bearer {user.access_token}", 181 | "Content-Type": "application/json" 182 | } 183 | ) as request: 184 | data = await request.json() 185 | 186 | return { 187 | "device_id": data['deviceId'], 188 | "account_id": data['accountId'], 189 | "secret": data['secret'], 190 | "user_agent": data['userAgent'], 191 | "created": { 192 | "location": data['created']['location'], 193 | "ip_address": data['created']['ipAddress'], 194 | "datetime": data['created']['dateTime'] 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /partybot/helper.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # Third party imports. 28 | # import psutil 29 | 30 | 31 | # class HelperFunctions: 32 | # @staticmethod 33 | # def check_if_process_running(name: str) -> bool: 34 | # for process in psutil.process_iter(): 35 | # try: 36 | # if name.lower() in process.name().lower(): 37 | # return True 38 | # except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): 39 | # pass 40 | 41 | # return False 42 | -------------------------------------------------------------------------------- /partybot/party.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # System imports. 28 | import asyncio 29 | import datetime 30 | import random 31 | 32 | from typing import Optional, Union 33 | 34 | # Third party imports. 35 | import rebootpy 36 | import aiohttp 37 | import crayons 38 | 39 | from rebootpy.ext import commands 40 | 41 | 42 | class PartyCommands(commands.Cog): 43 | def __init__(self, bot: commands.Bot) -> None: 44 | self.bot = bot 45 | 46 | async def get_playlist(self, display_name: str) -> str: 47 | async with aiohttp.ClientSession() as session: 48 | request = await session.request( 49 | method='GET', 50 | url='http://scuffedapi.xyz/api/playlists/search', 51 | params={ 52 | 'displayName': display_name 53 | }) 54 | 55 | response = await request.json() 56 | 57 | return response['id'] if 'error' not in response else None 58 | 59 | @commands.dm_only() 60 | @commands.command() 61 | async def selfmeta(ctx): 62 | print(json.dumps(bot.party.me.meta.schema, sort_keys=False, indent=4)) 63 | 64 | @commands.dm_only() 65 | @commands.command( 66 | description="[Party] Sets the banner of the self.bot.", 67 | help="Sets the banner of the self.bot.\n" 68 | "Example: !banner BRSeason01 defaultcolor15 100" 69 | ) 70 | async def banner(self, ctx: rebootpy.ext.commands.Context, 71 | icon: Optional[str] = None, 72 | colour: Optional[str] = None, 73 | banner_level: Optional[int] = None 74 | ) -> None: 75 | await self.bot.party.me.set_banner(icon=icon, color=colour, season_level=banner_level) 76 | 77 | await ctx.send(f'Banner set to: {icon} with {colour} at level {banner_level}.') 78 | print(self.bot.message % f"Banner set to: {icon} with {colour} at level {banner_level}.") 79 | 80 | @commands.dm_only() 81 | @commands.command( 82 | description="[Party] Sets the readiness of the client to ready.", 83 | help="Sets the readiness of the client to ready.\n" 84 | "Example: !ready" 85 | ) 86 | async def ready(self, ctx: rebootpy.ext.commands.Context) -> None: 87 | await self.bot.party.me.set_ready(rebootpy.ReadyState.READY) 88 | await ctx.send('Ready!') 89 | 90 | @commands.dm_only() 91 | @commands.command( 92 | aliases=['sitin'], 93 | description="[Party] Sets the readiness of the client to unready.", 94 | help="Sets the readiness of the client to unready.\n" 95 | "Example: !unready" 96 | ) 97 | async def unready(self, ctx: rebootpy.ext.commands.Context) -> None: 98 | await self.bot.party.me.set_ready(rebootpy.ReadyState.NOT_READY) 99 | await ctx.send('Unready!') 100 | 101 | @commands.dm_only() 102 | @commands.command( 103 | description="[Party] Sets the readiness of the client to SittingOut.", 104 | help="Sets the readiness of the client to SittingOut.\n" 105 | "Example: !sitout" 106 | ) 107 | async def sitout(self, ctx: rebootpy.ext.commands.Context) -> None: 108 | await self.bot.party.me.set_ready(rebootpy.ReadyState.SITTING_OUT) 109 | await ctx.send('Sitting Out!') 110 | 111 | @commands.dm_only() 112 | @commands.command( 113 | description="[Party] Sets the battlepass info of the self.bot.", 114 | help="Sets the battlepass info of the self.bot.\n" 115 | "Example: !bp 100" 116 | ) 117 | async def bp(self, ctx: rebootpy.ext.commands.Context, tier: int) -> None: 118 | await self.bot.party.me.set_battlepass_info( 119 | has_purchased=True, 120 | level=tier, 121 | ) 122 | 123 | await ctx.send(f'Set battle pass tier to {tier}.') 124 | 125 | @commands.dm_only() 126 | @commands.command( 127 | description="[Party] Sets the level of the self.bot.", 128 | help="Sets the level of the self.bot.\n" 129 | "Example: !level 999" 130 | ) 131 | async def level(self, ctx: rebootpy.ext.commands.Context, banner_level: int) -> None: 132 | await self.bot.party.me.set_banner( 133 | season_level=banner_level 134 | ) 135 | 136 | await ctx.send(f'Set level to {level}.') 137 | 138 | @commands.dm_only() 139 | @commands.command( 140 | description="[Party] Sends message to party chat with the given content.", 141 | help="Sends message to party chat with the given content.\n" 142 | "Example: !echo i cant fix the fucking public lobby bots" 143 | ) 144 | async def echo(self, ctx: rebootpy.ext.commands.Context, *, content: str) -> None: 145 | await self.bot.party.send(content) 146 | await ctx.send('Sent message to party chat.') 147 | 148 | @commands.dm_only() 149 | @commands.command( 150 | description="[Party] Leaves the current party.", 151 | help="Leaves the current party.\n" 152 | "Example: !leave" 153 | ) 154 | async def leave(self, ctx: rebootpy.ext.commands.Context) -> None: 155 | await self.bot.party.me.set_emote('EID_Wave') 156 | await asyncio.sleep(2) 157 | await self.bot.party.me.leave() 158 | await ctx.send('Bye!') 159 | 160 | print(self.bot.message % f'Left the party as I was requested.') 161 | 162 | @commands.dm_only() 163 | @commands.command( 164 | description="[Party] Kicks the inputted user.", 165 | help="Kicks the inputted user.\n" 166 | "Example: !kick Cxnyaa" 167 | ) 168 | async def kick(self, ctx: rebootpy.ext.commands.Context, *, epic_username: Optional[str] = None) -> None: 169 | if epic_username is None: 170 | user = await self.bot.fetch_user(ctx.author.display_name) 171 | member = self.bot.party.get_member(user.id) 172 | else: 173 | user = await self.bot.fetch_user(epic_username) 174 | member = self.bot.party.get_member(user.id) 175 | 176 | if member is None: 177 | await ctx.send("Failed to find that user, are you sure they're in the party?") 178 | else: 179 | try: 180 | await member.kick() 181 | await ctx.send(f"Kicked user: {member.display_name}.") 182 | print(self.bot.message % f"Kicked user: {member.display_name}") 183 | except rebootpy.errors.Forbidden: 184 | await ctx.send(f"Failed to kick {member.display_name}, as I'm not party leader.") 185 | print(crayons.red(self.bot.message % f"[ERROR] " 186 | "Failed to kick member as I don't have the required permissions.")) 187 | 188 | @commands.dm_only() 189 | @commands.command( 190 | aliases=['unhide'], 191 | description="[Party] Promotes the defined user to party leader. If friend is left blank, " 192 | "the message author will be used.", 193 | help="Promotes the defined user to party leader. If friend is left blank, the message author will be used.\n" 194 | "Example: !promote Terbau" 195 | ) 196 | async def promote(self, ctx: rebootpy.ext.commands.Context, *, epic_username: Optional[str] = None) -> None: 197 | if epic_username is None: 198 | user = await self.bot.fetch_user(ctx.author.display_name) 199 | member = self.bot.party.get_member(user.id) 200 | else: 201 | user = await self.bot.fetch_user(epic_username) 202 | member = self.bot.party.get_member(user.id) 203 | 204 | if member is None: 205 | await ctx.send("Failed to find that user, are you sure they're in the party?") 206 | else: 207 | try: 208 | await member.promote() 209 | await ctx.send(f"Promoted user: {member.display_name}.") 210 | print(self.bot.message % f"Promoted user: {member.display_name}") 211 | except rebootpy.errors.Forbidden: 212 | await ctx.send(f"Failed topromote {member.display_name}, as I'm not party leader.") 213 | print(crayons.red(self.bot.message % f"[ERROR] " 214 | "Failed to promote member as I don't have the required permissions.")) 215 | 216 | @commands.dm_only() 217 | @commands.command( 218 | description="[Party] Sets the lobbies selected playlist.", 219 | help="Sets the lobbies selected playlist.\n" 220 | "Example: !playlist_id Playlist_Tank_Solo" 221 | ) 222 | async def playlist_id(self, ctx: rebootpy.ext.commands.Context, playlist_: str) -> None: 223 | try: 224 | await self.bot.party.set_playlist(playlist=playlist_) 225 | await ctx.send(f'Gamemode set to {playlist_}') 226 | except rebootpy.errors.Forbidden: 227 | await ctx.send(f"Failed to set gamemode to {playlist_}, as I'm not party leader.") 228 | print(crayons.red(self.bot.message % f"[ERROR] " 229 | "Failed to set gamemode as I don't have the required permissions.")) 230 | 231 | @commands.dm_only() 232 | @commands.command( 233 | description="[Party] Sets the parties current privacy.", 234 | help="Sets the parties current privacy.\n" 235 | "Example: !privacy private" 236 | ) 237 | async def privacy(self, ctx: rebootpy.ext.commands.Context, privacy_type: str) -> None: 238 | try: 239 | if privacy_type.lower() == 'public': 240 | await self.bot.party.set_privacy(rebootpy.PartyPrivacy.PUBLIC) 241 | elif privacy_type.lower() == 'private': 242 | await self.bot.party.set_privacy(rebootpy.PartyPrivacy.PRIVATE) 243 | elif privacy_type.lower() == 'friends': 244 | await self.bot.party.set_privacy(rebootpy.PartyPrivacy.FRIENDS) 245 | elif privacy_type.lower() == 'friends_allow_friends_of_friends': 246 | await self.bot.party.set_privacy(rebootpy.PartyPrivacy.FRIENDS_ALLOW_FRIENDS_OF_FRIENDS) 247 | elif privacy_type.lower() == 'private_allow_friends_of_friends': 248 | await self.bot.party.set_privacy(rebootpy.PartyPrivacy.PRIVATE_ALLOW_FRIENDS_OF_FRIENDS) 249 | 250 | await ctx.send(f'Party privacy set to {self.bot.party.privacy}.') 251 | print(self.bot.message % f'Party privacy set to {self.bot.party.privacy}.') 252 | 253 | except rebootpy.errors.Forbidden: 254 | await ctx.send(f"Failed to set party privacy to {privacy_type}, as I'm not party leader.") 255 | print(crayons.red(self.bot.message % f"[ERROR] " 256 | "Failed to set party privacy as I don't have the required permissions.")) 257 | 258 | @commands.dm_only() 259 | @commands.command( 260 | description="[Party] Sets the parties custom matchmaking code.", 261 | help="Sets the parties custom matchmaking code.\n" 262 | "Example: !matchmakingcode solo123" 263 | ) 264 | async def matchmakingcode(self, ctx: rebootpy.ext.commands.Context, *, custom_matchmaking_key: str) -> None: 265 | await self.bot.party.set_custom_key( 266 | key=custom_matchmaking_key 267 | ) 268 | 269 | await ctx.send(f'Custom matchmaking code set to: {custom_matchmaking_key}') 270 | 271 | @commands.dm_only() 272 | @commands.command( 273 | description="[Party] Sets the client to the \"In Match\" state. If the first argument is 'progressive', " 274 | "the players remaining will gradually drop to mimic a real game.", 275 | help="Sets the client to the \"In Match\" state.\n" 276 | "Example: !match 69 420" 277 | ) 278 | async def match(self, ctx: rebootpy.ext.commands.Context, players: Union[str, int] = 0, 279 | match_time: int = 0) -> None: 280 | if players == 'progressive': 281 | match_time = datetime.datetime.utcnow() 282 | 283 | await self.bot.party.me.set_in_match( 284 | players_left=100, 285 | started_at=match_time 286 | ) 287 | 288 | while (100 >= self.bot.party.me.match_players_left > 0 289 | and self.bot.party.me.in_match()): 290 | await self.bot.party.me.set_in_match( 291 | players_left=self.bot.party.me.match_players_left - random.randint(3, 6), 292 | started_at=match_time 293 | ) 294 | 295 | await asyncio.sleep(random.randint(45, 65)) 296 | 297 | else: 298 | await self.bot.party.me.set_in_match( 299 | players_left=int(players), 300 | started_at=datetime.datetime.utcnow() - datetime.timedelta(minutes=match_time) 301 | ) 302 | 303 | await ctx.send(f'Set state to in-game in a match with {players} players.' 304 | '\nUse the command: !lobby to revert back to normal.') 305 | 306 | @commands.dm_only() 307 | @commands.command( 308 | description="[Party] Sets the client to normal pre-game lobby state.", 309 | help="Sets the client to normal pre-game lobby state.\n" 310 | "Example: !lobby" 311 | ) 312 | async def lobby(self, ctx: rebootpy.ext.commands.Context) -> None: 313 | if self.bot.default_party_member_config.cls == rebootpy.JustChattingClientPartyMember: 314 | self.bot.default_party_member_config.cls = rebootpy.ClientPartyMember 315 | 316 | party_id = self.bot.party.id 317 | await self.bot.party.me.leave() 318 | 319 | await ctx.send('Removed state of Just Chattin\'. Now attempting to rejoin party.') 320 | 321 | try: 322 | await self.bot.join_party(party_id) 323 | except rebootpy.errors.Forbidden: 324 | await ctx.send('Failed to join back as party is set to private.') 325 | except rebootpy.errors.NotFound: 326 | await ctx.send('Party not found, are you sure Fortnite is open?') 327 | 328 | await self.bot.party.me.clear_in_match() 329 | 330 | await ctx.send('Set state to the pre-game lobby.') 331 | 332 | @commands.dm_only() 333 | @commands.command( 334 | description="[Party] Joins the party of the defined friend. If friend is left blank, " 335 | "the message author will be used.", 336 | help="Joins the party of the defined friend.\n" 337 | "Example: !join Terbau" 338 | ) 339 | async def join(self, ctx: rebootpy.ext.commands.Context, *, epic_username: Optional[str] = None) -> None: 340 | if epic_username is None: 341 | epic_friend = self.bot.get_friend(ctx.author.id) 342 | else: 343 | user = await self.bot.fetch_user(epic_username) 344 | 345 | if user is not None: 346 | epic_friend = self.bot.get_friend(user.id) 347 | else: 348 | epic_friend = None 349 | await ctx.send(f'Failed to find user with the name: {epic_username}.') 350 | 351 | if isinstance(epic_friend, rebootpy.Friend): 352 | try: 353 | await epic_friend.join_party() 354 | await ctx.send(f'Joined the party of {epic_friend.display_name}.') 355 | except rebootpy.errors.Forbidden: 356 | await ctx.send('Failed to join party since it is private.') 357 | except rebootpy.errors.PartyError: 358 | await ctx.send('Party not found, are you sure Fortnite is open?') 359 | else: 360 | await ctx.send('Cannot join party as the friend is not found.') 361 | 362 | @commands.dm_only() 363 | @commands.command( 364 | description="[Party] Sets the lobbies selected playlist using playlist name.", 365 | help="Sets the lobbies selected playlist using playlist name.\n" 366 | "Example: !playlist Food Fight" 367 | ) 368 | async def playlist(self, ctx: rebootpy.ext.commands.Context, *, playlist_name: str) -> None: 369 | try: 370 | scuffedapi_playlist_id = await self.get_playlist(playlist_name) 371 | 372 | if scuffedapi_playlist_id is not None: 373 | await self.bot.party.set_playlist(playlist=scuffedapi_playlist_id) 374 | await ctx.send(f'Playlist set to {scuffedapi_playlist_id}.') 375 | print(self.bot.message % f'Playlist set to {scuffedapi_playlist_id}.') 376 | 377 | else: 378 | await ctx.send(f'Failed to find a playlist with the name: {playlist_name}.') 379 | print(crayons.red(self.bot.message % f"[ERROR] " 380 | f"Failed to find a playlist with the name: {playlist_name}.")) 381 | 382 | except rebootpy.errors.Forbidden: 383 | await ctx.send(f"Failed to set playlist to {playlist_name}, as I'm not party leader.") 384 | print(crayons.red(self.bot.message % f"[ERROR] " 385 | "Failed to set playlist as I don't have the required permissions.")) 386 | 387 | @commands.dm_only() 388 | @commands.command( 389 | name="invite", 390 | description="[Party] Invites the defined friend to the party. If friend is left blank, " 391 | "the message author will be used.", 392 | help="Invites the defined friend to the party.\n" 393 | "Example: !invite Terbau" 394 | ) 395 | async def _invite(self, ctx: rebootpy.ext.commands.Context, *, epic_username: Optional[str] = None) -> None: 396 | if epic_username is None: 397 | epic_friend = self.bot.get_friend(ctx.author.id) 398 | else: 399 | user = await self.bot.fetch_user(epic_username) 400 | 401 | if user is not None: 402 | epic_friend = self.bot.get_friend(user.id) 403 | else: 404 | epic_friend = None 405 | await ctx.send(f'Failed to find user with the name: {epic_username}.') 406 | print(crayons.red(self.bot.message % f"[ERROR] " 407 | f"Failed to find user with the name: {epic_username}.")) 408 | 409 | if isinstance(epic_friend, rebootpy.Friend): 410 | try: 411 | await epic_friend.invite() 412 | await ctx.send(f'Invited {epic_friend.display_name} to the party.') 413 | print(self.bot.message % f"[ERROR] Invited {epic_friend.display_name} to the party.") 414 | except rebootpy.errors.PartyError: 415 | await ctx.send('Failed to invite friend as they are either already in the party or it is full.') 416 | print(crayons.red(self.bot.message % f"[ERROR] " 417 | "Failed to invite to party as friend is already either in party or it is full.")) 418 | else: 419 | await ctx.send('Cannot invite to party as the friend is not found.') 420 | print(crayons.red(self.bot.message % f"[ERROR] " 421 | "Failed to invite to party as the friend is not found.")) 422 | 423 | @commands.dm_only() 424 | @commands.command( 425 | description="[Party] Hides everyone in the party except for the bot but if a player is specified, " 426 | "that specific player will be hidden.", 427 | help="Hides members of the party.\n" 428 | "Example: !hide" 429 | ) 430 | async def hide(self, ctx: rebootpy.ext.commands.Context, party_member: Optional[str] = None) -> None: 431 | if self.bot.party.me.leader: 432 | if party_member is not None: 433 | user = await self.bot.fetch_user(party_member) 434 | member = self.bot.party.get_member(user.id) 435 | 436 | if member is not None: 437 | raw_squad_assignments = self.bot.party.meta.get_prop( 438 | 'Default:RawSquadAssignments_j' 439 | )["RawSquadAssignments"] 440 | 441 | for player in raw_squad_assignments: 442 | if player['memberId'] == member.id: 443 | raw_squad_assignments.remove(player) 444 | 445 | await self.bot.set_and_update_party_prop( 446 | 'Default:RawSquadAssignments_j', { 447 | 'RawSquadAssignments': raw_squad_assignments 448 | } 449 | ) 450 | else: 451 | await ctx.send(f'Failed to find user with the name: {party_member}.') 452 | print(crayons.red(self.bot.message % f"[ERROR] " 453 | f"Failed to find user with the name: {party_member}.")) 454 | else: 455 | await self.bot.set_and_update_party_prop( 456 | 'Default:RawSquadAssignments_j', { 457 | 'RawSquadAssignments': [{'memberId': self.bot.user.id, 'absoluteMemberIdx': 1}] 458 | } 459 | ) 460 | 461 | await ctx.send('Hid everyone in the party. Use !unhide if you want to unhide everyone.' 462 | '\nReminder: Crashing lobbies is bannable offense which will result in a permanent ban.') 463 | print(self.bot.message % f'Hid everyone in the party.') 464 | else: 465 | await ctx.send("Failed to hide everyone, as I'm not party leader") 466 | print(crayons.red(self.bot.message % f"[ERROR] " 467 | "Failed to hide everyone as I don't have the required permissions.")) 468 | 469 | @commands.dm_only() 470 | @commands.command( 471 | description="[Party] Sets the client to the \"Just Chattin'\" state.", 472 | help="Sets the client to the \"Just Chattin'\" state.\n" 473 | "Example: !justchattin" 474 | ) 475 | async def justchattin(self, ctx: rebootpy.ext.commands.Context) -> None: 476 | self.bot.default_party_member_config.cls = rebootpy.JustChattingClientPartyMember 477 | 478 | party_id = self.bot.party.id 479 | await self.bot.party.me.leave() 480 | 481 | await ctx.send('Set state to Just Chattin\'. Now attempting to rejoin party.' 482 | '\nUse the command: !lobby to revert back to normal.') 483 | 484 | try: 485 | await self.bot.join_party(party_id) 486 | except rebootpy.errors.Forbidden: 487 | await ctx.send('Failed to join back as party is set to private.') 488 | except rebootpy.errors.NotFound: 489 | await ctx.send('Party not found, are you sure Fortnite is open?') 490 | -------------------------------------------------------------------------------- /partybot/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | “Commons Clause” License Condition v1.0 3 | Copyright Oli 2019-2023 4 | 5 | The Software is provided to you by the Licensor under the 6 | License, as defined below, subject to the following condition. 7 | 8 | Without limiting other conditions in the License, the grant 9 | of rights under the License will not include, and the License 10 | does not grant to you, the right to Sell the Software. 11 | 12 | For purposes of the foregoing, “Sell” means practicing any or 13 | all of the rights granted to you under the License to provide 14 | to third parties, for a fee or other consideration (including 15 | without limitation fees for hosting or consulting/ support 16 | services related to the Software), a product or service whose 17 | value derives, entirely or substantially, from the functionality 18 | of the Software. Any license notice or attribution required by 19 | the License must also include this Commons Clause License 20 | Condition notice. 21 | 22 | Software: PartyBot (fortnitepy-bot) 23 | 24 | License: Apache 2.0 25 | """ 26 | 27 | # System imports. 28 | import json 29 | 30 | # Third party imports. 31 | import aiofiles 32 | 33 | 34 | class BotSettings: 35 | def __init__(self, 36 | email: str = "", 37 | password: str = "", 38 | cid: str = "", 39 | bid: str = "", 40 | eid: str = "", 41 | pickaxe_id: str = "", 42 | banner: str = "", 43 | banner_colour: str = "", 44 | level: int = 0, 45 | bp_tier: int = 0, 46 | status: str = "", 47 | platform: str = "", 48 | debug: bool = False, 49 | friend_accept: bool = True 50 | ) -> None: 51 | self.email = email 52 | self.password = password 53 | self.cid = cid 54 | self.bid = bid 55 | self.eid = eid 56 | self.pickaxe_id = pickaxe_id 57 | self.banner = banner 58 | self.banner_colour = banner_colour 59 | self.level = level 60 | self.bp_tier = bp_tier 61 | self.status = status 62 | self.platform = platform 63 | self.debug = debug 64 | self.friend_accept = friend_accept 65 | 66 | async def load_settings_from_file(self, filename: str) -> None: 67 | async with aiofiles.open(filename, mode='r+') as f: 68 | raw = await f.read() 69 | 70 | data = json.loads(raw) 71 | 72 | self.email = data.get('email', self.email) 73 | self.password = data.get('password', self.password) 74 | self.cid = data.get('cid', self.cid) 75 | self.bid = data.get('bid', self.bid) 76 | self.eid = data.get('eid', self.eid) 77 | self.pickaxe_id = data.get('pickaxe_id', self.pickaxe_id) 78 | self.banner = data.get('banner', self.banner) 79 | self.banner_colour = data.get('banner_colour', self.banner_colour) 80 | self.level = data.get('level', self.level) 81 | self.bp_tier = data.get('bp_tier', self.bp_tier) 82 | self.status = data.get('status', self.status) 83 | self.platform = data.get('platform', self.platform) 84 | self.debug = data.get('debug', self.debug) 85 | self.friend_accept = data.get('friend_accept', self.friend_accept) 86 | 87 | def to_dict(self) -> dict: 88 | return { 89 | "email": self.email, 90 | "password": self.password, 91 | "cid": self.cid, 92 | "bid": self.bid, 93 | "eid": self.eid, 94 | "pickaxe_id": self.pickaxe_id, 95 | "banner": self.banner, 96 | "banner_colour": self.banner_colour, 97 | "level": self.level, 98 | "bp_tier": self.bp_tier, 99 | "status": self.status, 100 | "platform": self.platform, 101 | "debug": self.debug, 102 | "friend_accept": self.friend_accept 103 | } 104 | 105 | 106 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | rebootpy 2 | BenBotAsync 3 | crayons 4 | pypresence 5 | FortniteAPIAsync 6 | aiofiles 7 | cryptography==3.2.1 8 | aioconsole 9 | pytz 10 | tzdata 11 | tzlocal 12 | --------------------------------------------------------------------------------