Forráskód Böngészése

Unfinished, with ON CONFLICT ON CONSTRAINT DO UPDATE syntax error

root 3 éve
commit
cb70aa2372

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+# Virtual environment
+/bin/
+/lib/
+/lib64
+/pyvenv.cfg
+
+# Python
+__pycache__/
+
+# Configuration
+local_settings.py

+ 14 - 0
README.md

@@ -0,0 +1,14 @@
+# Iris Pseudácorus Mallard
+A Discord robot for the Angels Discord written in Python.
+
+## Requirements
+1. Clone the git repository
+1. Change into the repo directory
+1. `virtualenv --python=python3 .`
+1. `source bin/activate`
+1. `pip3 install -r requirements.txt`
+
+## Run
+1. Change into the repo directory
+1. `source bin/activate`
+1. `python3 rotbot.py`

+ 83 - 0
bot/commands/games.py

@@ -0,0 +1,83 @@
+from discord.ext import commands
+import discord
+import random
+from typing import Optional
+
+def setup(bot: commands.Bot):
+	bot.add_cog(Games(bot))
+
+class Games(commands.Cog):
+	"""Gaming commands."""
+
+	def __init__(self, bot: commands.Bot):
+		self.bot = bot
+
+
+	@commands.command(
+		description="Simulate dice rolls.",
+		brief="Roll dice",
+		help="Roll two dice."
+	)
+	async def dice(self, ctx: commands.Context, amount: Optional[int], sides: Optional[int]):
+		if amount and amount < 1:
+			await ctx.send("You want me to roll less as one die? How!?")
+		elif amount and amount > 25:
+			await ctx.send("I can not hold so many dice at one time.")
+		elif sides and sides < 2:
+			await ctx.send("A die has physical minimum of 2 sides. Don't ask for impossible objects.")
+		else:
+			if sides:
+				sides = sides
+			else:
+				sides = 6
+			if not amount:
+				amount = 2
+			embed = discord.Embed(title = "Dice roll", description=f"Rolling {amount} dice, with {sides} sides.")
+			while amount > 0:
+				embed.insert_field_at(0, name=f"Die {amount}", value=random.randint(1, sides), inline=True)
+				amount -= 1
+			await ctx.send(embed=embed)
+
+	@commands.command(
+		description="Ask the magic 8-ball.",
+		brief="Pose question",
+		help="Simulate the iconic 8-ball gimmic.",
+		name="8ball"
+	)
+	async def eightball(self, ctx: commands.Context, *, question: str = None):
+		if not question:
+			messages = [
+				"Don't forget to ask a question...",
+				"Hey, that's not a question!",
+				"What would you like to know?",
+				"You want me to predict nothing?",
+				"Are you intentionally not asking a question?",
+				"Ask a question you tease!",
+				"You will die alone.",
+			]
+		else:
+			messages = [
+				"Yes.",
+				"No.",
+				"Affirmative.",
+				"No way!",
+				"Negative.",
+				"Positive.",
+				"Correct.",
+				"Incorrect.",
+				"Likely",
+				"Unlikely",
+				"Maybe.",
+				"Definately!",
+				"Perhaps?",
+				"Most indubitably.",
+				"Does the pope shit in the woods?",
+				"When hell freezes over.",
+				"Only between 9 and 5.",
+				"Only just before you die.",
+				"ERROR: Probability failure.",
+				"Ask again later.",
+				"I don't know.",
+				"Unpredictable.",
+			]
+		await ctx.send(random.choice(messages))

+ 67 - 0
bot/commands/general.py

@@ -0,0 +1,67 @@
+from discord.ext import commands
+import discord
+import time
+from typing import Optional
+
+def setup(bot: commands.Bot):
+	bot.add_cog(General(bot))
+
+class General(commands.Cog):
+	"""General functionality."""
+
+	def __init__(self, bot: commands.Bot):
+		self.bot = bot
+
+
+	@commands.command(
+		description="Get the bot's current websocket and API latency.",
+		brief="Test latency",
+		help="Test latency by polling the gateway and API."
+	)
+	async def ping(self, ctx: commands.Context):
+		start_time = time.time()
+		message = await ctx.send(f"Pong!\nGateway heartbeat in {round(self.bot.latency * 1000)}ms.")
+		end_time = time.time()
+
+		await ctx.send(f"API roundtrip latency {round((end_time - start_time) * 1000)}ms.")
+
+	@commands.command(
+		description="Rubbish information.",
+		brief="Get info",
+		help="Display some crap."
+	)
+	async def info(self, ctx: commands.Context):
+		"""Displays some rubbish info."""
+		embed = discord.Embed(title=f"Guild: {ctx.guild}.")
+		await ctx.send(embed=embed)
+		#await ctx.send(f"Guild: {ctx.guild}.")
+
+	@commands.command(
+		description="Send a message",
+		brief="Chat",
+		help="Make a chat message",
+		aliases= ("say", "pm", "dm", "echo", "print")
+	)
+	async def msg(self, ctx: commands.Context, channel: Optional[discord.TextChannel], user: Optional[discord.User], *, message: str = None):
+		if not message:
+			if channel:
+				await ctx.send(f"What would you like me to say in {channel}?")
+			elif user:
+				await ctx.send(f"What would you like me to say to {user}?")
+			else:
+				await ctx.send("What would you like me to say?")
+		elif channel:
+			await channel.send(message)
+		elif user:
+			await user.send(message)
+		else:
+			await ctx.send(message)
+
+
+	@commands.command(
+		description="Change status.",
+		brief="Set status",
+		help="Update the bot's status."
+	)
+	async def status(self, ctx: commands.Context, *, text: str):
+		await self.bot.change_presence(activity=discord.Game(name=text))

+ 76 - 0
bot/events/general.py

@@ -0,0 +1,76 @@
+import discord, random
+from discord.ext import commands
+from query.guild import update_guild
+
+def setup(bot: commands.Bot):
+	bot.add_cog(General(bot))
+
+class General(commands.Cog):
+	"""A couple of simple commands."""
+
+	def __init__(self, bot: commands.Bot):
+		self.bot = bot
+		self.last_msg = None
+
+	@commands.Cog.listener()
+	async def on_ready(self):
+		print('Logged in as')
+		print(self.bot.user.name)
+		print(self.bot.user.id)
+		print('------')
+
+	@commands.Cog.listener()
+	async def on_guild_join(self, guild: discord.Guild):
+		update_guild(guild)
+
+	@commands.Cog.listener()
+	async def on_message_delete(self, message: discord.Message):
+		self.last_msg = message
+
+	@commands.Cog.listener()
+	async def on_message(self, message: discord.Message):
+		await self.bot.pg.execute("INSERT INTO channel_user(channel, \"user\") VALUES($1, $2) ON CONFLICT ON CONSTRAINT channel_user_channel_user_key DO UPDATE SET total_messages=total_messages+1 WHERE channel=$1 AND \"user\"=$2", message.channel.id, message.author.id)
+
+		if self.bot.user.mentioned_in(message):
+			print("mentioned in ")
+			print(message.channel)
+			interact = await self.bot.pg.fetchrow("SELECT interact FROM channel_settings WHERE channel_id=$1::bigint", message.channel.id)
+			print(interact)
+			if interact:
+				messages = [
+					f"Hello {message.author.mention}. <3",
+					f"How are you today {message.author.mention}?",
+					f"Piss off {message.author.mention}!",
+					f"{message.author.mention}, what are you botherring me for?",
+					"Go bother someone else...",
+					"Is life treating you fair?",
+					"What's up?",
+					"Why are you talking to me?",
+					"I'm not talking to you!",
+					"What have you been up to?",
+					"How is life?",
+	                "Kill all humans!",
+					"What do you want from me?",
+					f"{message.author.mention}, why are you bothering me?",
+					f"{message.author.mention}, when will you stop talking about me?",
+					f"{message.author.mention} I hate you!",
+					f"{message.author.mention} I love you!",
+					"Get bent!",
+					"Go and touch grass!",
+					"Do you think i care about you?",
+					f"Stop pinging me {message.author.mention}, you munchkin!",
+				]
+				await message.reply(random.choice(messages))
+
+	@commands.command(name="snipe")
+	async def snipe(self, ctx: commands.Context):
+		"""A command to snipe delete messages."""
+		if not self.last_msg:  # on_message_delete hasn't been triggered since the bot started
+			await ctx.send("There is no message to snipe!")
+			return
+
+		author = self.last_msg.author
+		content = self.last_msg.content
+
+		embed = discord.Embed(title=f"Message from {author}", description=content)
+		await ctx.send(embed=embed)

+ 12 - 0
bot/local_settings_example.py

@@ -0,0 +1,12 @@
+# Rename this file to local_settings.py
+#
+import logging
+LOG_LEVEL = logging.INFO	# Options: CRITICAL, ERROR, WARNING, INFO, and DEBUG
+
+DATABASE_NAME = ""
+DATABASE_USER = ""
+DATABASE_HOST = ""
+DATABASE_PASSWORD = ""
+
+DISCORD_TOKEN = ""
+COMMAND_PREFIX = ""

+ 77 - 0
bot/main.py

@@ -0,0 +1,77 @@
+import logging
+
+from os.path import exists
+def missing_config():
+    logging.basicConfig(level=logging.DEBUG)
+    if not exists("local_settings.py"):
+        logging.error("Settings file not found.")
+        logging.info("Rename local_settings_example.py to local_settings.py:")
+        logging.info("")
+        logging.info("    mv local_settings_example.py local_settings.py")
+        logging.info("")
+    logging.error("Settings undefined.")
+    logging.info("Configure the settings:")
+    logging.info("")
+    logging.info("    edit local_settings.py")
+    logging.info("")
+    quit()
+
+# Import settings
+try:
+    import local_settings as settings   # Environment dependant settings stored in local_settings.py, untracked by .gitinore
+except ModuleNotFoundError:
+    missing_config()
+
+# Set loglevel
+try:
+    logging.basicConfig(level=settings.LOG_LEVEL)
+except AttributeError:
+    missing_config()
+
+# Define database pool
+import asyncpg
+async def create_db_pool():
+    try:
+        bot.pg = await asyncpg.create_pool(
+            database=settings.DATABASE_NAME,
+            user=settings.DATABASE_USER,
+            host=settings.DATABASE_HOST,
+            password=settings.DATABASE_PASSWORD,
+        )
+    except AttributeError:
+        missing_config()
+
+# Create robot
+import discord
+from discord.ext import commands
+try:
+    bot = commands.Bot(
+        command_prefix = settings.COMMAND_PREFIX,
+        description = "Charlie's Angels bot",
+        intents = discord.Intents.default(),    # Required: Guilds
+        case_insensitive = True,
+    )
+except AttributeError:
+    missing_config()
+
+# Create database pool
+bot.loop.run_until_complete(create_db_pool())
+
+# Create database tables if they do not exist
+from query.initialise_database import init_db
+bot.loop.run_until_complete(init_db(bot.pg))
+
+# Load extensions
+default_extensions = [
+  "commands.general",
+  "commands.games",
+  "events.general"
+]
+for ext in default_extensions:
+  bot.load_extension(ext)
+
+# Run robot
+try:
+    bot.run(settings.DISCORD_TOKEN)
+except AttributeError:
+    missing_config()

+ 4 - 0
bot/query/guild.py

@@ -0,0 +1,4 @@
+async def update_guild(guild):
+	await bot.pg.execute("INSERT INTO guild(guild_id) VALUES($1) ON CONFLICT DO NOTHING", guild.id)
+	for chan in guild.text_channels:
+		await self.bot.pg.execute("INSERT INTO channel(channel_id, guild) ON CONFLICT DO NOTHING", chan.id, guild.id)

+ 5 - 0
bot/query/initialise_database.py

@@ -0,0 +1,5 @@
+async def init_db(pg):
+    await pg.execute("CREATE TABLE IF NOT EXISTS guild (id SERIAL PRIMARY KEY, guild_id BIGINT UNIQUE NOT NULL)")
+    await pg.execute("CREATE TABLE IF NOT EXISTS channel (id SERIAL PRIMARY KEY, channel_id BIGINT UNIQUE NOT NULL, guild BIGINT REFERENCES guild (guild_id))")
+    await pg.execute("CREATE TABLE IF NOT EXISTS channel_settings (id SERIAL PRIMARY KEY, channel BIGINT UNIQUE NOT NULL REFERENCES channel (channel_id), guild BIGINT REFERENCES guild (guild_id), interact BOOL DEFAULT FALSE)")
+    await pg.execute("CREATE TABLE IF NOT EXISTS channel_user (id SERIAL PRIMARY KEY, channel BIGINT NOT NULL REFERENCES channel (channel_id), \"user\" BIGINT NOT NULL, total_messages BIGINT DEFAULT 1, UNIQUE (channel, \"user\"))")

+ 1 - 0
requirements.txt

@@ -0,0 +1 @@
+discord.py==1.7.3