Initial commit
This commit is contained in:
commit
250ed0e40f
6 changed files with 297 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
.vscode
|
||||||
|
__py*
|
253
bot.py
Normal file
253
bot.py
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
from os import getenv
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
from typing import Iterator, Optional
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from asyncache import cached
|
||||||
|
from cachetools import TTLCache
|
||||||
|
from requests import get, post
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import tasks
|
||||||
|
|
||||||
|
from helper import nth_element
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
GUILD_ID = int(getenv("GUILD_ID"))
|
||||||
|
TOKEN = getenv("DISCORD_TOKEN")
|
||||||
|
WEATHER_API_KEY = getenv("WEATHER_API_KEY")
|
||||||
|
GENERAL_CHANNEL_ID = int(getenv("GENERAL_CHANNEL_ID"))
|
||||||
|
|
||||||
|
MY_GUILD = discord.Object(id=GUILD_ID)
|
||||||
|
|
||||||
|
|
||||||
|
tt = datetime.now(ZoneInfo("Europe/Paris"))
|
||||||
|
md = tt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
|
||||||
|
mid = md.timetz()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def embed_from_quote(my_quote: dict):
|
||||||
|
embed = discord.Embed(title="Quote")
|
||||||
|
embed.description = my_quote['content']
|
||||||
|
embed.set_author(name=my_quote['author'])
|
||||||
|
return embed
|
||||||
|
|
||||||
|
|
||||||
|
class Dropdown(discord.ui.Select):
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
# Set the options that will be presented inside the dropdown
|
||||||
|
options = [
|
||||||
|
discord.SelectOption(
|
||||||
|
label='Red', description='Your favourite colour is red', emoji='🟥'),
|
||||||
|
discord.SelectOption(
|
||||||
|
label='Green', description='Your favourite colour is green', emoji='🟩'),
|
||||||
|
discord.SelectOption(
|
||||||
|
label='Blue', description='Your favourite colour is blue', emoji='🟦'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The placeholder is what will be shown when no option is chosen
|
||||||
|
# The min and max values indicate we can only pick one of the three options
|
||||||
|
# The options parameter defines the dropdown options. We defined this above
|
||||||
|
super().__init__(placeholder='Choose your favourite colour...',
|
||||||
|
min_values=1, max_values=1, options=options)
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
# Use the interaction object to send a response message containing
|
||||||
|
# the user's favourite colour or choice. The self object refers to the
|
||||||
|
# Select object, and the values attribute gets a list of the user's
|
||||||
|
# selected options. We only want the first one.
|
||||||
|
await interaction.response.send_message(f'Your favourite colour is {self.values[0]}')
|
||||||
|
|
||||||
|
|
||||||
|
class DropdownView(discord.ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Adds the dropdown to our view object.
|
||||||
|
self.add_item(Dropdown())
|
||||||
|
|
||||||
|
|
||||||
|
class MyClient(discord.Client):
|
||||||
|
def __init__(self, *, client_intents: discord.Intents):
|
||||||
|
super().__init__(intents=client_intents)
|
||||||
|
self.tree = app_commands.CommandTree(self)
|
||||||
|
|
||||||
|
async def setup_hook(self):
|
||||||
|
self.tree.copy_global_to(guild=MY_GUILD)
|
||||||
|
self.quote_task.start()
|
||||||
|
await self.tree.sync(guild=MY_GUILD)
|
||||||
|
|
||||||
|
@tasks.loop(time=mid)
|
||||||
|
async def quote_task(self):
|
||||||
|
channel = self.get_channel(GENERAL_CHANNEL_ID)
|
||||||
|
response = get("https://api.quotable.io/random?maxLength=230")
|
||||||
|
my_quote = response.json()
|
||||||
|
await channel.send(embed=embed_from_quote(my_quote))
|
||||||
|
|
||||||
|
@quote_task.before_loop
|
||||||
|
async def before_quote_task(self):
|
||||||
|
await self.wait_until_ready()
|
||||||
|
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
client = MyClient(client_intents=intents)
|
||||||
|
|
||||||
|
|
||||||
|
@client.event
|
||||||
|
async def on_ready():
|
||||||
|
print(f'Logged in as {client.user} (ID: {client.user.id})')
|
||||||
|
print('------')
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
async def hello(interaction: discord.Interaction):
|
||||||
|
"""Says hello!"""
|
||||||
|
await interaction.response.send_message(f'Hi, {interaction.user.mention}')
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
@app_commands.describe(
|
||||||
|
first_value='The first value you want to add something to',
|
||||||
|
second_value='The value you want to add to the first value',
|
||||||
|
)
|
||||||
|
async def add(interaction: discord.Interaction, first_value: int, second_value: int):
|
||||||
|
"""Adds two numbers together."""
|
||||||
|
await interaction.response.send_message(f'{first_value} + {second_value} = {first_value + second_value}')
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
@app_commands.rename(text_to_send='text')
|
||||||
|
@app_commands.describe(text_to_send='Text to send in the current channel')
|
||||||
|
async def send(interaction: discord.Interaction, text_to_send: str):
|
||||||
|
"""Sends the text into the current channel."""
|
||||||
|
await interaction.response.send_message(text_to_send)
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
@app_commands.describe(member='The member you want to get the joined date from; defaults to the user who uses the '
|
||||||
|
'command')
|
||||||
|
async def joined(interaction: discord.Interaction, member: Optional[discord.Member] = None):
|
||||||
|
"""Says when a member joined."""
|
||||||
|
member = member or interaction.user
|
||||||
|
await interaction.response.send_message(f'{member} joined {discord.utils.format_dt(member.joined_at)}', ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.context_menu(name='Show Join Date')
|
||||||
|
async def show_join_date(interaction: discord.Interaction, member: discord.Member):
|
||||||
|
await interaction.response.send_message(f'{member} joined at {discord.utils.format_dt(member.joined_at)}', ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.context_menu(name='Translate')
|
||||||
|
async def translate(interaction: discord.Interaction, message: discord.Message):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
"""Translate text to another language."""
|
||||||
|
try:
|
||||||
|
response = post('https://translate.argosopentech.com/translate', json={
|
||||||
|
'q': message.content,
|
||||||
|
'source': 'fr',
|
||||||
|
'target': 'en'
|
||||||
|
}, timeout=30)
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f'An error occurred: {e}', ephemeral=True)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
json = response.json()
|
||||||
|
translated_text = json['translatedText']
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f'An error occurred: {e}', ephemeral=True)
|
||||||
|
return
|
||||||
|
await interaction.followup.send(f'Translated text: {translated_text}', ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
async def quote(interaction: discord.Interaction):
|
||||||
|
"""Sends a random quote."""
|
||||||
|
response = get('https://api.quotable.io/random?maxLength=230')
|
||||||
|
my_quote = response.json()
|
||||||
|
await interaction.response.send_message(embed=embed_from_quote(my_quote))
|
||||||
|
|
||||||
|
|
||||||
|
@cached(cache=TTLCache(maxsize=1024, ttl=600))
|
||||||
|
async def get_weather_json(city: str):
|
||||||
|
try:
|
||||||
|
weather_response = get(
|
||||||
|
f'https://api.weatherapi.com/v1/current.json?key={WEATHER_API_KEY}&q={city}&aqi=no')
|
||||||
|
weather_response.raise_for_status()
|
||||||
|
forecast_response = get(
|
||||||
|
f'https://api.weatherapi.com/v1/forecast.json?key={WEATHER_API_KEY}&q={city}&days=1&aqi=no&alerts=no')
|
||||||
|
forecast_response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
return weather_response.json(), forecast_response.json()
|
||||||
|
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
@app_commands.describe(city='The city you want to get the weather for')
|
||||||
|
async def weather(interaction: discord.Interaction, city: str):
|
||||||
|
"""Get the current weather"""
|
||||||
|
try:
|
||||||
|
weather_response, forecast_response = await get_weather_json(city)
|
||||||
|
except Exception as _e:
|
||||||
|
await interaction.response.send_message(f'Error: the city "{city}" was not found', ephemeral=True)
|
||||||
|
return
|
||||||
|
current_temp = weather_response['current']['temp_c']
|
||||||
|
forecast = forecast_response['forecast']['forecastday'][0]
|
||||||
|
forecast_min = forecast['day']['mintemp_c']
|
||||||
|
forecast_max = forecast['day']['maxtemp_c']
|
||||||
|
embed = discord.Embed(title="Weather")
|
||||||
|
embed.description = f'Current temperature: {current_temp}°C\n' f'Forecast: Min: {forecast_min}°C, Max: {forecast_max}°C '
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
|
# A command tha tell the time until midnight
|
||||||
|
@client.tree.command()
|
||||||
|
async def midnight(interaction: discord.Interaction):
|
||||||
|
"""Sends the time until midnight."""
|
||||||
|
paris_dt = datetime.now(ZoneInfo("Europe/Paris"))
|
||||||
|
midnight = paris_dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
|
||||||
|
time_until_midnight = midnight - paris_dt
|
||||||
|
await interaction.response.send_message(f'Time until midnight: {time_until_midnight}')
|
||||||
|
|
||||||
|
|
||||||
|
# A command that say what the best programming language is
|
||||||
|
@client.tree.command()
|
||||||
|
async def best_language(interaction: discord.Interaction):
|
||||||
|
"""Sends the best programming language."""
|
||||||
|
await interaction.response.send_message('The best programming language is Rust')
|
||||||
|
|
||||||
|
@client.tree.command()
|
||||||
|
async def color(interaction: discord.Interaction):
|
||||||
|
"""Sends a random color."""
|
||||||
|
await interaction.response.send_message(view=DropdownView())
|
||||||
|
|
||||||
|
|
||||||
|
def fibo_gen() -> Iterator[int]:
|
||||||
|
a, b = 0, 1
|
||||||
|
while True:
|
||||||
|
yield a
|
||||||
|
a, b = b, a + b
|
||||||
|
|
||||||
|
|
||||||
|
def fib(n: int) -> int:
|
||||||
|
return nth_element(fibo_gen(), n)
|
||||||
|
|
||||||
|
|
||||||
|
# @client.tree.command()
|
||||||
|
# async def fibonacci(interaction: discord.Interaction, n: int):
|
||||||
|
# """Sends the nth fibonacci number."""
|
||||||
|
# await interaction.response.send_message(f'{n}th fibonacci number is {fib(n)}')
|
||||||
|
|
||||||
|
|
||||||
|
@client.event
|
||||||
|
async def on_member_join(member: discord.Member):
|
||||||
|
guild = member.guild
|
||||||
|
channel = client.get_channel(GENERAL_CHANNEL_ID)
|
||||||
|
await channel.send(f'Welcome {member.mention} to {guild.name}!')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
client.run(TOKEN)
|
12
disco.service
Normal file
12
disco.service
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=disco
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=pierre
|
||||||
|
WorkingDirectory=/home/pierre/disco
|
||||||
|
ExecStart=/home/pierre/disco/launch.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
7
helper.py
Normal file
7
helper.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
|
|
||||||
|
def nth_element(my_iter: Iterator, n: int) -> int:
|
||||||
|
for _ in range(n):
|
||||||
|
next(my_iter)
|
||||||
|
return next(my_iter)
|
5
launch.sh
Executable file
5
launch.sh
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python bot.py
|
16
requirements.txt
Normal file
16
requirements.txt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
aiohttp==3.9.1
|
||||||
|
aiosignal==1.3.1
|
||||||
|
asyncache==0.3.1
|
||||||
|
attrs==23.1.0
|
||||||
|
cachetools==5.3.2
|
||||||
|
certifi==2023.11.17
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
discord.py==2.3.2
|
||||||
|
frozenlist==1.4.1
|
||||||
|
idna==3.6
|
||||||
|
multidict==6.0.4
|
||||||
|
pipdeptree==2.13.1
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
requests==2.31.0
|
||||||
|
urllib3==2.1.0
|
||||||
|
yarl==1.9.4
|
Loading…
Add table
Add a link
Reference in a new issue