├── .gitignore ├── LICENSE ├── Mazi.code-workspace ├── README.md ├── assets ├── catart.png ├── mazilogodark.png ├── mazilogolight.png └── mazilogosquare.png ├── bot.py ├── cogs ├── counts.py ├── donate.py ├── eggotyou.py ├── faq.py ├── help.py ├── host.py ├── join.py ├── link.py ├── movies.py ├── ping.py ├── project.py ├── search.py └── website.py ├── dockerfile ├── requirements.txt ├── static ├── favicon.ico └── styles.css └── templates ├── failpage.html └── statuspage.html /.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 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | creds.db 131 | USEFUL.txt 132 | modules/** 133 | resources/** 134 | frontend/** 135 | backend/** 136 | creds.json 137 | .env 138 | .dockerignore 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 David 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mazi.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "dotenv.enableAutocloaking": false 9 | } 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mazi 2 | A discord bot to watch plex with your friends easily and through discord. The first ever Movie Night Discord Bot. 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | wakatime 12 | 13 | 14 | # 15 | 16 |
17 | 18 | 19 |
20 | 21 | # Invite = [Invite Mazi to your server](https://discord.com/api/oauth2/authorize?client_id=978163786886311977&permissions=93184&redirect_uri=https%3A%2F%2Fapi.mazi.pw%2Fdiscordsuccess&scope=bot%20applications.commands) 22 | If you have any problems please read the [FAQ](https://github.com/Wamy-Dev/Mazi/wiki/FAQ) first, then message me on Discord using the server invite below if you still need help. The bot is also on [Top.gg](https://top.gg/bot/978163786886311977?s=0c29b1e62673d)! Please go give it a vote! 23 | 24 | # Website = [Mazi.pw](https://mazi.pw) 25 | 26 | Here you can link your accounts so you can use the Mazi Bot. Accounts are required to use the Mazi bot. You may get an alert from Plex when linking about a "security alert", and it will show the server IP, this will be fixed eventually. 27 | 28 | # How does this work? 29 | 1. First sign up for an account at [Mazi.pw](https://mazi.pw/user). 30 | 2. Link both your Discord and Plex accounts to it. If you are hosting Watch Together Sessions, make sure to add your public server url. This can be an ip or a url to your server. Make sure it is valid before submitting! 31 | 3. If you are hosting, use `/host` to begin a session. It will make a session, and will wait 5 minutes for people to join the room. If you are joining, use `/join` to join a joinable room. Make sure you are in the same channel as where the room was started. 32 | 4. After the 5 minutes has passed, the room will close and requests will go out to users who are not already friends with the hoster on Plex with a grace period of 5 minutes. After those 5 minutes, all users who have joined need to now open up Plex on any device, and join the Watch Together session. 33 | 5. Get your popcorn and drinks ready, cause the movie is starting! 34 | 35 | # Do you store my data? 36 | Yes, in an effort to be as transparent as possible, this is all the data we store. `Discord ID, Discord Email, Plex Auth Token (encrypted securely in AES), Plex email, Plex ID, Plex UUID, Plex Username, Plex Server URL, Plex User Profile Image, and your Mazi Email`. This data is stored securely in [Firebase Firestore](https://firebase.google.com/support/privacy) and I cannot decrypt your Plex Auth Token, or access either of your Discord or Plex accounts in any way. If you would like to delete all of your data, there is an "Unlink Data" button in the bottom left of the user panel. For more please read the [Mazi Privacy Policy](https://github.com/Wamy-Dev/Mazi/wiki/Privacy-Policy) for more information. 37 | 38 | # Features 39 | - [x] Easy Plex and Discord Linking 40 | - [x] Beautiful 41 | - [X] Ease of Use 42 | - [X] Fast 43 | 44 | # 45 | 46 | ##### This was made by me, Wamy. 47 | ##### Join my Discord server: [Here](https://discord.gg/47SnjxgBFb) 48 | ##### If you want to contact me: [My personal contact page](https://homeonacloud.com/contact) 49 | ##### If you want to donate to show your thanks: [My personal donation page](https://homeonacloud.com/donate) 50 | ##### This project was made with community in mind, please be nice and don't steal my work. 51 | -------------------------------------------------------------------------------- /assets/catart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wamy-Dev/Mazi/5ccff717bab1b1db16e239e51c3de896053e067d/assets/catart.png -------------------------------------------------------------------------------- /assets/mazilogodark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wamy-Dev/Mazi/5ccff717bab1b1db16e239e51c3de896053e067d/assets/mazilogodark.png -------------------------------------------------------------------------------- /assets/mazilogolight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wamy-Dev/Mazi/5ccff717bab1b1db16e239e51c3de896053e067d/assets/mazilogolight.png -------------------------------------------------------------------------------- /assets/mazilogosquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wamy-Dev/Mazi/5ccff717bab1b1db16e239e51c3de896053e067d/assets/mazilogosquare.png -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | # ______ __ __ __ 2 | # / ____/___ _____ _ / / / /_ ______ / /____ ___________ 3 | # / __/ / __ `/ __ `/ / /_/ / / / / __ \/ __/ _ \/ ___/ ___(_) 4 | # / /___/ /_/ / /_/ / / __ / /_/ / / / / /_/ __/ / (__ ) 5 | #/_____/\__, /\__, / /_/ /_/\__,_/_/ /_/\__/\___/_/ /____(_) 6 | # /____//____/ 7 | # directlycrazy.1812 8 | # Mato.5201 9 | # Andr01dx86.1886 10 | # DARTUBE.6145 11 | # Cholo Lord.6202 12 | # cocolaninano#7812 13 | # piny. 14 | # kayliatalon 15 | # yiga_ 16 | # cekirge1972 17 | # Matthew Ariel 18 | import requests 19 | import os 20 | import urllib3 21 | import logging 22 | from logging import getLogger 23 | from quart.logging import default_handler 24 | getLogger('quart.serving').removeHandler(default_handler) 25 | getLogger('quart.serving').setLevel(logging.ERROR) 26 | session = requests.Session() 27 | session.verify = False 28 | urllib3.disable_warnings() 29 | import discord 30 | from discord.ext import commands 31 | from quart import Quart, render_template 32 | from decouple import config 33 | import firebase_admin 34 | from firebase_admin import credentials 35 | import asyncio 36 | from threading import Thread 37 | import requests 38 | from datetime import datetime 39 | from humanfriendly import format_timespan 40 | #quart 41 | app = Quart(__name__) 42 | ready = False 43 | inittime = datetime.now() 44 | @app.route("/", methods = ["get"]) 45 | async def index(): 46 | while ready: 47 | latencies = [] 48 | for i in client.latencies: 49 | data = {} 50 | shard = i[0] 51 | latency = round(i[1] * 1000)#in ms 52 | data["shard"] = shard 53 | data["latency"] = latency 54 | uptime = datetime.now() - inittime 55 | secuptime = uptime.total_seconds() 56 | totaluptime = format_timespan(secuptime) 57 | data["uptime"] = totaluptime 58 | latencies.append(data) 59 | members = 0 60 | servers = client.guilds 61 | servercount = {} 62 | for guild in servers: 63 | id = guild.shard_id 64 | try: 65 | count = servercount[id] 66 | except: 67 | count = 0 68 | count += 1 69 | servercount[id] = count 70 | members += guild.member_count 71 | timeutc = str(datetime.utcnow()) 72 | timenow = timeutc[:-7] 73 | #get full number of servers 74 | serverstotal = len(servers) 75 | servers = serverstotal 76 | shardcount = client.shard_count 77 | return await render_template("statuspage.html", latencies=latencies, members=members, servercount=servercount, timenow=timenow, serverstotal=serverstotal, shardcount=shardcount) 78 | else: 79 | return await render_template("failpage.html") 80 | #firebase 81 | cred = credentials.Certificate("creds.json") 82 | firebase_admin.initialize_app(cred) 83 | #discord 84 | CLIENTTOKEN = config('CLIENTTOKEN') 85 | intents = discord.Intents.default() 86 | client = commands.AutoShardedBot(command_prefix = '>', intents=intents) 87 | client.remove_command('help') 88 | 89 | @client.event 90 | async def on_ready(): 91 | print(f'Bot is ready. Logged in as {client.user}(ID: {client.user.id})') 92 | print(f'Shards: {client.shard_count}') 93 | await client.change_presence(activity = discord.Activity(type = discord.ActivityType.watching, name = "/help")) 94 | global ready 95 | ready = True 96 | await asyncio.sleep(5) 97 | await client.tree.sync() 98 | print("Tree synced") 99 | 100 | # @client.tree.command(name="reload", description="Reloads a cog.") 101 | # async def reload(interaction: discord.Interaction, cog: str = None): 102 | # try: 103 | # await client.reload_extension(f"cogs.{cog}") 104 | # await interaction.response.send_message(f"Reloaded {cog}") 105 | # await client.tree.sync() 106 | # except Exception as e: 107 | # await interaction.response.send_message(f"{e}") 108 | 109 | class async_discord_thread(Thread): 110 | #thanks @FrankWhoee for this code snippet 111 | def __init__(self): 112 | Thread.__init__(self) 113 | self.loop = asyncio.get_event_loop() 114 | self.start() 115 | async def starter(self): 116 | async def load_extensions(): 117 | for filename in os.listdir("./cogs"): 118 | if filename.endswith(".py"): 119 | await client.load_extension(f"cogs.{filename[:-3]}") 120 | await load_extensions() 121 | await client.start(CLIENTTOKEN) 122 | def run(self): 123 | self.name = 'Discord.py' 124 | self.loop.create_task(self.starter()) 125 | self.loop.run_forever() 126 | discord_thread = async_discord_thread() 127 | app.run(host="0.0.0.0", port="5001") 128 | # async def load_extensions(): 129 | # for filename in os.listdir("./cogs"): 130 | # if filename.endswith(".py"): 131 | # await client.load_extension(f"cogs.{filename[:-3]}") 132 | # async def start(): 133 | # await load_extensions() 134 | # await client.start(CLIENTTOKEN) 135 | # asyncio.run(start()) 136 | -------------------------------------------------------------------------------- /cogs/counts.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord import Interaction 3 | from discord import app_commands 4 | from firebase_admin import firestore 5 | 6 | db = firestore.client() 7 | countsdoc = db.collection(u'counts').document(u'counts') 8 | 9 | class Counts(commands.Cog): 10 | 11 | def __init__(self, client): 12 | self.client = client 13 | 14 | @app_commands.command(name="counts", description="View how many movie nights have been hosted globally.") 15 | async def donate(self, interaction: Interaction): 16 | counts = countsdoc.get() 17 | previouscount = counts.to_dict() 18 | txt = str(f"""```css\nThe bot has hosted movie night {str(previouscount["counts"] - 1)} times.```""") 19 | await interaction.response.send_message(txt) 20 | 21 | async def setup(client): 22 | await client.add_cog(Counts(client)) -------------------------------------------------------------------------------- /cogs/donate.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | 6 | class Donate(commands.Cog): 7 | 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | @app_commands.command(name="donate", description="Doante to the Mazi project.") 12 | async def donate(self, interaction: Interaction): 13 | 14 | embed = discord.Embed(title = "Donate to the project", colour = discord.Colour.from_rgb(229,160,13)) 15 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 16 | embed.add_field(name = '🔗', value='https://homeonacloud.com/donate', inline = False) 17 | await interaction.response.send_message(embed=embed) 18 | 19 | async def setup(client): 20 | await client.add_cog(Donate(client)) -------------------------------------------------------------------------------- /cogs/eggotyou.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | 6 | class Modal(discord.ui.Modal, title='You got me!'): 7 | feedback = discord.ui.TextInput( 8 | label='You found the Easter Egg!', 9 | style=discord.TextStyle.long, 10 | default='You found the Easter Egg! If you want your name in the source code, send a screenshot of this to Wamy#0002 and your name will be added.', 11 | required=False, 12 | max_length=135, 13 | ) 14 | async def on_submit(self, interaction: discord.Interaction): 15 | await interaction.response.send_message('Thank you for using Mazi.', ephemeral=True) 16 | 17 | class Eggotyou(commands.Cog): 18 | 19 | def __init__(self, client): 20 | self.client = client 21 | 22 | @app_commands.command(name="egg", extras={"hidden": True}) 23 | async def egg(self, interaction: Interaction): 24 | await interaction.response.send_modal(Modal()) 25 | 26 | async def setup(client): 27 | await client.add_cog(Eggotyou(client)) -------------------------------------------------------------------------------- /cogs/faq.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | 6 | class Faq(commands.Cog): 7 | 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | @app_commands.command(name="faq", description="Read the Mazi F.A.Q.") 12 | async def faq(self, interaction: Interaction): 13 | 14 | embed = discord.Embed(title = "Mazi F.A.Q.", colour = discord.Colour.from_rgb(229,160,13)) 15 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 16 | embed.add_field(name = '🔗', value='https://github.com/Wamy-Dev/Mazi/wiki/FAQ', inline = False) 17 | await interaction.response.send_message(embed=embed) 18 | 19 | async def setup(client): 20 | await client.add_cog(Faq(client)) -------------------------------------------------------------------------------- /cogs/help.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | import random 6 | 7 | class Help(commands.Cog): 8 | 9 | def __init__(self, client): 10 | self.client = client 11 | 12 | @app_commands.command(name="help", description="Shows the help dialogue.") 13 | async def help(self, interaction: Interaction): 14 | list = ["Now with 100% more help!", "Now with slash commands!", "Cool cats watch movies.", "Runs on Plex and a little love.", "Help support Mazi by running /donate!", "Now showing, your movies!"] 15 | embed = discord.Embed(title = "Available commands", colour = discord.Colour.from_rgb(229,160,13)) 16 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 17 | embed.set_footer(text = random.choice(list)) 18 | try: 19 | for command in self.client.tree.get_commands(): 20 | if command.extras.get("hidden"): 21 | continue 22 | embed.add_field(name = f"", value = command.description, inline = False) 23 | except Exception as e: 24 | print(e) 25 | await interaction.response.send_message(embed=embed) 26 | 27 | async def setup(client): 28 | await client.add_cog(Help(client)) -------------------------------------------------------------------------------- /cogs/host.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | from firebase_admin import firestore 6 | from decouple import config 7 | from base64 import b64decode 8 | from nacl.secret import SecretBox 9 | from plexapi.server import PlexServer 10 | import requests 11 | import urllib3 12 | import random 13 | import asyncio 14 | 15 | db = firestore.client() 16 | countsdoc = db.collection(u'counts').document(u'counts') 17 | session = requests.Session() 18 | session.verify = False 19 | urllib3.disable_warnings() 20 | 21 | class Host(commands.Cog): 22 | 23 | def __init__(self, client): 24 | self.client = client 25 | @app_commands.command(name="host", description="Host a Plex movie watch together with your friends.") 26 | @app_commands.describe(library="Define the Plex library you would like to host from.", moviechoice = "The exact title of the movie you want to watch. Use /movies or /search to get the title.", timetostart = "The time in minutes to give your friends to join the watch together session. (Default: 5 minutes)") 27 | async def host(self, interaction: Interaction, moviechoice: str, timetostart: int = 5, library: str = None): 28 | await interaction.response.defer() #wait until the bot is finished thinking 29 | discordid = interaction.user.id 30 | # First check if the user is in the database 31 | try: 32 | docs = db.collection(u'userdata').where(u'discordid', u'==', discordid).stream() 33 | empty = True 34 | for doc in docs: 35 | empty = False 36 | data = doc.to_dict() 37 | if empty: 38 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 39 | view = discord.ui.View() 40 | view.add_item(button) 41 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 42 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 43 | await interaction.followup.send(embed=embed, view=view) 44 | return 45 | except: 46 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 47 | view = discord.ui.View() 48 | view.add_item(button) 49 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 50 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 51 | await interaction.followup.send(embed=embed, view=view) 52 | return 53 | # Check if the user has a Plex account linked 54 | try: 55 | plexstatus = data['plex'] 56 | except: 57 | button = discord.ui.Button(label="Link your Plex account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 58 | view = discord.ui.View() 59 | view.add_item(button) 60 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex account linked to your Mazi account. Please link one before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 61 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 62 | await interaction.followup.send(embed=embed, view=view) 63 | return 64 | # Check if the user has a Plex server linked 65 | try: 66 | plexserver = data['plexserver'] 67 | if len(plexserver) == 0: 68 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 69 | button2 = discord.ui.Button(label="View example server URLS", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 70 | view = discord.ui.View() 71 | view.add_item(button) 72 | view.add_item(button2) 73 | embed = discord.Embed(title = "Plex Server not linked!", description=f"```❌ You don't have a Plex server linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 74 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 75 | await interaction.followup.send(embed=embed, view=view) 76 | return 77 | except: 78 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 79 | button2 = discord.ui.Button(label="View example server URLS", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 80 | view = discord.ui.View() 81 | view.add_item(button) 82 | view.add_item(button2) 83 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex server linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 84 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 85 | await interaction.followup.send(embed=embed, view=view) 86 | return 87 | # Check if the user has a Plex library linked 88 | try: 89 | if library == None: 90 | plexlibrary = data['plexlibrary'] 91 | else: 92 | plexlibrary = library 93 | if len(plexlibrary) == 0: 94 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 95 | button2 = discord.ui.Button(label="View example library names", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 96 | view = discord.ui.View() 97 | view.add_item(button) 98 | view.add_item(button2) 99 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex library linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 100 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 101 | await interaction.followup.send(embed=embed, view=view) 102 | return 103 | except: 104 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 105 | button2 = discord.ui.Button(label="View example library names", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 106 | view = discord.ui.View() 107 | view.add_item(button) 108 | view.add_item(button2) 109 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex library linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 110 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 111 | await interaction.followup.send(embed=embed, view=view) 112 | return 113 | if plexstatus and plexserver and plexlibrary: 114 | if library == None: 115 | libraryname = data["plexlibrary"] 116 | else: 117 | libraryname = library 118 | try: 119 | if getHosting(data, moviechoice, libraryname) is None: 120 | embed = discord.Embed(title = "Item not found!", description=f"```❌ {moviechoice} cannot be found in library: {libraryname}. Please try again.```", colour = discord.Colour.from_rgb(229,160,13)) 121 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 122 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 123 | await interaction.followup.send(embed=embed) 124 | return 125 | else: 126 | token, machineid, movie, key, plex = getHosting(data, moviechoice, libraryname) 127 | except Exception as e: 128 | print(e) 129 | button = discord.ui.Button(label="Fix accounts", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 130 | button2 = discord.ui.Button(label="View examples", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 131 | view = discord.ui.View() 132 | view.add_item(button) 133 | view.add_item(button2) 134 | embed = discord.Embed(title = "Server not accessible!", description=f"```❌ Your sever is not accessible. This could be due to your server url being not accessible or wrong, or to your server library not being correct. Please view the examples if you need help adding your link or library.```", colour = discord.Colour.from_rgb(229,160,13)) 135 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 136 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 137 | await interaction.followup.send(embed = embed, view=view) 138 | return 139 | try: 140 | # create room 141 | counts = countsdoc.get() 142 | previouscount = counts.to_dict() 143 | roomnames = ["Action", "Animation", "Horror", "Adventure", "Comedy", "Romance", "SciFi", "Fantasy", "Musical"] 144 | roomname = random.choice(roomnames)+"-"+str(previouscount["counts"]) 145 | msg = await interaction.followup.send(f"```Please join this thread to join Watch Together session: {roomname}.```") 146 | thread = await interaction.channel.create_thread(name=f"Watch Together Session: {roomname}", message=msg, reason="Mazi created Watch Together room.", auto_archive_duration=60) 147 | await thread.send("```To join, please run '/join' in this thread.```") 148 | doc = db.collection(u'counts').document(u'counts') 149 | userid = data["plexid"] 150 | thumb = data["plexthumb"] 151 | title = data["plextitle"] 152 | email = data["plexemail"] 153 | channel = thread.id 154 | discordid = discordid 155 | # add to database 156 | ref = db.collection(u'rooms').document(roomname) 157 | ref.set({ 158 | u'Time Started': firestore.SERVER_TIMESTAMP, 159 | u'Thread': channel, 160 | u'MovieKey': key, 161 | u'Server': plexserver, 162 | u'MachineID': machineid, 163 | }, merge=True) 164 | #now add first user 165 | userref = db.collection(u'rooms').document(roomname).collection(u'Users').document(str(discordid)) 166 | userref.set({ 167 | u'id': userid, 168 | u'thumb': thumb, 169 | u'title': title, 170 | u'email': email, 171 | }, merge=True) 172 | #send message that the room is ready 173 | embed = discord.Embed(title = f"{roomname} is now open to join!", colour = discord.Colour.from_rgb(229,160,13)) 174 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 175 | embed.add_field(name = f'{libraryname}', value=f"We are watching {movie.title}!", inline = False) 176 | embed.add_field(name = 'Join Now', value="The room is now open to join! Run /join to join. Make sure you have linked your Plex and Discord accounts.", inline = False) 177 | if timetostart == 0: 178 | embed.set_footer(text = "Room joining will not be disabled. Movie will start immediately. This means that nobody will be able to join.") 179 | else: 180 | embed.set_footer(text = f"Room joining closes in {timetostart} minutes.") 181 | await thread.send(embed = embed) 182 | except Exception as e: 183 | print(e) 184 | print("error creating room") 185 | return 186 | # update counts 187 | try: 188 | doc = db.collection(u'counts').document(u'counts') 189 | counts = doc.get() 190 | previouscount = counts.to_dict() 191 | newcount = int(previouscount['counts']) + 1 192 | doc.update({u'counts': newcount}) 193 | except Exception as e: 194 | print(e) 195 | print('Adding count failed') 196 | # timer for room 197 | await asyncio.sleep(timetostart*60) 198 | #start room 199 | try: 200 | #get users from room 201 | ref = db.collection(u'rooms').document(roomname) 202 | doc = ref.get() 203 | data = doc.to_dict() 204 | machineid = data["MachineID"] 205 | moviekey = data["MovieKey"] 206 | #for users in data get user objects and add them to users 207 | users = [] 208 | collections = db.collection(u'rooms').document(roomname).collections() 209 | for collection in collections: 210 | for doc in collection.stream(): 211 | data = doc.to_dict() 212 | #send request to all users in rooms 213 | username = data["email"] 214 | try: 215 | plex.myPlexAccount().inviteFriend(user=username, server=plex) 216 | except Exception as e: 217 | print(e) 218 | data.pop("email") 219 | users.append(data) 220 | url = f"https://together.plex.tv/rooms?X-Plex-Token={token}" 221 | obj = { 222 | "sourceUri": f"server://{machineid}/com.plexapp.plugins.library/library/metadata/{moviekey}", 223 | "title": movie.title, 224 | "users": users 225 | } 226 | try: 227 | if timetostart == 0: 228 | requests.post(url, json = obj) 229 | else: 230 | db.collection(u'rooms').document(roomname).delete() 231 | await thread.send(f"```Joining for {roomname} is closed. Open Plex on any device and accept the friend request if you are not already friends with the hoster. Then in 5 minutes, join the Watch Together session. {movie.title.capitalize()} will begin shortly.```") 232 | await asyncio.sleep(timetostart*60) 233 | requests.post(url, json = obj) 234 | await thread.send(f"```{roomname} has now started watching {movie.title}!```") 235 | except: 236 | embed = discord.Embed(title = "Server not accessible!", description=f"```❌ Something went wrong and couldn't get a room set up. Please try again later or report this as an error using /project.```", colour = discord.Colour.from_rgb(229,160,13)) 237 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 238 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 239 | await interaction.followup.send(embed = embed, view=view) 240 | return 241 | except: 242 | embed = discord.Embed(title = "Server not accessible!", description=f"```❌ Something went wrong and couldn't get a room set up. Please try again later or report this as an error using /project.```", colour = discord.Colour.from_rgb(229,160,13)) 243 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 244 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 245 | await interaction.followup.send(embed = embed, view=view) 246 | return 247 | def getHosting(data, moviechoice, libraryname): 248 | try: 249 | encrypted = b64decode(data["plexauth"].encode("utf-8")) 250 | secret_key = config('AESKEY') 251 | box = SecretBox(bytes(secret_key, encoding='utf8')) 252 | plexauth = box.decrypt(encrypted).decode("utf-8") 253 | plexurl = data["plexserver"] 254 | if not (plexurl.startswith('http') or plexurl.startswith('https')): 255 | try: 256 | plexurl = f'http://{plexurl}' 257 | plex = PlexServer(plexurl, plexauth, session=session) 258 | except: 259 | plexurl = f'https://{plexurl}' 260 | plex = PlexServer(plexurl, plexauth, session=session) 261 | else: 262 | plex = PlexServer(plexurl, plexauth, session=session) 263 | token = plex._token 264 | machineid = plex.machineIdentifier 265 | movie = plex.library.section(libraryname).get(moviechoice) 266 | key = movie.ratingKey 267 | return token, machineid, movie, key, plex 268 | except: 269 | return None 270 | 271 | async def setup(client): 272 | await client.add_cog(Host(client)) -------------------------------------------------------------------------------- /cogs/join.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | from firebase_admin import firestore 6 | import requests 7 | import urllib3 8 | 9 | db = firestore.client() 10 | session = requests.Session() 11 | session.verify = False 12 | urllib3.disable_warnings() 13 | 14 | class Join(commands.Cog): 15 | 16 | def __init__(self, client): 17 | self.client = client 18 | 19 | @app_commands.command(name="join", description="Join an active Plex watch together session room.") 20 | async def search(self, interaction: Interaction): 21 | await interaction.response.defer() #wait until the bot is finished thinking 22 | discordid = interaction.user.id 23 | channel = interaction.channel.id 24 | # First check if the user is in the database 25 | try: 26 | docs = db.collection(u'userdata').where(u'discordid', u'==', discordid).stream() 27 | empty = True 28 | for doc in docs: 29 | empty = False 30 | data = doc.to_dict() 31 | if empty: 32 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 33 | view = discord.ui.View() 34 | view.add_item(button) 35 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 36 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 37 | await interaction.followup.send(embed=embed, view=view) 38 | return 39 | except: 40 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 41 | view = discord.ui.View() 42 | view.add_item(button) 43 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 44 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 45 | await interaction.followup.send(embed=embed, view=view) 46 | return 47 | # Check if the user has a Plex account linked 48 | try: 49 | plexstatus = data['plex'] 50 | except: 51 | button = discord.ui.Button(label="Link your Plex account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 52 | view = discord.ui.View() 53 | view.add_item(button) 54 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex account linked to your Mazi account. Please link one before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 55 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 56 | await interaction.followup.send(embed=embed, view=view) 57 | return 58 | if plexstatus: 59 | try: 60 | docs = db.collection(u'rooms').where(u'Thread', u'==', channel).stream() 61 | norooms = True 62 | for rooms in docs: 63 | norooms = False 64 | roomname = rooms.id 65 | if norooms: 66 | embed = discord.Embed(title = "No joinable rooms!", description=f"```❌ No joinable rooms found. Please try again later or in another thread.```", colour = discord.Colour.from_rgb(229,160,13)) 67 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 68 | await interaction.followup.send(embed=embed) 69 | return 70 | except: 71 | embed = discord.Embed(title = "No joinable rooms!", description=f"```❌ No joinable rooms found. Please try again later or in another thread.```", colour = discord.Colour.from_rgb(229,160,13)) 72 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 73 | await interaction.followup.send(embed=embed) 74 | return 75 | try: 76 | document = db.collection(u'userdata').where(u'discordid', u'==', discordid).stream() 77 | empty = True 78 | for doc in document: 79 | empty = False 80 | data = doc.to_dict() 81 | userid = data["plexid"] 82 | thumb = data["plexthumb"] 83 | title = data["plextitle"] 84 | email = data["plexemail"] 85 | #add to roomname 86 | userref = db.collection(u'rooms').document(roomname).collection(u'Users').document(str(discordid)) 87 | userref.set({ 88 | u'id': userid, 89 | u'thumb': thumb, 90 | u'title': title, 91 | u'email': email 92 | }, merge=True) 93 | #announce user join 94 | embed = discord.Embed(title = f"{interaction.user.display_name} has joined {roomname}!", description=f"```{interaction.user.display_name} will recieve an invite to {roomname}.```", colour = discord.Colour.from_rgb(229,160,13)) 95 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 96 | await interaction.followup.send(embed=embed) 97 | if empty: 98 | embed = discord.Embed(title = "Error joining room!", description=f"```❌ Error joining room. Please try again later or report this error using /project.```", colour = discord.Colour.from_rgb(229,160,13)) 99 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 100 | await interaction.followup.send(embed=embed) 101 | except Exception as e: 102 | embed = discord.Embed(title = "Error joining room!", description=f"```❌ Error joining room. Please try again later or report this error using /project.```", colour = discord.Colour.from_rgb(229,160,13)) 103 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 104 | await interaction.followup.send(embed=embed) 105 | 106 | async def setup(client): 107 | await client.add_cog(Join(client)) -------------------------------------------------------------------------------- /cogs/link.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | 6 | class Link(commands.Cog): 7 | 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | @app_commands.command(name="link", description="Link your Plex and Discord accounts.") 12 | async def link(self, interaction: Interaction): 13 | 14 | embed = discord.Embed(title = "Mazi Website", colour = discord.Colour.from_rgb(229,160,13)) 15 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 16 | embed.add_field(name = '🔗', value='https://mazi.pw/user', inline = False) 17 | await interaction.response.send_message(embed=embed) 18 | 19 | async def setup(client): 20 | await client.add_cog(Link(client)) -------------------------------------------------------------------------------- /cogs/movies.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | from firebase_admin import firestore 6 | from decouple import config 7 | from base64 import b64decode 8 | from nacl.secret import SecretBox 9 | from plexapi.server import PlexServer 10 | import requests 11 | import urllib3 12 | import traceback 13 | 14 | db = firestore.client() 15 | session = requests.Session() 16 | session.verify = False 17 | urllib3.disable_warnings() 18 | 19 | class Movies(commands.Cog): 20 | 21 | def __init__(self, client): 22 | self.client = client 23 | 24 | @app_commands.command(name="movies", description="View the all the movies available to watch on your linked Plex account.") 25 | @app_commands.describe(library="Define the Plex library you would like to host from.") 26 | async def search(self, interaction: Interaction, library: str = None): 27 | await interaction.response.defer() #wait until the bot is finished thinking 28 | discordid = interaction.user.id 29 | # First check if the user is in the database 30 | try: 31 | docs = db.collection(u'userdata').where(u'discordid', u'==', discordid).stream() 32 | empty = True 33 | for doc in docs: 34 | empty = False 35 | data = doc.to_dict() 36 | if empty: 37 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 38 | view = discord.ui.View() 39 | view.add_item(button) 40 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 41 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 42 | await interaction.followup.send(embed=embed, view=view) 43 | return 44 | except Exception as e: 45 | print(e) 46 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 47 | view = discord.ui.View() 48 | view.add_item(button) 49 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 50 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 51 | await interaction.followup.send(embed=embed, view=view) 52 | return 53 | # Check if the user has a Plex account linked 54 | try: 55 | plexstatus = data['plex'] 56 | except: 57 | button = discord.ui.Button(label="Link your Plex account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 58 | view = discord.ui.View() 59 | view.add_item(button) 60 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex account linked to your Mazi account. Please link one before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 61 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 62 | await interaction.followup.send(embed=embed, view=view) 63 | return 64 | # Check if the user has a Plex server linked 65 | try: 66 | plexserver = data['plexserver'] 67 | if len(plexserver) == 0: 68 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 69 | button2 = discord.ui.Button(label="View example server URLS", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 70 | view = discord.ui.View() 71 | view.add_item(button) 72 | view.add_item(button2) 73 | embed = discord.Embed(title = "Plex Server not linked!", description=f"```❌ You don't have a Plex server linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 74 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 75 | await interaction.followup.send(embed=embed, view=view) 76 | return 77 | except: 78 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 79 | button2 = discord.ui.Button(label="View example server URLS", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 80 | view = discord.ui.View() 81 | view.add_item(button) 82 | view.add_item(button2) 83 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex server linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 84 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 85 | await interaction.followup.send(embed=embed, view=view) 86 | return 87 | # Check if the user has a Plex library linked 88 | try: 89 | if library == None: 90 | plexlibrary = data['plexlibrary'] 91 | else: 92 | plexlibrary = library 93 | if len(plexlibrary) == 0: 94 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 95 | button2 = discord.ui.Button(label="View example library names", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 96 | view = discord.ui.View() 97 | view.add_item(button) 98 | view.add_item(button2) 99 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex library linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 100 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 101 | await interaction.followup.send(embed=embed, view=view) 102 | return 103 | except: 104 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 105 | button2 = discord.ui.Button(label="View example library names", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 106 | view = discord.ui.View() 107 | view.add_item(button) 108 | view.add_item(button2) 109 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex library linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 110 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 111 | await interaction.followup.send(embed=embed, view=view) 112 | return 113 | if plexstatus and plexserver and plexlibrary: 114 | if library == None: 115 | libraryname = data["plexlibrary"] 116 | else: 117 | libraryname = library 118 | try: 119 | moviefields = [] 120 | for movie in getMovies(data, libraryname): 121 | moviefields.append(movie) 122 | movieslist = "" 123 | for items in moviefields: 124 | movieslist += f'{items}\n' 125 | if len(movieslist) == 0: 126 | embed = discord.Embed(title = "No items found!", description=f"```❌ No items found in {libraryname}. Please add items to your library.```", colour = discord.Colour.from_rgb(229,160,13)) 127 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 128 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 129 | await interaction.followup.send(embed = embed) 130 | if len(movieslist) > 4096: 131 | moviefields = [] 132 | for movie in getMoviesRecent(data, libraryname): 133 | moviefields.append(movie) 134 | movieslist2 = "" 135 | for items in moviefields: 136 | movieslist2 += f'{items}\n' 137 | embed = discord.Embed(title = f"Recently added in {libraryname}", description=movieslist2, colour = discord.Colour.from_rgb(229,160,13)) 138 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 139 | embed.set_footer(text = "Mazi couldn't show all items so it is showing the most recently added.") 140 | await interaction.followup.send(embed = embed) 141 | if len(movieslist) < 4096: 142 | embed = discord.Embed(title = f"Available items in {libraryname}", description=movieslist, colour = discord.Colour.from_rgb(229,160,13)) 143 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 144 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 145 | await interaction.followup.send(embed = embed) 146 | except: 147 | button = discord.ui.Button(label="Fix accounts", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 148 | button2 = discord.ui.Button(label="View examples", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 149 | view = discord.ui.View() 150 | view.add_item(button) 151 | view.add_item(button2) 152 | embed = discord.Embed(title = "Server not accessible!", description=f"```❌ Your sever is not accessible. This could be due to your server url being not accessible or wrong, or to your server library not being correct. Please view the examples if you need help adding your link or library.```", colour = discord.Colour.from_rgb(229,160,13)) 153 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.avatar.url) 154 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 155 | await interaction.followup.send(embed = embed, view=view) 156 | return 157 | 158 | def getMoviesRecent(data, libraryname): 159 | try: 160 | encrypted = b64decode(data["plexauth"].encode("utf-8")) 161 | secret_key = config('AESKEY') 162 | box = SecretBox(bytes(secret_key, encoding='utf8')) 163 | plexauth = box.decrypt(encrypted).decode("utf-8") 164 | plexurl = data["plexserver"] 165 | if not (plexurl.startswith('http') or plexurl.startswith('https')): 166 | try: 167 | plexurl = f'http://{plexurl}' 168 | plex = PlexServer(plexurl, plexauth, session=session) 169 | except: 170 | plexurl = f'https://{plexurl}' 171 | plex = PlexServer(plexurl, plexauth, session=session) 172 | else: 173 | plex = PlexServer(plexurl, plexauth, session=session) 174 | movies = plex.library.section(libraryname) 175 | except Exception as e: 176 | print(e) 177 | return None 178 | list = [] 179 | total = 0 180 | for video in movies.recentlyAdded(maxresults=50): 181 | list.append(video.title) 182 | total += len(video.title) 183 | if total > 4096: 184 | break 185 | return(list) 186 | def getMovies(data, libraryname): 187 | try: 188 | encrypted = b64decode(data["plexauth"].encode("utf-8")) 189 | secret_key = config('AESKEY') 190 | box = SecretBox(bytes(secret_key, encoding='utf8')) 191 | plexauth = box.decrypt(encrypted).decode("utf-8") 192 | plexurl = data["plexserver"] 193 | if not (plexurl.startswith('http') or plexurl.startswith('https')): 194 | try: 195 | plexurl = f'http://{plexurl}' 196 | plex = PlexServer(plexurl, plexauth, session=session) 197 | except: 198 | plexurl = f'https://{plexurl}' 199 | plex = PlexServer(plexurl, plexauth, session=session) 200 | else: 201 | plex = PlexServer(plexurl, plexauth, session=session) 202 | movies = plex.library.section(libraryname) 203 | except Exception as e: 204 | traceback.print_exc() 205 | return None 206 | list = [] 207 | for video in movies.all(): 208 | list.append(video.title) 209 | return(list) 210 | 211 | async def setup(client): 212 | await client.add_cog(Movies(client)) -------------------------------------------------------------------------------- /cogs/ping.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord import Interaction 3 | from discord import app_commands 4 | 5 | class Ping(commands.Cog): 6 | 7 | def __init__(self, client): 8 | self.client = client 9 | 10 | @app_commands.command(name="ping", description="View the ping between Discord and Mazi.") 11 | async def donate(self, interaction: Interaction): 12 | 13 | txt = str(f"""```css\nIm not too slow right? {round(self.client.latency * 1000)}ms.```""") 14 | await interaction.response.send_message(txt) 15 | 16 | async def setup(client): 17 | await client.add_cog(Ping(client)) -------------------------------------------------------------------------------- /cogs/project.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | 6 | class Project(commands.Cog): 7 | 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | @app_commands.command(name="project", description="View the GitHub repository for Mazi.") 12 | async def project(self, interaction: Interaction): 13 | 14 | embed = discord.Embed(title = "Mazi Github", colour = discord.Colour.from_rgb(229,160,13)) 15 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 16 | embed.add_field(name = '🔗', value='https://github.com/Wamy-Dev/Mazi', inline = False) 17 | await interaction.response.send_message(embed=embed) 18 | 19 | async def setup(client): 20 | await client.add_cog(Project(client)) -------------------------------------------------------------------------------- /cogs/search.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | from firebase_admin import firestore 6 | from decouple import config 7 | from nacl.secret import SecretBox 8 | from plexapi.server import PlexServer 9 | import requests 10 | import urllib3 11 | from base64 import b64decode 12 | 13 | db = firestore.client() 14 | session = requests.Session() 15 | session.verify = False 16 | urllib3.disable_warnings() 17 | 18 | class Search(commands.Cog): 19 | 20 | def __init__(self, client): 21 | self.client = client 22 | 23 | @app_commands.command(name="search", description="Search for an item in your Plex library.") 24 | @app_commands.describe(search="The item you want to search for.", library="Define the Plex library you would like to host from.") 25 | async def search(self, interaction: Interaction, search: str, library: str = None): 26 | await interaction.response.defer() #wait until the bot is finished thinking 27 | discordid = interaction.user.id 28 | # First check if the user is in the database 29 | try: 30 | docs = db.collection(u'userdata').where(u'discordid', u'==', discordid).stream() 31 | empty = True 32 | for doc in docs: 33 | empty = False 34 | data = doc.to_dict() 35 | if empty: 36 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 37 | view = discord.ui.View() 38 | view.add_item(button) 39 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 40 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 41 | await interaction.followup.send(embed=embed, view=view) 42 | return 43 | except: 44 | button = discord.ui.Button(label="Link your Discord account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 45 | view = discord.ui.View() 46 | view.add_item(button) 47 | embed = discord.Embed(title = "Discord account not linked!", description=f"```❌ You don't have a Discord account linked to your Mazi account. You also may not have a Mazi account. Please create one if needed. Please link your Discord account before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 48 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 49 | await interaction.followup.send(embed=embed, view=view) 50 | return 51 | # Check if the user has a Plex account linked 52 | try: 53 | plexstatus = data['plex'] 54 | except: 55 | button = discord.ui.Button(label="Link your Plex account", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 56 | view = discord.ui.View() 57 | view.add_item(button) 58 | embed = discord.Embed(title = "Plex account not linked!", description=f"```❌ You don't have a Plex account linked to your Mazi account. Please link one before using any features as it is required.```", colour = discord.Colour.from_rgb(229,160,13)) 59 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 60 | await interaction.followup.send(embed=embed, view=view) 61 | return 62 | # Check if the user has a Plex server linked 63 | try: 64 | plexserver = data['plexserver'] 65 | if len(plexserver) == 0: 66 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 67 | button2 = discord.ui.Button(label="View example server URLS", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 68 | view = discord.ui.View() 69 | view.add_item(button) 70 | view.add_item(button2) 71 | embed = discord.Embed(title = "Plex Server not linked!", description=f"```❌ You don't have a Plex server linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 72 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 73 | await interaction.followup.send(embed=embed, view=view) 74 | return 75 | except: 76 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 77 | button2 = discord.ui.Button(label="View example server URLS", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 78 | view = discord.ui.View() 79 | view.add_item(button) 80 | view.add_item(button2) 81 | embed = discord.Embed(title = "Plex Server not linked!", description=f"```❌ You don't have a Plex server linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 82 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 83 | await interaction.followup.send(embed=embed, view=view) 84 | return 85 | # Check if the user has a Plex library linked 86 | try: 87 | if library == None: 88 | plexlibrary = data['plexlibrary'] 89 | else: 90 | plexlibrary = library 91 | if len(plexlibrary) == 0: 92 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 93 | button2 = discord.ui.Button(label="View example library names", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 94 | view = discord.ui.View() 95 | view.add_item(button) 96 | view.add_item(button2) 97 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex library linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 98 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 99 | await interaction.followup.send(embed=embed, view=view) 100 | return 101 | 102 | except: 103 | button = discord.ui.Button(label="Link your Plex server", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 104 | button2 = discord.ui.Button(label="View example library names", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 105 | view = discord.ui.View() 106 | view.add_item(button) 107 | view.add_item(button2) 108 | embed = discord.Embed(title = "Plex Library not linked!", description=f"```❌ You don't have a Plex library linked to your Mazi account. Please add one as it is required to host sessions.```", colour = discord.Colour.from_rgb(229,160,13)) 109 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 110 | await interaction.followup.send(embed=embed, view=view) 111 | return 112 | 113 | if plexstatus and plexserver and plexlibrary: 114 | if library == None: 115 | libraryname = data["plexlibrary"] 116 | else: 117 | libraryname = library 118 | try: 119 | moviefields = [] 120 | for movie in searchPlex(data, search, libraryname): 121 | moviefields.append(movie) 122 | movieslist = "" 123 | for items in moviefields: 124 | movieslist += f'{items}\n' 125 | if len(moviefields) == 0: 126 | embed = discord.Embed(title = "No results found!", description=f"```❌ No results found in library: {libraryname} when searching for {search}. Please try again later or change search.```", colour = discord.Colour.from_rgb(229,160,13)) 127 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 128 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 129 | await interaction.followup.send(embed = embed) 130 | else: 131 | embed = discord.Embed(title = f"Top results in {libraryname}", description=movieslist, colour = discord.Colour.from_rgb(229,160,13)) 132 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 133 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 134 | await interaction.followup.send(embed = embed) 135 | except Exception as e: 136 | print(e) 137 | button = discord.ui.Button(label="Fix accounts", style=discord.ButtonStyle.link, url="https://mazi.pw/user") 138 | button2 = discord.ui.Button(label="View examples", style=discord.ButtonStyle.link, url="https://github.com/Wamy-Dev/Mazi/wiki/Examples") 139 | view = discord.ui.View() 140 | view.add_item(button) 141 | view.add_item(button2) 142 | embed = discord.Embed(title = "Server not accessible!", description=f"```❌ Your sever is not accessible. This could be due to your server url being not accessible or wrong, or to your server library not being correct. Please view the examples if you need help adding your link or library.```", colour = discord.Colour.from_rgb(229,160,13)) 143 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 144 | embed.set_footer(text = f"{interaction.user.display_name}'s {libraryname}") 145 | await interaction.followup.send(embed = embed, view=view) 146 | return 147 | 148 | def searchPlex(data, title, libraryname): 149 | try: 150 | encrypted = b64decode(data["plexauth"].encode("utf-8")) 151 | secret_key = config('AESKEY') 152 | box = SecretBox(bytes(secret_key, encoding='utf8')) 153 | plexauth = box.decrypt(encrypted).decode("utf-8") 154 | plexurl = data["plexserver"] 155 | if not (plexurl.startswith('http') or plexurl.startswith('https')): 156 | try: 157 | plexurl = f'http://{plexurl}' 158 | plex = PlexServer(plexurl, plexauth, session=session) 159 | except: 160 | plexurl = f'https://{plexurl}' 161 | plex = PlexServer(plexurl, plexauth, session=session) 162 | else: 163 | plex = PlexServer(plexurl, plexauth, session=session) 164 | movies = plex.library.section(libraryname) 165 | except Exception as e: 166 | print(e) 167 | return None 168 | list = [] 169 | for video in movies.search(title = title, maxresults = 5): 170 | list.append(video.title) 171 | return(list) 172 | 173 | async def setup(client): 174 | await client.add_cog(Search(client)) -------------------------------------------------------------------------------- /cogs/website.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord import Interaction 4 | from discord import app_commands 5 | 6 | class Website(commands.Cog): 7 | 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | @app_commands.command(name="website", description="View the Mazi website.") 12 | async def website(self, interaction: Interaction): 13 | 14 | embed = discord.Embed(title = "Mazi Website", colour = discord.Colour.from_rgb(229,160,13)) 15 | embed.set_author(name = interaction.user.display_name, icon_url = interaction.user.display_avatar.url) 16 | embed.add_field(name = '🔗', value='https://mazi.pw', inline = False) 17 | await interaction.response.send_message(embed=embed) 18 | 19 | async def setup(client): 20 | await client.add_cog(Website(client)) -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-alpine 2 | WORKDIR /Mazi 3 | COPY . . 4 | 5 | RUN apk update && apk add --no-cache git gcc libffi-dev python3-dev libc-dev linux-headers 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | ENV CLIENTTOKEN= 8 | ENV AESKEY= 9 | CMD ["python", "bot.py"] 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/Rapptz/discord.py 2 | asyncio 3 | python-decouple 4 | quart 5 | firebase-admin 6 | plexapi 7 | pynacl 8 | requests 9 | plexapi 10 | humanfriendly 11 | hypercorn -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wamy-Dev/Mazi/5ccff717bab1b1db16e239e51c3de896053e067d/static/favicon.ico -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: "Ubuntu", sans-serif; 4 | background-color: #212534; 5 | } 6 | h1 { 7 | color: white; 8 | } 9 | p { 10 | color: white; 11 | } 12 | li { 13 | color: white; 14 | list-style: none; 15 | margin-bottom: 10px; 16 | } 17 | .header { 18 | position:relative; 19 | text-align:center; 20 | background: linear-gradient(60deg, #E5A00D 0%, #000000 100%); 21 | color:white; 22 | } 23 | .inner-header { 24 | height: 65vh; 25 | width: 100%; 26 | margin: 0; 27 | padding: 0; 28 | overflow: auto; 29 | } 30 | .flex { /*Flexbox for containers*/ 31 | display: flex; 32 | justify-content: center; 33 | text-align: center; 34 | } 35 | .waves { 36 | position:relative; 37 | width: 100%; 38 | height: 15vh; 39 | margin-bottom:-7px; /*Fix for safari gap*/ 40 | min-height:100px; 41 | max-height:150px; 42 | } 43 | .content { 44 | position:relative; 45 | height:20vh; 46 | text-align:center; 47 | background-color: white; 48 | } 49 | .footer { 50 | color: black; 51 | } 52 | .items { 53 | margin-top: 20px; 54 | } 55 | .parallax > use { 56 | animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite; 57 | } 58 | .parallax > use:nth-child(1) { 59 | animation-delay: -2s; 60 | animation-duration: 7s; 61 | } 62 | .parallax > use:nth-child(2) { 63 | animation-delay: -3s; 64 | animation-duration: 10s; 65 | } 66 | .parallax > use:nth-child(3) { 67 | animation-delay: -4s; 68 | animation-duration: 13s; 69 | } 70 | .parallax > use:nth-child(4) { 71 | animation-delay: -5s; 72 | animation-duration: 20s; 73 | } 74 | @keyframes move-forever { 75 | 0% { 76 | transform: translate3d(-90px,0,0); 77 | } 78 | 100% { 79 | transform: translate3d(85px,0,0); 80 | } 81 | } 82 | @media (max-width: 768px) { 83 | .waves { 84 | height: 40px; 85 | min-height: 40px; 86 | } 87 | .content { 88 | height: 30vh; 89 | } 90 | h1 { 91 | font-size: 24px; 92 | } 93 | p { 94 | font-size: 15px 95 | } 96 | } 97 | a { 98 | text-decoration: underline; 99 | color: black; 100 | } -------------------------------------------------------------------------------- /templates/failpage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Mazi Bot Status 11 | 12 | 13 |
14 | 15 |
16 |

Bot is starting... Please wait, and refresh the page. This can take awhile.

17 |
18 | 19 | 20 |
21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 |
39 | View Mazi.pw 40 |
41 | -------------------------------------------------------------------------------- /templates/statuspage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Mazi Bot Status 11 | 12 | 13 |
14 | 15 |
16 |
17 |

18 | Mazi Bot status 19 |

20 |

21 | Serving {{ members }} members in {{ serverstotal }} servers in {{ shardcount }} 22 | {% if shardcount == 1 %} 23 | shard. 24 | {% else %} 25 | shards. 26 | {% endif %} 27 |

28 |

29 | Updated at {{ timenow }} UTC. 30 |

31 |
    32 | {% for shard in latencies %} 33 |
  • 34 | Shard {{ shard["shard"] }}: {{ shard["latency"] }}ms ping. Up for {{ shard["uptime"]}}. 35 | {% if servercount[shard["shard"]] is defined %} 36 | {% if servercount[shard["shard"]] == 1 %} 37 | Running in 1 server. 38 | {% else %} 39 | Running in {{ servercount[shard["shard"]] }} servers. 40 | {% endif %} 41 | {% else %} 42 | Running in 0 servers. 43 | {% endif %} 44 |
  • 45 | {% endfor %} 46 |
47 |
48 |
49 |
50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 |
64 |
65 | Invite Bot 66 |
67 | 68 | 69 | --------------------------------------------------------------------------------