E5K7 commited on
Commit
7cc32a6
·
0 Parent(s):

Initial commit: Discord bot with hybrid commands, Flask keep-alive, and Docker support

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.dockerignore ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .env
12
+ .venv
13
+ venv/
14
+ ENV/
15
+ .git/
16
+ .gitignore
17
+ .vscode/
18
+ .idea/
19
+ *.log
20
+ .DS_Store
21
+ README.md
22
+ .env.example
.env.example ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Discord Bot Token - Get from Discord Developer Portal
2
+ DISCORD_TOKEN=your_discord_bot_token_here
3
+
4
+ # API Keys for various services
5
+ TIMEZONE_API_KEY=your_timezone_api_key
6
+ NASA_API_KEY=your_nasa_api_key
7
+ EXTREME_IP_API_KEY=your_extreme_ip_api_key
8
+
9
+ # Recipe API (Edamam)
10
+ EDAMAM_API_RECIPE_APP_ID=your_edamam_app_id
11
+ EDAMAM_API_RECIPE_APP_KEY=your_edamam_app_key
12
+
13
+ # Optional API Keys (if using additional features)
14
+ ATRS_MUSIC_TOKEN=your_music_token
15
+ BITLY_KEY=your_bitly_key
16
+ CAT_KEY=your_cat_api_key
17
+ WEATHER_KEY=your_weather_api_key
18
+ CUTTLY_KEY=your_cuttly_key
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .env
.vscode/settings.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "python-envs.defaultEnvManager": "ms-python.python:system",
3
+ "python-envs.pythonProjects": []
4
+ }
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ EXPOSE 7860
12
+
13
+ CMD ["python", "main.py"]
README.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Chitanda V2 Discord Bot
3
+ emoji: 🤖
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # Chitanda V2 Discord Bot
11
+
12
+ A feature-rich Discord bot with hybrid command support (both prefix and slash commands).
13
+
14
+ ## Features
15
+
16
+ - 🎮 Fun commands (roll, flip)
17
+ - 💕 Roleplay commands (pat, kiss, highfive)
18
+ - 🛠️ Utility commands (crypto prices, weather, recipes, Wikipedia, etc.)
19
+ - 🔨 Moderation commands (purge, kick, delete)
20
+ - ✨ Hybrid commands (works with both `lu` prefix and `/` slash commands)
21
+ - 🌐 Flask keep-alive server for 24/7 uptime
22
+
23
+ ## Deployment on Hugging Face Spaces
24
+
25
+ This bot is ready to deploy on Hugging Face Spaces using Docker.
26
+
27
+ ### Steps to Deploy
28
+
29
+ 1. Create a new Space on Hugging Face
30
+ 2. Choose "Docker" as the SDK
31
+ 3. Upload all files from this repository
32
+ 4. In Space settings, add your bot token and API keys as secrets
33
+ 5. The bot will automatically start!
34
+
35
+ ### Environment Configuration
36
+
37
+ The bot reads configuration from `config.json`. Make sure your token and API keys are properly set.
38
+
39
+ ## Local Development
40
+
41
+ ```bash
42
+ python -m venv .venv
43
+ source .venv/bin/activate
44
+ pip install -r requirements.txt
45
+ python main.py
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ **Total: 23 commands across 4 categories**
51
+
52
+ Use `lu help` or `/help` to see all available commands!
53
+
54
+ - Utilities: 16 commands (ping, crypto, weather, recipes, etc.)
55
+ - Fun: 2 commands (roll, flip)
56
+ - Roleplay: 3 commands (pat, kiss, highfive)
57
+ - Moderation: 3 commands (purge, kick, delete)
cogs/__init__.py ADDED
File without changes
cogs/fun.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from discord.ext import commands
3
+
4
+
5
+ class Fun(commands.Cog):
6
+ def __init__(self, bot: commands.Bot):
7
+ self.bot = bot
8
+
9
+ @commands.hybrid_command(name="roll")
10
+ async def roll(self, ctx: commands.Context, dice: str = "1d6"):
11
+ """Roll dice in NdM format (e.g., 2d6)."""
12
+ try:
13
+ rolls, faces = map(int, dice.lower().split("d"))
14
+ except Exception:
15
+ return await ctx.send("Format has to be NdM, e.g. 2d6.")
16
+
17
+ results = [random.randint(1, faces) for _ in range(rolls)]
18
+ await ctx.send(f"Rolled {dice}: {results} (total {sum(results)})")
19
+
20
+ @commands.hybrid_command(name="flip")
21
+ async def flip(self, ctx: commands.Context):
22
+ """Flip a coin."""
23
+ await ctx.send(random.choice(["Heads", "Tails"]))
24
+
25
+
26
+ async def setup(bot: commands.Bot):
27
+ await bot.add_cog(Fun(bot))
cogs/functions.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from unittest import result
3
+ import yt_dlp
4
+ import os
5
+ from shazamio import Shazam
6
+ import aiohttp
7
+ import base64
8
+ from functools import partial
9
+
10
+ CLIENT_ID = "550edc8d6d294381a0f4dac9c8c9fea5"
11
+ CLIENT_SECRET = "fade5c01cc8b40acb79ad1f598074b4c"
12
+
13
+ async def music_recognition(filepath):
14
+ shazam = Shazam()
15
+ out = await shazam.recognize(filepath)
16
+ songname = out['track']['title']
17
+ artist = out["track"]["subtitle"]
18
+ ydl_opts = {
19
+ "quiet": True,
20
+ "skip_download": True,
21
+ }
22
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
23
+ info = ydl.extract_info(f"ytsearch1:{songname} {artist}", download=False)
24
+ video = info["entries"][0]
25
+ title = video["title"]
26
+ yturl = video["webpage_url"]
27
+ thumbnail = video["thumbnail"]
28
+ duration = video["duration"]
29
+ spotifyshit = await spotify_search(f"{songname} {artist}")
30
+ spotifylink = spotifyshit["tracks"]["items"][0]["external_urls"]["spotify"]
31
+ return {
32
+ "songname": songname,
33
+ "artist": artist,
34
+ "title": title,
35
+ "yturl": yturl,
36
+ "thumbnail": thumbnail,
37
+ "duration": duration,
38
+ "spotifylink": spotifylink
39
+ }
40
+
41
+
42
+ async def spotify_auth():
43
+ auth_url = "https://accounts.spotify.com/api/token"
44
+ auth_string = f"{CLIENT_ID}:{CLIENT_SECRET}"
45
+ auth_header = base64.b64encode(auth_string.encode()).decode()
46
+
47
+ headers = {
48
+ "Authorization": f"Basic {auth_header}",
49
+ "Content-Type": "application/x-www-form-urlencoded"
50
+ }
51
+
52
+ data = {
53
+ "grant_type": "client_credentials"
54
+ }
55
+
56
+ async with aiohttp.ClientSession() as session:
57
+ async with session.post(auth_url, headers=headers, data=data) as resp:
58
+ response = await resp.json()
59
+ return response["access_token"]
60
+
61
+ async def spotify_search(query: str, search_type="track"):
62
+ token = await spotify_auth()
63
+
64
+ url = "https://api.spotify.com/v1/search"
65
+ params = {
66
+ "q": query,
67
+ "type": search_type,
68
+ "limit": 1
69
+ }
70
+
71
+ headers = {
72
+ "Authorization": f"Bearer {token}"
73
+ }
74
+
75
+ async with aiohttp.ClientSession() as session:
76
+ async with session.get(url, headers=headers, params=params) as resp:
77
+ data = await resp.json()
78
+ return data
79
+
80
+
81
+ async def download_mp3(query: str):
82
+ def _download():
83
+ ydl_opts = {
84
+ "quiet": True,
85
+ "format": "bestaudio/best",
86
+ "outtmpl": "./assets/audio.%(ext)s",
87
+ "postprocessors": [{
88
+ "key": "FFmpegExtractAudio",
89
+ "preferredcodec": "mp3",
90
+ "preferredquality": "192",
91
+ }],
92
+ }
93
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
94
+ ydl.extract_info(f"ytsearch1:{query}", download=True)
95
+ return "./assets/audio.mp3"
96
+ return await asyncio.to_thread(_download)
cogs/moderation.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import discord
2
+ from discord.ext import commands
3
+
4
+
5
+ class Moderation(commands.Cog):
6
+ def __init__(self, bot: commands.Bot):
7
+ self.bot = bot
8
+
9
+ @commands.hybrid_command(name="purge")
10
+ @commands.has_permissions(manage_messages=True)
11
+ async def purge(self, ctx: commands.Context, limit: int):
12
+ """Delete a number of messages from the current channel."""
13
+ deleted = await ctx.channel.purge(limit=limit + 1)
14
+ await ctx.send(f"Deleted {len(deleted) - 1} messages.", delete_after=5)
15
+
16
+ @commands.hybrid_command(name="kick")
17
+ @commands.has_permissions(kick_members=True)
18
+ async def kick(self, ctx: commands.Context, member: discord.Member, *, reason: str | None = None):
19
+ """Kick a member from the server."""
20
+ await member.kick(reason=reason)
21
+ await ctx.send(f"Kicked {member} " + (f"for: {reason}" if reason else ""))
22
+
23
+ @commands.hybrid_command(name="delete")
24
+ async def delete(self, ctx: commands.Context, amount: int, user: discord.Member = None):
25
+ """Delete messages in this channel (only works in specific server)"""
26
+ if ctx.guild.id != 811634680302010449:
27
+ await ctx.send("This command can only be used in the authorized server.")
28
+ return
29
+
30
+ if amount < 1:
31
+ await ctx.send("Please specify a valid number of messages to delete (at least 1).")
32
+ return
33
+
34
+ is_slash = ctx.interaction is not None
35
+
36
+ if user:
37
+ if is_slash:
38
+ await ctx.defer(ephemeral=True)
39
+
40
+ deleted_count = 0
41
+ def check(m):
42
+ nonlocal deleted_count
43
+ if m.author.id == user.id and deleted_count < amount:
44
+ deleted_count += 1
45
+ return True
46
+ return False
47
+
48
+ await ctx.channel.purge(limit=100, check=check)
49
+ success_msg = f"Deleted {deleted_count} message(s) from {user.mention}."
50
+
51
+ if is_slash:
52
+ await ctx.send(success_msg, ephemeral=True)
53
+ else:
54
+ await ctx.send(success_msg, delete_after=10)
55
+ else:
56
+ limit = amount if is_slash else amount + 1
57
+ deleted = await ctx.channel.purge(limit=limit)
58
+ actual_deleted = len(deleted) if is_slash else len(deleted) - 1
59
+ success_msg = f"Deleted {actual_deleted} message(s)."
60
+
61
+ if is_slash:
62
+ await ctx.send(success_msg, ephemeral=True)
63
+ else:
64
+ await ctx.send(success_msg, delete_after=10)
65
+
66
+
67
+ @purge.error
68
+ async def purge_error(self, ctx: commands.Context, error: Exception):
69
+ if isinstance(error, commands.MissingPermissions):
70
+ await ctx.send("You lack the manage_messages permission.")
71
+
72
+ @kick.error
73
+ async def kick_error(self, ctx: commands.Context, error: Exception):
74
+ if isinstance(error, commands.MissingPermissions):
75
+ await ctx.send("You lack the kick_members permission.")
76
+
77
+
78
+ async def setup(bot: commands.Bot):
79
+ await bot.add_cog(Moderation(bot))
cogs/roleplay.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import discord
2
+ from discord.ext import commands
3
+ import aiohttp
4
+
5
+ bot_embed_color = 0x4548A8
6
+
7
+
8
+ class Roleplay(commands.Cog):
9
+ def __init__(self, bot: commands.Bot):
10
+ self.bot = bot
11
+
12
+ async def _fetch_waifu(self, endpoint: str) -> str | None:
13
+ """Fetch an image URL from waifu.pics for the given endpoint."""
14
+ url = f"https://api.waifu.pics/sfw/{endpoint}"
15
+ try:
16
+ async with aiohttp.ClientSession() as session:
17
+ async with session.get(url, timeout=10) as resp:
18
+ if resp.status != 200:
19
+ return None
20
+ data = await resp.json()
21
+ return data.get("url")
22
+ except Exception:
23
+ return None
24
+
25
+ @commands.hybrid_command(name="pat", aliases=["patting"])
26
+ async def pat(self, ctx: commands.Context, mentioned_member: discord.Member):
27
+ """Pat someone"""
28
+ image_url = await self._fetch_waifu("pat")
29
+ em = discord.Embed(
30
+ description=f"**{ctx.author.mention} _pats_ {mentioned_member.mention}**",
31
+ color=bot_embed_color,
32
+ )
33
+ if image_url:
34
+ em.set_image(url=image_url)
35
+ await ctx.reply(embed=em)
36
+
37
+ @commands.hybrid_command(name="kiss", aliases=["kissing", "kisss"])
38
+ async def kiss(self, ctx: commands.Context, mentioned_member: discord.Member):
39
+ """Kiss someone"""
40
+ image_url = await self._fetch_waifu("kiss")
41
+ em = discord.Embed(
42
+ description=f"**{ctx.author.mention} _kisses_ {mentioned_member.mention}**",
43
+ color=bot_embed_color,
44
+ )
45
+ if image_url:
46
+ em.set_image(url=image_url)
47
+ await ctx.reply(embed=em)
48
+
49
+ @commands.hybrid_command(name="highfive", aliases=["hfive", "hfv"])
50
+ async def highfive(self, ctx: commands.Context, mentioned_member: discord.Member):
51
+ """Give someone a high five"""
52
+ image_url = await self._fetch_waifu("highfive")
53
+ if not image_url:
54
+ image_url = await self._fetch_waifu("hug")
55
+ em = discord.Embed(
56
+ description=f"**{ctx.author.mention} _gives a high five to_ {mentioned_member.mention}**",
57
+ color=bot_embed_color,
58
+ )
59
+ if image_url:
60
+ em.set_image(url=image_url)
61
+ await ctx.reply(embed=em)
62
+
63
+
64
+ async def setup(bot: commands.Bot):
65
+ await bot.add_cog(Roleplay(bot))
cogs/utilities.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import discord
2
+ from discord.ext import commands
3
+ import random
4
+ import requests
5
+ from datetime import timedelta
6
+ import re
7
+ import json
8
+ import asyncio
9
+ import os
10
+ from datetime import datetime
11
+ from dateutil.relativedelta import relativedelta
12
+ import bs4
13
+ import wikipedia
14
+ import html
15
+ import aiohttp
16
+ from cogs.functions import *
17
+ bot_embed_color = 0x4548a8
18
+
19
+ with open('config.json') as f:
20
+ config = json.load(f)
21
+
22
+ UNITS = {'s':'seconds', 'm':'minutes', 'h':'hours', 'd':'days', 'w':'weeks'}
23
+
24
+ def convert_to_seconds(s):
25
+ return int(timedelta(**{
26
+ UNITS.get(m.group('unit').lower(), 'seconds'): float(m.group('val'))
27
+ for m in re.finditer(r'(?P<val>\d+(\.\d+)?)(?P<unit>[smhdw]?)', s, flags=re.I)
28
+ }).total_seconds())
29
+
30
+ extreme_ip_api_key = os.getenv('EXTREME_IP_API_KEY')
31
+ timezone_api_key = os.getenv('TIMEZONE_API_KEY')
32
+ edamam_api_recipe_app_id = os.getenv('EDAMAM_API_RECIPE_APP_ID')
33
+ edamam_api_recipe_app_key = os.getenv('EDAMAM_API_RECIPE_APP_KEY')
34
+
35
+ class Utilities(commands.Cog):
36
+
37
+ def __init__(self, bot):
38
+ self.bot = bot
39
+
40
+ @commands.hybrid_command()
41
+ async def ping(self, ctx):
42
+ """Check the bot's latency"""
43
+ latency = round(self.bot.latency * 1000)
44
+ embed = discord.Embed(
45
+ title="🏓 Pong!",
46
+ description=f"Latency: `{latency}ms`",
47
+ color=bot_embed_color
48
+ )
49
+ await ctx.reply(embed=embed)
50
+
51
+ @commands.hybrid_command(aliases=['ethereum'])
52
+ async def eth(self, ctx):
53
+ """Get current Ethereum price"""
54
+ r = requests.get('https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR')
55
+ r = r.json()
56
+ usd = r['USD']
57
+ eur = r['EUR']
58
+ em = discord.Embed(description=f'USD: `{str(usd)}$`\nEUR: `{str(eur)}€`')
59
+ em.set_author(name='Ethereum', icon_url='https://cdn.disnakeapp.com/attachments/271256875205525504/374282740218200064/2000px-Ethereum_logo.png')
60
+ await ctx.reply(embed=em)
61
+
62
+
63
+
64
+ @commands.hybrid_command(aliases=['bitcoin'])
65
+ async def btc(self, ctx):
66
+ """Get current Bitcoin price"""
67
+ r = requests.get('https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,EUR')
68
+ r = r.json()
69
+ usd = r['USD']
70
+ eur = r['EUR']
71
+ em = discord.Embed(description=f'USD: `{str(usd)}$`\nEUR: `{str(eur)}€`')
72
+ em.set_author(name='Bitcoin', icon_url='https://cdn.discordapp.com/attachments/271256875205525504/374282740218200064/2000px-Ethereum_logo.png')
73
+ await ctx.reply(embed=em)
74
+
75
+
76
+ @commands.hybrid_command()
77
+ async def pingweb(self, ctx, website: str):
78
+ """Check if a website is up"""
79
+ if website is None:
80
+ embed=discord.Embed(title="Error!", description="You didn't enter a website to ping for ;-;", color=0x243e7b)
81
+ await ctx.send(embed=embed)
82
+ else:
83
+ try:
84
+ r = requests.get(website).status_code
85
+ except Exception as e:
86
+ await ctx.send(f'''Error raised :- ```{e}```''')
87
+ if r == 404:
88
+ await ctx.send(f'Site is down, responded with a status code of {r}')
89
+ else:
90
+ await ctx.send(f'Site is up, responded with a status code of {r}')
91
+
92
+
93
+ @commands.hybrid_command(aliases=['pfp', 'avatar'])
94
+ async def av(self, ctx, *, user: discord.Member):
95
+ """Get a user's avatar"""
96
+ av = user.display_avatar.url
97
+ embed = discord.Embed(title="{}'s pfp".format(user.name), description="Here it is!", color=bot_embed_color)
98
+ embed.set_image(url=av)
99
+ await ctx.send(embed=embed)
100
+
101
+
102
+ @commands.hybrid_command(aliases=['server', 'serverinfo'])
103
+ async def sinfo(self, ctx):
104
+ """Get server information"""
105
+ embed = discord.Embed(title=f"{ctx.guild.name}", description="Here is the server info :-", color=bot_embed_color)
106
+ embed.add_field(name="Server created at", value=f"{ctx.guild.created_at}")
107
+ embed.add_field(name="Server Owner", value=f"{ctx.guild.owner}")
108
+ embed.add_field(name="Member Count", value=f"{ctx.guild.member_count}")
109
+ embed.add_field(name="Server ID", value=f"{ctx.guild.id}")
110
+ embed.set_thumbnail(url=ctx.guild.icon.url if ctx.guild.icon else None)
111
+ await ctx.reply(embed=embed)
112
+
113
+ @commands.hybrid_command(aliases = ["timezone", "timing"])
114
+ async def time(self, ctx, * , location: str):
115
+ """Get current time for a location"""
116
+ details = location.replace(" ", "+")
117
+ r = requests.get(f"https://timezone.abstractapi.com/v1/current_time/?api_key={timezone_api_key}&location={details}")
118
+ res = r.json()
119
+ timezone_location = str(res['timezone_location'])
120
+ raw_time = str(res['datetime'])
121
+ datetime_time = datetime.strptime(raw_time, "%Y-%m-%d %H:%M:%S")
122
+ time = str(datetime_time.strftime("%I:%M %p"))
123
+ date = str(datetime_time.strftime("%d %B %Y"))
124
+ embedVar = discord.Embed(title=f"Time in {location} is {time}", description=f'''{date}\nTimezone - {timezone_location}''', color=bot_embed_color)
125
+ embedVar.set_thumbnail(url="https://i.pinimg.com/originals/26/be/b0/26beb09153b8df233d82e66bef3edfbb.jpg")
126
+ await ctx.reply(embed=embedVar)
127
+
128
+ @commands.hybrid_command()
129
+ async def recipe(self, ctx, *, food_name: str):
130
+ """Search for a recipe"""
131
+ food = food_name.replace(" ", "+")
132
+ api_url = f"https://api.edamam.com/api/recipes/v2?type=public&q={food}&app_id={edamam_api_recipe_app_id}&app_key={edamam_api_recipe_app_key}"
133
+ async with aiohttp.ClientSession() as session:
134
+ async with session.get(api_url) as resp:
135
+ res = await resp.json()
136
+ ing = res['hits'][0]['recipe']['ingredients']
137
+ naam = res["hits"][0]["recipe"]["label"]
138
+ url = res["hits"][0]["recipe"]["url"]
139
+ image = res["hits"][0]["recipe"]["image"]
140
+ inge = ""
141
+ for ingredient in ing:
142
+ inge += f"{ingredient['text']}\n-------\n"
143
+ embed = discord.Embed(title=naam, url=url, description=inge, color=bot_embed_color)
144
+ embed.set_thumbnail(url=image)
145
+ await ctx.reply(embed=embed)
146
+
147
+
148
+ @commands.hybrid_command(aliases=['geolocate', 'iptogeo', 'iptolocation', 'ip2geo', 'ip'])
149
+ async def geoip(self, ctx, *, ipaddr: str = '1.3.3.7'):
150
+ """Get geolocation info for an IP address"""
151
+ r = requests.get(f'http://extreme-ip-lookup.com/json/{ipaddr}?key={extreme_ip_api_key}')
152
+ geo = r.json()
153
+ em = discord.Embed()
154
+ fields = [
155
+ {'name': 'IP', 'value': geo['query']},
156
+ {'name': 'ipType', 'value': geo['ipType']},
157
+ {'name': 'Country', 'value': geo['country']},
158
+ {'name': 'City', 'value': geo['city']},
159
+ {'name': 'Continent', 'value': geo['continent']},
160
+ {'name': 'Country', 'value': geo['country']},
161
+ {'name': 'IPName', 'value': geo['ipName']},
162
+ {'name': 'ISP', 'value': geo['isp']},
163
+ {'name': 'Latitute', 'value': geo['lat']},
164
+ {'name': 'Longitude', 'value': geo['lon']},
165
+ {'name': 'Org', 'value': geo['org']},
166
+ {'name': 'Region', 'value': geo['region']},
167
+ {'name': 'Status', 'value': geo['status']},
168
+ ]
169
+ for field in fields:
170
+ if field['value']:
171
+ em.add_field(name=field['name'], value=field['value'], inline=True)
172
+ return await ctx.send(embed=em)
173
+
174
+ @commands.hybrid_command(aliases=['wikiped', 'wikipedia'])
175
+ async def wiki(self, ctx, *, query: str):
176
+ """Search Wikipedia"""
177
+ global wikipedia_language
178
+ wikipedia_language = "en"
179
+ try:
180
+ page=wikipedia.page(wikipedia.search(query)[0])
181
+ except wikipedia.exceptions.DisambiguationError as e:
182
+ counter=0
183
+ while page is None:
184
+ try:
185
+ page=wikipedia.page(e.options[counter])
186
+ except:
187
+ counter+=1
188
+ except IndexError as e:
189
+ await ctx.send(languages[guild_language.setdefault(str(ctx.guild.id), "en")]["wikipedia_page_error"])
190
+ print(e)
191
+ return
192
+ summary=page.summary.split("\n")[0]
193
+ if len(summary)>2048:
194
+ summary=summary[:2045]+"..."
195
+ embed=discord.Embed(colour=0xfefefe,
196
+ title=page.title,
197
+ description=summary,
198
+ url=page.url,)
199
+ embed.set_author(name="Wikipedia",
200
+ icon_url="https://upload.wikimedia.org/wikipedia/commons/d/de/Wikipedia_Logo_1.0.png",
201
+ url="https://www.wikipedia.org/")
202
+ if page.images:
203
+ embed.set_thumbnail(url=page.images[0])
204
+ await ctx.send(embed=embed)
205
+
206
+ @commands.hybrid_command()
207
+ async def truth(self, ctx, rating: str = None):
208
+ """Get a truth question (ratings: pg, pg13, r)"""
209
+ if rating is None:
210
+ r = requests.get(f"https://api.truthordarebot.xyz/api/truth")
211
+ res = r.json()
212
+ Tile = f"Here is a truth for you"
213
+ Desc = res['question']
214
+ embed=discord.Embed(title=Tile, description=Desc, color=bot_embed_color)
215
+ await ctx.reply(embed=embed)
216
+ else:
217
+ try:
218
+ r = requests.get(f"https://api.truthordarebot.xyz/api/truth/?rating={rating}")
219
+ res = r.json()
220
+ Tile = f"Here is a {rating} rated question for you"
221
+ Desc = res['question']
222
+ embed=discord.Embed(title=Tile, description=Desc, color=bot_embed_color)
223
+ await ctx.reply(embed=embed)
224
+ except:
225
+ await ctx.reply("Please send a valid rating!! Valid parameters are `pg`,`pg13`,`r`")
226
+
227
+
228
+ @commands.hybrid_command()
229
+ async def question(self, ctx):
230
+ """Get a random trivia question"""
231
+ r = requests.get('https://opentdb.com/api.php?amount=1')
232
+ res = json.loads(r.text)
233
+ question1 = res['results'][0]['question']
234
+ question = html.unescape(question1)
235
+ correct_answer = res['results'][0]['correct_answer']
236
+ category = res['results'][0]['category']
237
+ difficulty = res['results'][0]['difficulty']
238
+ em = discord.Embed(title=category, description=question,colour=bot_embed_color)
239
+ em.add_field(name="Correct Answer :-", value=f"||{correct_answer}||")
240
+ em.set_footer(text=f"Difficulty : {difficulty}")
241
+ await ctx.reply(embed=em)
242
+
243
+
244
+ @commands.hybrid_command()
245
+ async def dare(self, ctx, rating: str = None):
246
+ """Get a dare (ratings: pg, pg13, r)"""
247
+ if rating is None:
248
+ r = requests.get(f"https://api.truthordarebot.xyz/api/dare")
249
+ res = r.json()
250
+ Tile = f"Here is a dare for you"
251
+ Desc = res['question']
252
+ embed=discord.Embed(title=Tile, description=Desc, color=bot_embed_color)
253
+ await ctx.reply(embed=embed)
254
+ else:
255
+ try:
256
+ r = requests.get(f"https://api.truthordarebot.xyz/api/dare/?rating={rating}")
257
+ res = r.json()
258
+ Tile = f"Here is a {rating} rated question for you"
259
+ Desc = res['question']
260
+ embed=discord.Embed(title=Tile, description=Desc, color=bot_embed_color)
261
+ await ctx.reply(embed=embed)
262
+ except:
263
+ await ctx.reply("Please send a valid rating!! Valid parameters are `pg`,`pg13`,`r`")
264
+
265
+ @commands.hybrid_command()
266
+ async def pypi(self, ctx, package: str):
267
+ """Search for a Python package on PyPI"""
268
+ r = requests.get(f"https://pypi.org/pypi/{package}/json")
269
+ try:
270
+ res = r.json()
271
+ author = res['info']['author']
272
+ embed = discord.Embed(title=res['info']['name'], url = f"https://pypi.org/project/{package}/", description=f"{res['info']['summary']}\n\n\n", color=bot_embed_color)
273
+ embed.set_author(name="PyPI", url="https://pypi.org/", icon_url="https://thumbs.dreamstime.com/b/python-icon-filled-python-icon-website-design-mobile-app-development-python-icon-filled-development-collection-155362649.jpg")
274
+ embed.add_field(name="More Details :- \n\n", value=res['info']['home_page'], inline=False)
275
+ embed.set_thumbnail(url=f"https://pypi.org/static/images/twitter.6fecba6f.jpg")
276
+ await ctx.reply(embed=embed)
277
+ except:
278
+ embed = discord.Embed(title="Package Not Found!", description="Please check the spelling of the package name and try again!", color=bot_embed_color)
279
+ await ctx.reply(embed=embed)
280
+
281
+ @commands.hybrid_command()
282
+ async def remind(self, ctx, time: str = None, *, reminder: str = None):
283
+ """Set a reminder"""
284
+ time = convert_to_seconds(s=time)
285
+ if time > 0:
286
+ embedVar = discord.Embed(title=f"Reminding in {time} seconds!", description=f"Reason: {reminder}", color=bot_embed_color)
287
+ await ctx.reply(embed=embedVar)
288
+ await asyncio.sleep(time)
289
+ embed = discord.Embed(title=f"Reminder!", description=f"Reason: {reminder}", color=bot_embed_color)
290
+ await ctx.reply(f"{ctx.author.mention}", embed=embed)
291
+ else:
292
+ embed = discord.Embed(title="Invalid Time!", description="Please enter a valid time!", color=bot_embed_color)
293
+ await ctx.reply(embed=embed)
294
+
295
+
296
+ @commands.hybrid_command(aliases=['gni', 'gno', 'recognise', 'recognize'])
297
+ async def musicrecognise(self, ctx, *, musicurl: str = None):
298
+ """Recognize a song from audio file or URL"""
299
+ if musicurl is None:
300
+ if not ctx.message.attachments:
301
+ await ctx.reply("Please provide a music file or a link to a music file!")
302
+ return
303
+ attachment = ctx.message.attachments[0]
304
+ musicurl = attachment.url
305
+
306
+ await download_mp3(musicurl)
307
+
308
+ data = await music_recognition("./assets/audio.mp3")
309
+ em = discord.Embed(title=data['songname'], url=data['yturl'], color=bot_embed_color)
310
+ em.description = data['artist']
311
+ em.set_thumbnail(url=data['thumbnail'])
312
+ await ctx.reply(embed=em)
313
+
314
+
315
+ async def setup(bot):
316
+ await bot.add_cog(Utilities(bot))
config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "prefix": "lu",
3
+ "embed_color": "0x4548a8",
4
+ "stream_url": "https://www.twitch.tv/lonelyguy523",
5
+ "tts_language": "en",
6
+ "common_footer": "Send me a hello in DMs"
7
+ }
keep_alive.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ from threading import Thread
3
+
4
+ app = Flask(__name__)
5
+
6
+ @app.route('/')
7
+ def home():
8
+ return "Bot is alive!"
9
+
10
+ @app.route('/health')
11
+ def health():
12
+ return {"status": "healthy", "bot": "Chitanda V2"}
13
+
14
+ def run():
15
+ app.run(host='0.0.0.0', port=7860)
16
+
17
+ def keep_alive():
18
+ t = Thread(target=run)
19
+ t.daemon = True
20
+ t.start()
main.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pathlib
3
+ import discord
4
+ from discord.ext import commands
5
+ import json
6
+ import asyncio
7
+ import traceback
8
+ from keep_alive import keep_alive
9
+
10
+ try:
11
+ from dotenv import load_dotenv
12
+ load_dotenv()
13
+ except Exception:
14
+ pass
15
+
16
+
17
+ with open('config.json') as f:
18
+ config = json.load(f)
19
+
20
+ TOKEN = os.getenv("DISCORD_TOKEN")
21
+ PREFIX = config.get("prefix", "!")
22
+
23
+ intents = discord.Intents.all()
24
+ intents.message_content = True
25
+ bot = commands.Bot(
26
+ command_prefix=PREFIX,
27
+ intents=intents,
28
+ case_insensitive=True,
29
+ strip_after_prefix = True,
30
+ )
31
+
32
+
33
+ @bot.event
34
+ async def on_ready():
35
+ print(f"Logged in as {bot.user} (ID: {bot.user.id})")
36
+ try:
37
+ synced = await bot.tree.sync()
38
+ print(f"Synced {len(synced)} slash command(s)")
39
+ except Exception as e:
40
+ print(f"Failed to sync commands: {e}")
41
+
42
+
43
+ @bot.event
44
+ async def on_command_error(ctx: commands.Context, error: commands.CommandError):
45
+ error = getattr(error, 'original', error)
46
+
47
+ error_msg = ''.join(traceback.format_exception(type(error), error, error.__traceback__))
48
+
49
+ if len(error_msg) > 1000:
50
+ error_msg = error_msg[-1000:]
51
+ error_msg = "..." + error_msg
52
+
53
+ embed = discord.Embed(
54
+ title="❌ Error Log",
55
+ description=f"An error occurred while executing the command.",
56
+ color=0xff0000
57
+ )
58
+
59
+ embed.add_field(
60
+ name="Error Details",
61
+ value=f"```py\n{error_msg}\n```",
62
+ inline=False
63
+ )
64
+
65
+ embed.set_footer(text=f"Command: {ctx.command.name if ctx.command else 'Unknown'}")
66
+
67
+ try:
68
+ await ctx.send(embed=embed)
69
+ except:
70
+ await ctx.send(f"```py\n{error_msg}\n```")
71
+
72
+
73
+ async def load_cogs():
74
+ cogs_dir = pathlib.Path(__file__).parent / "cogs"
75
+ for file in cogs_dir.glob("*.py"):
76
+ if file.stem.startswith("_"):
77
+ continue
78
+ ext = f"cogs.{file.stem}"
79
+ try:
80
+ await bot.load_extension(ext)
81
+ print(f"Loaded extension: {ext}")
82
+ except Exception as e:
83
+ print(f"Failed to load {ext}: {e}")
84
+
85
+
86
+ async def main():
87
+ keep_alive()
88
+ try:
89
+ await bot.load_extension('jishaku')
90
+ print("Loaded extension: jishaku")
91
+ except Exception as e:
92
+ print(f"Failed to load jishaku: {e}")
93
+ await load_cogs()
94
+ await bot.start(TOKEN)
95
+
96
+ if __name__ == "__main__":
97
+ asyncio.run(main())
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ discord.py>=2.3.2
2
+ python-dotenv>=1.0.1
3
+ jishaku
4
+ requests
5
+ wikipedia
6
+ python-dateutil
7
+ beautifulsoup4
8
+ shazamio
9
+ flask
10
+ aiohttp