44VERSION_MANIFEST = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
55BUNGEECORD_DOWNLOAD_URL = "https://ci.md-5.net/job/BungeeCord/lastStableBuild/artifact/bootstrap/target/BungeeCord.jar"
66APP_VERSION = 1 #The API Version.
7- APP_UF_VERSION = "1.50.5 "
7+ APP_UF_VERSION = "1.50.6 "
88#The semver version
99UPDATEINSTALLED = False
1010DOCFILE = "https://github.com/Enderbyte-Programs/CraftServerSetup/raw/main/doc/craftserversetup.epdoc"
1919import sys #C Utilities
2020import os #OS System Utilities
2121import curses #NCurses Terminal Library
22- import curses .textpad #Geometry drawing extension
2322import json #JSON Parser
2423import signal #Unix Signal Interceptor
2524import datetime #Getting current date
4241import io #File streams
4342import shlex #Data parsing
4443import re #Pattern matching
44+ import typing #HArd types
4545
4646WINDOWS = platform .system () == "Windows"
4747
4848if sys .version_info < (3 ,10 ):
49- print ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!" )
50- print ("!!!!!! Serious Error !!!!!!" )
51- print ("!! Python version too old !!" )
52- print ("! Use version 3.7 or newer !" )
53- print ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!" )
49+ print ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!! " )
50+ print ("!!!!!!! Serious Error ! !!!!!!" )
51+ print ("!! Python version too old !!" )
52+ print ("! Use version 3.10 or newer !" )
53+ print ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!! " )
5454 input ("Press enter to halt -->" )
5555 sys .exit (5 )
5656
@@ -531,6 +531,7 @@ def extract_links_from_page(data: str) -> list[str]:
531531 return final
532532
533533class ServerRunWrapper :
534+ process :typing .Any
534535 def __init__ (self ,command ):
535536 self .command = command
536537 self .datastream = io .StringIO ()
@@ -560,16 +561,16 @@ def fullhalt(self):
560561 self .process .kill ()
561562 except :
562563 return #Already dead
563- def getexitcode (self ):
564+ def getexitcode (self ) -> None | int :
564565 if not self .isprocessrunning () :
565566 return self .process .returncode
566567 else :
567568 return None
568569 def hascrashed (self ):
569- if self .getexitcode () is None :
570+ code = self .getexitcode ()
571+ if code is None :
570572 return None
571573 else :
572- code = self .getexitcode ()
573574 if code != 0 :
574575 if code < 128 or code > 130 :
575576 return True
@@ -647,6 +648,7 @@ def send_telemetry():
647648 #cursesplus.textview(_SCREEN,text=str(rdx))
648649 r = requests .post ("http://enderbyteprograms.net:11111/craftserversetup/call" ,data = str (json .dumps (rdx )),headers = {"Content-Type" :"application/json" })
649650def parse_size (data : int ) -> str :
651+ result :str = ""
650652 if data < 0 :
651653 neg = True
652654 data = - data
@@ -699,7 +701,7 @@ def error_handling(e:Exception,message="A serious error has occured"):
699701 cursesplus .messagebox .showerror (_SCREEN ,["This feature is not available on Windows" ])
700702 continue
701703 if cursesplus .messagebox .askyesno (_SCREEN ,["This will re-install CraftServerSetup and restore any lost files." ,"You will need to have a release downloaded" ]):
702- flz = cursesplus .filedialog .openfiledialog (_SCREEN ,"Please choose the release file" ,[["*.xz" ,"crss XZ Release" ],["*" ,"All files" ]])
704+ flz = cursesplus .filedialog .openfiledialog (_SCREEN ,"Please choose the release file" ,[["*.xz" ,"crss XZ Release" ],["*" ,"All files" ]]) # type: ignore
703705 _SCREEN .erase ()
704706 _SCREEN .refresh ()
705707 curses .reset_shell_mode ()
@@ -871,7 +873,7 @@ def package_server(stdscr,serverdir:str,chosenserver:int):
871873 os .remove (serverdir + "/exdata.json" )
872874 cursesplus .messagebox .showinfo (stdscr ,["Server is packaged." ])
873875
874- def download_vanilla_software (stdscr ,serverdir ) -> dict :
876+ def download_vanilla_software (stdscr ,serverdir ) -> dict | None :
875877 cursesplus .displaymsg (stdscr ,["Getting version information" ],False )
876878
877879 stdscr .clear ()
@@ -908,6 +910,8 @@ def download_spigot_software(stdscr,serverdir,javapath) -> dict:
908910 proc = subprocess .Popen ([javapath ,"-jar" ,"BuildTools.jar" ,"--rev" ,xver ],shell = False ,stdout = subprocess .PIPE )
909911 tick = 0
910912 while True :
913+ if proc .stdout is None :
914+ continue
911915 output = proc .stdout .readline ()
912916 if proc .poll () is not None :
913917 break
@@ -1000,6 +1004,9 @@ def __init__(self,file:str,date:datetime.date,data:str,indict=None):
10001004 if is_log_line_a_chat_line (data ):
10011005 if "[Server]" in data :
10021006 self .playername = "[Server]"
1007+ elif "issued server command" in data :
1008+ #Handle targetted chat
1009+ self .playername = data .split (" issued server" )[0 ].split (" " )[- 1 ]
10031010 else :
10041011 self .playername = data .split ("<" )[1 ].split (">" )[0 ]
10051012 else :
@@ -1041,7 +1048,7 @@ def load_log_entries_from_raw_data(data:str,fromfilename:str) -> list[LogEntry]:
10411048 return final
10421049
10431050def is_log_line_a_chat_line (line :str ) -> bool :
1044- if (("INFO" in line and "<" in line and ">" in line ) or "[Server]" in line ) and "chat" in line .lower ():
1051+ if ((( "INFO" in line and "<" in line and ">" in line ) or "[Server]" in line ) and "chat" in line .lower ()) or "issued server command: /msg" in line or "issued server command: /w " in line or "issued server command: /tell" in line :
10451052 return True
10461053 else :
10471054 return False
@@ -2666,11 +2673,45 @@ def load_server_logs(stdscr,serverdir:str,dosort=True,load_no_earlier_than:datet
26662673 #p.done()
26672674 return allentries
26682675
2676+ class ChatEntry :
2677+ logtime :datetime .datetime
2678+ playername :str
2679+ destination :str | None = None #Only used if targetted message
2680+ message :str
2681+
2682+ def __init__ (self ):
2683+ pass
2684+
2685+ def from_logentry (l :LogEntry ):
2686+ r = ChatEntry ()
2687+ r .logtime = l .get_full_log_time ()
2688+ r .playername = l .playername
2689+ rawmessage :str = l .data
2690+
2691+ is_targetted = "issued server command" in rawmessage .lower ()#Spigot/paper servers only
2692+ if not is_targetted :
2693+ r .message = rawmessage .split (l .playername )[1 ][1 :]
2694+ else :
2695+ try :
2696+ cmdsection = rawmessage .split ("issued server command: " )[1 ]
2697+ target = cmdsection .split (" " )[1 ]
2698+ r .destination = target
2699+ r .message = f"--> { r .destination } " + " " .join (cmdsection .split (" " )[2 :])
2700+ pass
2701+ except :
2702+ r .message = rawmessage #If it fails, use the raw data
2703+
2704+ return r
2705+
2706+ def get_parsed_date (self ) -> str :
2707+ #Return a friendly date
2708+ return self .logtime .strftime ("%Y-%m-%d %H:%M:%S" )
2709+
26692710def who_said_what (stdscr ,serverdir ):
26702711 if resource_warning (stdscr ):
26712712 return
26722713 allentries = load_server_logs (stdscr ,serverdir )
2673- allentries :list [LogEntry ] = [a for a in allentries if is_log_entry_a_chat_line (a )]
2714+ allentries :list [ChatEntry ] = [ChatEntry . from_logentry ( a ) for a in allentries if is_log_entry_a_chat_line (a )]
26742715 #allentries.sort(key=lambda x: x.logdate,reverse=False)
26752716 #Not needed after LSL does sorting
26762717 #allentries.reverse()
@@ -2683,26 +2724,26 @@ def who_said_what(stdscr,serverdir):
26832724 strict = cursesplus .messagebox .askyesno (stdscr ,["Do you want to search strictly?" ,"(Full words only)" ])
26842725 cassen = cursesplus .messagebox .askyesno (stdscr ,["Do you want to be case sensitive?" ])
26852726 if not strict and cassen :
2686- ft = "\n " .join ([f"{ a .logdate } { a . data . split ( ' ' )[ 0 ][ 1 :]. replace ( ']' , '' ) } { a .playername } : { a .data . split ( a . playername )[ 1 ][ 1 :] } " for a in allentries if wws in a .data ])
2727+ ft = "\n " .join ([f"{ a .get_parsed_date () } { a .playername } : { a .message } " for a in allentries if wws in a .message ])
26872728 elif not strict and not cassen :
2688- ft = "\n " .join ([f"{ a .logdate } { a . data . split ( ' ' )[ 0 ][ 1 :]. replace ( ']' , '' ) } { a .playername } : { a .data . split ( a . playername )[ 1 ][ 1 :] } " for a in allentries if wws .lower () in a .data .lower ()])
2729+ ft = "\n " .join ([f"{ a .get_parsed_date () } { a .playername } : { a .message } " for a in allentries if wws .lower () in a .message .lower ()])
26892730 elif strict and cassen :
2690- ft = "\n " .join ([f"{ a .logdate } { a . data . split ( ' ' )[ 0 ][ 1 :]. replace ( ']' , '' ) } { a .playername } : { a .data . split ( a . playername )[ 1 ][ 1 :] } " for a in allentries if strict_word_search (a .data ,wws )])
2731+ ft = "\n " .join ([f"{ a .get_parsed_date () } { a .playername } : { a .message } " for a in allentries if strict_word_search (a .message ,wws )])
26912732 elif strict and not cassen :
2692- ft = "\n " .join ([f"{ a .logdate } { a . data . split ( ' ' )[ 0 ][ 1 :]. replace ( ']' , '' ) } { a .playername } : { a .data . split ( a . playername )[ 1 ][ 1 :] } " for a in allentries if strict_word_search (a .data .lower (),wws .lower ())])
2733+ ft = "\n " .join ([f"{ a .get_parsed_date () } { a .playername } : { a .message } " for a in allentries if strict_word_search (a .message .lower (),wws .lower ())])
26932734 cursesplus .textview (stdscr ,text = ft ,message = "Search Results" )
26942735 elif wtd == 2 :
26952736 wws = crssinput (stdscr ,"What player would you like to search for?" )
26962737 cursesplus .textview (stdscr ,text =
26972738 "\n " .join (
26982739 [
2699- f"{ a .logdate } { a . data . split ( ' ' )[ 0 ][ 1 :]. replace ( ']' , '' ) } { a .playername } : { a .data . split ( a . playername )[ 1 ][ 1 :] } " for a in allentries if wws in a .playername
2740+ f"{ a .get_parsed_date () } { a .playername } : { a .message } " for a in allentries if wws in a .playername
27002741 ]),message = "Search Results" )
27012742 elif wtd == 3 :
27022743 cursesplus .textview (stdscr ,text =
27032744 "\n " .join (
27042745 [
2705- f"{ a .logdate } { a . data . split ( ' ' )[ 0 ][ 1 :]. replace ( ']' , '' ) } { a .playername } : { a .data . split ( a . playername )[ 1 ][ 1 :] } " for a in allentries
2746+ f"{ a .get_parsed_date () } { a .playername } : { a .message } " for a in allentries
27062747 ]),message = "Search Results" )
27072748 elif wtd == 4 :
27082749 sdata = {}
@@ -2752,7 +2793,7 @@ def ip_lookup(stdscr,serverdir):
27522793 with open (ipdir ,'rb' ) as f :
27532794 formattedips = [FormattedIP .fromdict (d ) for d in json .loads (gzip .decompress (f .read ()).decode ())]
27542795 except Exception as e :
2755- + cursesplus .messagebox .showerror (stdscr ,["Error loading cache" ,str (e )])
2796+ cursesplus .messagebox .showerror (stdscr ,["Error loading cache" ,str (e )])
27562797 os .remove (ipdir )
27572798
27582799 bigfatstring = "\n " .join (l .data for l in allentries )
@@ -3780,7 +3821,7 @@ def manage_server(stdscr,_sname: str,chosenserver: int):
37803821 playerstat (stdscr ,SERVER_DIR )
37813822 elif w == 15 :
37823823 file_manager (stdscr ,SERVER_DIR ,f"Files of { APPDATA ['servers' ][chosenserver - 1 ]['name' ]} " )
3783- _SCREEN = None
3824+ _SCREEN : typing . Any = None
37843825
37853826def get_nested_keys (d , parent_key = '' ):
37863827 keys = []#COPIED
0 commit comments