mirror of
https://github.com/G2-Games/simple-discord-music-bot.git
synced 2025-04-19 07:52:54 -05:00
Reorganized to package structure
This commit is contained in:
parent
16f9709f99
commit
534b2df0bf
8 changed files with 69 additions and 44 deletions
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -1,2 +1,14 @@
|
||||||
.env
|
# Python-generated files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Other stuff
|
||||||
|
.env
|
||||||
|
.python-version
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
# simple-discord-music-bot
|
|
||||||
A simple discord music bot. Run it yourself!
|
|
Binary file not shown.
Before Width: | Height: | Size: 98 KiB |
|
@ -1,8 +1,11 @@
|
||||||
[project]
|
[project]
|
||||||
name = "oden-music-bot"
|
name = "oden"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A simple music bot."
|
description = "A simple music bot."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{ name = "G2-Games", email = "ke0bhogsg@gmail.com" }
|
||||||
|
]
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"audioop-lts>=0.2.1",
|
"audioop-lts>=0.2.1",
|
||||||
|
@ -13,3 +16,10 @@ dependencies = [
|
||||||
"stringprogressbar>=1.1.1",
|
"stringprogressbar>=1.1.1",
|
||||||
"yt-dlp>=2025.1.15",
|
"yt-dlp>=2025.1.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
oden = "oden:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
1
src/oden/__init__.py
Normal file
1
src/oden/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import oden.main
|
|
@ -1,9 +1,14 @@
|
||||||
import io
|
import io
|
||||||
import discord, subprocess, glob, os, os.path, ffmpeg
|
import discord
|
||||||
import re, time, datetime, yt_dlp, typing, functools
|
import subprocess
|
||||||
import logging, json
|
import glob
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import yt_dlp
|
||||||
|
import logging
|
||||||
from time import strftime, gmtime
|
from time import strftime, gmtime
|
||||||
from yt_dlp import YoutubeDL
|
|
||||||
from asyncio import sleep
|
from asyncio import sleep
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from StringProgressBar import progressBar
|
from StringProgressBar import progressBar
|
||||||
|
@ -15,12 +20,12 @@ from typing import Optional, TypedDict
|
||||||
|
|
||||||
## LOCAL IMPORTS ##
|
## LOCAL IMPORTS ##
|
||||||
#####
|
#####
|
||||||
import utils
|
import oden.utils as utils
|
||||||
from utils import QueueItem
|
from oden.utils import QueueItem
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
|
|
||||||
ServerInfo = TypedDict('ServerInfo', {
|
ServerInfo = TypedDict("ServerInfo", {
|
||||||
"loop": bool,
|
"loop": bool,
|
||||||
"paused": bool,
|
"paused": bool,
|
||||||
"elapsed": int,
|
"elapsed": int,
|
||||||
|
@ -108,7 +113,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
|
|
||||||
# Make sure the file is either audio or video
|
# Make sure the file is either audio or video
|
||||||
filetype = song.content_type
|
filetype = song.content_type
|
||||||
if filetype is None or (filetype.split('/')[0] != "audio" and filetype.split('/')[0] != "video"):
|
if filetype is None or (filetype.split("/")[0] != "audio" and filetype.split("/")[0] != "video"):
|
||||||
success_string += ":no_entry_sign: — `" + song.filename + "`, invalid file!\n"
|
success_string += ":no_entry_sign: — `" + song.filename + "`, invalid file!\n"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -138,7 +143,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
notice = await ctx.send(f":mag_right: Searching for \"{query}\" ...", suppress_embeds=True)
|
notice = await ctx.send(f":mag_right: Searching for \"{query}\" ...", suppress_embeds=True)
|
||||||
|
|
||||||
# Search metadata for youtube video
|
# Search metadata for youtube video
|
||||||
with yt_dlp.YoutubeDL({'quiet': True}) as ydl:
|
with yt_dlp.YoutubeDL({"quiet": True}) as ydl:
|
||||||
search_list = ydl.extract_info(f"ytsearch:{query}", download = False)
|
search_list = ydl.extract_info(f"ytsearch:{query}", download = False)
|
||||||
if search_list is None or len(search_list["entries"]) == 0:
|
if search_list is None or len(search_list["entries"]) == 0:
|
||||||
await notice.edit(content=":question: No songs found for query, try something else!", delete_after=3)
|
await notice.edit(content=":question: No songs found for query, try something else!", delete_after=3)
|
||||||
|
@ -177,7 +182,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Search metadata for youtube video
|
# Search metadata for youtube video
|
||||||
with yt_dlp.YoutubeDL({'quiet': True}) as ydl:
|
with yt_dlp.YoutubeDL({"quiet": True}) as ydl:
|
||||||
info = ydl.extract_info(query, download = False)
|
info = ydl.extract_info(query, download = False)
|
||||||
|
|
||||||
pprint(info)
|
pprint(info)
|
||||||
|
@ -226,19 +231,19 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
current_item = queue[queue_position]
|
current_item = queue[queue_position]
|
||||||
|
|
||||||
# Set song variables
|
# Set song variables
|
||||||
song_id = current_item['id']
|
song_id = current_item["id"]
|
||||||
song_url = current_item['url']
|
song_url = current_item["url"]
|
||||||
song_name = current_item['name']
|
song_name = current_item["name"]
|
||||||
song_thumb = current_item['thumbnail']
|
song_thumb = current_item["thumbnail"]
|
||||||
song_duration = current_item['duration']
|
song_duration = current_item["duration"]
|
||||||
song_thumb_url = current_item['thumbnail_url']
|
song_thumb_url = current_item["thumbnail_url"]
|
||||||
song_thumbname = str(int(time.time())) + ".png"
|
song_thumbname = str(int(time.time())) + ".png"
|
||||||
|
|
||||||
color = 0x42f5a7
|
color = 0x42f5a7
|
||||||
|
|
||||||
# Create the embed
|
# Create the embed
|
||||||
if current_item["artist"] and current_item["album"]:
|
if current_item["artist"] and current_item["album"]:
|
||||||
song_desc = "Artist: " + current_item['artist'] + "\nAlbum: " + current_item['album']
|
song_desc = "Artist: " + current_item["artist"] + "\nAlbum: " + current_item["album"]
|
||||||
else:
|
else:
|
||||||
song_desc = ""
|
song_desc = ""
|
||||||
|
|
||||||
|
@ -255,7 +260,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
pipe = False
|
pipe = False
|
||||||
if song_url is not None and song_url != "":
|
if song_url is not None and song_url != "":
|
||||||
song_source = subprocess.Popen(
|
song_source = subprocess.Popen(
|
||||||
['yt-dlp', '-q', '-o', '-', '-x', song_url],
|
["yt-dlp", "-q", "-o", "-", "-x", song_url],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
).stdout
|
).stdout
|
||||||
|
|
||||||
|
@ -263,7 +268,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
await notice.edit(content=":no_entry_sign: Could not get metadata.", delete_after=3)
|
await notice.edit(content=":no_entry_sign: Could not get metadata.", delete_after=3)
|
||||||
return
|
return
|
||||||
|
|
||||||
song_source = io.BufferedReader(song_source)
|
song_source = io.BufferedReader(song_source) # type: ignore
|
||||||
pipe = True
|
pipe = True
|
||||||
print(str(server_id) + " | " + "Playing song through yt-dlp")
|
print(str(server_id) + " | " + "Playing song through yt-dlp")
|
||||||
else:
|
else:
|
||||||
|
@ -282,7 +287,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
time2 = int(time.time())
|
time2 = int(time.time())
|
||||||
current = time2 - time1
|
current = time2 - time1
|
||||||
server_info[server_id]["elapsed"] = current
|
server_info[server_id]["elapsed"] = current
|
||||||
bardata = progressBar.filledBar(total, current, size=20)
|
bardata = progressBar.filledBar(total, current, size=20) # type: ignore
|
||||||
|
|
||||||
# Create embed
|
# Create embed
|
||||||
embed=discord.Embed(title="▶️ Playing: " + song_name, url=song_url, description=song_desc, color=color)
|
embed=discord.Embed(title="▶️ Playing: " + song_name, url=song_url, description=song_desc, color=color)
|
||||||
|
@ -329,7 +334,7 @@ async def play(ctx, *, query: Optional[str] = None):
|
||||||
|
|
||||||
# Remove all queued files and folders... This is a bit dangerous, maybe it
|
# Remove all queued files and folders... This is a bit dangerous, maybe it
|
||||||
# should be made failsafe somehow? TODO
|
# should be made failsafe somehow? TODO
|
||||||
fileList = glob.glob(os.path.join(str(server_id),'*'))
|
fileList = glob.glob(os.path.join(str(server_id), "*"))
|
||||||
for filePath in fileList:
|
for filePath in fileList:
|
||||||
try:
|
try:
|
||||||
os.remove(filePath)
|
os.remove(filePath)
|
||||||
|
@ -399,7 +404,7 @@ async def skip(ctx, command: Optional[str] = None, number: Optional[int] = None)
|
||||||
voice_channel.stop()
|
voice_channel.stop()
|
||||||
elif command == "to" and number is not None:
|
elif command == "to" and number is not None:
|
||||||
if number <= 0:
|
if number <= 0:
|
||||||
await ctx.send(f":no_entry_sign: Cannot skip to negative number or 0!")
|
await ctx.send(":no_entry_sign: Cannot skip to negative number or 0!")
|
||||||
return
|
return
|
||||||
elif number >= queue_length:
|
elif number >= queue_length:
|
||||||
await ctx.send(f":no_entry_sign: Invalid skip-to number! Max is {queue_length}.")
|
await ctx.send(f":no_entry_sign: Invalid skip-to number! Max is {queue_length}.")
|
||||||
|
@ -487,21 +492,21 @@ async def q(ctx, action = None, selection = None):
|
||||||
position_string += "⠀\n"
|
position_string += "⠀\n"
|
||||||
|
|
||||||
|
|
||||||
if len(entry['name']) >= 30:
|
if len(entry["name"]) >= 30:
|
||||||
entry_cut = entry['name'][0:30]
|
entry_cut = entry["name"][0:30]
|
||||||
else:
|
else:
|
||||||
entry_cut = entry['name']
|
entry_cut = entry["name"]
|
||||||
|
|
||||||
queue_string += "**" + str(index + 1) + ":** " + entry_cut + "\n"
|
queue_string += "**" + str(index + 1) + ":** " + entry_cut + "\n"
|
||||||
if entry['duration'] < 3600:
|
if entry["duration"] < 3600:
|
||||||
duration_string += str(strftime("%M:%S", gmtime(entry['duration']))) + "\n"
|
duration_string += str(strftime("%M:%S", gmtime(entry["duration"]))) + "\n"
|
||||||
else:
|
else:
|
||||||
duration_string += str(strftime("%H:%M:%S", gmtime(entry['duration']))) + "\n"
|
duration_string += str(strftime("%H:%M:%S", gmtime(entry["duration"]))) + "\n"
|
||||||
|
|
||||||
if index == server_info[server_id]["queue_position"]:
|
if index == server_info[server_id]["queue_position"]:
|
||||||
total_duration += entry['duration'] - server_info[server_id]["elapsed"]
|
total_duration += entry["duration"] - server_info[server_id]["elapsed"]
|
||||||
if index > server_info[server_id]["queue_position"]:
|
if index > server_info[server_id]["queue_position"]:
|
||||||
total_duration += entry['duration']
|
total_duration += entry["duration"]
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
# Calculate the time remaining in the queue
|
# Calculate the time remaining in the queue
|
||||||
|
@ -527,7 +532,7 @@ async def q(ctx, action = None, selection = None):
|
||||||
if server_info[server_id]["queue"][selection]["thumbnail"] != "/assets/unknown.png":
|
if server_info[server_id]["queue"][selection]["thumbnail"] != "/assets/unknown.png":
|
||||||
thumbnail = None
|
thumbnail = None
|
||||||
else:
|
else:
|
||||||
thumbnail = server_info[server_id]["queue"][selection]['thumbnail']
|
thumbnail = server_info[server_id]["queue"][selection]["thumbnail"]
|
||||||
|
|
||||||
if selection is current_position:
|
if selection is current_position:
|
||||||
await ctx.send(":no_entry_sign: Error, cannot remove currently playing item", delete_after=3)
|
await ctx.send(":no_entry_sign: Error, cannot remove currently playing item", delete_after=3)
|
||||||
|
@ -593,6 +598,5 @@ async def on_command_error(ctx, error):
|
||||||
else:
|
else:
|
||||||
print(error)
|
print(error)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
# Run the bot using the Discord bot token
|
||||||
# Run the bot using the Discord bot token
|
bot.run(os.environ["ODEN_DISCORD_SECRET"])
|
||||||
bot.run(os.environ['DISCORD_SECRET'])
|
|
|
@ -1,11 +1,11 @@
|
||||||
# pyright: reportUnusedExpression=true
|
# pyright: reportUnusedExpression=true
|
||||||
from pprint import pprint
|
import uuid
|
||||||
import uuid, os, ffmpeg
|
import os
|
||||||
from colorthief import ColorThief
|
import ffmpeg
|
||||||
|
|
||||||
## LINTING / TYPING ##
|
## LINTING / TYPING ##
|
||||||
#####
|
#####
|
||||||
from typing import Any, Optional, TypedDict
|
from typing import Optional, TypedDict
|
||||||
|
|
||||||
async def getIds(ctx):
|
async def getIds(ctx):
|
||||||
"""Get server id, voice channel id, and user voice channel id"""
|
"""Get server id, voice channel id, and user voice channel id"""
|
4
uv.lock
4
uv.lock
|
@ -240,9 +240,9 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oden-music-bot"
|
name = "oden"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "audioop-lts" },
|
{ name = "audioop-lts" },
|
||||||
{ name = "colorthief" },
|
{ name = "colorthief" },
|
||||||
|
|
Loading…
Reference in a new issue