-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
2415 lines (2003 loc) · 97.5 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import discord
import subprocess
import time
import asyncio
import discord.state
import json
import random
import os
import sys
import logging
import aiohttp
import aiofiles
import re
import yaml
import psutil
from discord.ext import commands
from discord.ui import View, Button, Select
from discord import Interaction
from datetime import datetime, timedelta, timezone
from dotenv import load_dotenv
from urllib.parse import urlencode
from filelock import FileLock
from omikuji import draw_lots
from responses import food_responses, death_responses, life_death_responses, self_responses, friend_responses, maid_responses, mistress_responses, reimu_responses, get_random_response
from decimal import Decimal, ROUND_DOWN
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN_MAIN_BOT')
AUTHOR_ID = int(os.getenv('AUTHOR_ID', 0))
LOG_FILE_PATH = "feedback_log.txt"
WORK_COOLDOWN_SECONDS = 230
if not TOKEN or not AUTHOR_ID:
raise ValueError("缺少必要的環境變量 DISCORD_TOKEN_MAIN_BOT 或 AUTHOR_ID")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(filename='main-error.log', encoding='utf-8', mode='w'),
logging.StreamHandler()
]
)
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.members = True
bot = commands.Bot(command_prefix='!', intents=intents)
start_time = time.time()
def load_yaml(file_name, default=None):
if default is None:
default = {}
"""通用 YAML 文件加載函數"""
try:
with open(file_name, 'r', encoding='utf-8') as f:
return yaml.safe_load(f) or default
except FileNotFoundError:
print(f"{file_name} 文件未找到。")
return default
except yaml.YAMLError as e:
print(f"{file_name} 加載錯誤: {e}")
return default
def save_yaml(file_name, data):
"""通用 YAML 文件保存函數"""
with open(file_name, 'w', encoding='utf-8') as f:
yaml.dump(data, f, allow_unicode=True)
def load_json(file_name, default=None):
if default is None:
default = {}
"""通用 JSON 文件加載函數"""
try:
with open(file_name, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"{file_name} 加載錯誤: {e}")
return default
def save_json(file_name, data):
"""通用 JSON 文件保存函數"""
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4)
user_balance = load_yaml('balance.yml')
config = load_json("config.json")
user_data = load_yaml("config_user.yml")
quiz_data = load_yaml('quiz.yml')
raw_jobs = config.get("jobs", [])
jobs_data = {job: details for item in raw_jobs for job, details in item.items()}
fish_data = config.get("fish", {})
shop_data = config.get("shop_item", {})
if not jobs_data:
print("警告: 職業數據 (jobs) 為空!請檢查 config.json 文件。")
if not fish_data:
print("警告: 魚類數據 (fish) 為空!請檢查 config.json 文件。")
if not shop_data:
print("警告: 商店數據 (shop_item) 為空!請檢查 config.json 文件。")
dm_messages = load_json('dm_messages.json')
questions = load_yaml('trivia_questions.yml', {}).get('questions', [])
user_rod = load_yaml('user_rod.yml', {})
if not os.path.exists('user_rod.yml'):
save_yaml('user_rod.yml', {})
def get_random_question():
return random.choice(questions) if questions else None
cooldowns = {}
active_giveaways = {}
@bot.event
async def on_message(message):
global last_activity_time
if message.author == bot.user:
return
if message.webhook_id:
return
content = message.content
if '關於機器人幽幽子' in message.content.lower():
await message.channel.send('幽幽子的創建時間是<t:1623245700:D>')
if '關於製作者' in message.content.lower():
await message.channel.send('製作者是個很好的人 雖然看上有有點怪怪的')
if '幽幽子的生日' in message.content.lower():
await message.channel.send('機器人幽幽子的生日在<t:1623245700:D>')
if message.content.startswith('關閉幽幽子'):
if message.author.id == AUTHOR_ID:
await message.channel.send("正在關閉...")
await asyncio.sleep(2)
await bot.close()
return
else:
await message.channel.send("你無權關閉我 >_< ")
return
elif message.content.startswith('重啓幽幽子'):
if message.author.id == AUTHOR_ID:
await message.channel.send("正在重啟幽幽子...")
subprocess.Popen([sys.executable, os.path.abspath(__file__)])
await bot.close()
return
else:
await message.channel.send("你無權重啓我 >_< ")
return
if '幽幽子待機多久了' in message.content.lower():
current_time = time.time()
idle_seconds = current_time - last_activity_time
idle_minutes = idle_seconds / 60
idle_hours = idle_seconds / 3600
idle_days = idle_seconds / 86400
if idle_days >= 1:
await message.channel.send(f'幽幽子目前已待機了 **{idle_days:.2f} 天**')
elif idle_hours >= 1:
await message.channel.send(f'幽幽子目前已待機了 **{idle_hours:.2f} 小时**')
else:
await message.channel.send(f'幽幽子目前已待機了 **{idle_minutes:.2f} 分钟**')
if isinstance(message.channel, discord.DMChannel):
user_id = str(message.author.id)
dm_messages = load_json('dm_messages.json', {})
if user_id not in dm_messages:
dm_messages[user_id] = []
dm_messages[user_id].append({
'content': message.content,
'timestamp': message.created_at.isoformat()
})
save_json('dm_messages.json', dm_messages)
print(f"Message from {message.author}: {message.content}")
if 'これが最後の一撃だ!名に恥じぬ、ザ・ワールド、時よ止まれ!' in message.content.lower():
await message.channel.send('ザ・ワールド\nhttps://tenor.com/view/the-world-gif-18508433')
await asyncio.sleep(1)
await message.channel.send('一秒経過だ!')
await asyncio.sleep(3)
await message.channel.send('二秒経過だ、三秒経過だ!')
await asyncio.sleep(4)
await message.channel.send('四秒経過だ!')
await asyncio.sleep(5)
await message.channel.send('五秒経過だ!')
await asyncio.sleep(6)
await message.channel.send('六秒経過だ!')
await asyncio.sleep(7)
await message.channel.send('七秒経過した!')
await asyncio.sleep(8)
await message.channel.send('ジョジョよ、**私のローラー**!\nhttps://tenor.com/view/dio-roada-rolla-da-dio-brando-dio-dio-jojo-dio-part3-gif-16062047')
await asyncio.sleep(9)
await message.channel.send('遅い!逃げられないぞ!\nhttps://tenor.com/view/dio-jojo-gif-13742432')
if '星爆氣流斬' in message.content.lower():
await message.channel.send('アスナ!クライン!')
await message.channel.send('**頼む、十秒だけ持ち堪えてくれ!**')
await asyncio.sleep(2)
await message.channel.send('スイッチ!')
await asyncio.sleep(10)
await message.channel.send('# スターバースト ストリーム!')
await asyncio.sleep(5)
await message.channel.send('**速く…もっと速く!!**')
await asyncio.sleep(15)
await message.channel.send('終わった…のか?')
if '關於食物' in content:
await message.channel.send(get_random_response(food_responses))
elif '對於死亡' in content:
await message.channel.send(get_random_response(death_responses))
elif '對於生死' in content:
await message.channel.send(get_random_response(life_death_responses))
elif '關於幽幽子' in content:
await message.channel.send(get_random_response(self_responses))
elif '幽幽子的朋友' in content:
await message.channel.send(get_random_response(friend_responses))
elif '關於紅魔館的女僕' in content:
await message.channel.send(get_random_response(maid_responses))
elif '關於紅魔舘的大小姐和二小姐' in content:
await message.channel.send(get_random_response(mistress_responses))
elif '關於神社的巫女' in content:
await message.channel.send(get_random_response(reimu_responses))
if '吃蛋糕嗎' in message.content:
await message.channel.send(f'蛋糕?! 在哪在哪?')
await asyncio.sleep(3)
await message.channel.send(f'妖夢 蛋糕在哪裏?')
await asyncio.sleep(3)
await message.channel.send(f'原來是個夢呀')
if '吃三色糰子嗎' in message.content:
await message.channel.send(f'三色糰子啊,以前妖夢...')
await asyncio.sleep(3)
await message.channel.send(f'...')
await asyncio.sleep(3)
await message.channel.send(f'算了 妖夢不在 我就算不吃東西 反正我是餓不死的存在')
await asyncio.sleep(3)
await message.channel.send(f'... 妖夢...你在哪...我好想你...')
await asyncio.sleep(3)
await message.channel.send(f'To be continued...\n-# 妖夢機器人即將到來')
if message.content == "早安":
if message.author.id == AUTHOR_ID:
await message.channel.send("早安 主人 今日的開發目標順利嗎")
else:
await message.reply("早上好 今天有什麽事情儘早完成喲", mention_author=False)
if message.content == "午安":
if message.author.id == AUTHOR_ID:
await message.channel.send("下午好呀 今天似乎沒有什麽事情可以做呢")
else:
await message.reply("中午好啊 看起來汝似乎無所事事的呢", mention_author=False)
if message.content == "晚安":
current_time = datetime.now().strftime("%H:%M")
if message.author.id == AUTHOR_ID:
await message.channel.send(f"你趕快去睡覺 現在已經是 {current_time} 了 別再熬夜了!")
else:
await message.reply(f"現在的時間是 {current_time} 汝還不就寢嗎?", mention_author=False)
if '閉嘴蜘蛛俠' in message.content:
await message.channel.send(f'deadpool:This is Deadpool 2, not Titanic! Stop serenading me, Celine!')
await asyncio.sleep(3)
await message.channel.send(f'deadpool:You’re singing way too good, can you sing it like crap for me?!')
await asyncio.sleep(3)
await message.channel.send(f'Celine Dion:Shut up, Spider-Man!')
await asyncio.sleep(3)
await message.channel.send(f'deadpool:sh*t, I really should have gone with NSYNC!')
if '普奇神父' in message.content:
await message.channel.send(f"你相信引力嗎?")
await asyncio.sleep(3)
await message.channel.send(f"我很敬佩第一個吃蘑菇的人,説不定是毒蘑菇呢")
await asyncio.sleep(5)
await message.channel.send(f"DIO")
await asyncio.sleep(2)
await message.channel.send(f"等我得心應手后,我一定會讓你覺醒的")
await asyncio.sleep(5)
await message.channel.send(f"人...終是要上天堂的.")
await asyncio.sleep(3)
await message.channel.send(f"最後再説一遍 時間要開始加速了,下來吧")
await asyncio.sleep(1)
await message.channel.send(f"螺旋阶梯、独角仙、废墟街道、无花果塔、德蕾莎之道、特异点、乔托、天使、绣球花、秘密皇帝。")
await asyncio.sleep(2)
await message.channel.send(f"話已至此,")
await message.channel.send(f"# Made in Heaven!!")
if '關於停雲' in message.content:
await message.channel.send(f"停雲小姐呀")
await asyncio.sleep(3)
await message.channel.send(f"我記的是一位叫yan的開發者製作的一個discord bot 吧~")
await asyncio.sleep(3)
await message.channel.send(f"汝 是否是想説 “我爲何知道的呢” 呵呵")
await asyncio.sleep(3)
await message.channel.send(f"那是我的主人告訴我滴喲~ 欸嘿~")
await bot.process_commands(message)
@bot.event
async def on_ready():
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
print("------")
print("斜線指令已自動同步。")
try:
await bot.change_presence(
status=discord.Status.idle,
activity=discord.Activity(type=discord.ActivityType.playing, name='蔚藍檔案')
)
print("已設置機器人的狀態。")
except Exception as e:
print(f"Failed to set presence: {e}")
end_time = time.time()
startup_time = end_time - start_time
print(f'Bot startup time: {startup_time:.2f} seconds')
print('加入的伺服器列表:')
for guild in bot.guilds:
print(f'- {guild.name} (ID: {guild.id})')
global last_activity_time
last_activity_time = time.time()
@bot.slash_command(name="invite", description="生成机器人的邀请链接")
async def invite(ctx: discord.ApplicationContext):
if not bot.user:
await ctx.respond(
"抱歉,无法生成邀请链接,机器人尚未正确启动。",
ephemeral=True
)
return
client_id = bot.user.id
permissions = discord.Permissions(
manage_channels=True,
manage_roles=True,
ban_members=True,
kick_members=True
)
query = {
"client_id": client_id,
"permissions": permissions,
"scope": "bot applications.commands"
}
invite_url = f"https://discord.com/oauth2/authorize?{urlencode(query)}"
embed = discord.Embed(
title="邀请 幽幽子 到你的服务器",
description=(
"探索与幽幽子的专属互动,感受她的优雅与神秘。\n"
f"✨ [点击这里邀请幽幽子]({invite_url}) ✨"
),
color=discord.Color.purple()
)
if bot.user.avatar:
embed.set_thumbnail(url=bot.user.display_avatar.url)
embed.set_footer(text="感谢您的支持,让幽幽子加入您的服务器!")
await ctx.respond(embed=embed)
@bot.slash_command(name="about-me", description="關於機器人")
async def about_me(ctx: discord.ApplicationContext):
if not bot.user:
await ctx.respond(
"抱歉,無法提供關於機器人的資訊,目前機器人尚未正確啟動。",
ephemeral=True
)
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
embed = discord.Embed(
title="關於我",
description=(
"早上好,用戶!\n\n"
"我是幽幽子機器人 \n"
"你可以使用 `/` 來查看我的指令。\n"
"同時,你也可以使用 `/help` 來獲取更詳細的幫助。\n\n"
"不過,如果你想知道我是用什麼庫製作的話...... 不告訴你 "
),
color=discord.Color.from_rgb(255, 182, 193)
)
if bot.user.avatar:
embed.set_thumbnail(url=bot.user.display_avatar.url)
embed.set_footer(text=f"{now}")
await ctx.respond(embed=embed)
@bot.slash_command(name="blackjack", description="開啟21點遊戲")
async def blackjack(ctx: discord.ApplicationContext, bet: float):
bet = round(bet, 2)
user_id = str(ctx.author.id)
guild_id = str(ctx.guild.id)
def load_yaml(file):
try:
with open(file, "r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
except FileNotFoundError:
return {}
def save_yaml(file, data):
with open(file, "w", encoding="utf-8") as f:
yaml.safe_dump(data, f, allow_unicode=True)
config = load_yaml("config_user.yml")
balance = load_yaml("balance.yml")
player_job = config.get(user_id, {}).get("job", "")
user_balance = round(balance.get(guild_id, {}).get(user_id, 0), 2)
if user_balance < bet:
await ctx.respond(embed=discord.Embed(
title="餘額不足!",
description=f"你當前的餘額是 {user_balance:.2f} 幽靈幣,無法下注 {bet:.2f} 幽靈幣。",
color=discord.Color.red()
))
return
if bet > 100_000_000.00:
embed = discord.Embed(
title="高額賭注!",
description="你的賭注超過 100,000,000。你是否要購買保險?\n\n**保險金額為一半的賭注金額**,如果莊家有Blackjack,你將獲得2倍保險金額作為賠付。",
color=discord.Color.gold()
)
view = discord.ui.View()
class InsuranceButtons(discord.ui.View):
def __init__(self):
super().__init__(timeout=30)
self.insurance = False
@discord.ui.button(label="購買保險", style=discord.ButtonStyle.success)
async def buy_insurance(self, button: discord.ui.Button, interaction: discord.Interaction):
self.insurance = True
self.stop()
@discord.ui.button(label="放棄保險", style=discord.ButtonStyle.danger)
async def decline_insurance(self, button: discord.ui.Button, interaction: discord.Interaction):
self.insurance = False
self.stop()
view = InsuranceButtons()
await ctx.respond(embed=embed, view=view)
await view.wait()
insurance_bought = view.insurance
else:
insurance_bought = False
# 從餘額中扣除下注金額
balance.setdefault(guild_id, {})[user_id] = round(user_balance - bet, 2)
save_yaml("balance.yml", balance)
def create_deck():
return [2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K", "A"] * 4
deck = create_deck()
random.shuffle(deck)
def draw_card():
"""從洗好的牌堆抽一張牌"""
return deck.pop()
def calculate_hand(cards):
"""計算手牌點數"""
value = 0
aces = 0
for card in cards:
if card in ["J", "Q", "K"]:
value += 10
elif card == "A":
aces += 1
value += 11
else:
value += card
while value > 21 and aces:
value -= 10
aces -= 1
return value
player_cards = [draw_card(), draw_card()]
dealer_cards = [draw_card(), draw_card()]
if insurance_bought and calculate_hand(dealer_cards) == 21:
insurance_payout = round(bet / 2 * 2, 2)
balance[guild_id][user_id] += insurance_payout
save_yaml("balance.yml", balance)
await ctx.respond(embed=discord.Embed(
title="保險賠付成功!",
description=f"莊家的手牌是 {dealer_cards},你獲得了保險賠付 {insurance_payout:.2f} 幽靈幣。",
color=discord.Color.gold()
))
return
embed = discord.Embed(
title="21點遊戲開始!",
description=(f"你下注了 **{bet:.2f} 幽靈幣**\n"
f"你的初始手牌: {player_cards} (總點數: {calculate_hand(player_cards)})\n"
f"莊家的明牌: {dealer_cards[0]}"),
color=discord.Color.from_rgb(204, 0, 51)
)
embed.set_footer(text="選擇你的操作!")
class BlackjackButtons(discord.ui.View):
def __init__(self):
super().__init__(timeout=120)
@discord.ui.button(label="抽牌 (Hit)", style=discord.ButtonStyle.primary)
async def hit(self, button: discord.ui.Button, interaction: discord.Interaction):
nonlocal player_cards
player_cards.append(draw_card())
player_total = calculate_hand(player_cards)
if player_total > 21:
embed = discord.Embed(
title="殘念,你爆了!",
description=f"你的手牌: {player_cards}\n點數總計: {player_total}",
color=discord.Color.from_rgb(204, 0, 51)
)
await interaction.response.edit_message(embed=embed, view=None)
self.stop()
else:
embed = discord.Embed(
title="你抽了一張牌!",
description=f"你的手牌: {player_cards}\n目前點數: {player_total}",
color=discord.Color.from_rgb(204, 0, 51)
)
await interaction.response.edit_message(embed=embed, view=self)
@discord.ui.button(label="停牌 (Stand)", style=discord.ButtonStyle.danger)
async def stand(self, button: discord.ui.Button, interaction: discord.Interaction):
dealer_total = calculate_hand(dealer_cards)
while dealer_total < 17:
dealer_cards.append(draw_card())
dealer_total = calculate_hand(dealer_cards)
player_total = calculate_hand(player_cards)
if dealer_total > 21 or player_total > dealer_total:
reward = round(bet * 2, 2)
if player_job == "賭徒":
reward += bet
reward *= 2
balance[guild_id][user_id] += reward
save_yaml("balance.yml", balance)
embed = discord.Embed(
title="恭賀,你贏了!",
description=f"莊家的手牌: {dealer_cards}\n你的獎勵: {reward:.2f} 幽靈幣",
color=discord.Color.gold()
)
else:
embed = discord.Embed(
title="殘念,莊家贏了!",
description=f"莊家的手牌: {dealer_cards}",
color=discord.Color.from_rgb(204, 0, 51)
)
await interaction.response.edit_message(embed=embed, view=None)
self.stop()
@discord.ui.button(label="雙倍下注 (Double Down)", style=discord.ButtonStyle.success)
async def double_down(self, button: discord.ui.Button, interaction: discord.Interaction):
nonlocal player_cards
nonlocal bet
if balance[guild_id][user_id] < bet: # 確保玩家有足夠的錢翻倍
await interaction.response.send_message("餘額不足,無法雙倍下注!", ephemeral=True)
return
bet *= 2
bet = round(bet, 2)
balance[guild_id][user_id] -= bet // 2 # 立即扣除額外下注
save_yaml("balance.yml", balance)
player_cards.append(draw_card())
player_total = calculate_hand(player_cards)
if player_total > 21:
embed = discord.Embed(
title="殘念,你爆了!",
description=f"你的手牌: {player_cards}\n點數總計: {player_total}",
color=discord.Color.from_rgb(204, 0, 51)
)
else:
dealer_total = calculate_hand(dealer_cards)
while dealer_total < 17:
dealer_cards.append(draw_card())
dealer_total = calculate_hand(dealer_cards)
if dealer_total > 21 or player_total > dealer_total:
reward = round(bet * 2, 2)
if player_job == "賭徒":
reward += bet
reward *= 2
balance[guild_id][user_id] += reward
save_yaml("balance.yml", balance)
embed = discord.Embed(
title="恭賀,你贏了!",
description=f"你的手牌: {player_cards}\n莊家的手牌: {dealer_cards}\n你的獎勵: {reward:.2f} 幽靈幣",
color=discord.Color.gold()
)
else:
embed = discord.Embed(
title="殘念,莊家贏了!",
description=f"你的手牌: {player_cards}\n莊家的手牌: {dealer_cards}",
color=discord.Color.from_rgb(204, 0, 51)
)
await interaction.response.edit_message(embed=embed, view=None)
self.stop()
await ctx.respond(embed=embed, view=BlackjackButtons())
@bot.slash_command(name="balance", description="查询用户余额")
async def balance(ctx: discord.ApplicationContext):
try:
user_balance = load_yaml("balance.yml")
guild_id = str(ctx.guild.id)
user_id = str(ctx.user.id)
if guild_id not in user_balance:
user_balance[guild_id] = {}
balance = user_balance[guild_id].get(user_id, 0)
embed = discord.Embed(
title="💰 幽靈幣餘額查詢",
description=(
f"**{ctx.user.display_name}** 在此群组的幽靈幣餘額为:\n\n"
f"**{balance} 幽靈幣**"
),
color=discord.Color.from_rgb(219, 112, 147)
)
embed.set_footer(text="感谢使用幽靈幣系統!")
await ctx.respond(embed=embed)
except Exception as e:
logging.error(f"Unexpected error in balance command: {e}")
await ctx.respond(f"發生錯誤:{e}", ephemeral=True)
@bot.slash_command(name="balance_top", description="查看幽靈幣排行榜")
async def balance_top(interaction: discord.Interaction):
try:
if not interaction.guild:
await interaction.response.send_message("此命令只能在伺服器中使用。", ephemeral=True)
return
await interaction.response.defer()
try:
with open('balance.yml', 'r', encoding='utf-8') as file:
balance_data = yaml.safe_load(file) or {}
except FileNotFoundError:
await interaction.followup.send("找不到 balance.yml 文件。", ephemeral=True)
logging.error("找不到 balance.yml 文件。")
return
except yaml.YAMLError as yaml_error:
await interaction.followup.send("讀取 balance.yml 時發生錯誤。", ephemeral=True)
logging.error(f"讀取 balance.yml 時發生錯誤: {yaml_error}")
return
guild_id = str(interaction.guild.id)
if guild_id not in balance_data or not balance_data[guild_id]:
await interaction.followup.send("目前沒有排行榜數據。", ephemeral=True)
return
guild_balances = balance_data[guild_id]
sorted_balances = sorted(guild_balances.items(), key=lambda x: x[1], reverse=True)
leaderboard = []
for index, (user_id, balance) in enumerate(sorted_balances[:10], start=1):
try:
member = interaction.guild.get_member(int(user_id))
if member:
username = member.display_name
else:
user = await bot.fetch_user(int(user_id))
username = user.name if user else f"未知用戶(ID: {user_id})"
except Exception as fetch_error:
logging.error(f"無法獲取用戶 {user_id} 的名稱: {fetch_error}")
username = f"未知用戶(ID: {user_id})"
leaderboard.append(f"**#{index}** - {username}: {balance} 幽靈幣")
leaderboard_message = "\n".join(leaderboard)
embed = discord.Embed(
title="🏆 幽靈幣排行榜 🏆",
description=leaderboard_message or "排行榜數據為空。",
color=discord.Color.from_rgb(255, 182, 193)
)
embed.set_footer(text="排行榜僅顯示前 10 名")
await interaction.followup.send(embed=embed)
except Exception as e:
await interaction.followup.send("執行命令時發生未預期的錯誤,請稍後再試。", ephemeral=True)
logging.error(f"執行命令時發生錯誤: {e}")
@bot.slash_command(name="shop", description="查看商店中的商品列表")
async def shop(ctx: discord.ApplicationContext):
guild_id = str(ctx.guild.id)
user_id = str(ctx.author.id)
if not shop_data:
await ctx.respond("商店數據加載失敗,請使用**`/feedback`**指令回報問題!", ephemeral=True)
return
options = [
discord.SelectOption(
label=item["name"],
description=f"價格: {item['price']} + 稅: {item['tax']}, MP: {item['MP']}",
value=item["name"]
)
for item in shop_data
]
select_menu = Select(
placeholder="選擇一件商品",
options=options,
min_values=1,
max_values=1
)
async def select_callback(interaction: discord.Interaction):
if interaction.user.id != ctx.author.id:
await interaction.response.send_message("這不是你的選擇!", ephemeral=True)
return
selected_item_name = select_menu.values[0]
selected_item = next(
(item for item in shop_data if item["name"] == selected_item_name), None
)
if selected_item:
total_price = selected_item["price"] + selected_item["tax"]
embed = discord.Embed(
title="購買確認",
description=(f"您選擇了 {selected_item_name}。\n"
f"價格: {selected_item['price']} 幽靈幣\n"
f"稅金: {selected_item['tax']} 幽靈幣\n"
f"心理壓力 (MP): {selected_item['MP']}\n"
f"總價格: {total_price} 幽靈幣"),
color=discord.Color.green()
)
confirm_button = Button(label="確認購買", style=discord.ButtonStyle.success)
cancel_button = Button(label="取消", style=discord.ButtonStyle.danger)
async def confirm_callback(interaction: discord.Interaction):
if interaction.user.id != ctx.author.id:
await interaction.response.send_message("這不是你的選擇!", ephemeral=True)
return
user_balance = load_yaml('balance.yml')
user_balance.setdefault(guild_id, {})
user_balance[guild_id].setdefault(user_id, 0)
current_balance = user_balance[guild_id][user_id]
if current_balance >= total_price:
user_balance[guild_id][user_id] -= total_price
save_yaml('balance.yml', user_balance)
user_data = load_yaml('config_user.yml')
user_data.setdefault(guild_id, {})
user_data[guild_id].setdefault(user_id, {"MP": 100})
user_data[guild_id][user_id]["MP"] = max(
0, user_data[guild_id][user_id]["MP"] - selected_item["MP"]
)
save_yaml('config_user.yml', user_data)
effect_message = (
f"您使用了 {selected_item_name},心理壓力(MP)减少了 {selected_item['MP']} 点!\n"
f"當前心理壓力(MP):{user_data[guild_id][user_id]['MP']} 点。"
)
await interaction.response.edit_message(
content=f"購買成功!已扣除 {total_price} 幽靈幣。\n{effect_message}",
embed=None,
view=None
)
else:
await interaction.response.edit_message(
content="餘額不足,無法完成購買!", embed=None, view=None
)
async def cancel_callback(interaction: discord.Interaction):
if interaction.user.id != ctx.author.id:
await interaction.response.send_message("這不是你的選擇!", ephemeral=True)
return
await interaction.response.edit_message(
content="購買已取消!", embed=None, view=None
)
confirm_button.callback = confirm_callback
cancel_button.callback = cancel_callback
view = View()
view.add_item(confirm_button)
view.add_item(cancel_button)
await interaction.response.edit_message(embed=embed, view=view)
select_menu.callback = select_callback
embed = discord.Embed(
title="商店",
description="選擇想購買的商品:",
color=discord.Color.blue()
)
embed.set_footer(text="感謝您的光臨!")
view = View()
view.add_item(select_menu)
await ctx.respond(embed=embed, view=view, ephemeral=False)
@bot.slash_command(name="choose_job", description="選擇你的工作!")
async def choose_job(ctx: discord.ApplicationContext):
guild_id = str(ctx.guild.id)
user_id = str(ctx.user.id)
if guild_id in user_data and user_id in user_data[guild_id]:
current_job = user_data[guild_id][user_id].get("job")
if current_job:
embed = discord.Embed(
title="職業選擇",
description=f"你已經有職業了!你現在的是 **{current_job}**。",
color=discord.Color.blue()
)
await ctx.respond(embed=embed, ephemeral=True)
return
if not jobs_data or not isinstance(jobs_data, dict):
embed = discord.Embed(
title="錯誤",
description="職業數據尚未正確配置,請使用 **`/feedback`** 指令回報錯誤!",
color=discord.Color.red()
)
await ctx.respond(embed=embed, ephemeral=True)
return
class JobSelect(discord.ui.Select):
def __init__(self):
it_count = sum(
1 for u_id, u_info in user_data.get(guild_id, {}).items()
if u_info.get("job") == "IT程序員"
)
options = []
for job, data in jobs_data.items():
if isinstance(data, dict) and "min" in data and "max" in data:
if job == "IT程序員" and it_count >= 2: # 針對 IT程序員 檢查當前群組是否已滿
options.append(discord.SelectOption(
label=f" {job} ",
description=f"{data['min']}-{data['max']}幽靈幣 (已滿員)",
value=f"{job}_disabled",
emoji="❌"
))
else:
options.append(discord.SelectOption(
label=f" {job} ",
description=f"{data['min']}-{data['max']}幽靈幣",
value=job
))
super().__init__(
placeholder="選擇你的工作...",
options=options,
min_values=1,
max_values=1,
)
async def callback(self, interaction: discord.Interaction):
if interaction.user.id != ctx.user.id:
await interaction.response.send_message("這不是你的選擇!", ephemeral=True)
return
chosen_job = self.values[0]
if "_disabled" in chosen_job:
await interaction.response.send_message("該職業已滿員,請選擇其他職業!", ephemeral=True)
return
if guild_id not in user_data:
user_data[guild_id] = {}
if user_id not in user_data[guild_id]:
user_data[guild_id][user_id] = {}
user_info = user_data[guild_id][user_id]
work_cooldown = user_info.get("work_cooldown", None)
user_info["job"] = chosen_job
if work_cooldown is not None:
user_info["work_cooldown"] = work_cooldown
else:
user_info["work_cooldown"] = None
save_yaml("config_user.yml", user_data)
for child in self.view.children:
child.disabled = True
embed = discord.Embed(
title="職業選擇成功",
description=f"你選擇了 **{chosen_job}** 作為你的工作!🎉",
color=discord.Color.green()
)
await interaction.response.edit_message(embed=embed, view=self.view)
class JobView(discord.ui.View):
def __init__(self):
super().__init__(timeout=60)
self.add_item(JobSelect())
async def on_timeout(self):
for child in self.children:
child.disabled = True
embed = discord.Embed(
title="選擇超時",
description="選擇已超時,請重新使用指令!",
color=discord.Color.orange()
)
await self.message.edit(embed=embed, view=self)
view = JobView()
embed = discord.Embed(
title="選擇你的職業",
description="請從下方選擇你的工作:",
color=discord.Color.blurple()
)
message = await ctx.respond(embed=embed, view=view)
view.message = await message.original_message()
@bot.slash_command(name="reset_job", description="重置職業")
async def reset_job(ctx):
guild_id = str(ctx.guild.id)
user_id = str(ctx.author.id)
group_data = user_data.get(guild_id, {})
user_info = group_data.get(user_id, {})
current_job = user_info.get("job", "無職業")
embed = discord.Embed(
title="職業重置確認",
description=f"你當前的職業是:`{current_job}`\n\n確定要放棄現有職業嗎?",
color=discord.Color.orange()
)
embed.set_footer(text="請選擇 Yes 或 No")
class ConfirmReset(discord.ui.View):
def __init__(self):
super().__init__()
@discord.ui.button(label="Yes", style=discord.ButtonStyle.green)
async def confirm(self, button: discord.ui.Button, interaction: discord.Interaction):
if interaction.user != ctx.author: