-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
1756 lines (1562 loc) · 75.9 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
"""
CREDITS:
-GGProGaming / Nathan G: creator of this bot
-Micro: for hosting the bot and giving feedback
-funlennysub: for the original bot
-Thomasims: Teardown API scraper script
-Source Code: https://github.com/GGProGaming/Teardown-Support-Bot
"""
import interactions
from interactions import Client, slash_command, SlashCommandOption, SlashCommandChoice, SlashContext, Intents, EmbedAttachment, cooldown, Buckets, subcommand, slash_option, OptionType, AutocompleteContext, EmbedAttachment, Task, IntervalTrigger, listen, Member, File, Permissions, ActionRow, Button, ButtonStyle, StringSelectMenu, component_callback, ComponentContext, spread_to_rows, Extension
from interactions.client.errors import CommandOnCooldown
from interactions.ext import prefixed_commands
from interactions.ext.prefixed_commands import prefixed_command, PrefixedContext
import json
import os
import re
import io
import collections
import aiohttp
import asyncio
from datetime import datetime, timedelta
import logging
import steam_workshop
import typst
import subprocess
import tempfile
import random
import numpy as np
from PyPDF2 import PdfReader
from typing import Generator, Dict
from PIL import ImageOps, Image, ImageDraw
from pdf2image import convert_from_path
import matplotlib.pyplot as plt
# this import should be EXACTLY in this format
from interactions.client import const
const.CLIENT_FEATURE_FLAGS["FOLLOWUP_INTERACTIONS_FOR_IMAGES"] = True
bot = Client(intents=Intents.DEFAULT, sync_interactions=True, asyncio_debug=True)
prefixed_commands.setup(bot)
# List of roles that can delete tags
required_roles = ["Tech Support", "Moderator", "Admin"]
# Helper function to check if the user has required roles
def has_required_role(member):
if isinstance(member, Member):
return any(role.name in required_roles for role in member.roles)
else:
return False
def load_muted_users():
if os.path.exists('/root/TTS/muted_users.json'):
with open('/root/TTS/muted_users.json', 'r') as mute_file:
data = mute_file.read()
return json.loads(data) if data else {}
else:
return {}
# Create a task that runs every minute
@Task.create(IntervalTrigger(minutes=1))
async def decrease_mute_time():
# Check if the mute file exists
if os.path.exists('/root/TTS/muted_users.json'):
# Load the mute file
with open('/root/TTS/muted_users.json', 'r') as mute_file:
muted_users = json.load(mute_file)
# Get the current time
current_time = datetime.utcnow()
# Create a list of users to remove from the mute file
users_to_remove = []
# Iterate over the muted users
for user, end_time_str in muted_users.items():
# Convert the end time from string to datetime
end_time = datetime.fromisoformat(end_time_str)
# If the current time is later than or equal to the end time, add the user to the list of users to remove
if current_time >= end_time:
users_to_remove.append(user)
# Remove the users from the mute file
for user in users_to_remove:
del muted_users[user]
# Write the updated mute file
with open('muted_users.json', 'w') as mute_file:
json.dump(muted_users, mute_file)
@listen()
async def on_startup():
decrease_mute_time.start()
# Scope variable...
# Writing this like 4 months later. I have no idea what this does but something in the interactions-py docs need it.
scope = [760105076755922996, 831277268873117706]
@slash_command(name="managemute", description="Manage user mute status", scopes=scope, dm_permission=False, default_member_permissions=Permissions.MANAGE_EMOJIS_AND_STICKERS)
@slash_option(
name="action",
description="Choose an action",
opt_type=OptionType.STRING,
required=True,
choices=[
{"name": "Mute User", "value": "mute"},
{"name": "View Muted Users", "value": "view"},
{"name": "Unmute User", "value": "remove"},
],
)
@slash_option(name="user", description="The user", opt_type=OptionType.USER, required=False, autocomplete=True)
@slash_option(name="duration", description="Duration of the mute in minutes", opt_type=OptionType.INTEGER, required=False)
async def moderate(ctx: SlashContext, action: str, user: str = None, duration: int = None):
has_role = has_required_role(ctx.author)
if not has_role:
await ctx.send("You do not have permission to use this command.", silent=True, ephemeral=True)
return
if action == "mute":
if user and duration:
await mute_user(ctx, user, duration)
else:
await ctx.send("You must provide a user and duration to mute a user.", silent=True, ephemeral=True)
elif action == "view":
await view_muted_users(ctx)
elif action == "remove":
if user:
await remove_mute(ctx, user)
else:
await ctx.send("You must provide a user to remove a mute.", silent=True, ephemeral=True)
async def mute_user(ctx: SlashContext, user: Member, duration: int):
usage_statistics["Muted"] += 1
save_usage_statistics(usage_statistics) # Save the updated statistics
mute_end_time = datetime.utcnow() + timedelta(minutes=duration)
muted_users = load_muted_users()
muted_users[str(user.id)] = mute_end_time.isoformat()
with open('/root/TTS/muted_users.json', 'w') as mute_file:
json.dump(muted_users, mute_file)
if duration >= 1440:
mute_duration = f"{duration // 1440} days"
elif duration >= 60:
mute_duration = f"{duration // 60} hours, {duration % 60} minutes"
else:
mute_duration = f"{duration} minutes"
await ctx.send(f"Muted {user.display_name} for {mute_duration}.", silent=True, ephemeral=True)
async def view_muted_users(ctx: SlashContext):
muted_users = load_muted_users()
if muted_users:
response = "```"
for user_id, end_time_str in muted_users.items():
end_time = datetime.fromisoformat(end_time_str)
remaining_time = end_time - datetime.utcnow()
user = await ctx.bot.fetch_user(int(user_id))
if remaining_time.days > 0:
response += f"{user.display_name} : {remaining_time.days} days remaining\n"
elif remaining_time.seconds >= 3600:
hours = remaining_time.seconds // 3600
minutes = (remaining_time.seconds % 3600) // 60
response += f"{user.display_name} : {hours} hours, {minutes} minutes remaining\n"
elif remaining_time.seconds >= 60:
response += f"{user.display_name} : {remaining_time.seconds // 60} minutes, {remaining_time.seconds % 60} seconds remaining\n"
else:
response += f"{user.display_name} : {remaining_time.seconds} seconds remaining\n"
response += "```"
embed = interactions.Embed(title="Muted Users", description=response, color=0xe9254e)
await ctx.send(embed=embed, silent=True, ephemeral=True)
else:
await ctx.send("No users are currently muted.", silent=True, ephemeral=True)
async def remove_mute(ctx: SlashContext, user: Member):
muted_users = load_muted_users()
if str(user.id) in muted_users:
del muted_users[str(user.id)]
with open('/root/TTS/muted_users.json', 'w') as mute_file:
json.dump(muted_users, mute_file)
await ctx.send(f"Unmuted {user.display_name}.", silent=True, ephemeral=True)
async def check_mute(ctx: SlashContext):
muted_users = load_muted_users()
if str(ctx.author.id) in muted_users:
end_time = datetime.fromisoformat(muted_users[str(ctx.author.id)])
remaining_time = end_time - datetime.utcnow()
if remaining_time.days > 0:
await ctx.send(f"You are currently muted and cannot use the bot. You have {remaining_time.days} days remaining before you can try again.", ephemeral=True)
elif remaining_time.seconds >= 3600:
hours = remaining_time.seconds // 3600
minutes = (remaining_time.seconds % 3600) // 60
await ctx.send(f"You are currently muted and cannot use the bot. You have {hours} hours, {minutes} minutes remaining before you can try again.", ephemeral=True)
elif remaining_time.seconds >= 60:
await ctx.send(f"You are currently muted and cannot use the bot. You have {remaining_time.seconds // 60} minutes, {remaining_time.seconds % 60} seconds remaining before you can try again.", ephemeral=True)
else:
await ctx.send(f"You are currently muted and cannot use the bot. You have {remaining_time.seconds} seconds remaining before you can try again.", ephemeral=True)
raise ValueError("User is muted")
# Tech Support Slash Command
@slash_command(name="techsupport", description="Get answers to basic tech support questions", options=[SlashCommandOption(name="question", description="Enter your question", type=3, required=True)])
async def _techsupport(ctx: SlashContext, question: str):
try:
await check_mute(ctx)
except ValueError:
return
usage_statistics["Tech Support"] += 1
save_usage_statistics(usage_statistics) # Save the updated statistics
image = None
if question == 'drivers':
response = (
'1. For Nvidia Users:\nYou can update drivers via Geforce Experience or their [driver page](https://www.nvidia.com/download/index.aspx).\n'
'2. For AMD Users:\nYou can check for updates using the AMD Radeon Settings or by navigating to their [driver page](https://www.amd.com/en/support).\n'
'3. After updating drivers, search for "Windows Update" in the Start menu and install any available updates.\n'
'4. Once updates are installed, restart your computer.')
embed = interactions.Embed(title="Update drivers and Windows and reboot", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == 'verify':
response = ('1. Open the Steam client.\n'
'2. Navigate to your Library.\n'
'3. Right-click on Teardown and select "Properties".\n'
'4. Click on the "Local Files" tab.\n'
'5. Click "Verify Integrity of Game Files".')
image_url = 'https://media.discordapp.net/attachments/1070933425005002812/1102841814676951060/Screenshot_1.png'
image = EmbedAttachment(url=image_url)
embed = interactions.Embed(title="Verify Steam Files", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == 'appdata':
response = (
'1. Press the Windows key + R to open the Run dialog. Or open file explorer and select the top search bar.\n'
'2. Type %localappdata% and press Enter.\n'
'3. Navigate to the "Teardown" folder.')
embed = interactions.Embed(title="Find Appdata local files", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == 'cpu_gpu':
response = (
'1. Press the Windows key type in "Task Manager" or enter the shortcut, "ctrl+shift+esc"\n'
'2. Go to the Performance tab and look for CPU and GPU sections.\n'
'3. You can see the name, usage, speed, and temperature of your CPU and GPU.\n'
'4. Relevant information the support team will need is the name of the CPU and GPU.'
)
image_url = 'https://cdn.discordapp.com/attachments/1092731528808775780/1092820549203398787/image0.jpg'
image = EmbedAttachment(url=image_url)
embed = interactions.Embed(title="Find CPU and GPU information", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == 'ddu':
response = (
'1. Download Display Driver Uninstaller (DDU) from the official website: https://www.wagnardsoft.com/.\n'
'2. Boot your computer into [Safe Mode](https://support.microsoft.com/en-us/windows/start-your-pc-in-safe-mode-in-windows-92c27cff-db89-8644-1ce4-b3e5e56fe234#WindowsVersion=Windows_11).\n'
'3. Run DDU and select your GPU manufacturer from the drop-down menu.\n'
'4. Click "Clean and restart".\n'
'5. After restarting, download and install the latest GPU drivers from the manufacturer\'s website.'
)
embed = interactions.Embed(title="Perform DDU Process", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == 'artifacts':
response = (
'1. Update your graphics card drivers to the latest version.\n'
'2. Disable any overlays such as the Razer Overlay.\n'
)
image_url = 'https://media.discordapp.net/attachments/1069850310782234635/1069850311235207258/image.png?width=1562&height=905'
image = EmbedAttachment(url=image_url)
embed = interactions.Embed(title="Artifacts", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == "nosound":
response = (
'When starting Teardown, avoid alt-tabbing or unfocusing the window while it starts.'
)
embed = interactions.Embed(title="No Sound", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
elif question == "disable":
response = (
'Open the mod menu in Teardown, right click in the middle column, and select "Disable All"'
)
image_url = 'https://media.discordapp.net/attachments/831277268873117709/1108495756488364042/image.png'
image = EmbedAttachment(url=image_url)
embed = interactions.Embed(title="Disable Mods", description=response, color=0x41bfff)
if image:
embed.image = image
await ctx.send(embed=embed, silent=True)
else:
response = 'Invalid question'
embed = interactions.Embed(title="Tech Support", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True, delete_after=4)
@_techsupport.autocomplete("question")
async def techsupport_autocomplete(ctx: AutocompleteContext):
choices = [
{"name": "Update drivers and Windows", "value": "drivers"},
{"name": "Verify Steam files", "value": "verify"},
{"name": "Find AppData local files", "value": "appdata"},
{"name": "Find CPU and GPU information", "value": "cpu_gpu"},
{"name": "Perform DDU process", "value": "ddu"},
{"name": "Artifacts", "value": "artifacts"},
{"name": "No Sound", "value": "nosound"},
{"name": "Disable Mods", "value": "disable"},
]
matching_choices = [
choice for choice in choices if ctx.input_text.lower() in choice["name"].lower()
]
await ctx.send(choices=matching_choices)
# FAQ Slash Command
@slash_command(name="faq", description="Get answers to frequently asked questions", options=[SlashCommandOption(name="question", description="Enter your question", type=3, required=True)])
async def _faq(ctx: SlashContext, question: str):
try:
await check_mute(ctx)
except ValueError:
return
usage_statistics["FAQ"] += 1
save_usage_statistics(usage_statistics) # Save the updated statistics
if question == 'progress':
response = "You can reset your game progress by going to options in the main menu, clicking the game button, and then clicking reset progress."
embed = interactions.Embed(title="How do I reset my progress?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'resources':
response = (
'1. The official Teardown modding documentation can be found [here](https://teardowngame.com/modding/index.html).\n'
'2. The official Teardown modding API documentation can be found [here](https://teardowngame.com/modding/api.html).\n'
'3. The offical voxtool... tool can be found [here](https://teardowngame.com/voxtool/).\n'
'4. There are many tutorials and guides found here: https://discord.com/channels/760105076755922996/771750716456697886.\n'
'5. You can find the magicavoxel application [here](https://ephtracy.github.io/).\n'
'6. You can ask questions in https://discord.com/channels/760105076755922996/768940642767208468.\n'
)
embed = interactions.Embed(title="Modding Resources", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'part3':
response = "There will not be a Part 3. Teardown is a complete game and will not be receiving any more main campaign updates."
embed = interactions.Embed(title="Will there be a part 3?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'multiplayer':
response = "Currently, Teardown does not have native multiplayer support. It is possible multiplayer will be added in the future."
embed = interactions.Embed(title="Will there be multiplayer?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'languages':
response = "Teardown is currently only available in English. We might look into localization at a later stage."
embed = interactions.Embed(title="Is Teardown available in other languages?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'consolemods':
response = "Information about mods for consoles will be released soon"
embed = interactions.Embed(title="Console Mods", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'creativemode':
response = "To some extent, you are able to save out individual shapes and get them as .vox-files that can be packed to a mod, but you won't be able to modify a level and share that level with your additions."
embed = interactions.Embed(title="Can I Upload Creative Mode Creations?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'vr':
response = "Teardown does not support VR."
embed = interactions.Embed(title="Can you play the game in VR?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'expansions':
response = "Future expansions are unknown at this time, so keep an eye out for any announcements."
embed = interactions.Embed(title="Will there be more expansions?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'update':
response = "Updates are released when they are ready. We do not have a set schedule for updates."
embed = interactions.Embed(title="When will the next update be released?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'requirements':
response = "Requires a 64-bit processor and operating system \nThe minimum system requirements are as follows:\n**OS:** Windows 7 \n**Processor:** Quad Core \n**CPU Memory:** 4 GB RAM \n**Graphics:** NVIDIA GeForce GTX 1060 or similar. 3 Gb VRAM.\n**Storage:** 4 GB available space \n**Additional Notes:** Intel integrated graphics cards not supported."
embed = interactions.Embed(title="What are the minimum system requirements?", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
elif question == 'botinfo':
response = "**Credits:**\nGGProGaming: creator of this bot\nMicro: for hosting the bot and giving feedback\nfunlennysub: for the original bot\nThomasims: Teardown API scraper script\n\n**Source Code:**\nhttps://github.com/GGProGaming/Teardown-Support-Bot"
embed = interactions.Embed(title="Bot Info", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True)
else:
response = 'Invalid question'
embed = interactions.Embed(title="FAQ", description=response, color=0x41bfff)
await ctx.send(embed=embed, silent=True, delete_after=4)
@_faq.autocomplete("question")
async def faq_autocomplete(ctx: AutocompleteContext):
choices = [
{"name": "What are the minimum system requirements?", "value": "requirements"},
{"name": "Will there be multiplayer?", "value": "multiplayer"},
{"name": "Is Teardown available in other languages?", "value": "languages"},
{"name": "Will there be a part 3?", "value": "part3"},
{"name": "Modding Resources", "value": "resources"},
{"name": "Console Mods", "value": "consolemods"},
{"name": "Can I Upload Creative Mode Creations?", "value": "creativemode"},
{"name": "Can you play the game in VR?", "value": "vr"},
{"name": "Will there be more expansions?", "value": "expansions"},
{"name": "When will the next update be released?", "value": "update"},
{"name": "How do I reset my progress?", "value": "progress"},
{"name": "Bot Info", "value": "botinfo"},
]
matching_choices = [
choice for choice in choices if ctx.input_text.lower() in choice["name"].lower()
]
await ctx.send(choices=matching_choices)
with open('/root/TTS/teardown_api.json', 'r') as f:
TEARDOWN_API = json.load(f)
def search_teardown_api(query: str):
results = []
for function in TEARDOWN_API['functions']:
if function['name'].lower() == query.lower():
results.append(function)
return results
autocomplete_api = [function['name'] for function in TEARDOWN_API['functions']]
@slash_command(name="docs", description="Search the Teardown API documentation")
@slash_option(
name="autocomplete",
description="Enter your search query",
required=True,
opt_type=OptionType.STRING,
autocomplete=True
)
async def _teardowndocs(ctx: SlashContext, autocomplete: str):
try:
await check_mute(ctx)
except ValueError:
return
usage_statistics["Docs"] += 1
save_usage_statistics(usage_statistics) # Save the updated statistics
results = search_teardown_api(autocomplete)
if not results:
response = f'Doc "{autocomplete}" not found.'
title = f'**{autocomplete}**'
embed = interactions.Embed(title=title, description=response, color=0xbc9946)
await ctx.send(embed=embed, delete_after=4, silent=True)
return
for result in results:
title = f'**{result["name"]}**'
description = result['description']
# Include the function definition in a code block
function_def = f'```lua\n{result["name"]}({", ".join([arg["name"] for arg in result["arguments"]])})\n```'
description += f'\n\n**Function Definition**\n{function_def}'
if 'arguments' in result:
arguments = '\n'.join([
f'- **{arg["name"]}** ({arg["type"]}): {arg["desc"]}'
for arg in result['arguments']
])
if arguments == '':
arguments = 'None'
description += f'\n**Arguments**\n{arguments}'
# If'ssss
if 'returns' in result:
returns = '\n'.join(
[f'- **{ret["type"]}**: {ret["desc"]}' for ret in result['returns']])
if returns == '':
returns = 'None'
description += f'\n\n**Returns**\n{returns}'
if 'examples' in result:
example = f'```lua\n{result["examples"][0]}\n```'
if example == '':
example = 'None'
description += f'\n\n**Example**\n{example}'
title = f'**{result["name"]}**'
url = f'https://teardowngame.com/modding/api.html#{result["name"]}'
embed = interactions.Embed(title=title, url=url, description=description, color=0xbc9946)
# Set the footer with the API version in small italics
embed.set_footer(text=f'API Version: {TEARDOWN_API["version"]}')
await ctx.send(embed=embed, silent=True, delete_after=86400)
@_teardowndocs.autocomplete("autocomplete")
async def docs_autocomplete(ctx: AutocompleteContext):
matching_api = [api for api in autocomplete_api if ctx.input_text.lower() in api.lower()]
matching_api = sorted(matching_api)[:25] # Get the first 25 matching API functions
choices = [{"name": api, "value": api} for api in matching_api]
await ctx.send(choices=choices)
# Load the Autumn API from the parsed_functions.json file
with open('/root/TTS/parsed_functions.json', 'r') as f:
AUTUMN_API = json.load(f)
def search_autumn_api(query: str):
results = []
for function in AUTUMN_API['functions']:
if function['name'].lower() == query.lower():
results.append(function)
return results
autocomplete_api2 = [function['name'] for function in AUTUMN_API['functions']]
# This works fine, Autumn needs to fix her docs though because (with every offensive in mind ;) they suck
@slash_command(name="autodocs", description="Search the Automatic framework")
@slash_option(
name="autocomplete",
description="Enter your search query",
required=True,
opt_type=OptionType.STRING,
autocomplete=True
)
async def _autodocs(ctx: SlashContext, autocomplete: str):
results = search_autumn_api(autocomplete)
if not results:
response = f'Doc "{autocomplete}" not found.'
title = f'**{autocomplete}**'
embed = interactions.Embed(title=title, description=response, color=0xbc9946)
await ctx.send(embed=embed, delete_after=4, silent=True)
return
for result in results:
title = f'**{result["name"]}**'
description = f'```{result["description"]}```'
# Include the function definition in a code block
function_def = f'```lua\n{result["function_definition"]}\n```'
description += f'\n\n**Function Definition**\n{function_def}'
if 'arguments' in result:
arguments = '\n'.join([
f'- **{arg["name"]}** ({arg["type"]}): {arg["desc"]}'
for arg in result['arguments']
])
if arguments == '':
arguments = 'None'
description += f'\n**Arguments**\n{arguments}'
if 'returns' in result:
returns = '\n'.join(
[f'- **{ret["name"]}**: {ret["desc"]}' for ret in result['returns']])
if returns == '':
returns = 'None'
description += f'\n\n**Returns**\n{returns}'
if 'examples' in result and result['examples']:
example = f'```lua\n{result["examples"][0]}\n```'
description += f'\n\n**Example**\n{example}'
title = f'**{result["name"]}**'
embed = interactions.Embed(title=title, description=description, color=0xbc9946)
# Set the footer with the API version in small italics
embed.set_footer(text=f'API Version: {AUTUMN_API["version"]}')
await ctx.send(embed=embed, silent=True, delete_after=86400)
@_autodocs.autocomplete("autocomplete")
async def autodocs_autocomplete(ctx: AutocompleteContext):
matching_api2 = [api for api in autocomplete_api2 if ctx.input_text.lower() in api.lower()]
matching_api2 = sorted(matching_api2)[:25] # Get the first 25 matching API functions
choices = [{"name": api, "value": api} for api in matching_api2]
await ctx.send(choices=choices)
# List of the Teardown tags
# Provided by the Dennispedia site, link is above
TEARDOWN_TAGS = {
"autobreak":
"Body tag. Makes body require a smaller inertia to trigger a break event. This is used/set/handled by the engine for the vehicle body.",
"awake":
"Body and shape tag. When the level loads, the tagged body will not be asleep. Handled by data/script/awake.lua.",
"boat":
"Vehicle tag. Makes the vehicle act like a boat, including looking for and using a propeller location, and not being able to use wheels.",
"bomb":
'Shape tag. Will countdown the time until it reaches 0, which then the shape is exploded and removed. The time is in seconds. This is used/set/handled by the engine for bombs and pipebombs.',
"bombstrength":
'Shape tag. Used as explosion size by bomb tag. "size" is a number in the range 0.5 to 4. This is used/set/handled by the engine for bombs and pipebombs.',
"booster":
'Shape tag. "time" is a number in the range 0 to 10. A time of 10 or greater will cause the tag to be removed. A time of 0 will cause the tag to be ignored. A time less than 0 will be set to 0. A time between 0 and 7 (exclusive) will cause flames and smoke to shoot out from the origin of the shape in the +Z direction. The time will gradually increase at a rate of 1 per second. This is set by the engine for rocket boosters.',
"breakall":
'Shape tag. Automatically breaks the other shape that touches this tagged shape. This is used/set by data/script/robot.lua and used/set by the engine if an explosive entity is thrown.',
"camera":
'Location tag. Defines where the 3rd person camera is. Defaults to player location.',
"crane":
'Vehicle tag. All hinge joints on vehicle are controllable with vehicle_raise/vehicle_lower, and display crane controls. Hook is also controllable.',
"cranebottom":
'Vehicle tag. All hinge joints on vehicle are controllable with vehicle_raise/vehicle_lower, and display crane base controls.',
"cranetop": 'Vehicle tag. Display crane arm controls.',
"dumptruck":
'Vehicle tag. All hinge joints on vehicle are controllable with vehicle_raise/vehicle_lower, and display bed controls.',
"exhaust":
'Location tag. Defines where/what direction exhaust is emitted from the vehicle. "amount" is a number.',
"explosive":
'Body and shape tag. Entity explodes when broken. "size" is a number in the range 0.5 to 4. If applied to an already broken shape, it instantly blows up.',
"extend":
'Joint tag. Joint can be controlled by up/down when the player is in a crane vehicle.',
"forklift":
'Vehicle tag. All prismatic joints on the vehicle are controllable with vehicle_raise/vehicle_lower, and display forklift controls.',
"frontloader":
'Vehicle tag. All hinge joints on the vehicle are controllable with vehicle_raise/vehicle_lower, and display arm controls.',
"fxbreak":
'Shape tag. Generate particle effects when the shape is broken. "s" is either "l" for liquid or "g" for gas. "#" is a number in the range 1 to 9 representing time in seconds until effects end. Valid colors for liquids are yellow, black, blue, green, and brown. Valid colors for gases are yellow, blue, and green. Default color is white. This is handled by data/script/fx.lua.',
"fxflicker":
'Light tag. Makes the light flicker. "#" is a number in the range 1 to 9 (or 1 if not a number). This is handled by data/script/fx.lua.',
"fxsmoke":
'Location tag. When positioned near a shape, makes it spawn in smoke particles. "t" is the type; "p" to make the particles change size, anything else to not. "#" is a number in the range 1 to 9 representing time in seconds until effects end. Valid colors are brown and "subtle" (gray). This is handled by data/script/fx.lua.',
"hook":
'Body and shape tag. Shape body acts as a vehicle hook which can be controlled by vehicle_action.',
"inherittags":
'Body and shape tag. Takes all the tags from the tagged entity and sets them on its debris. It will recursively do this.',
"interact":
'Body and shape tag. Makes it so when the player is close to the tagged entity, a prompt shows up to interact with it. GetPlayerInteractShape/GetPlayerInteractBody only works on entities with this tag.',
"invisible":
'Shape tag. Makes the tagged shape invisible, however, still impacts the shadow volume.',
"level":
'Joint tag. Force skylift joints to have the same rotation amount. (skylift only?)',
"map":
'Body tag. Marks body on the map. Defaults to a blue color. Handled by data/ui/map.lua.',
"night":
'Light tag. Light will be turned off if the nightlight environment property is currently false.',
"nocull":
'Shape tag. Shape will continue to be rendered no matter how far away it is.',
"nodrive":
'Vehicle tag. Vehicle is not drivable to the player with no "Drive" prompt.',
"nonav": 'Shape tag. Shape is entirely disregarded by pathfinding.',
"nosnow":
'Shape tag. Shape does not have any snow on it when the level loads.',
"passive":
'Vehicle tag. Disables inputs to the vehicle so it does not drive, steer, or gear change, and leaves only the engine idle sounds.',
"player":
'Location tag. Defines where the entry point and first-person camera are for a vehicle.',
"plank": 'Joint tag. (what does it do?)',
"propeller": 'Location tag. Defines where the propeller is in a boat.',
"reversebeep": 'Vehicle tag. Vehicle makes beeping sound when reversing.',
"skylift":
'Vehicle tag. All hinge joints on the vehicle are controllable with vehicle_raise/vehicle_lower, and display lift controls.',
"smoke":
'Shape tag. Emit smoke from shape. This is used/set/handled by the engine for pipebombs.',
"tank":
'Vehicle tag. Display tank controls. Handled by data/script/tank.lua and data/ui/hud.lua.',
"turbo":
'Shape tag. Similar to booster, but no smoke, the fire is blue, and it only works if the shape is part of a body that is part of a vehicle. Does nothing when the vehicle is inactive, small flames if the vehicle is idle, and big flames if driving forward. Set by the engine for vehicle boosters.',
"turn":
'Joint tag. Force joint to be controlled by left/right when in a crane vehicle.',
"unbreakable":
'Body and shape tag. Entity is breakable, but still markable by spray paint or burn marks.',
"vital":
'Location tag. When voxels at this position are broken, the vehicle is instantly broken. Defines where engine smoke comes from.',
"wire": 'Joint tag. (What it do?)'
}
autocomplete_tags = []
for key in TEARDOWN_TAGS:
autocomplete_tags.append(key)
@slash_command(name="tags", description="Get information about Teardown tags")
@slash_option(
name="autocomplete",
description="Enter the name of the tag",
required=True,
opt_type=OptionType.STRING,
autocomplete=True
)
async def _teardowntags(ctx: SlashContext, autocomplete: str):
try:
await check_mute(ctx)
except ValueError:
return
usage_statistics["Tags"] += 1
save_usage_statistics(usage_statistics) # Save the updated statistics
if autocomplete.lower() in TEARDOWN_TAGS:
response = TEARDOWN_TAGS[autocomplete.lower()]
title = f'Tag: {autocomplete}'
embed = interactions.Embed(title=title, description=response, color=0xbc9946)
embed.add_field(name="Credit", value="[Dennispedia](https://x4fx77x4f.github.io/dennispedia/teardown/tags.html)")
await ctx.send(embed=embed, delete_after=14400, silent=True)
else:
response = f'Tag "{autocomplete}" not found.'
title = f'Tag: {autocomplete}'
embed = interactions.Embed(title=title, description=response, color=0xbc9946)
await ctx.send(embed=embed, delete_after=4, silent=True)
@_teardowntags.autocomplete("autocomplete")
async def tags_autocomplete(ctx: AutocompleteContext):
matching_tags = [tag for tag in autocomplete_tags if tag.startswith(ctx.input_text.lower())]
matching_tags = sorted(matching_tags)[:25] # Get the first 25 matching tags
choices = [{"name": tag, "value": tag} for tag in matching_tags]
await ctx.send(choices=choices)
# Long ass list of every reg key
# Provided by the Dennispedia site, link is above
TEARDOWN_REGISTRY = {
"game.break":
'No description',
"game.break.size":
'No description',
"game.break.x":
'No description',
"game.break.y":
'No description',
"game.break.z":
'No description',
"game.brokenvoxels":
'Number of destroyed voxels. Integer. Small bodies despawning do not count toward this. Breaking off a chunk of something will not consider that chunk destroyed; only actually removing the voxels well cause them to be count.',
"game.canquickload":
'No description',
"game.deploy":
'Always set to 1. Boolean. If falsy, the main menu and hub computer scripts will try to display additional developer only controls, but the script that contains them (data/ui/debug.lua) has its contents stripped in all known builds—except 0.3.0 (perftest), which doesn\'t use a separate file.',
"game.disableinput":
'No description',
"game.disableinteract":
'No description',
"game.disablemap":
'No description',
"game.disablepause":
'No description',
"game.explosion.strength":
'No description',
"game.explosion.x":
'No description',
"game.explosion.y":
'No description',
"game.explosion.z":
'No description',
"game.fire.maxcount":
'No description',
"game.fire.spread":
'No description',
"game.input.crouch":
'No description',
"game.input.curdevice":
'String. Can be either mouse or gamepad.',
"game.input.down":
'No description',
"game.input.flashlight":
'No description',
"game.input.grab":
'No description',
"game.input.handbrake":
'No description',
"game.input.interact":
'No description',
"game.input.jump":
'No description',
"game.input.left":
'No description',
"game.input.lmb":
'No description',
"game.input.locktool":
'No description',
"game.input.map":
'No description',
"game.input.mmb":
'No description',
"game.input.mousex":
'No description',
"game.input.mousey":
'No description',
"game.input.pause":
'No description',
"game.input.right":
'No description',
"game.input.rmb":
'No description',
"game.input.scroll_down":
'No description',
"game.input.scroll_up":
'No description',
"game.input.up":
'No description',
"game.input.usetool":
'No description',
"game.input.vehicle_action":
'No description',
"game.input.vehicle_lower":
'No description',
"game.input.vehicle_raise":
'No description',
"game.largeui":
'No description',
"game.levelid":
'No description',
"game.levelpath":
'No description',
"game.loading.alpha":
'No description',
"game.loading.text":
'No description',
"game.map.enabled":
'No description',
"game.map.fade":
'No description',
"game.map.focus":
'No description',
"game.mod":
'No description',
"game.mod.description":
'No description',
"game.mod.title":
'No description',
"game.music.volume":
'No description',
"game.path":
'No description',
"game.path.alpha":
'No description',
"game.path.current.x":
'No description',
"game.path.current.y":
'No description',
"game.path.current.z":
'No description',
"game.path.length":
'No description',
"game.path.loaded":
'No description',
"game.path.pos":
'No description',
"game.path.recording":
'No description',
"game.paused":
'No description',
"game.player.cangrab":
'No description',
"game.player.caninteract":
'No description',
"game.player.canusetool":
'No description',
"game.player.disableinput":
'No description',
"game.player.grabbing":
'No description',
"game.player.health":
'No description',
"game.player.hit":
'No description',
"game.player.idling":
'No description',
"game.player.interactive":
'No description',
"game.player.steroid":
'No description',
"game.player.throwing":
'No description',
"game.player.tool":
'No description',
"game.player.tool.scope":
'No description',
"game.player.toolselect":
'No description',
"game.player.usescreen":
'No description',
"game.player.usevehicle":
'No description',
"game.saveerror":
'No description',
"game.savegamepath":
'No description',
"game.screenshot":
'No description',
"game.screenshot.bloom":
'No description',
"game.screenshot.dof.max":
'No description',
"game.screenshot.dof.min":
'No description',
"game.screenshot.dof.scale":
'No description',
"game.screenshot.exposure":
'No description',
"game.screenshot.tool":
'No description',
"game.steam.active":
'No description',
"game.steam.hascontroller":
'No description',
"game.steamdeck":
'No description',
"game.tool":
'No description',
"game.vehicle.health":
'No description',
"game.vehicle.interactive":
'No description',
"game.version":
'No description',
"game.version.patch":
'No description',
"game.workshop":
'No description',
"game.workshop.publish":
'No description',
"hud.aimdot":
'Whether to display the crosshair',
"hud.disable":
'No description',
"hud.hasnotification":
'No description',
"hud.notification":
'No description',
"level.campaign":
'No description',
"level.sandbox":
'No description',
"level.spawn":
'No description',
"level.unlimitedammo":
'No description',
"options.audio.ambiencevolume":
'Ambience volume as an integer',
"options.audio.menumusic":
'Whether to play music on the main menu',
"options.audio.musicvolume":
'Volume of music as percentage',
"options.audio.soundvolume":
'Volume of audio (excluding music) as percentage',
"options.display.mode":
'Windowed mode or fullscreen',
"options.display.resolution":
'Resolution preset to use',
"options.game.campaign.ammo":
'Amount of ammo to add to all tools as a percentage multiplier',
"options.game.campaign.health":
'Amount of health to add to max health as a percentage',
"options.game.campaign.time":
'Amount of time in seconds to add to timer after alarm goes off in missions',
"options.game.missionskipping":
'Whether the option to skip missions on the terminal and mission fail screens should appear',
"options.game.sandbox.unlocklevels":
'Whether all sandbox levels should be available, or only unlocked sandbox levels should be available',
"options.game.sandbox.unlocktools":
'Whether all tools should always be available in sandbox, or whether only unlocked tools should be available',
"options.game.spawn":
'Whether the spawn menu should always be accessible, or only be accessible in sandbox mode. Integer. 1 is enabled, 0 is disabled.',
"options.gfx.barrel":
'Whether barrel distortion should be used. Integer. 1 is enabled, 0 is disabled.',
"options.gfx.dof":
'Whether depth of field should be used. Integer. 1 is enabled, 0 is disabled.',
"options.gfx.fov":
'Field of view in degrees. Integer. Defaults to 90. Intended range is 60 to 120.',
"options.gfx.gamma":
'Gamma correction. Integer. Lower makes the scene darker, higher makes the scene brighter. Defaults to 100. Intended range is 50 to 150.',
"options.gfx.motionblur":
'Whether motion blur should be used. Integer. 1 is enabled, 0 is disabled.',
"options.gfx.quality":
'Render quality. Integer. High is 1, low is 2, medium is 3.',
"options.gfx.renderscale":
'Render scale as a percentage. Integer. Intended values are 100, 75, or 50, but higher values work. Lower values not tested.',
"options.gfx.vsync":
'Whether vertical synchronization should be used. Integer. 0 is disabled, 1 is "every frame", 2 is "every other frame". -1 is adaptive.',
"options.input.headbob":
'Percentage for how much the camera should tilt in response to player movement, and screen shaking on swing/gunshot. Does not affect shake on explosion. Integer. Defaults to 0. Intended range is 0 to 100.',
"options.input.invert":
'Whether to invert vertical look movement. Integer. 1 is enabled, 0 is disabled.',
"options.input.keymap.backward":
'No description',
"options.input.keymap.crouch":
'No description',
"options.input.keymap.flashlight":
'No description',
"options.input.keymap.forward":
'No description',
"options.input.keymap.interact":
'No description',
"options.input.keymap.jump":
'No description',
"options.input.keymap.left":
'No description',
"options.input.keymap.right":
'No description',
"options.input.sensitivity":
'Mouse sensitivity percentage multiplier. Integer. Defaults to 100. Intended range is 25 to 200.',
"options.input.smoothing":
'Mouse smoothing percentage. Integer. Defaults to 0. Intended range is 0 to 100.',
"options.input.toolsway":
'Whether tool models should sway and move in response to player movement. Integer. 1 is enabled, 0 is disabled.',
"promo.available":
'No description',
"promo.groups":
'No description',
"savegame.cash":
'No description',
"savegame.challenge":
'No description',
"savegame.hint.targetlocation":
'No description',