-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommands_bot.py
More file actions
867 lines (736 loc) · 49.1 KB
/
Copy pathcommands_bot.py
File metadata and controls
867 lines (736 loc) · 49.1 KB
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
"""
Coder : Omar
Version : v2.5.12B
version Date : 11 / 3 / 2026
Code Type : python | Discrod | GEMINI | HTTP | ASYNC
Title : Commands Code for Discord bot
WIN Interpreter : cPython v3.11.8 [Compiler : MSC v.1937 64 bit (AMD64)]
Linux Interpreter : Python 3.11.8 (main, Apr 9 2025, 17:28:17) [GCC 13.3.0]
"""
from init_bot import *
from utils_bot import ask_deepSeek, ask_gemini, ask_gpt, get_rand_greeting, prepare_discord_embed, prepare_quote
from utils_bot import check_msg, get_new_reply_prompt, YTDLSource, await_me_maybe
import keys
from typing import List
#------------------------------------------------------------------------------------------------------------------------------------------#
#disabled default global help message from bot constructor then override default by this command
slash_cmd_ok_msg = "OK ✅"
@bot.hybrid_command(name="help")
@commands.cooldown(1, 5)
async def help( ctx: commands.Context ):
"""
Displays the bot's help message with command information.
This command overrides the default help command and displays a custom help message
split into two parts. It includes a cooldown of 5 seconds between uses.
Args:
ctx (commands.Context): The context of the command invocation.
"""
async with ctx.typing():
bot_help_msg_p1: discord.Message = await ctx.reply(content= help_bot.override_help_msgP1)
await ctx.send(content= help_bot.override_help_msgP2)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="boringwizard", help= "wizy sends a random meme")
@commands.cooldown(1, 5)
async def boring( ctx: commands.Context ):
"""
Sends a random meme to the channel.
This command fetches and sends a random meme with a wizard-themed title.
Includes a cooldown of 5 seconds between uses.
Args:
ctx (commands.Context): The context of the command invocation.
"""
await ctx.send(embed= await pyrandmeme2(_title= "Some Wizardy Humor👻"))
#if used via slash command will not add reaction cuz it raises discord error msg not found
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="wizyawakened", help= "wizy wakes you up to the truth!")
@commands.cooldown(1, 5)
async def awake( ctx: commands.Context ):
"""
Posts content from r/Palestine subreddit.
This command fetches and posts content from the Palestine subreddit,
including handling video content if present. Includes a cooldown of 5 seconds.
Args:
ctx (commands.Context): The context of the command invocation.
"""
ps_post_get_task = await bot.loop.create_task(palestina_free(_title= ":flag_ps: *r/Palestine* :flag_ps:"))
ps_post_embed_is_video, ps_post_data, ps_post_url = await await_me_maybe(ps_post_get_task)
await util.final_send_special_ps(ctx, ps_post_embed_is_video, ps_post_data, ps_post_url)
#if used via slash command will not add reaction cuz it raises discord error msg not found
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="wisewiz", help= 'wizy sends a random Quote (quote of type 1 for now glitches slash cmd)')
async def wise( ctx: commands.Context):
"""
Sends a random inspirational quote.
This command fetches and sends a random quote. Note that type 1 quote API
is not async and may timeout with slash commands. Includes retry logic
with a limit of 10 attempts.
Args:
ctx (commands.Context): The context of the command invocation.
"""
quote_api = 1 if ctx.interaction else 0 #type 1 quote api is not async and timesout with slash commands
prepare_quote_task = bot.loop.create_task(prepare_quote(invoker= quote_api, retrylimit= 10)) #invoker == 0 cuases error if used by a slash command (probably cuz its not async)
quotes = await prepare_quote_task
await ctx.reply( embed= quotes ) if quote_api == 1 else await ctx.reply( quotes )
#------------------------------------------------------------------------------------------------------------------------------------------#
allowed_roles_togglerandom = {
"ULT! SAQF": 889532272989053019,
"ULT! ADMIN": 889931295365414993,
"ULT! CODER": 1014300476814147656,
"Spirits Overlord": 1118266805006389289
} #check by id not the names cuz they're missing emojies
allowed_roles_quotesz = {"ULT! SAQF": 889532272989053019,
"ULT! ADMIN": 889931295365414993,
"ULT! CODER": 1014300476814147656,
"Spirits Overlord": 1118266805006389289
} #check by id not the names cuz they're missing emojies
allowed_roles_change_ai = {"ULT! SAQF": 889532272989053019,
"ULT! ADMIN": 889931295365414993,
"ULT! CODER": 1014300476814147656,
"Land MASTERS": 890586687623798834,
"ISLE! Booster": 1013995284470169761,
"Spirits Overlord": 1118266805006389289
} #check by id not the names cuz they're missing emojies
@bot.hybrid_command(name="togglerandom", help= 'toggles & controls the auto meme-quote sender feature & feed channel of the bot wizy')
@commands.cooldown(1, 5)
async def toggle_rand_meme_quote_sender( ctx: commands.Context, state: int = None, interval_minutes: int = default_feed_channel_frequency_minutes ):
"""
Toggles and controls the automatic meme and quote sending feature.
This command allows authorized users to control the bot's automatic content
sending feature. It can be used to enable/disable the feature and set the
interval between sends.
Args:
ctx (commands.Context): The context of the command invocation.
state (int, optional): The state to set (0 for off, 1 for on). Defaults to None.
interval_minutes (int, optional): The interval between sends in minutes.
Defaults to default_feed_channel_frequency_minutes.
Note:
Only users with specific roles (ULT! SAQF, ULT! ADMIN, ULT! CODER, Spirits Overlord)
or administrators can use this command.
"""
global allowed_roles_togglerandom
user_comanded_roles : list[discord.Role] = ctx.message.author.roles
is_allowed = [role for role in user_comanded_roles if role.id in tuple(allowed_roles_togglerandom.values()) ]
is_allowed = [True for role in user_comanded_roles if role.permissions.administrator == True] #if admin override any prev. is_allowed value with True
if is_allowed == None or len(is_allowed) <= 0 :
allowed_ids = list(allowed_roles_togglerandom.values())
await ctx.message.delete(delay= 15.0) #delete user message it self ( then in .reply we delete bot msg also)
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False) ,
delete_after= 15.0 ,
content=f"Ops! __*only*__ _{' , '.join(map(lambda id : '<@&' + str(id) + '>' , allowed_ids))}_ or ADMINS! are allowed to use this command..."
) #used a join and map and lambda function just as fast fancy way to print all allowed roles
else:
await util.process_send_togglerandom_cmd(ctx= ctx, _state_wanted= state, _interval_minutes = interval_minutes)
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="wizyaimode", help= "choose between generative AI models used in wizy's chat channel: gpt / gemini / deep")
@commands.cooldown(1, 5)
@commands.check_any(commands.has_any_role(*list(allowed_roles_quotesz.values())), commands.has_permissions(administrator=True))
async def change_wizy_ai_type( ctx: commands.Context, ai_name:str = None ):
"""
Changes the AI model used in the wizard's chat channel.
This command allows authorized users to switch between different AI models
(GPT, Gemini, or DeepSeek) used for the bot's responses in the chat channel.
Args:
ctx (commands.Context): The context of the command invocation.
ai_name (str, optional): The name of the AI model to switch to.
Must be one of: 'gpt', 'gemini', or 'deep'. Defaults to None.
Note:
Only users with specific roles or administrators can use this command.
The command includes a cooldown of 5 seconds between uses.
"""
await util.process_send_change_wizy_ai_cmd(ctx= ctx, _ai_name= None if ai_name is None else ai_name.strip().lower())
@change_wizy_ai_type.error
async def change_wizy_ai_type_error(ctx: commands.Context , error):
print (f'\n\nTESTING#################E R R O R: {error}\n\n')
if isinstance(error , commands.MissingAnyRole):
allowed_ids = list(allowed_roles_change_ai.values())
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! __*only*__ _{' , '.join(map(lambda id : '<@&' + str(id) + '>' , allowed_ids))}_ or ADMINS ! are allowed to use this command..."
) #used a join and map and lambda function just as fast fancy way to print all allowed roles
elif isinstance(error , commands.MissingPermissions):
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! __*only*__ ADMINS ! are allowed to use this command..."
)
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="wizymusicgenre", help= "choose between local available music genras: lofi / mmochill / mmoanime / orsmix / nostalgia")
@commands.cooldown(1, 5)
# @commands.check_any(commands.has_any_role(*list(allowed_roles_quotesz.values())), commands.has_permissions(administrator=True))
async def change_wizy_music_genre( ctx: commands.Context, genre_name:str = None ):
"""
Changes the music genre for the bot's background music.
This command allows users to switch between different music genres available
in the bot's local collection. Available genres include lofi, mmochill,
mmoanime, orsmix, nostalgia and holyquran.
Args:
ctx (commands.Context): The context of the command invocation.
genre_name (str, optional): The name of the genre to switch to.
Must be one of: 'lofi', 'mmochill', 'mmoanime', 'orsmix', 'nostalgia', 'holyquran'.
Defaults to None.
Note:
The command includes a cooldown of 5 seconds between uses.
"""
await util.process_send_change_wizy_music_genre_cmd(ctx= ctx, _genre_name= None if genre_name is None else genre_name.strip().lower())
# @change_wizy_music_genre.error
# async def change_wizy_music_genre_error(ctx: commands.Context , error):
# print (f'\n\nTESTING#################E R R O R: {error}\n\n')
# if isinstance(error , commands.MissingAnyRole):
# allowed_ids = list(allowed_roles_change_ai.values())
# await ctx.reply(
# allowed_mentions=discord.AllowedMentions(roles=False),
# delete_after= 15.0,
# content=f"Ops! __*only*__ _{' , '.join(map(lambda id : '<@&' + str(id) + '>' , allowed_ids))}_ or ADMINS ! are allowed to use this command..."
# ) #used a join and map and lambda function just as fast fancy way to print all allowed roles
# elif isinstance(error , commands.MissingPermissions):
# await ctx.reply(
# allowed_mentions=discord.AllowedMentions(roles=False),
# delete_after= 15.0,
# content=f"Ops! __*only*__ ADMINS ! are allowed to use this command..."
# )
# ------------------------------------------------------------------------------------------------------------------------------------------#
custom_quote_threshhold = 200 #defaulted to 200 but you can change it via quotesz at runtime easily
max_quote_size = 5070
@bot.hybrid_command(name="quotesz" , help= 'modifies quote MAX number of Characters (default is 200)' )#defaulted to 200
@commands.cooldown(1, 5)
@commands.check_any(commands.has_any_role(*list(allowed_roles_quotesz.values())), commands.has_permissions(administrator=True))
async def change_quote_mx_sz( ctx: commands.Context, new_quote_sz: str):
"""
Modifies the maximum number of characters allowed in quotes.
This command allows authorized users to change the maximum length of quotes
that can be generated by the bot. The default is 200 characters, and there
is a hard limit of 5070 characters.
Args:
ctx (commands.Context): The context of the command invocation.
new_quote_sz (str): The new maximum quote size to set.
Note:
Only users with specific roles or administrators can use this command.
The command includes a cooldown of 5 seconds between uses.
The maximum allowed size is 5070 characters.
"""
global custom_quote_threshhold
await util.process_send_quotesz_cmd(ctx= ctx, _quote_sz = new_quote_sz, _quote_threshhold= custom_quote_threshhold, _max_sz= max_quote_size)
@change_quote_mx_sz.error
async def change_quote_mx_sz_error(ctx: commands.Context , error):
print (f'\n\nTESTING#################E R R O R: {error}')
if isinstance(error , commands.MissingAnyRole):
allowed_ids = list(allowed_roles_quotesz.values())
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! __*only*__ _{' , '.join(map(lambda id : '<@&' + str(id) + '>' , allowed_ids))}_ are allowed to use this command..."
) #used a join and map and lambda function just as fast fancy way to print all allowed roles
elif isinstance(error , commands.MissingPermissions):
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! __*only*__ ADMINS ! are allowed to use this command..."
)
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="ping", help= "roughly gets the time bot needed to respond in _ms_ ||__(needs testing)__||")
@commands.cooldown(1, 5)
async def ping(ctx: commands.Context):
"""
Measures the bot's response time in milliseconds.
This command calculates the time difference between when the user's message
was sent and when the bot responds. The result is displayed in milliseconds.
Args:
ctx (commands.Context): The context of the command invocation.
Note:
The command includes a cooldown of 5 seconds between uses.
The response message is automatically deleted after 15 seconds.
"""
send_time = ctx.message.created_at
# next line is naieve datetime obj (all datime obj must be in same type naieve/aware in order to do arithmatics on them)
recieve_time = datetime.now()
recieve_time = recieve_time.astimezone(pytz.utc) # converte aware time zone to naieve time zone and set tz to utc
msg_latency = (abs(recieve_time - send_time)).total_seconds() * 1000 #mul by 1000 to get in ms #TODO : why recieve time happends before create time !?
tot_ping = round(msg_latency , 2)
#NOTE:next line gets the time needed to recieve user msg and send the respond (usually what users wnat to know)
bot_reply_msg = await ctx.reply(delete_after= 15.0 , content= f"Pong! `{tot_ping}ms`" )
await bot_reply_msg.add_reaction('\U0001F4F6') #📶 emoji unicide == '\U0001F4F6'
await ctx.message.delete(delay= 15.0)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="wiz_ping", help= "gets time of bot heartbeat and discrod ack in _ms_ ||__(needs testing)__||." )
@commands.cooldown(1, 5)
async def wiz_ping(ctx: commands.Context):
"""
Measures the bot's heartbeat latency with Discord servers.
This command displays the bot's latency to Discord's servers in milliseconds.
This is different from the regular ping command as it measures the WebSocket
heartbeat latency rather than command response time.
Args:
ctx (commands.Context): The context of the command invocation.
Note:
The command includes a cooldown of 5 seconds between uses.
The response message is automatically deleted after 15 seconds.
"""
#NOTE : this gets bot latency between discord servers and the bot client
bot_reply_msg = await ctx.reply(delete_after= 15.0 , content= f'Pong! Bot Latency is `{round (bot.latency * 1000 , 2)}ms`')
await bot_reply_msg.add_reaction('\U0001F4F6') #📶 emoji unicide == '\U0001F4F6'
await ctx.message.delete(delay= 15.0)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command( name="wizygpt", help="Wizard finds your answer between old scrolls from the depth of OpenAi GPT dungeons!") # command name is defaulted to method name 'gpt'
@commands.cooldown(1, 5)
async def gpt (ctx : commands.Context, * , full_prompt:str ): #(search keyword-only arguments) astrisk in alone arg is to force the later argument to be passed by name e.g.( prompt="string1" )
"""
Answers user queries using OpenAI GPT.
This command fetches an answer from OpenAI GPT based on the user's prompt, unless the command is invoked in the wizard chat channel (to avoid duplicate answers).
Args:
ctx (commands.Context): The context of the command invocation.
full_prompt (str): The user's question or prompt for GPT.
"""
#since I prefer no classic command 'prefix' ignore chat commands when inside wizy speical chat channel i.e.(avoid duplicated ans)
if ctx.channel.id not in wizy_chat_channels :
valid_reply : tuple(bool, discord.Message ) = await check_msg ( _message= ctx.message , chk_type= 2)
if valid_reply[0] and valid_reply[1] is not None :
full_prompt = await get_new_reply_prompt(valid_reply[1] , full_prompt)
#NOTE: (next line) if you put ctx.message.reference instead of ctx.message in reference arg this will reply to very first message you replied to (if you have)
try:
user_msg = ctx.message
ask_gpt_task = bot.loop.create_task(ask_gpt(full_prompt, user= ctx.author, is_wizy_ch= False ))
if str(ctx.author.id) not in util.UserAiQuery.chats_ai_dict:
send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message, content= "**"+get_rand_greeting(ctx.author.display_name)+"**" ))
await send_initMsg_task
send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message, content= f"**Ah I see! Searching Ancient Scrolls for you on: ** \n \n `{full_prompt}` ..."))
await send_initMsg_task
task_response: tuple(str, str) = await ask_gpt_task
embed = prepare_discord_embed(task_response, is_reply= valid_reply[0], is_gemini= False)
send_func_return = bot.loop.create_task(user_msg.reply(embed=embed) if not ctx.interaction else ctx.reply(embed=embed))
returned_msg : discord.Message = await send_func_return # short cut for ctx.send()
except:
async with ctx.typing():
await ctx.message.delete(delay= 15.0)
bot_reply_msg: discord.Message = await ctx.reply("**Ops! This feature is not working wizy very sorry!**", delete_after= 15)
@gpt.error
async def gpt_error(ctx: commands.Context , error):
print (f'\n\nTESTING#################E R R O R: {error}')
if isinstance(error , commands.HybridCommandError):
allowed_ids = list(allowed_roles_quotesz.values())
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! this is not avialble right now🧙♂️"
)
# #------------------------------------------------------------------------------------------------------------------------------------------#
#TODO: for commands & events using deepSeek AI: add attach files/images feature cuz deepseek supports it!
@bot.hybrid_command( name="wizydeep", aliases=["wizy", "wizardspirit"], help="Wizard finds your answer within the old scrolls from the depth of DeepSeek dungeons!") # command name is defaulted to method name 'gpt'
@commands.cooldown(1, 5)
async def deepSeek (ctx : commands.Context, * , full_prompt:str ): #(search keyword-only arguments) astrisk in alone arg is to force the later argument to be passed by name e.g.( prompt="string1" )
"""
Answers user queries using DeepSeek AI.
This command fetches an answer from DeepSeek AI based on the user's prompt, unless the command is invoked in the wizard chat channel (to avoid duplicate answers).
Args:
ctx (commands.Context): The context of the command invocation.
full_prompt (str): The user's question or prompt for DeepSeek.
"""
#since I prefer no classic command 'prefix' ignore chat commands when inside wizy speical chat channel i.e.(avoid duplicated ans)
if ctx.channel.id not in wizy_chat_channels :
valid_reply : tuple(bool, discord.Message ) = await check_msg ( _message= ctx.message , chk_type= 2)
if valid_reply[0] and valid_reply[1] is not None :
full_prompt = await get_new_reply_prompt(valid_reply[1] , full_prompt)
#NOTE: (next line) if you put ctx.message.reference instead of ctx.message in reference arg this will reply to very first message you replied to (if you have)
try:
user_msg = ctx.message
ask_deepSeek_task = bot.loop.create_task(ask_deepSeek(full_prompt, user= ctx.author, is_wizy_ch= False))
ctx.interaction or await user_msg.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
if str(ctx.author.id) not in util.UserAiQuery.chats_ai_dict:
send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message, content= "**"+get_rand_greeting(ctx.author.display_name)+"**" ))
await send_initMsg_task
send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message, content= f"**Ah I see! Searching Scrolls Labyrinth for your scroll: ** \n \n `{full_prompt}` ...", delete_after= 15))
await send_initMsg_task
task_response: tuple(str, str) = await ask_deepSeek_task
embed = prepare_discord_embed(task_response, is_reply= valid_reply[0], is_gemini= False)
send_func_return = bot.loop.create_task(user_msg.reply(embed=embed) if not ctx.interaction else ctx.reply(embed=embed))
returned_msg : discord.Message = await send_func_return # short cut for ctx.send()
except:
async with ctx.typing():
await ctx.message.delete(delay= 15.0)
bot_reply_msg: discord.Message = await ctx.reply("**Ops! This feature is not working wizy very sorry!**", delete_after= 15)
@deepSeek.error
async def deepSeek_error(ctx: commands.Context , error):
print (f'\n\nTESTING#################E R R O R: {error}')
if isinstance(error , commands.HybridCommandError):
allowed_ids = list(allowed_roles_quotesz.values())
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! this is not avialble right now🧙♂️"
)
# #------------------------------------------------------------------------------------------------------------------------------------------#
#NOTE: this left as non-hybrid command cuz if it's name (command name being a bot mention is good for classic commands only anyway)
#TODO: add attach files/images feature cuz deepseek supports it!
@bot.command(name=f"<@{wizard_bot_id}>", help= 'wizy answers you questions using DeepSeekp ... etc') # command name is defaulted to method name
@commands.cooldown(1, 5)
#(search keyword-only arguments) astrisk in alone arg is to force the later argument to be passed by name e.g.( prompt="string1" )
async def ChatDeepSeekfast (ctx: commands.Context, * ,full_prompt: str = "EMPTY PROMPT. CHECK REPLY: " ):
"""
Answers user queries using DeepSeek AI (classic command).
This command fetches an answer from DeepSeek AI based on the user's prompt, unless the command is invoked in the wizard chat channel (to avoid duplicate answers). This is a classic command, not a hybrid one.
Args:
ctx (commands.Context): The context of the command invocation.
full_prompt (str, optional): The user's question or prompt for DeepSeek. Defaults to a placeholder string.
"""
#using GEMINI API
#since I prefer no classic command 'prefix' ignore chat commands when inside wizy speical chat channel i.e.(avoid duplicated ans)
if ctx.channel.id not in wizy_chat_channels :
valid_reply : tuple(bool, discord.Message ) = await check_msg ( _message= ctx.message, chk_type= 2)
if valid_reply[0] and valid_reply[1] is not None :
full_prompt = await get_new_reply_prompt(valid_reply[1], full_prompt)
try:
#NOTE: (next line) if you put ctx.message.reference instead of ctx.message in reference arg this will reply to very first message you replied to (if you have)
user_msg = ctx.message
ask_deepSeek_task = bot.loop.create_task(ask_deepSeek(full_prompt, user= ctx.author, is_wizy_ch= False ))
ctx.interaction or await user_msg.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
if str(ctx.author.id) not in util.UserAiQuery.chats_ai_dict:
send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message, content= "**"+get_rand_greeting(ctx.author.display_name)+"**" ))
await send_initMsg_task
else:
send_initMsg_task = bot.loop.create_task(ctx.send( reference= ctx.message, content= "**Searching Ancient Scrolls for you!...**", delete_after= 15 ))
await send_initMsg_task
task_response : tuple = await ask_deepSeek_task
embed = prepare_discord_embed(task_response, is_reply= valid_reply[0], is_gemini= False)
send_func_return = bot.loop.create_task(user_msg.reply(embed=embed))
returned_msg : discord.Message = await send_func_return # short cut for ctx.send()
except:
async with ctx.typing():
await ctx.message.delete(delay= 15.0)
bot_reply_msg: discord.Message = await ctx.reply("**Ops! This feature is not working wizy very sorry!**", delete_after= 15)
# img_embds = list()
# if task_response[2] is not None and len(task_response[2]) != 0 and send_func_return.done():
# for img in task_response[2]:
# img_embds.append(discord.Embed(type='image').set_image(img))
# send_img_msg_task = bot.loop.create_task(ctx.send(reference= returned_msg , embeds= img_embds) )#if error replace display_name with name
# await send_img_msg_task
#------------------------------------------------------------------------------------------------------------------------------------------#
# #NOTE: this left as non-hybrid command cuz if it's name (command name being a bot mention is good for classic commands only anyway)
# @bot.command(name=f"<@{wizard_bot_id}>", help= 'wizy answers you questions using GEMINI ... etc') # command name is defaulted to method name
# @commands.cooldown(1, 5)
# #(search keyword-only arguments) astrisk in alone arg is to force the later argument to be passed by name e.g.( prompt="string1" )
# async def geminiAIfast (ctx: commands.Context, * ,full_prompt: str = "EMPTY PROMPT. CHECK REPLY: " ):
# #using GEMINI API
# valid_reply : tuple(bool, discord.Message ) = await check_msg ( _message= ctx.message, chk_type= 2)
# if valid_reply[0] and valid_reply[1] is not None :
# full_prompt = await get_new_reply_prompt(valid_reply[1], full_prompt)
# #NOTE: (next line) if you put ctx.message.reference instead of ctx.message in reference arg this will reply to very first message you replied to (if you have)
# send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message, content= "**"+get_rand_greeting(ctx.author.display_name)+"**" ))
# ask_gemini_task = bot.loop.create_task(ask_gemini(full_prompt, user_name= ctx.author.display_name ))
# await send_initMsg_task
# task_response : tuple = await ask_gemini_task
# embed = prepare_discord_embed(task_response , is_reply= valid_reply[0])
# send_func_return = bot.loop.create_task(ctx.reply(embed=embed))
# returned_msg : discord.Message = await send_func_return # short cut for ctx.send()
# del embed
# del valid_reply
# del ctx
# # img_embds = list()
# # if task_response[2] is not None and len(task_response[2]) != 0 and send_func_return.done():
# # for img in task_response[2]:
# # img_embds.append(discord.Embed(type='image').set_image(img))
# # send_img_msg_task = bot.loop.create_task(ctx.send(reference= returned_msg , embeds= img_embds) )#if error replace display_name with name
# # await send_img_msg_task
@ChatDeepSeekfast.error
async def ChatDeepSeekfast_error(ctx: commands.Context , error):
print (f'\n\nTESTING#################E R R O R: {error}')
if isinstance(error , commands.CommandError):
allowed_ids = list(allowed_roles_quotesz.values())
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! this is not avialble right now🧙♂️"
)
# #------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="wizygemini", aliases=["gemini"], help= 'wizy answers your questions using GEMINI ... etc')
# command name is defaulted to method name 'geminiAI'
@commands.cooldown(1, 5)
#NOTE: (search keyword-only arguments) astrisk in alone arg is to force the later argument to be passed by name e.g.( prompt="string1" )
async def geminiAI (ctx: commands.Context , * , full_prompt: str = "EMPTY PROMPT. CHECK REPLY: "):
"""
Answers user queries using Gemini AI.
This command fetches an answer from Gemini AI based on the user's prompt, unless the command is invoked in the wizard chat channel (to avoid duplicate answers).
Args:
ctx (commands.Context): The context of the command invocation.
full_prompt (str, optional): The user's question or prompt for Gemini. Defaults to a placeholder string.
"""
#using GEMINI API
#since I prefer no classic command 'prefix' ignore chat commands when inside wizy speical chat channel i.e.(avoid duplicated ans)
if ctx.channel.id not in wizy_chat_channels :
valid_reply : tuple[ bool , discord.Message ] = await check_msg ( _message= ctx.message , chk_type= 2)
if valid_reply[0] and valid_reply[1] is not None :
full_prompt = await get_new_reply_prompt(valid_reply[1] , full_prompt)
#NOTE: (next line) if you put ctx.message.reference instead of ctx.message in reference arg this will reply to very first message you replied to (if you have)
try:
ask_gemini_task = bot.loop.create_task(ask_gemini(full_prompt , user_name= ctx.author.display_name ))
await send_initMsg_task
task_response : tuple = await ask_gemini_task
send_initMsg_task = bot.loop.create_task(ctx.send(reference= ctx.message , content= "**"+get_rand_greeting(ctx.author.display_name)+"**" ))
embed = prepare_discord_embed(task_response , is_reply= valid_reply[0], is_gemini= True)
send_func_return = bot.loop.create_task(ctx.reply(embed=embed))
returned_msg : discord.Message = await send_func_return # short cut for ctx.send()
del embed
del valid_reply
except:
async with ctx.typing():
await ctx.message.delete(delay= 15.0)
bot_reply_msg: discord.Message = await ctx.reply("**Ops! This feature is not working wizy very sorry!**", delete_after= 15)
# img_embds = list()
# if task_response[2] is not None and len(task_response[2]) != 0 and send_func_return.done():
# for img in task_response[2]:
# img_embds.append(discord.Embed(type='image').set_image(img))
# send_img_msg_task = bot.loop.create_task(ctx.send(reference= returned_msg , embeds= img_embds) )#if error replace display_name with name
# await send_img_msg_task
@geminiAI.error
async def geminiAI_error(ctx: commands.Context , error):
print (f'\n\nTESTING#################E R R O R: {error}')
if isinstance(error , commands.HybridCommandError):
allowed_ids = list(allowed_roles_quotesz.values())
await ctx.reply(
allowed_mentions=discord.AllowedMentions(roles=False),
delete_after= 15.0,
content=f"Ops! this is not avialble right now🧙♂️"
)
# #------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="leave", aliases=['wizyleave'], help= 'wizy leaves voice channel _if_ connected to one')
@commands.cooldown(1, 5)
async def leave_voice_wizard( ctx: commands.Context ):
"""
Disconnects the bot from the current voice channel.
This command makes the bot leave the voice channel it is currently connected to, if any.
The command can be invoked via slash or classic command.
Args:
ctx (commands.Context): The context of the command invocation.
"""
if ctx.interaction: #if invoked using slash commmand
bot_reply_msg = await ctx.reply(slash_cmd_ok_msg)
await bot_reply_msg.delete(delay= 5)
await ctx.message.delete(delay= 15.0)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
if ctx.guild.voice_client is not None:
await ctx.guild.voice_client.disconnect()
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name="join", aliases=['wizyjoin'], help= 'wizy joins voice channel you are in' )
@commands.cooldown(1, 5)
async def join_voice_wizard( ctx: commands.Context ):
"""
Makes the bot join the voice channel the user is currently in.
This command connects the bot to the same voice channel as the user, disconnecting from any other channel if necessary.
If the user is not in a voice channel, an error message is sent.
Args:
ctx (commands.Context): The context of the command invocation.
"""
#TESTING
print(f"######## 'wizyjoin' INVOKER VOICE STATE (TESTING): \n\n\n\n\n{ctx.author.voice}\n\n\n\n {True if ctx.author.voice else False }\n\n\n\n\n {ctx.guild.voice_client} ###########\n\n\n\n")
#TESTING
if ( ctx.author.voice ):
target_voice_channel = ctx.message.author.voice.channel
if ctx.guild.voice_client is not None:
await ctx.guild.voice_client.move_to(target_voice_channel)
else:
await target_voice_channel.connect()
if ctx.interaction: #if invoked using slash commmand
bot_reply_msg = await ctx.reply(slash_cmd_ok_msg)
await bot_reply_msg.delete(delay= 5)
await ctx.message.delete(delay= 15.0)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
else :
user = ctx.message.author.mention
bot_reply_msg: discord.Message = await ctx.reply(
delete_after= 15.0,
content= f"Ops! {user} you must be in a voice channel!"
)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
#------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name='play', aliases=['wizyplay'], help='To play audio')
@commands.cooldown(1, 5)
async def play(ctx: commands.Context, url: str= None):
"""
Plays audio in the user's current voice channel.
This command plays a YouTube audio stream or the bot's default tracks in the user's current voice channel.
If the bot is already in another channel, it will not join unless moved.
Args:
ctx (commands.Context): The context of the command invocation.
url (str, optional): The YouTube URL to play. If not provided, plays default tracks.
"""
print(f"TESTING ######### \n\n\n {url} \n\n\n ###########")
try :
guild_obj = ctx.message.guild
bot_voice_client: discord.voice_client = guild_obj.voice_client
#make bot (join + play) in one command! (ONLY! IF he is already in a VC you must move him first!)
if ( ctx.author.voice ):
is_bot_connected_to_other_ch = bot_voice_client is not None and bot_voice_client.is_connected() and (bot_voice_client.channel.id != ctx.author.voice.channel.id)
if is_bot_connected_to_other_ch:
bot_reply_msg: discord.Message = await ctx.reply(content="Bot is Being Used in another Voice Channel!", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
return
# raise Exception("Bot is Being Used in another Voice Channel!")
elif bot_voice_client is None or not bot_voice_client.is_connected(): #user is in a voice ch and bot is not in any voice channel
await ctx.author.voice.channel.connect()
bot_voice_client: discord.voice_client = guild_obj.voice_client
if url != None:
async with ctx.typing():
#NOTE: if stream arg causes error set to false (download file then play from local pc)
song_obj , filename = await YTDLSource.from_url(url, loop=bot.loop, stream= True)
if bot_voice_client.is_playing():
bot_voice_client.stop()
bot_voice_client.play(song_obj)
bot_reply_msg: discord.Message = await ctx.reply(f'**Now playing:** {filename} **-** _islander_ {ctx.message.author.mention}')
ctx.interaction or await bot_reply_msg.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
await ctx.message.delete(delay= 15.0)
else: #play default chill tracks locally on your pc (infinity loop)
async with ctx.typing():
if bot_voice_client.is_playing():
bot_voice_client.stop()
aio.create_task(util.play_chill_track(guild_obj))
bot_reply_msg: discord.Message = await ctx.reply(f"**{ctx.message.author.mention} started Wizy's Default Wizy Tracks** enjoy! :blue_heart::notes:")
ctx.interaction or await bot_reply_msg.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
await ctx.message.delete(delay= 15.0)
else:
raise Exception("USER NOT IN VALID VOICE STATE (probably not inside a Voice Channel)!")
except Exception as e:
print(f"######### \n\n\n Exception Raised from 'wizyplay' cmd: {e} \n\n\n ###########")
bot_reply_msg: discord.Message = await ctx.reply(f"Ops:kissing: !{e} __OR__ provided link is not a YouTube Music",
delete_after= 15
)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
# #------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name='pause', aliases=['wizypause'], help='This command pauses the audio')
@commands.cooldown(1, 5)
async def pause(ctx: commands.Context):
"""
Pauses the currently playing audio.
This command pauses the audio that the bot is currently playing in the voice channel.
If nothing is playing, an error message is sent.
Args:
ctx (commands.Context): The context of the command invocation.
"""
guild_obj = ctx.message.guild
bot_voice_client: discord.voice_client = guild_obj.voice_client
is_bot_connected_to_other_ch = ctx.author.voice is not None and bot_voice_client is not None and bot_voice_client.is_connected() and (bot_voice_client.channel.id != ctx.author.voice.channel.id)
if is_bot_connected_to_other_ch:
bot_reply_msg: discord.Message = await ctx.reply(content="The bot is in another channel!", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
# raise Exception("Bot is Being Used in another Voice Channel!")
else:
if bot_voice_client.is_playing():
await await_me_maybe( bot_voice_client.pause() )
if ctx.interaction: #if invoked using slash commmand
bot_reply_msg = await ctx.reply(slash_cmd_ok_msg)
await bot_reply_msg.delete(delay= 5)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
await ctx.message.delete(delay= 15.0)
else:
bot_reply_msg: discord.Message = await ctx.reply(content="The bot is not playing anything at the moment.", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
@bot.hybrid_command(name='resume', aliases=['wizyresume'], help='Resumes the audio')
@commands.cooldown(1, 5)
async def resume(ctx: commands.Context):
"""
Resumes paused audio playback.
This command resumes the audio that was previously paused by the bot in the voice channel.
If nothing is paused, an error message is sent.
Args:
ctx (commands.Context): The context of the command invocation.
"""
guild_obj = ctx.message.guild
bot_voice_client: discord.voice_client = guild_obj.voice_client
is_bot_connected_to_other_ch = ctx.author.voice is not None and bot_voice_client is not None and bot_voice_client.is_connected() and (bot_voice_client.channel.id != ctx.author.voice.channel.id)
if is_bot_connected_to_other_ch:
bot_reply_msg: discord.Message = await ctx.reply(content="The bot is in another channel!", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
# raise Exception("Bot is Being Used in another Voice Channel!")
else:
if bot_voice_client.is_paused():
await await_me_maybe(bot_voice_client.resume())
if ctx.interaction: #if invoked using slash commmand
bot_reply_msg = await ctx.reply(slash_cmd_ok_msg)
await bot_reply_msg.delete(delay= 5)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
await ctx.message.delete(delay= 15.0)
else:
bot_reply_msg: discord.Message = await ctx.reply(content="wizy wasn't pausing any track. maybe try **wizyplay** command", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
# #------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name='stop', aliases=['wizystop'], help='Stops the audio')
@commands.cooldown(1, 5)
async def stop(ctx: commands.Context):
"""
Stops the currently playing or paused audio.
This command stops the audio that the bot is currently playing or has paused in the voice channel.
If nothing is playing or paused, an error message is sent.
Args:
ctx (commands.Context): The context of the command invocation.
"""
guild_obj = ctx.message.guild
bot_voice_client: discord.voice_client = guild_obj.voice_client
is_bot_connected_to_other_ch = ctx.author.voice is not None and bot_voice_client is not None and bot_voice_client.is_connected() and (bot_voice_client.channel.id != ctx.author.voice.channel.id)
if is_bot_connected_to_other_ch:
bot_reply_msg: discord.Message = await ctx.reply(content="The bot is in another channel!", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
# raise Exception("Bot is Being Used in another Voice Channel!")
else:
if bot_voice_client.is_playing() or bot_voice_client.is_paused():
await await_me_maybe(bot_voice_client.stop())
if ctx.interaction: #if invoked using slash commmand
bot_reply_msg = await ctx.reply(slash_cmd_ok_msg)
await bot_reply_msg.delete(delay= 5)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
await ctx.message.delete(delay= 15.0)
else:
bot_reply_msg: discord.Message = await ctx.reply(content="The bot is not playing anything at the moment.", delete_after= 15)
ctx.interaction or await bot_reply_msg.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
ctx.interaction or await ctx.message.add_reaction('\U0000274C') #❌ mark unicode == '\U0000274C'
await ctx.message.delete(delay= 15.0)
# #------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name='wizyque', help='Shows the current queue or clears it **(TODO)**') #TODO
@commands.cooldown(1, 5)
async def queue(ctx: commands.Context, do_clear: str= None):
"""
Shows or clears the current music queue.
This command displays the current queue of tracks or clears it if the 'CLEAR' argument is provided.
Args:
ctx (commands.Context): The context of the command invocation.
do_clear (str, optional): If set to 'CLEAR', clears the queue.
"""
if do_clear != None and do_clear.strip().capitalize() == 'CLEAR':
util.tracks_queue.guilds_connected_queues[ctx.guild.id].clear()
ctx.reply(content="Track Queue is Cleared!", delete_after= 15)
ctx.interaction or await ctx.message.add_reaction('\U00002705') #✅ mark unicode == '\U00002705'
await ctx.message.delete(delay= 15.0)
# #------------------------------------------------------------------------------------------------------------------------------------------#
@bot.hybrid_command(name='wizyadd', help='Appends track to end of tracks queue **(TODO)**') #TODO
@commands.cooldown(1, 5)
async def addTOqueue(ctx: commands.Context):
"""
Appends a track to the end of the music queue. (TODO)
This command is a placeholder for future implementation to add tracks to the queue.
Args:
ctx (commands.Context): The context of the command invocation.
"""
pass #TODO
# #------------------------------------------------------------------------------------------------------------------------------------------#