├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
--------------------------------------------------------------------------------