├── .gitattributes
├── LICENSE
├── README.md
├── blacklist.json
├── bot.py
├── cogs
├── general.py
├── help.py
└── music.py
├── config.json
├── helpers
└── json_manager.py
└── requirements.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tidal-dl discord bot
2 | A discord bot to download high quality music from streaming services.
3 |
4 | #### This bot was designed to work on Ubuntu
5 | Edit the commands in [music.py](https://github.com/parnexcodes/tidal-dl-discord-bot/blob/main/cogs/music.py) if you're using another operating system.
6 |
7 | ## Note
8 | Make a channel for requests channel.
9 |
10 | The bot performs tasks **synchronously** (one task at a time) , so the channel needs to be locked after each request.
11 |
12 | If you have a better solution , open a PR.
13 |
14 | I have restricted Qobuz , Youtube , Soundcloud and Artist Profile/Playlists. Edit these [lines](https://github.com/parnexcodes/tidal-dl-discord-bot/blob/f3abf3a9c05455b88df999a7653ac26c7fbccbe0/cogs/music.py#L50-L63) if you want to allow them.
15 |
16 | ## Install the following packages before proceeding.
17 | ```python```
18 |
19 | ```pip```
20 |
21 | ```pip3 install git+https://github.com/uwulily/streamrip.git```
22 |
23 | ```rclone```
24 |
25 | ```7zip```
26 |
27 | ```pip install -r requirements.txt```
28 |
29 | Keep rclone and 7zip in the bot folder if you are using Windows.
30 |
31 | ## Setting up the bot is pretty straight forward.
32 | Run ```rip config --open``` , it will show you the file location of streamrip's config. Open it with a text editor and change
33 | ```folder = "/Users/nathan/StreamripDownloads"``` to ```folder = "Your bot folder/download/Temp/"```
34 |
35 | Set ```[database]
36 | enabled = true``` to **false**.
37 |
38 | Open the config.json file and edit it.
39 |
40 | [This line](https://github.com/parnexcodes/tidal-dl-discord-bot/blob/f3abf3a9c05455b88df999a7653ac26c7fbccbe0/config.json#L5) Should have a ```/``` at the end.
41 |
42 | Put the channel id in [line](https://github.com/parnexcodes/tidal-dl-discord-bot/blob/f3abf3a9c05455b88df999a7653ac26c7fbccbe0/config.json#L6) 6.
43 |
44 | ## Run Bot
45 | ```python3 bot.py```
46 |
47 | When you run the $dl command for the first time , check your terminal and follow the steps to login to tidal/qobuz.
48 |
49 | Your token is saved so you don't have to do it everytime.
50 |
51 | ## Thanks
52 | [Streamrip](https://github.com/nathom/streamrip) by nathom
53 |
54 | [Discord.py Bot Template](https://github.com/kkrypt0nn/Python-Discord-Bot-Template) by kkrypt0nn
55 |
56 |
57 | ## Discord
58 |
59 |
--------------------------------------------------------------------------------
/blacklist.json:
--------------------------------------------------------------------------------
1 | {
2 | "ids": [
3 | ]
4 | }
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import platform
4 | import random
5 | import sys
6 |
7 | import discord
8 | from discord.ext import commands, tasks
9 | from discord.ext.commands import Bot
10 |
11 | if not os.path.isfile("config.json"):
12 | sys.exit("'config.json' not found! Please add it and try again.")
13 | else:
14 | with open("config.json") as file:
15 | config = json.load(file)
16 |
17 | """
18 | Setup bot intents (events restrictions)
19 | For more information about intents, please go to the following websites:
20 | https://discordpy.readthedocs.io/en/latest/intents.html
21 | https://discordpy.readthedocs.io/en/latest/intents.html#privileged-intents
22 |
23 |
24 | Default Intents:
25 | intents.messages = True
26 | intents.reactions = True
27 | intents.guilds = True
28 | intents.emojis = True
29 | intents.bans = True
30 | intents.guild_typing = False
31 | intents.typing = False
32 | intents.dm_messages = False
33 | intents.dm_reactions = False
34 | intents.dm_typing = False
35 | intents.guild_messages = True
36 | intents.guild_reactions = True
37 | intents.integrations = True
38 | intents.invites = True
39 | intents.voice_states = False
40 | intents.webhooks = False
41 |
42 | Privileged Intents (Needs to be enabled on dev page), please use them only if you need them:
43 | intents.presences = True
44 | intents.members = True
45 | """
46 |
47 | intents = discord.Intents.default()
48 |
49 | bot = Bot(command_prefix=config["bot_prefix"], intents=intents)
50 |
51 | # The code in this even is executed when the bot is ready
52 | @bot.event
53 | async def on_ready():
54 | print(f"Logged in as {bot.user.name}")
55 | print(f"Discord.py API version: {discord.__version__}")
56 | print(f"Python version: {platform.python_version()}")
57 | print(f"Running on: {platform.system()} {platform.release()} ({os.name})")
58 | print("-------------------")
59 |
60 | # Removes the default help command of discord.py to be able to create our custom help command.
61 | bot.remove_command("help")
62 |
63 | if __name__ == "__main__":
64 | for file in os.listdir("./cogs"):
65 | if file.endswith(".py"):
66 | extension = file[:-3]
67 | try:
68 | bot.load_extension(f"cogs.{extension}")
69 | print(f"Loaded extension '{extension}'")
70 | except Exception as e:
71 | exception = f"{type(e).__name__}: {e}"
72 | print(f"Failed to load extension {extension}\n{exception}")
73 |
74 |
75 | # The code in this event is executed every time someone sends a message, with or without the prefix
76 | @bot.event
77 | async def on_message(message):
78 | # Ignores if a command is being executed by a bot or by the bot itself
79 | if message.author == bot.user or message.author.bot:
80 | return
81 | # Ignores if a command is being executed by a blacklisted user
82 | with open("blacklist.json") as file:
83 | blacklist = json.load(file)
84 | if message.author.id in blacklist["ids"]:
85 | return
86 | await bot.process_commands(message)
87 |
88 |
89 | # The code in this event is executed every time a command has been *successfully* executed
90 | @bot.event
91 | async def on_command_completion(ctx):
92 | fullCommandName = ctx.command.qualified_name
93 | split = fullCommandName.split(" ")
94 | executedCommand = str(split[0])
95 | print(
96 | f"Executed {executedCommand} command in {ctx.guild.name} (ID: {ctx.message.guild.id}) by {ctx.message.author} (ID: {ctx.message.author.id})")
97 |
98 |
99 | # The code in this event is executed every time a valid commands catches an error
100 | @bot.event
101 | async def on_command_error(context, error):
102 | if isinstance(error, commands.CommandOnCooldown):
103 | minutes, seconds = divmod(error.retry_after, 60)
104 | hours, minutes = divmod(minutes, 60)
105 | hours = hours % 24
106 | embed = discord.Embed(
107 | title="Hey, please slow down!",
108 | description=f"You can use this command again in {f'{round(hours)} hours' if round(hours) > 0 else ''} {f'{round(minutes)} minutes' if round(minutes) > 0 else ''} {f'{round(seconds)} seconds' if round(seconds) > 0 else ''}.",
109 | color=0xE02B2B
110 | )
111 | await context.send(embed=embed)
112 | elif isinstance(error, commands.MissingPermissions):
113 | embed = discord.Embed(
114 | title="Error!",
115 | description="You are missing the permission `" + ", ".join(
116 | error.missing_perms) + "` to execute this command!",
117 | color=0xE02B2B
118 | )
119 | await context.send(embed=embed)
120 | elif isinstance(error, commands.MissingRequiredArgument):
121 | embed = discord.Embed(
122 | title="Error!",
123 | description=str(error).capitalize(),
124 | # We need to capitalize because the command arguments have no capital letter in the code.
125 | color=0xE02B2B
126 | )
127 | await context.send(embed=embed)
128 | elif isinstance(error, commands.MaxConcurrencyReached):
129 | embed = discord.Embed(
130 | title="Error!",
131 | description="Please wait for the previous job to finish!\nTry Again later.",
132 | color=0xE02B2B
133 | )
134 | embed.set_footer(text=f"Requested by {context.message.author}.")
135 | await context.send(embed=embed)
136 | raise error
137 |
138 |
139 | # Run the bot with the token
140 | bot.run(config["token"])
141 |
--------------------------------------------------------------------------------
/cogs/general.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import platform
4 | import random
5 | import sys
6 |
7 | import aiohttp
8 | import discord
9 | from discord.ext import commands
10 |
11 | if not os.path.isfile("config.json"):
12 | sys.exit("'config.json' not found! Please add it and try again.")
13 | else:
14 | with open("config.json") as file:
15 | config = json.load(file)
16 |
17 |
18 | class general(commands.Cog, name="general"):
19 | def __init__(self, bot):
20 | self.bot = bot
21 |
22 | @commands.command(name="info", aliases=["botinfo"])
23 | async def info(self, context):
24 | """
25 | Get some useful (or not) information about the bot.
26 | """
27 | embed = discord.Embed(
28 | description="Music DL Bot",
29 | color=0x42F56C
30 | )
31 | embed.set_author(
32 | name="Bot Information"
33 | )
34 | embed.add_field(
35 | name="Owner:",
36 | value="parnex#2368",
37 | inline=True
38 | )
39 | embed.add_field(
40 | name="Python Version:",
41 | value=f"{platform.python_version()}",
42 | inline=True
43 | )
44 | embed.add_field(
45 | name="Prefix:",
46 | value=f"{config['bot_prefix']}",
47 | inline=False
48 | )
49 | embed.set_footer(
50 | text=f"Requested by {context.message.author}"
51 | )
52 | await context.send(embed=embed)
53 |
54 | @commands.command(name="ping")
55 | async def ping(self, context):
56 | """
57 | Check if the bot is alive.
58 | """
59 | embed = discord.Embed(
60 | title="🏓 Pong!",
61 | description=f"The bot latency is {round(self.bot.latency * 1000)}ms.",
62 | color=0x42F56C
63 | )
64 | await context.send(embed=embed)
65 |
66 | @commands.command(name="invite", aliases=["support", "supportserver"])
67 | async def invite(self, context):
68 | """
69 | Get the invite link of the discord server of the bot.
70 | """
71 | embed = discord.Embed(
72 | description=f"Join the server for the bot by clicking [here](https://discord.gg/EGSkMEMkrZ).",
73 | color=0xD75BF4
74 | )
75 | try:
76 | await context.author.send(embed=embed)
77 | await context.send("I sent you a private message!")
78 | except discord.Forbidden:
79 | await context.send(embed=embed)
80 |
81 |
82 | def setup(bot):
83 | bot.add_cog(general(bot))
84 |
--------------------------------------------------------------------------------
/cogs/help.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 |
5 | import discord
6 | from discord.ext import commands
7 |
8 | if not os.path.isfile("config.json"):
9 | sys.exit("'config.json' not found! Please add it and try again.")
10 | else:
11 | with open("config.json") as file:
12 | config = json.load(file)
13 |
14 |
15 | class Help(commands.Cog, name="help"):
16 | def __init__(self, bot):
17 | self.bot = bot
18 |
19 | @commands.command(name="help")
20 | async def help(self, context):
21 | """
22 | List all commands from every Cog the bot has loaded.
23 | """
24 | prefix = config["bot_prefix"]
25 | if not isinstance(prefix, str):
26 | prefix = prefix[0]
27 | embed = discord.Embed(title="Help", description="List of available commands:", color=0x42F56C)
28 | for i in self.bot.cogs:
29 | cog = self.bot.get_cog(i.lower())
30 | commands = cog.get_commands()
31 | command_list = [command.name for command in commands]
32 | command_description = [command.help for command in commands]
33 | help_text = '\n'.join(f'{prefix}{n} - {h}' for n, h in zip(command_list, command_description))
34 | embed.add_field(name=i.capitalize(), value=f'```{help_text}```', inline=False)
35 | await context.send(embed=embed)
36 |
37 |
38 | def setup(bot):
39 | bot.add_cog(Help(bot))
40 |
--------------------------------------------------------------------------------
/cogs/music.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import platform
4 | import random
5 | import sys
6 | import subprocess
7 | import asyncio
8 | import aiohttp
9 | import discord
10 | import base64
11 | from discord.ext import commands
12 | from discord.ext.commands.cooldowns import BucketType
13 | import time
14 | import base64
15 | from datetime import timedelta
16 | from time import gmtime, strftime
17 |
18 | # Only if you want to use variables that are in the config.json file.
19 | if not os.path.isfile("config.json"):
20 | sys.exit("'config.json' not found! Please add it and try again.")
21 | else:
22 | with open("config.json") as file:
23 | config = json.load(file)
24 |
25 | download_folder = config['bot_folder']
26 | request_channel = config['request_channel']
27 |
28 | # status = False
29 |
30 | # Here we name the cog and create a new class for the cog.
31 | class Music(commands.Cog, name="music"):
32 | def __init__(self, bot):
33 | self.bot = bot
34 |
35 | # Here you can just add your own commands, you'll always need to provide "self" as first parameter.
36 | @commands.command(name="dl")
37 | # @commands.max_concurrency(1, per=BucketType.guild, wait=False)
38 | # @commands.cooldown(1, 300.0, commands.BucketType.guild)
39 | # @commands.has_any_role("Admin", "Super Moderator", "Mod", "Uploader", "Server Booster", "VIP", "beta tester")
40 | async def dl(self, ctx, link):
41 | """
42 | Downloads music.
43 | """
44 |
45 | req_channel = self.bot.get_channel(request_channel)
46 | # global status
47 |
48 | rclone_drives = ["tidal"]
49 | random_rclone_drives = random.choice(rclone_drives)
50 |
51 | if ctx.channel.id == request_channel:
52 | if link.find("artist") != -1 and link.find("tidal") != -1:
53 | await ctx.send(f"Downloading **Artist Profile** and **Playlists** not allowed.\n{ctx.author.mention}")
54 | elif link.find("playlist") != -1 and link.find("tidal") != -1:
55 | await ctx.send(f"Downloading **Artist Profile** and **Playlists** not allowed.\n{ctx.author.mention}")
56 | if link.find("interpreter") != -1 and link.find("qobuz") != -1:
57 | await ctx.send(f"Downloading **Artist Profile** and **Playlists** not allowed.\n{ctx.author.mention}")
58 | elif link.find("youtube") != -1:
59 | await ctx.send(f"**YouTube Music** can't be downloaded.\n{ctx.author.mention}")
60 | elif link.find("youtu.be") != -1:
61 | await ctx.send(f"**YouTube Music** can't be downloaded.\n{ctx.author.mention}")
62 | # elif link.find("tidal") != -1:
63 | # await ctx.send(f"**Tidal** is down for maintainence.\n{ctx.author.mention}")
64 | # elif link.find("qobuz") != -1:
65 | # await ctx.send(f"**qobuz** is down for maintainence.\n{ctx.author.mention}")
66 | elif link.find("soundcloud") != -1:
67 | await ctx.send(f"**Soundcloud** can't be downloaded.\n{ctx.author.mention}")
68 | elif link.find("spotify") != -1:
69 | await ctx.send(f"**Spotify** can't be downloaded.\n{ctx.author.mention}")
70 | elif link.find("deezer") != -1:
71 | await ctx.send(f"**Deezer** can't be downloaded.\n{ctx.author.mention}")
72 | elif not link.find(".com") != -1:
73 | await ctx.send(f"**Invalid** Link.\n{ctx.author.mention}")
74 | elif not link.find("https") != -1:
75 | await ctx.send(f"Add **https://** to Link.\n{ctx.author.mention}")
76 | else:
77 | await req_channel.set_permissions(ctx.guild.default_role, send_messages=False)
78 | await ctx.send(f"{ctx.author.mention} Please wait while your request is being downloaded.\nChannel will be unlocked after completing the request.")
79 | # while True:
80 | # if status == True:
81 | # await asyncio.sleep(3)
82 | # else: break
83 | # status = True
84 | download_start_time = time.time()
85 | try:
86 | with open('rip_log.txt', 'wb') as f:
87 | process = subprocess.Popen(["rip", 'url', f'{link}', '-i'], stdout=subprocess.PIPE)
88 | for line in iter(process.stdout.readline, b''):
89 | sys.stdout.buffer.write(line)
90 | f.write(line)
91 |
92 | download_end_time = time.time() - download_start_time
93 | download_time = timedelta(seconds=round(download_end_time))
94 |
95 | search_path = f'{download_folder}download/Temp'
96 | root, dirs, files = next(os.walk(search_path), ([],[],[]))
97 | try:
98 | folder_name = dirs[0]
99 | except:
100 | file_name = files[0]
101 |
102 | zipping_start_time = time.time()
103 |
104 | time_format_file_name = strftime("%Y-%m-%d_%H%M%S", gmtime())
105 |
106 | try:
107 | zip_file = f"{folder_name}_{time_format_file_name}.zip"
108 | except:
109 | zip_file = f"{file_name}_{time_format_file_name}.zip"
110 |
111 | subprocess.run(["7z", "a", "-mx0", "-tzip", f"{download_folder}download/Temp/{zip_file}", f'{download_folder}download/Temp/'])
112 |
113 | zipping_end_time = time.time() - zipping_start_time
114 | zipping_time = timedelta(seconds=round(zipping_end_time))
115 |
116 | upload_start_time = time.time()
117 |
118 | with open('upload_log.txt', 'wb') as f:
119 | process = subprocess.Popen(["python3", "krakenupload.py", f"{search_path}/{zip_file}"], stdout=subprocess.PIPE)
120 | for line in iter(process.stdout.readline, b''):
121 | sys.stdout.buffer.write(line)
122 | kf = line
123 | f.write(line)
124 |
125 | upload_end_time = time.time() - upload_start_time
126 | upload_time = timedelta(seconds=round(upload_end_time))
127 |
128 | subprocess.run(["rm", "-rf", f'{download_folder}download/Temp'])
129 |
130 | all_done = discord.Embed(
131 | name="**Music DL**",
132 | description="**Uploaded**\nAll Done.",
133 | color=0x20e84f
134 | )
135 | request_link = link
136 | kf_link = kf.decode("utf-8")
137 | b64link = base64.b64encode(bytes(kf_link, "utf-8")).decode()
138 | encoded_link = f'https://links.gamesdrive.net/#/link/{b64link}.U2xhdiBNdXNpYyBCb3Q='
139 | all_done.add_field(name="Name", value=zip_file, inline=False)
140 | all_done.add_field(name="Request Link", value=request_link, inline=False)
141 | all_done.add_field(name="Link", value=encoded_link, inline=False)
142 | all_done.add_field(name="Download Time", value=download_time, inline=False)
143 | all_done.add_field(name="Zip Time", value=zipping_time, inline=False)
144 | all_done.add_field(name="Upload Time", value=upload_time, inline=False)
145 |
146 |
147 | all_done.set_footer(text=f"Requested by {ctx.message.author}\nBot Developed by parnex#2368")
148 |
149 | try:
150 | await ctx.author.send(embed=all_done)
151 | await ctx.send(f"It's uploaded, Slide into my dms. {ctx.author.mention}")
152 | await req_channel.set_permissions(ctx.guild.default_role, send_messages=True)
153 | # status = False
154 | except discord.Forbidden:
155 | await ctx.send(f"Why do you have your dm's disabled ? Sorry I can't message you. {ctx.author.mention}")
156 | # status = False
157 | await req_channel.set_permissions(ctx.guild.default_role, send_messages=True)
158 | except:
159 | await ctx.send("The following Song/Album isn't available to download because of Geo Restriction or Internal Error from Streaming Platform.")
160 | # status = False
161 | await req_channel.set_permissions(ctx.guild.default_role, send_messages=True)
162 |
163 | else:
164 | await ctx.send(f"This command can only be used in <#{request_channel}>")
165 |
166 |
167 | # And then we finally add the cog to the bot so that it can load, unload, reload and use it's content.
168 | def setup(bot):
169 | bot.add_cog(Music(bot))
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "bot_prefix": "$",
3 | "token": "your bot token",
4 | "application_id": "discord application id",
5 | "bot_folder": "/home/ubuntu/Music DL Bot/",
6 | "request_channel": 123456,
7 | "owners": [
8 | 123456,
9 | 123456
10 | ]
11 | }
--------------------------------------------------------------------------------
/helpers/json_manager.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 |
4 | def add_user_to_blacklist(user_id: int):
5 | with open("blacklist.json", "r+") as file:
6 | file_data = json.load(file)
7 | file_data["ids"].append(user_id)
8 | with open("blacklist.json", "w") as file:
9 | file.seek(0)
10 | json.dump(file_data, file, indent=4)
11 |
12 |
13 | def remove_user_from_blacklist(user_id: int):
14 | with open("blacklist.json", "r") as file:
15 | file_data = json.load(file)
16 | file_data["ids"].remove(user_id)
17 | with open("blacklist.json", "w") as file:
18 | file.seek(0)
19 | json.dump(file_data, file, indent=4)
20 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp==3.7.4.post0
2 | discord.py==1.7.2
3 | discord==1.7.3
4 |
--------------------------------------------------------------------------------