Dumb April Fools Bot for Touhou Project Discord in 2021
1import asyncio
2import json
3import random
4import discord
5import os
6import csv
7import collections
8from discord.ext import commands
9
10IMPORT_DATA = "roles.csv"
11OUTPUT_DATA = "data.json"
12BASE_COST = 100000
13COST_GROWTH = 2.50
14PAYOUT_MIN = 50000
15PAYOUT_MAX = 75000
16
17bot = commands.Bot(
18 max_messages=None,
19 command_prefix='!',
20 member_cache_flags=discord.MemberCacheFlags.all(),
21 intents=discord.Intents.all())
22
23with open(OUTPUT_DATA, "r+") as f:
24 data = json.load(f)
25
26def save_data(data):
27 with open(OUTPUT_DATA, "w") as f:
28 json.dump(data, f)
29
30@bot.event
31async def on_ready():
32 print("Bot ready!")
33 print(f"Logged in as {bot.user}!")
34
35async def get_confirmation(bot, check):
36 decided = None
37 while decided is None:
38 msg = await bot.wait_for('message', check=check, timeout=30)
39 decided = {
40 'y': True,
41 'yes': True,
42 'n': False,
43 'no': False,
44 }.get(msg.content.lower())
45 return decided
46
47@bot.listen()
48async def on_message(message):
49 if message.author.bot or message.channel.id == 372835728574382090:
50 return
51 global BASE_COST, PAYOUT_MIN, PAYOUT_MAX, data
52 cnt = message.content
53 if any(cnt.startswith(a) for a in ['!', '~', '%', '\'']):
54 return
55 key = str(message.author.id)
56 payout = random.randint(PAYOUT_MIN, PAYOUT_MAX)
57 if key in data['users']:
58 data['users'][key]['money'] += payout
59 else:
60 data['users'][key] = {
61 'money': payout,
62 'next_roll': BASE_COST,
63 }
64 save_data(data)
65 print(f"Gave ${payout} to {message.author}")
66
67# @bot.command(name="import")
68# async def import_roles(ctx):
69 # """Imports all of the preplanned roles into the server."""
70 # global data
71 # with open(IMPORT_DATA, 'r') as f:
72 # reader = csv.reader(f)
73 # for row in reader:
74 # print(row)
75 # name = row[0]
76 # weight = int(row[2])
77 # lore = row[5]
78 # if row[1]:
79 # color = int(row[1][1:], 16)
80 # else:
81 # color = discord.Colour.default()
82 # if 'roles' not in data:
83 # data['roles'] = {}
84 # if any(r['name'] == name for r in data['roles'].values()):
85 # print("Skipping ", name, " already made.")
86 # continue
87 # role = await ctx.guild.create_role(
88 # name=name,
89 # color=color,
90 # permissions=discord.Permissions.none(),
91 # mentionable=False,
92 # hoist=False)
93
94 # data['roles'][role.id] = {
95 # 'id': role.id,
96 # 'name': name,
97 # 'weight': weight,
98 # 'lore': lore
99 # }
100 # save_data(data)
101 # print("Created role ", name)
102
103@bot.command()
104async def gacha(ctx):
105 """Pays money to randomly get one of the roles in the bot."""
106 global data
107 user = data['users'].get(str(ctx.author.id))
108 if user is None:
109 await ctx.send(f"Sorry {ctx.author.mention}, I can't give credit! "
110 f"Come back when you are a little, mmmmmmm, RICHER! "
111 f" (Wallet: 0, Roll Cost: **${BASE_COST})**")
112 return
113 elif user['money'] < user['next_roll']:
114 await ctx.send(f"Sorry {ctx.author.mention}, I can't give credit! "
115 f"Come back when you are a little, mmmmmmm, RICHER! "
116 f" (Wallet: {user['money']}, Roll Cost: **${user['next_roll']})**")
117 return
118 user['money'] -= user['next_roll']
119 total_weight = sum(r['weight'] for r in data['roles'].values())
120 target_weight = random.randint(0, total_weight)
121 role = None
122 for gacha_role in data['roles'].values():
123 target_weight -= gacha_role['weight']
124 if target_weight < 0:
125 role = ctx.guild.get_role(int(gacha_role['id']))
126 break
127 if role in ctx.author.roles:
128 await ctx.send(f"You rolled {role.mention}, but you already have it!"
129 f"Your next gacha roll will cost "
130 f"**${user['next_roll']}**")
131 else:
132 next_cost = int(COST_GROWTH * user['next_roll'])
133 user['next_roll'] = next_cost
134 suffix = f"Your next gacha roll will cost: {next_cost}"
135 await ctx.author.add_roles(role)
136 await ctx.send(f"You rolled {role.mention}! " + suffix)
137 save_data(data)
138
139@bot.command()
140async def goomble(ctx, amount: int, multiplier: float):
141 """Stake your money on a life or death gamble!
142
143 Examples:
144 ~goomble 20000 5.0 => Bets 20,000 for a payout of -100,000 to 100,000
145 ~goomble 50000 1.0 => Bets 50,000 for a payout of -50,000 to 50,000
146
147 There is no limit to your multiplier.
148
149 YOU CAN GO INTO DEBT USING THIS. BEWARE.
150 ZAWA. ZAWA. ZAWA. ZAWA. ZAWA.
151 """
152 global data
153 if amount < 0:
154 await ctx.send(f"{ctx.author.mention}, cannot bet negative money!")
155 return
156 user = data['users'].get(str(ctx.author.id))
157 if user is None or user['money'] < 0:
158 await ctx.send(f"{ctx.author.mention}, you have no money!")
159 return
160 elif user['money'] < amount:
161 await ctx.send(f"{ctx.author.mention}, you don't have ${amount} to bet!")
162 return
163 elif multiplier < 5:
164 await ctx.send(f"{ctx.author.mention}, YOU WEAK WILLED FOOL. GAMBLE MORE!"
165 f"(Minimum Mulitplier 5x)")
166 return
167
168 base_range = random.random()
169 direction = random.choice([-1.0, 1.0])
170 delta = int(amount * direction * base_range * multiplier)
171 user['money'] = int(user['money'] + delta)
172 save_data(data)
173 await ctx.send(f"{ctx.author.mention}, you gambled {amount} and "
174 f"earned {delta}. You now have **${user['money']}**.")
175
176@bot.command()
177async def allin(ctx):
178 """Stake ALL money on a life or death gamble with a 10,000x multiplier!
179
180 YOU **WILL** GO INTO DEBT USING THIS. BEWARE.
181 ZAWA. ZAWA. ZAWA. ZAWA. ZAWA.
182 """
183 global data
184 user = data['users'].get(str(ctx.author.id))
185 if user is None or user['money'] < 0:
186 await ctx.send(f"{ctx.author.mention}, you have no money!")
187 return
188 await goomble(ctx, user['money'], 10000)
189
190@bot.command()
191async def shion(ctx):
192 """Burn all of your money."""
193 global data
194 user = data['users'].get(str(ctx.author.id))
195 if user is None:
196 await ctx.send("Sorry who are you?")
197 return
198 if user['money'] < 0:
199 await ctx.send("Shion doesn't want your debt!")
200 return
201 user['money'] = 0
202 save_data(data)
203 await ctx.send("Shion hungrily devours your wallet. You now have $0.")
204
205@bot.command(hidden=True)
206async def gachi(ctx):
207 await ctx.send("Fuck you. You probably meant !gacha.")
208
209@bot.command(hidden=True)
210async def fumo(ctx):
211 """Fumo."""
212 global data
213 user = data['users'].get(str(ctx.author.id))
214 if user is None:
215 await ctx.send("Sorry who are you?")
216 return
217 await ctx.send(f"You could buy {user['money'] / 40} fumos with your money.")
218
219@bot.command()
220async def wallet(ctx):
221 """Shows how much money you have."""
222 try:
223 key = str(ctx.author.id)
224 await ctx.send(f"You have ${data['users'][key]['money']}")
225 except:
226 await ctx.send(f"You have nothing. You are poor.")
227
228@bot.command()
229async def give(ctx, member: discord.Member, offer: discord.Role):
230 """Gives one role to another user."""
231 if offer not in ctx.author.roles:
232 await ctx.send(f"**{ctx.author.mention}** does not have {offer.mention}")
233 return
234 if str(offer.id) not in data['roles']:
235 await ctx.send(f"**{offer}** is not a gacha role.")
236 return
237
238 await ctx.send(f"{member.mention}, **{ctx.author}** would like to give you"
239 f"{offer.mention}. Do you accept? (y/yes/n/no)")
240
241 def check(msg):
242 return msg.channel == ctx.channel and msg.author == member
243 try:
244 decision = await get_confirmation(ctx.bot, check)
245 except asyncio.TimeoutError:
246 await ctx.send(f"{ctx.author.mention}: Exchange cancelled.")
247 return
248
249 if decision:
250 await asyncio.gather(*[
251 ctx.author.remove_roles(offer),
252 member.add_roles(offer),
253 ])
254 await ctx.send(":handshake: Exchange complete!")
255 else:
256 await ctx.send(":x: Exchange refused!")
257
258@bot.command()
259async def charity(ctx, member: discord.Member, amount: int):
260 """Gives another user money."""
261 if amount < 0:
262 await ctx.send(f"{ctx.author.mention}, DEBIT IS NOT CHARITY!")
263 return
264 global data, BASE_COST
265 src = data['users'].get(str(ctx.author.id))
266 dst = data['users'].get(str(member.id))
267
268 if src is None or src['money'] < amount:
269 await ctx.send(f"{ctx.author.mention}, you don't have **${amount}** to give!")
270 return
271
272 await ctx.send(f"{member.mention}, **{ctx.author}** would like to give you"
273 f"${amount}. Do you accept? (y/yes/n/no)")
274
275 def check(msg):
276 return msg.channel == ctx.channel and msg.author == member
277 try:
278 decision = await get_confirmation(ctx.bot, check)
279 except asyncio.TimeoutError:
280 await ctx.send(f"{ctx.author.mention}: Charity cancelled.")
281 return
282
283 src['money'] -= amount
284 if dst is not None:
285 dst['money'] += amount
286 else:
287 data['users'][str(member.id)] = {
288 'money': amount,
289 'next_roll': BASE_COST,
290 }
291 save_data(data)
292 await ctx.send(":handshake: Exchange complete!")
293
294@bot.command()
295async def sacrifice(ctx, role: discord.Role):
296 """Sacrifices roles to the gods."""
297 global data
298 if str(role.id) not in data['roles']:
299 await ctx.send(f"**{role}** is not a gacha role.")
300 return
301 await ctx.author.remove_roles(role)
302 await ctx.send(f"{ctx.author.mention}: Kanako ate your {role}.")
303
304@bot.command()
305async def lore(ctx, role: discord.Role):
306 """Shows the Touhou Discord History behind a role."""
307 global data
308 role_data = data['roles'].get(str(role.id))
309 if role_data is not None:
310 await ctx.send(f"{role.name}: {role_data['lore']}")
311 else:
312 await ctx.send("Lmao what lore?")
313
314@bot.command()
315async def trade(ctx, member: discord.Member,
316 offer: discord.Role,
317 exchange: discord.Role):
318 """Trades one role for another with another user."""
319 global data
320 if offer not in ctx.author.roles:
321 await ctx.send(f"**{member}** does not have {offer.mention}")
322 return
323 if offer not in ctx.author.roles:
324 await ctx.send(f"**{member}** does not have {offer.mention}")
325 return
326 if exchange not in member.roles:
327 await ctx.send(f"**{member}** does not have {exchange.mention}")
328 return
329 if str(offer.id) not in data['roles']:
330 await ctx.send(f"**{offer}** is not a gacha role.")
331 return
332 if str(exchange.id) not in data['roles']:
333 await ctx.send(f"**{exchange}** is not a gacha role.")
334 return
335
336 await ctx.send(f"{member.mention}, **{ctx.author}** would like to trade"
337 f"{offer.mention} for {exchange.mention}. Do you accept?"
338 f" (y/yes/n/no)")
339
340 def check(msg):
341 return msg.channel == ctx.channel and msg.author == member
342 try:
343 decision = await get_confirmation(ctx.bot, check)
344 except asyncio.TimeoutError:
345 await ctx.send(f"{ctx.author.mention}: Exchange cancelled.")
346 return
347
348 if decision:
349 await asyncio.gather(*[
350 ctx.author.add_roles(exchange),
351 ctx.author.remove_roles(offer),
352 member.add_roles(offer),
353 member.remove_roles(exchange)
354 ])
355 await ctx.send(":handshake: Exchange complete!")
356 else:
357 await ctx.send(":x: Exchange refused!")
358
359
360
361bot.run(os.environ["DISCORD_TOKEN"])