1+ """
2+ @ Name: KomFetch
3+ @ Author: ElofHew
4+ @ Version: 1.2
5+ @ Description: A tool to display system information on Quarter OS.
6+ @ Date: 2025-07-17
7+ @ Last Modified: 2025-07-28
8+ @ License: GNU General Public License v3.0
9+ @ Repository: https://github.com/ElofHew/komfetch
10+ @ Class: Biscuit Application Package
11+ """
12+
13+ try :
14+ # 导入所需模块
15+ import os
16+ import sys
17+ import json
18+ import shutil
19+ import time as tm
20+ import platform
21+ from colorama import Fore , Back , Style , init
22+ except ImportError as e :
23+ print (f"Error: { e } " )
24+ exit ()
25+ except Exception as e :
26+ print (f"Error: { e } " )
27+ exit ()
28+
29+ # 初始化颜色模块
30+ init (autoreset = True )
31+
32+ qos_big_logo = """
33+ %%%%%%%%%%
34+ %%%%%%%%%%%%%%%%%%%%
35+ %%%%%%%%%%%%%%%%%%%%%%%%%%
36+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37+ %%%%%%%%%%%% %%%%%%%%%%%%
38+ %%%%%%%%%% %%%%%%%%%%
39+ %%%%%%%%% %%%%%%%%%
40+ %%%%%%%%% %%%%%%%%%
41+ %%%%%%%% %%%% %%%%%%%%
42+ %%%%%%%% %%%%%%%% %%%%%%%%
43+ %%%%%%%% %%%%%%%% %%%%%%%%
44+ %%%%%%%% %%%%% %%%%%%%%
45+ %%%%%%%%% %%% %%%%%%%%%
46+ %%%%%%%%% %%%%%%%%%%%%%%
47+ %%%%%%%%%% %%%%%%%%%%%%%
48+ %%%%%%%%%%%% %%%%%%%%%%%%
49+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
50+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
51+ %%%%%%%%%%%%%%%%%%%% %%%%%%%%
52+ %%%%%%%%%% %%%%
53+ """
54+ # 喜不喜欢我们Quarter OS的比格楼狗[doge]
55+
56+ def get_package_value ():
57+ """获取Quarter OS中,Biscuit和Shizuku的软件包数量"""
58+ # 定义全局变量:biscuit_value和shizuku_value
59+ global biscuit_value , shizuku_value
60+ # 获取Biscuit软件包数量
61+ try :
62+ with open (os .path .join (".." , ".." , ".." , "system" , "shell" , "apps.json" ), "r" ) as biscuit_sys_file :
63+ biscuit_sys_data = json .load (biscuit_sys_file )
64+ biscuit_value_sys = len (biscuit_sys_data )
65+ with open (os .path .join (".." , ".." , "shell" , "apps.json" ), "r" ) as biscuit_3rd_file :
66+ biscuit_3rd_data = json .load (biscuit_3rd_file )
67+ biscuit_value_3rd = len (biscuit_3rd_data )
68+ biscuit_value = biscuit_value_sys + biscuit_value_3rd
69+ except (FileNotFoundError , json .JSONDecodeError , TypeError , KeyError , IndexError , AttributeError , ValueError , OSError , IOError ):
70+ # 若文件不存在或格式错误,则置0
71+ biscuit_value = 0
72+ except Exception as e :
73+ # 其他错误,打印错误信息并退出程序
74+ print (f"{ Fore .RED } Error: { e } { Style .RESET_ALL } " )
75+ sys .exit (1 )
76+ # 获取Shizuku软件包数量
77+ try :
78+ with open (os .path .join (".." , ".." , "shizuku" , "apps.json" ), "r" ) as shizuku_file :
79+ shizuku_data = json .load (shizuku_file )
80+ shizuku_value = len (shizuku_data )
81+ except (FileNotFoundError , json .JSONDecodeError , TypeError , KeyError , IndexError , AttributeError , ValueError , OSError , IOError ):
82+ # 若文件不存在或格式错误,则置0
83+ shizuku_value = 0
84+ except Exception as e :
85+ # 其他错误,打印错误信息并退出程序
86+ print (f"{ Fore .RED } Error: { e } { Style .RESET_ALL } " )
87+ sys .exit (1 )
88+
89+ def get_logo_text (i , logo_lines_num , qos_logo_lines , qos_logo_line_width ):
90+ """获取logo的每一行文字"""
91+ logo_text = f"{ Fore .BLUE } { qos_logo_lines [i ]} { Style .RESET_ALL } " if i < logo_lines_num else " " * qos_logo_line_width
92+ return logo_text
93+
94+ def get_cut_length (i , infos_num , terminal_width , qos_logo_line_width , infos , double ):
95+ """获取info的一行文字截断处理后的文字"""
96+ if i < infos_num :
97+ used_space = qos_logo_line_width if double else 0 + infos [i ][2 ] + 1 # 最后留一个空字符
98+ free_space = terminal_width - used_space # 供info[i][1]使用的剩余空间
99+ dynamical_space = len (infos [i ][1 ]) # 计算当前值需要占用的空间
100+ combined_space = dynamical_space - free_space # 计算需要截断的字符长度(后面的部分)
101+ if combined_space > 0 : # 计算结果大于0,说明需要截断
102+ dyna_words = infos [i ][1 ][0 :dynamical_space - combined_space ] # 截断后的字符串
103+ else : # 计算结果小于等于0,说明不需要截断
104+ dyna_words = infos [i ][1 ] # 直接输出原字符串
105+ info_text = infos [i ][0 ] + dyna_words # 键 + 值
106+ else :
107+ info_text = " " * (terminal_width - qos_logo_line_width ) # 若info_num大于实际数量,则填充空白
108+ return info_text
109+
110+ def main ():
111+ """主函数"""
112+ # 获取参数
113+ args = sys .argv [1 :] if len (sys .argv ) > 1 else []
114+ mode = args [0 ] if args else "--default"
115+
116+ # 获取Quarter OS的配置文件数据
117+ try :
118+ config_path = os .path .join (".." , ".." , "config" , "config.json" )
119+ with open (config_path , "r" ) as qos_config_file :
120+ config_data = json .load (qos_config_file )
121+ qos_name = config_data .get ("name" , "other" )
122+ qos_version = config_data .get ("version" , "?" )
123+ qos_vercode = config_data .get ("vercode" , "?" )
124+ qos_node = config_data .get ("system_name" , "qos" )
125+ qos_path = config_data .get ("qos_path" , "unknown path" )
126+ qos_activate = config_data .get ("activate_statue" , False )
127+ qos_edition = config_data .get ("qos_edition" , "unknown edition" )
128+ last_login = config_data .get ("last_login" , "user" )
129+ if qos_name .lower ().replace (" " , "" ) != "quarteros" :
130+ raise ValueError # 不是Quarter OS,提示错误并退出程序
131+ except ValueError :
132+ # 提示信息
133+ print (f"{ Fore .LIGHTGREEN_EX } This tool only works on Quarter OS version Alpha 0.2.2 or later.{ Style .RESET_ALL } " )
134+ sys .exit (1 )
135+ except FileNotFoundError :
136+ print (f"{ Fore .RED } Error: Config file not found.{ Style .RESET_ALL } " )
137+ sys .exit (1 )
138+ except json .JSONDecodeError :
139+ print (f"{ Fore .RED } Error: Config file is not a valid JSON file.{ Style .RESET_ALL } " )
140+ sys .exit (1 )
141+ except Exception as e :
142+ print (f"{ Fore .RED } Error: { e } { Style .RESET_ALL } " )
143+ sys .exit (1 )
144+
145+ get_package_value () # 获取Biscuit和Shizuku的软件包数量
146+
147+ # 从动态数据中获取信息转换为实打实的字符串
148+ item_OS_name = str (f"{ qos_name } { qos_version } ({ qos_vercode } )" )
149+ item_qos_path = str (qos_path )
150+ item_qos_login = str (f"{ last_login } @{ qos_node } " )
151+ item_qos_shell = str (f"KomShell - { qos_version } " )
152+ item_qos_pks = str (f"Biscuit({ biscuit_value } ) | Shizuku({ shizuku_value } )" )
153+ item_qos_edition = str (qos_edition )
154+ item_sys_platform = str (f"{ platform .system ()} { platform .release ()} " )
155+ item_sys_version = str (platform .version ())
156+ item_sys_name = str (platform .node ())
157+ item_sys_arch = str (platform .machine ())
158+ item_sys_cpu = str (platform .processor ())
159+ item_py_ver = str (platform .python_version ())
160+ item_py_exec = str (sys .executable )
161+
162+ # 准备输出信息的一些数据变量
163+ qos_logo_lines = qos_big_logo .split ('\n ' ) # 将logo分割成行(应该是22行)
164+ qos_logo_line_width = len (qos_logo_lines [0 ]) # logo每一行的长度(单位:字符)
165+ terminal_width = shutil .get_terminal_size ().columns # 获取终端宽度(单位:字符)
166+
167+ # 输出信息的键(以列表形式存储)
168+ infos = [
169+ [" " , "" , 1 ],
170+ [f"{ Fore .LIGHTMAGENTA_EX } KomFetch System Fetcher{ Fore .RESET } " , "" , 23 ],
171+ ["-" * 23 , "" , 23 ],
172+ [f"{ Fore .YELLOW } OS: { Style .RESET_ALL } " , item_OS_name , 4 ],
173+ [f"{ Fore .YELLOW } Path: { Style .RESET_ALL } " , item_qos_path , 6 ],
174+ [f"{ Fore .YELLOW } Login: { Style .RESET_ALL } " , item_qos_login , 7 ],
175+ [f"{ Fore .YELLOW } Shell: { Style .RESET_ALL } " , item_qos_shell , 7 ],
176+ [f"{ Fore .YELLOW } Packages: { Style .RESET_ALL } " , item_qos_pks , 10 ],
177+ [f"{ Fore .YELLOW } Edition: { Style .RESET_ALL } " , item_qos_edition if qos_activate else "" , 9 ],
178+ [f"{ Fore .CYAN } Platform: { Style .RESET_ALL } " , item_sys_platform , 10 ],
179+ [f"{ Fore .CYAN } Version: { Style .RESET_ALL } " , item_sys_version , 9 ],
180+ [f"{ Fore .CYAN } Node: { Style .RESET_ALL } " , item_sys_name , 6 ],
181+ [f"{ Fore .CYAN } Arch: { Style .RESET_ALL } " , item_sys_arch , 6 ],
182+ [f"{ Fore .CYAN } CPU: { Style .RESET_ALL } " , item_sys_cpu , 5 ],
183+ [f"{ Fore .LIGHTRED_EX } Python: { Style .RESET_ALL } " , item_py_ver , 8 ],
184+ [f"{ Fore .LIGHTRED_EX } Exec: { Style .RESET_ALL } " , item_py_exec , 6 ],
185+ [" " , "" , 1 ],
186+ [f"{ Fore .LIGHTGREEN_EX } Fetched at { tm .strftime ('%Y-%m-%d %H:%M:%S' , tm .localtime ())} { Style .RESET_ALL } " , "" , 30 ],
187+ [" " , "" , 1 ],
188+ [f"{ Back .BLACK } { Back .RED } { Back .GREEN } { Back .YELLOW } { Back .BLUE } { Back .MAGENTA } { Back .CYAN } { Back .WHITE } { Style .RESET_ALL } " , "" , 24 ],
189+ [f"{ Back .LIGHTBLACK_EX } { Back .LIGHTRED_EX } { Back .LIGHTGREEN_EX } { Back .LIGHTYELLOW_EX } { Back .LIGHTBLUE_EX } { Back .LIGHTMAGENTA_EX } { Back .LIGHTCYAN_EX } { Back .LIGHTWHITE_EX } { Style .RESET_ALL } " , "" , 24 ],
190+ [" " , "" , 1 ]
191+ ]
192+ # 其中,0为info的键,1为info的值,2为键的长度
193+
194+ # Logo和info的行数
195+ logo_lines_num = len (qos_logo_lines )
196+ infos_num = len (infos )
197+
198+ # 确定需要打印的行数(分两列,哪一列长取哪一列)
199+ if logo_lines_num > infos_num : # logo行数大于info行数,则需要补充空行
200+ need_lines_num = logo_lines_num
201+ else : # logo行数小于等于info行数,则直接输出
202+ need_lines_num = infos_num
203+
204+ max_line_width = qos_logo_line_width + 30 + 1 # 最大行宽度()
205+
206+ def default_mode ():
207+ """默认模式:终端宽度足够时打印两列,否则logo在上,info在下,组成一列。"""
208+ if terminal_width >= max_line_width : # 终端宽度大于等于最大行宽度,则打印两列
209+ # 循环打印一行logo和一条info
210+ for i in range (need_lines_num ):
211+ logo_text = get_logo_text (i , logo_lines_num , qos_logo_lines , qos_logo_line_width )
212+ info_text = get_cut_length (i , infos_num , terminal_width , qos_logo_line_width , infos , True )
213+ line_text = logo_text + info_text # 组合成一行
214+ print (line_text .ljust (terminal_width )) # 左对齐,右边用空格填充,打印出来
215+ else : # 终端宽度小于最大行宽度,则打印一列
216+ # 先循环打印logo,然后循环打印info
217+ for i1 in range (logo_lines_num ):
218+ logo_text = get_logo_text (i1 , logo_lines_num , qos_logo_lines , qos_logo_line_width )
219+ print (logo_text .ljust (terminal_width )) # 左对齐,右边用空格填充,打印出来
220+ for i2 in range (infos_num ):
221+ info_text = get_cut_length (i2 , infos_num , terminal_width , qos_logo_line_width , infos , False )
222+ print (info_text .ljust (terminal_width )) # 左对齐,右边用空格填充,打印出来
223+ # 各输出各的
224+
225+ def stdout_mode ():
226+ """标准输出模式:打印一列,只有info自成一列,且没有颜色块。"""
227+ # 循环打印一行info
228+ for i in range (infos_num - 3 ): # 这个-3是为了去掉最后的颜色块
229+ info_text = get_cut_length (i , infos_num , terminal_width , qos_logo_line_width , infos , False )
230+ print (info_text .ljust (terminal_width )) # 左对齐,右边用空格填充,打印出来
231+
232+ if mode == "-h" : # 打印帮助信息
233+ print (f"{ Fore .LIGHTGREEN_EX } Usage: komfetch [mode]{ Style .RESET_ALL } " )
234+ print (f"{ Fore .CYAN } Mode:{ Style .RESET_ALL } " )
235+ print (f"{ Fore .CYAN } (No Argument): Default mode.{ Style .RESET_ALL } " )
236+ print (f"{ Fore .CYAN } --default: Default mode, print two columns.{ Style .RESET_ALL } " )
237+ print (f"{ Fore .CYAN } --stdout: Standard output mode, print one column.{ Style .RESET_ALL } " )
238+ print (f"{ Fore .CYAN } -h: Print help information.{ Style .RESET_ALL } " )
239+ print ()
240+ sys .exit (0 )
241+ elif mode == "--default" : # 默认模式,打印两列
242+ default_mode ()
243+ elif mode == "--stdout" : # 标准输出模式,打印一列
244+ stdout_mode ()
245+ else : # 其他参数,打印错误信息
246+ print (f"{ Fore .RED } Error: Invalid argument.{ Style .RESET_ALL } " )
247+ sys .exit (1 )
248+
249+ if __name__ == "__main__" :
250+ main ()
0 commit comments