20 Commits

Author SHA1 Message Date
Michelle 7b09d1a348 update version to 0.6.1
Build and publish ich_iel bot / build (push) Has been cancelled
Build and publish ich_iel bot / build (release) Successful in 6m3s
2026-05-08 20:53:12 +02:00
Michelle 01e5208cba add bnuy command
Build and publish ich_iel bot / build (push) Successful in 6m2s
2026-05-08 20:46:10 +02:00
Michelle 9d97b10c20 update version to 0.6.0
Build and publish ich_iel bot / build (push) Successful in 5m57s
Build and publish ich_iel bot / build (release) Successful in 5m56s
2026-05-02 16:35:58 +02:00
Michelle 8739f889d3 try using RSS feed instead of json, since RSS doesn't seem to block server ips
Build and publish ich_iel bot / build (push) Successful in 6m31s
2026-05-02 16:12:24 +02:00
Michelle 0e368ade2f ok now it should work
Build and publish ich_iel bot / build (push) Successful in 5m53s
2026-04-30 17:13:01 +02:00
Michelle 9acb6b04eb forgor adding GUILD_VOICE_STATES oopsie
Build and publish ich_iel bot / build (push) Successful in 6m0s
2026-04-30 16:59:31 +02:00
Michelle 2a33bc70b7 fix play command (hopefully)
Build and publish ich_iel bot / build (push) Successful in 5m56s
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 16:46:55 +02:00
Michelle a775a55291 update Dockerfile with ffmpeg
Build and publish ich_iel bot / build (push) Successful in 6m4s
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 16:18:26 +02:00
Michelle c365cd1ff2 try adding youtube player support
Build and publish ich_iel bot / build (push) Successful in 2m47s
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 16:17:39 +02:00
Michelle 4d802aba94 feat: update version number to 0.5.1
Build and publish ich_iel bot / build (push) Successful in 1m31s
Build and publish ich_iel bot / build (release) Successful in 1m36s
2026-04-25 17:35:54 +02:00
Michelle 668eb52673 fix: correct response status check in racoon command
Build and publish ich_iel bot / build (push) Successful in 1m31s
2026-04-25 17:31:04 +02:00
Michelle 90684ba27f fix: use aiohttp for racoon command
Build and publish ich_iel bot / build (push) Successful in 1m29s
2026-04-25 17:28:16 +02:00
Michelle 08778bf0a5 fix: add logging for failed racoon image decoding
Build and publish ich_iel bot / build (push) Successful in 1m32s
2026-04-25 17:18:29 +02:00
Michelle 63972faa63 fix: racoon command should work now
Build and publish ich_iel bot / build (push) Successful in 1m29s
2026-04-25 17:14:25 +02:00
Michelle f0729f43c9 feat: add racoon command to post racoon images
Build and publish ich_iel bot / build (push) Successful in 1m30s
2026-04-25 17:06:26 +02:00
Michelle 91b31ed114 feat: update version number to 0.5.0
Build and publish ich_iel bot / build (push) Successful in 1m29s
Build and publish ich_iel bot / build (release) Successful in 1m34s
2026-04-25 16:50:37 +02:00
Michelle 6df7d68f5a fix: update dog and fox commands, they should actually work now
Build and publish ich_iel bot / build (push) Successful in 1m31s
2026-04-25 16:47:08 +02:00
Michelle 8615cc4d0d fix: sorry i just copy pasted the cat command which didn't work
Build and publish ich_iel bot / build (push) Successful in 1m31s
2026-04-25 16:43:32 +02:00
Michelle b9e1740e10 feat: add dog and fox commands
Build and publish ich_iel bot / build (push) Successful in 1m32s
2026-04-25 16:39:23 +02:00
Michelle 4c874cbaa4 fix: remove on_guild_remove to avoid wiping data on outages
Build and publish ich_iel bot / build (push) Successful in 1m34s
2026-04-25 14:29:21 +02:00
4 changed files with 141 additions and 50 deletions
+2 -1
View File
@@ -1,8 +1,9 @@
FROM python:3.13.12-slim-trixie
FROM python:3.13.13-slim-trixie
ENV PATH=/usr/local/bin:$PATH
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
COPY . .
CMD ["python", "-u", "main.py"]
+92 -47
View File
@@ -1,20 +1,25 @@
import aiohttp
import fluxer
import requests
import json
import asyncio
import base64
from dotenv import load_dotenv
import sqlite3
import os
import re
import logging
import random
import yt_player
import xml.etree.ElementTree as ET
# this is a bot which posts the latest image post from ich_iel
# the code probably sucks, but it works, so I don't care
load_dotenv()
prefix = os.getenv("COMMAND_PREFIX", "/")
bot = fluxer.Bot(command_prefix=prefix, intents=fluxer.Intents.GUILD_MESSAGES | fluxer.Intents.GUILDS | fluxer.Intents.MESSAGE_CONTENT)
bot = fluxer.Bot(command_prefix=prefix, intents=fluxer.Intents.GUILD_MESSAGES | fluxer.Intents.GUILDS | fluxer.Intents.MESSAGE_CONTENT | fluxer.Intents.GUILD_VOICE_STATES)
yt_player.setup(bot)
task = None
@@ -49,32 +54,30 @@ async def post_reddit_periodically():
async def get_latest_post(subreddit):
post_limit = os.getenv("POST_LIMIT", 20)
url = f"https://www.reddit.com/r/{subreddit}/hot.json?limit={post_limit}"
headers = {"User-Agent": "Mozilla/5.0 (compatible; ich_iel-Bot/0.3)"}
url = f"https://www.reddit.com/r/{subreddit}/hot.rss?limit={post_limit}"
headers = {"User-Agent": "Mozilla/5.0 (compatible; ich_iel-Bot/0.6.1)"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
if response.headers.get("Content-Type", "").startswith("application/json"):
try:
data = response.json()
logging.debug(f"Fetched data from Reddit: {data}")
posts = []
if data["data"]["children"]:
for child in data["data"]["children"]:
post = child["data"]
if post.get("post_hint") == "image" and post.get("url", "").endswith((".jpg", ".png", ".jpeg", ".gif")):
logging.debug(f"Found image post: {post['title']} - {post['url']}")
posts.append((post["title"], post["url"]))
return posts
except (KeyError, json.JSONDecodeError):
logging.error(f"Error parsing Reddit response: {response.text}")
return []
else:
logging.warning(f"Unexpected content type from Reddit: {response.headers.get('Content-Type')}")
logging.warning(f"Response content: {response.text}")
return []
else:
logging.error(f"Failed to fetch Reddit data (maybe a block?): {response.status_code}")
if response.status_code != 200:
logging.error(f"Failed to fetch RSS feed: {response.status_code}")
return []
posts = []
try:
root = ET.fromstring(response.text)
ns = {"atom": "http://www.w3.org/2005/Atom"}
for entry in root.findall("atom:entry", ns):
post_title = entry.find("atom:title", ns)
content = entry.find("atom:content", ns)
if post_title is None or content is None or content.text is None:
continue
link_match = re.search(r'<a href="([^"]+)">\[link\]</a>', content.text)
if link_match and link_match.group(1).lower().endswith(('.jpg', '.jpeg', '.png', '.gif')):
logging.debug(f"Found image post: {post_title.text} - {link_match.group(1)}")
posts.append((post_title.text, link_match.group(1)))
except ET.ParseError as e:
logging.error(f"Error parsing RSS feed: {e}")
return []
return posts
async def init_db():
try:
@@ -118,7 +121,7 @@ async def setChannel(message):
@bot.command()
async def version(message):
await message.channel.send("Version 0.4.1 is running\nSource code: https://github.com/michelleDeko/ich_iel-bot")
await message.channel.send("Version 0.6.1 is running\nSource code: https://github.com/michelleDeko/ich_iel-bot")
# the cat bot died, so i wanted to add this command to this bot
@bot.command()
@@ -133,6 +136,68 @@ async def cat(message):
else:
await message.channel.send("Failed to fetch cat image")
# i thought dogs and foxes would be nice to have too
@bot.command()
async def dog(message):
response = requests.get("https://dog.ceo/api/breeds/image/random")
if response.status_code == 200:
data = response.json()
if data and isinstance(data, dict) and "message" in data:
await message.channel.send(data["message"])
else:
await message.channel.send("Could not fetch dog image")
else:
await message.channel.send("Failed to fetch dog image")
@bot.command()
async def fox(message):
response = requests.get("https://randomfox.ca/floof/")
if response.status_code == 200:
data = response.json()
if data and isinstance(data, dict) and "image" in data:
await message.channel.send(data["image"])
else:
await message.channel.send("Could not fetch fox image")
else:
await message.channel.send("Failed to fetch fox image")
# time for racoons
@bot.command()
async def racoon(message):
urls = random.choice([
"https://api.mapach.es/v1/meme",
"https://api.mapach.es/v1/coon"
])
async with aiohttp.ClientSession() as session:
async with session.get(urls) as response:
image_bytes = await response.read()
if response.status != 200:
await message.channel.send("Failed to fetch racoon image")
return
await message.channel.send(file=fluxer.File(image_bytes, filename="racoon.jpg"))
# i just watched beastars and i realised that this bot needs a bnuy command
# maybe I will switch the API later since this one seems to only have gifs
@bot.command()
async def bnuy(message):
async with aiohttp.ClientSession() as session:
async with session.get("https://api.bunnies.io/v2/loop/random/?media=gif,png") as response:
data = await response.json()
if response.status != 200 or not data or "media" not in data:
await message.channel.send("Failed to fetch image of a bunny")
return
image_url = data["media"].get("gif")
if not image_url:
await message.channel.send("Failed to fetch image of a bunny")
return
file_ext = "gif" if image_url.lower().endswith(".gif") else "png"
async with session.get(image_url) as image_response:
if image_response.status != 200:
await message.channel.send("Failed to fetch image of a bunny")
return
image_bytes = await image_response.read()
await message.channel.send(file=fluxer.File(image_bytes, filename=f"bnuy.{file_ext}"))
async def post_reddit():
subreddit = os.getenv("SUBREDDIT", "ich_iel")
posts = await get_latest_post(subreddit)
@@ -170,6 +235,7 @@ async def post_reddit():
else:
logging.warning(f"Bot is not in guild {guild_id}, removing guild from database")
cur.execute("DELETE FROM channels WHERE guild_id = ?", (guild_id,))
cur.execute("DELETE FROM posted WHERE guild_id = ?", (guild_id,))
con.commit()
break
else:
@@ -201,27 +267,6 @@ async def check_guild(guild_id):
logging.warning(f"Bot doesn't have access to guild {guild_id}")
return False
@bot.event
async def on_guild_remove(guild):
if isinstance(guild, fluxer.Guild):
guild_id = guild.id
logging.info(f"Removed from guild: {guild.name} (ID: {guild_id}), removing from database")
else:
if guild.get("unavailable"):
logging.info(f"Guild {guild.get('id')} is temporarily unavailable, ignoring")
return
guild_id = int(guild["id"])
logging.info(f"Removed from guild {guild_id}, removing from database")
try:
con = sqlite3.connect('data/ich_iel-bot.db')
cur = con.cursor()
cur.execute("DELETE FROM channels WHERE guild_id = ?", (guild_id,))
cur.execute("DELETE FROM posted WHERE guild_id = ?", (guild_id,))
con.commit()
logging.info(f"Guild {guild_id} removed from database successfully")
except sqlite3.Error as e:
logging.error(f"Database error while removing guild {guild_id}: {e}")
if __name__ == "__main__":
logging.info("Starting bot...")
asyncio.run(init_db())
+4 -2
View File
@@ -1,4 +1,6 @@
fluxer.py
fluxer.py[voice]
requests
asyncio
dotenv
dotenv
aiohttp
yt-dlp
+43
View File
@@ -0,0 +1,43 @@
# at this point this bot isn't just a reddit bot anymore, maybe i should start renaming it lol
import yt_dlp
import logging
import os
AUDIO_DIR = "data/audio"
_bot = None
def setup(bot):
global _bot
_bot = bot
os.makedirs(AUDIO_DIR, exist_ok=True)
bot.command()(play)
async def play(ctx, *, url: str):
guild_id = ctx._guild.id
voice_state = _bot.get_voice_state(guild_id, ctx.author.id)
if voice_state is None or voice_state.channel_id is None:
await ctx.reply("You're not in a voice channel!")
return
channel = await _bot.fetch_channel(str(voice_state.channel_id))
logging.info(f"Playing {url}")
ydl_opts = {
'format': 'm4a/bestaudio/best',
'outtmpl': f'{AUDIO_DIR}/%(id)s.%(ext)s',
'postprocessors': [{ # Extract audio using ffmpeg
'key': 'FFmpegExtractAudio',
'preferredcodec': 'm4a',
}]
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
filename = ydl.prepare_filename(info).rsplit('.', 1)[0] + '.m4a'
title = info.get('title', 'Unknown Title')
logging.info(f"Downloaded to {filename}")
await ctx.reply(f"Playing {title} in {channel.mention}")
async with await channel.connect(_bot) as vc:
await vc.play_file(filename)