Compare commits
13 commits
ac87fa9b90
...
main
Author | SHA1 | Date | |
---|---|---|---|
xenofem | d8270f558f | ||
xenofem | b6a7d38bed | ||
xenofem | 5e38af2b63 | ||
xenofem | 26a4fa7000 | ||
xenofem | b702aaa475 | ||
xenofem | dc0a6ce68f | ||
xenofem | 8c2eeeae3c | ||
xenofem | 7d9d6012d9 | ||
xenofem | 9117f0dfa1 | ||
xenofem | 5a658a107f | ||
xenofem | 2d9d30f4f7 | ||
xenofem | 8b6da55729 | ||
xenofem | f4235d8245 |
12
README.md
12
README.md
|
@ -2,6 +2,15 @@
|
||||||
|
|
||||||
Discord bot to give questionable elevator therapy
|
Discord bot to give questionable elevator therapy
|
||||||
|
|
||||||
|
## Privacy
|
||||||
|
|
||||||
|
This bot does not persistently store any data except for the numerical
|
||||||
|
IDs of each server it's in, and the numerical ID of the channel it
|
||||||
|
creates for itself in each server. It does not make use of any data
|
||||||
|
except for the list of channels. It does not read message
|
||||||
|
contents. It only creates one channel for itself, and does not
|
||||||
|
interact at all outside of that channel.
|
||||||
|
|
||||||
## Discord setup
|
## Discord setup
|
||||||
|
|
||||||
Required permissions:
|
Required permissions:
|
||||||
|
@ -9,8 +18,7 @@ Required permissions:
|
||||||
- Read messages/view channels
|
- Read messages/view channels
|
||||||
- Send messages
|
- Send messages
|
||||||
|
|
||||||
|
## Bot setup
|
||||||
## Setup
|
|
||||||
|
|
||||||
git clone https://git.xeno.science/xenofem/mikage-bot
|
git clone https://git.xeno.science/xenofem/mikage-bot
|
||||||
cd mikage-bot
|
cd mikage-bot
|
||||||
|
|
100
bot.py
100
bot.py
|
@ -1,18 +1,31 @@
|
||||||
import discord
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
STATE_FILE = 'state.json'
|
STATE_FILE = 'state.json'
|
||||||
CHANNEL_NAME = 'therapy-elevator'
|
CHANNEL_NAME = 'therapy-elevator'
|
||||||
DELETION_SECONDS = 300
|
DELETION_SECONDS = 15
|
||||||
|
TYPING_DELAY = 2
|
||||||
|
|
||||||
SETUP_MESSAGE = 'Welcome to Nemuro Memorial Hall. Please give your name and class year for our records.'
|
SETUP_MESSAGE = 'Welcome to Nemuro Memorial Hall. Please give your name and class year for our records.'
|
||||||
INITIAL_MESSAGE = 'All right, please begin.'
|
INITIAL_MESSAGE = 'All right, please begin.'
|
||||||
BUTTERFLIES = ['🦋', '🥐', 'Deeper. Go deeper.' '🐛', '🍃']
|
BUTTERFLIES = ['🦋', 'Deeper. Go deeper.', '🥐', '🐛', '🥚', '🍃']
|
||||||
|
ELEVATOR_SHIT_PERIOD = 5
|
||||||
|
ELEVATOR_SHIT = [
|
||||||
|
'*the elevator creaks eerily*',
|
||||||
|
'*menacing discordant music plays*',
|
||||||
|
'*drawers rush past on the walls outside*',
|
||||||
|
'*the lights flicker*',
|
||||||
|
'*the elevator jolts, then continues its descent*',
|
||||||
|
'*a rattling. the elevator begins to shake*',
|
||||||
|
'*a squealing of cables and pulleys*',
|
||||||
|
]
|
||||||
|
CRASH = ('|\n'*10) + '***the elevator crashes to the bottom***' + ('\n'*10) + '...'
|
||||||
FINAL_MESSAGE = 'I understand. Your only choice is to revolutionize the world. The path you must take has been prepared for you.'
|
FINAL_MESSAGE = 'I understand. Your only choice is to revolutionize the world. The path you must take has been prepared for you.'
|
||||||
ROSE = '🌹'
|
ROSE = '🌹💍'
|
||||||
|
|
||||||
async def try_send(channel, message):
|
async def try_send(channel, message):
|
||||||
try:
|
try:
|
||||||
|
@ -20,6 +33,13 @@ async def try_send(channel, message):
|
||||||
except discord.DiscordException as e:
|
except discord.DiscordException as e:
|
||||||
print(f"error sending message in channel {channel}: {e}", file=sys.stderr)
|
print(f"error sending message in channel {channel}: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
async def try_move(channel, **kwargs):
|
||||||
|
try:
|
||||||
|
await channel.move(**kwargs)
|
||||||
|
except discord.DiscordException as e:
|
||||||
|
print(f"error moving channel {channel}: {e}", file=sys.stderr)
|
||||||
|
await try_send(channel, "Dammit, the elevator's stuck!")
|
||||||
|
|
||||||
def text_only(channels):
|
def text_only(channels):
|
||||||
return [channel for channel in channels if isinstance(channel, discord.TextChannel)]
|
return [channel for channel in channels if isinstance(channel, discord.TextChannel)]
|
||||||
|
|
||||||
|
@ -27,18 +47,19 @@ class MikageClient(discord.Client):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
try:
|
try:
|
||||||
with open(STATE_FILE) as f:
|
with open(STATE_FILE, encoding='utf-8') as f:
|
||||||
self.state = json.load(f)
|
self.state = json.load(f)
|
||||||
except:
|
except FileNotFoundError:
|
||||||
self.state = {}
|
self.state = {}
|
||||||
self.save_state()
|
self.save_state()
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
with open(STATE_FILE, 'w') as f:
|
with open(STATE_FILE, 'w', encoding='utf-8') as f:
|
||||||
json.dump(self.state, f)
|
json.dump(self.state, f)
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
print(f"logged in as {self.user}", file=sys.stderr)
|
print(f"logged in as {self.user}", file=sys.stderr)
|
||||||
|
await self.change_presence(activity=discord.Game('the dueling game, for keeps'))
|
||||||
for guild in self.guilds:
|
for guild in self.guilds:
|
||||||
await self.initialize_elevator(guild)
|
await self.initialize_elevator(guild)
|
||||||
|
|
||||||
|
@ -51,8 +72,12 @@ class MikageClient(discord.Client):
|
||||||
if guild_id in self.state and guild.get_channel(self.state[guild_id]['channel']) is not None:
|
if guild_id in self.state and guild.get_channel(self.state[guild_id]['channel']) is not None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
(top_category, channels) = guild.by_category()[0]
|
top_category = guild.by_category()[0][0]
|
||||||
channel = await guild.create_text_channel(CHANNEL_NAME, category=top_category)
|
channel = await guild.create_text_channel(
|
||||||
|
CHANNEL_NAME,
|
||||||
|
category=top_category,
|
||||||
|
topic="the he/they therapist - you know, the one who lives in the morgue in the basement of that building that doesn't exist? - is in"
|
||||||
|
)
|
||||||
await channel.move(category=top_category, beginning=True)
|
await channel.move(category=top_category, beginning=True)
|
||||||
self.state[guild_id] = {'channel': channel.id}
|
self.state[guild_id] = {'channel': channel.id}
|
||||||
self.save_state()
|
self.save_state()
|
||||||
|
@ -78,39 +103,34 @@ class MikageClient(discord.Client):
|
||||||
if category_index == 0 and channel_index == 0:
|
if category_index == 0 and channel_index == 0:
|
||||||
await try_send(message.channel, INITIAL_MESSAGE)
|
await try_send(message.channel, INITIAL_MESSAGE)
|
||||||
|
|
||||||
if channel_index == len(channels) - 1:
|
if category_index == len(categories) - 1:
|
||||||
if category_index == len(categories) - 1:
|
|
||||||
return
|
|
||||||
target_category = categories[category_index+1][0]
|
|
||||||
target_position = 0
|
|
||||||
else:
|
|
||||||
target_category = category
|
|
||||||
target_position = channel_index+1
|
|
||||||
try:
|
|
||||||
await message.channel.move(category=target_category, beginning=True, offset=target_position)
|
|
||||||
except discord.DiscordException as e:
|
|
||||||
print(f"error moving channel {message.channel} in guild {message.guild}: {e}", file=sys.stderr)
|
|
||||||
await try_send(message.channel, "Dammit, the elevator's stuck!")
|
|
||||||
return
|
return
|
||||||
|
if channel_index == len(channels) - 1:
|
||||||
if target_position == 0:
|
if category_index == len(categories) - 2:
|
||||||
await try_send(message.channel, BUTTERFLIES[int(category_index*len(BUTTERFLIES)/(len(categories)-1))])
|
await message.channel.move(category=categories[-1][0], end=True)
|
||||||
|
await try_send(message.channel, CRASH)
|
||||||
final_category_channels = text_only(message.guild.by_category()[-1][1])
|
await asyncio.sleep(TYPING_DELAY)
|
||||||
if len(final_category_channels) > 0 and message.channel == final_category_channels[-1]:
|
await try_send(message.channel, FINAL_MESSAGE)
|
||||||
await try_send(message.channel, FINAL_MESSAGE)
|
await asyncio.sleep(TYPING_DELAY)
|
||||||
await try_send(message.channel, ROSE)
|
await try_send(message.channel, ROSE)
|
||||||
await asyncio.sleep(DELETION_SECONDS)
|
await asyncio.sleep(DELETION_SECONDS)
|
||||||
try:
|
try:
|
||||||
await message.channel.delete()
|
await message.channel.delete()
|
||||||
del self.state[guild_id]
|
del self.state[guild_id]
|
||||||
self.save_state()
|
self.save_state()
|
||||||
except discord.DiscordException as e:
|
except discord.DiscordException as e:
|
||||||
print(f"error deleting elevator for guild {message.guild}: {e}", file=sys.stderr)
|
print(f"error deleting elevator for guild {message.guild}: {e}", file=sys.stderr)
|
||||||
|
return
|
||||||
|
await self.initialize_elevator(message.guild)
|
||||||
return
|
return
|
||||||
await self.initialize_elevator(message.guild)
|
await try_move(message.channel, category=categories[category_index+1][0], beginning=True)
|
||||||
|
await try_send(message.channel, BUTTERFLIES[int(category_index*len(BUTTERFLIES)/(len(categories)-2))])
|
||||||
|
else:
|
||||||
|
await try_move(message.channel, category=category, beginning=True, offset=channel_index+1)
|
||||||
|
if random.randrange(ELEVATOR_SHIT_PERIOD) == 0:
|
||||||
|
await try_send(message.channel, random.choice(ELEVATOR_SHIT))
|
||||||
|
|
||||||
client = MikageClient()
|
client = MikageClient()
|
||||||
with open('token') as f:
|
with open('token', encoding='utf-8') as f:
|
||||||
token = f.read().strip()
|
token = f.read().strip()
|
||||||
client.run(token)
|
client.run(token)
|
||||||
|
|
Loading…
Reference in a new issue