pymud 0.20.4__py3-none-any.whl → 0.21.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pymud/session.py CHANGED
@@ -1,17 +1,17 @@
1
- import asyncio, logging, re, math, os, pickle, datetime, importlib, importlib.util, sysconfig, time, dataclasses
1
+ import asyncio, logging, re, math, os, pickle, datetime, sysconfig, time, dataclasses
2
+ from pathlib import Path
2
3
  from collections.abc import Iterable
3
4
  from collections import OrderedDict
4
- import logging, queue
5
- from logging import FileHandler
6
- from logging.handlers import QueueHandler, QueueListener
5
+ from prompt_toolkit.utils import get_cwidth
7
6
  from wcwidth import wcswidth, wcwidth
7
+ from typing import Union, Optional, Any, List, Tuple, Dict, Type
8
8
  from .logger import Logger
9
9
  from .extras import SessionBuffer, DotDict
10
10
  from .protocol import MudClientProtocol
11
- from .modules import ModuleInfo
11
+ from .modules import ModuleInfo, Plugin
12
12
  from .objects import BaseObject, Trigger, Alias, Command, Timer, SimpleAlias, SimpleTrigger, SimpleTimer, GMCPTrigger, CodeBlock, CodeLine
13
13
  from .settings import Settings
14
-
14
+ from .decorators import exception, async_exception
15
15
 
16
16
  class Session:
17
17
  """
@@ -102,6 +102,7 @@ class Session:
102
102
  "t+" : "ignore",
103
103
  "t-" : "ignore",
104
104
  "show": "test",
105
+ "echo": "test",
105
106
  }
106
107
 
107
108
  def __init__(self, app, name, host, port, encoding = None, after_connect = None, loop = None, **kwargs):
@@ -159,28 +160,36 @@ class Session:
159
160
  self.encoding = encoding or self.encoding
160
161
  self.after_connect = after_connect
161
162
 
162
- # 插件处置移动到 pymud.py 2024-3-22
163
- # for plugin in app.plugins.values():
164
- # if isinstance(plugin, Plugin):
165
- # plugin.onSessionCreate(self)
166
-
167
163
  self._modules = OrderedDict()
168
164
 
169
165
  # 将变量加载和脚本加载调整到会话创建时刻
170
166
  if Settings.client["var_autoload"]:
171
- file = f"{self.name}.mud"
167
+ muddir = Path.cwd().joinpath('save')
168
+ if not muddir.exists() or not muddir.is_dir():
169
+ muddir.mkdir()
170
+
171
+ # 处理老版本当前目录的.mud文件,移动到save目录下
172
+ file = f"{self.name}.mud"
173
+ new_loc_file = muddir.joinpath(file)
174
+ if not os.path.exists(new_loc_file):
172
175
  if os.path.exists(file):
173
- with open(file, "rb") as fp:
174
- try:
175
- vars = pickle.load(fp)
176
- self._variables.update(vars)
177
- self.info(f"自动从{file}中加载保存变量成功")
178
- except Exception as e:
179
- self.warning(f"自动从{file}中加载变量失败,错误消息为: {e}")
176
+ os.rename(file, new_loc_file)
177
+ else:
178
+ if os.path.exists(file):
179
+ os.remove(file)
180
+
181
+ if os.path.exists(new_loc_file):
182
+ with open(new_loc_file, "rb") as fp:
183
+ try:
184
+ vars = pickle.load(fp)
185
+ self._variables.update(vars)
186
+ self.info(Settings.gettext("msg_var_autoload_success", file))
187
+ except Exception as e:
188
+ self.warning(Settings.gettext("msg_var_autoload_fail", file, e))
180
189
 
181
190
 
182
191
  if self._auto_script:
183
- self.info(f"即将自动加载以下模块:{self._auto_script}")
192
+ self.info(Settings.gettext("msg_auto_script", self._auto_script))
184
193
  self.load_module(self._auto_script)
185
194
 
186
195
  if Settings.client["auto_connect"]:
@@ -205,7 +214,7 @@ class Session:
205
214
  #self._tasks = []
206
215
  self._tasks = set()
207
216
 
208
- self._command_history = []
217
+ self._command_history: List[str] = []
209
218
 
210
219
  def open(self):
211
220
  "创建到远程服务器的连接,同步方式。通过调用异步connect方法实现。"
@@ -214,7 +223,7 @@ class Session:
214
223
  async def connect(self):
215
224
  "创建到远程服务器的连接,异步非阻塞方式。"
216
225
  def _protocol_factory():
217
- return MudClientProtocol(self, onDisconnected = self.onDisconnected)
226
+ return MudClientProtocol(self, onDisconnected = self.onDisconnected, encoding = self.encoding, encoding_errors = Settings.server["encoding_errors"])
218
227
 
219
228
  try:
220
229
  #self.loop = asyncio.get_running_loop()
@@ -229,27 +238,27 @@ class Session:
229
238
 
230
239
  except Exception as exc:
231
240
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
232
- self.error(f"创建连接过程中发生错误, 错误发生时刻 {now}, 错误信息为 {exc}, ")
241
+ self.error(Settings.gettext("msg_connection_fail", now, exec))
233
242
  self._state = "EXCEPTION"
234
243
 
235
244
  if Settings.client["auto_reconnect"]:
236
245
  wait = Settings.client.get("reconnect_wait", 15)
237
246
  asyncio.ensure_future(self.reconnect(wait), loop = self.loop)
238
247
 
239
- async def reconnect(self, timeout = 15):
248
+ async def reconnect(self, timeout: float = 15):
240
249
  """
241
250
  重新连接到远程服务器,异步非阻塞方式。该方法在 `Settings.client['auto_reconnect']` 设置为真时,断开后自动调用
242
251
 
243
252
  :param timeout: 重连之前的等待时间,默认15s,可由 `Settings.client['reconnect_wait']` 设置所覆盖
244
253
  """
245
- self.info(f"{timeout}秒之后将自动重新连接...")
254
+ self.info(Settings.gettext("msg_auto_reconnect", timeout))
246
255
  await asyncio.sleep(timeout)
247
256
  await self.create_task(self.connect())
248
257
 
249
258
  def onConnected(self):
250
259
  "当连接到服务器之后执行的操作。包括打印连接时间,执行自定义事件(若设置)等。"
251
260
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
252
- self.info(f"{now}: 已成功连接到服务器")
261
+ self.info(Settings.gettext("msg_connected", now))
253
262
  if isinstance(self.after_connect, str):
254
263
  self.writeline(self.after_connect)
255
264
 
@@ -270,7 +279,7 @@ class Session:
270
279
 
271
280
  self.clean()
272
281
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
273
- self.info(f"{now}: 与服务器连接已断开")
282
+ self.info(Settings.gettext("msg_disconnected", now))
274
283
 
275
284
  event_disconnected = self._events["disconnected"]
276
285
  if callable(event_disconnected):
@@ -367,7 +376,7 @@ class Session:
367
376
  def event_disconnected(self, event):
368
377
  self._events["disconnected"] = event
369
378
 
370
- def getLogger(self, name, mode = 'a', encoding = 'utf-8', encoding_errors = 'ignore', raw = False) -> Logger:
379
+ def getLogger(self, name: str, mode = 'a', encoding = 'utf-8', encoding_errors = 'ignore', raw = False) -> Logger:
371
380
  """
372
381
  根据指定名称和参数获取并返回一个记录器。若指定名称不存在,则创建一个该名称记录器。
373
382
 
@@ -386,7 +395,7 @@ class Session:
386
395
 
387
396
  else:
388
397
  if name not in self._loggers.keys():
389
- self.warning(f"其它会话中已存在一个名为 {name} 的记录器,将直接返回该记录器")
398
+ self.warning(Settings.gettext("msg_duplicate_logname", name))
390
399
 
391
400
  logger = self.application.loggers[name]
392
401
  logger.mode = mode
@@ -517,7 +526,7 @@ class Session:
517
526
 
518
527
  def get_status(self):
519
528
  "返回状态窗口内容的真实函数。 **脚本中无需调用。**"
520
- text = f"这是一个默认的状态窗口信息\n会话: {self.name} 连接状态: {self.connected}"
529
+ text = Settings.gettext("msg_default_statuswindow", self.name, self.connected)
521
530
  if callable(self._status_maker):
522
531
  text = self._status_maker()
523
532
 
@@ -539,7 +548,7 @@ class Session:
539
548
 
540
549
  return plainText
541
550
 
542
- def writetobuffer(self, data, newline = False):
551
+ def writetobuffer(self, data: str, newline = False):
543
552
  """
544
553
  将数据写入到用于本地显示的缓冲中。 **脚本中无需调用。**
545
554
 
@@ -582,7 +591,7 @@ class Session:
582
591
  由协议对象调用,处理收到远程 eof 数据,即远程断开连接。 **脚本中无需调用。**
583
592
  """
584
593
  self._eof = True
585
- if self.connected:
594
+ if self.connected and self._transport:
586
595
  self._transport.write_eof()
587
596
  self.state = "DISCONNECTED"
588
597
  self.syslog.info(f"服务器断开连接! {self._protocol.__repr__}")
@@ -646,7 +655,7 @@ class Session:
646
655
  self.write(b"\x1b[1z<SUPPORTS>")
647
656
  else:
648
657
  #self.write(b"\x1b[0z")
649
- self.warning("MXP支持尚未开发,请暂时不要打开MXP支持设置")
658
+ self.warning(Settings.gettext("msg_mxp_not_support"))
650
659
 
651
660
  # 全局变量%line
652
661
  self.setVariable("%line", tri_line)
@@ -659,7 +668,7 @@ class Session:
659
668
 
660
669
  if not self._ignore:
661
670
  # 修改实现,形成列表时即排除非使能状态触发器,加快响应速度
662
- #all_tris = list(self._triggers.values())
671
+
663
672
  all_tris = [tri for tri in self._triggers.values() if isinstance(tri, Trigger) and tri.enabled]
664
673
  all_tris.sort(key = lambda tri: tri.priority)
665
674
 
@@ -689,10 +698,10 @@ class Session:
689
698
 
690
699
  :param exc: 异常对象
691
700
  """
692
- self.error(f"连接过程中发生异常,异常信息为: {exc}")
701
+ self.error(Settings.gettext("msg_connection_fail", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), exc))
693
702
 
694
703
 
695
- def create_task(self, coro, *args, name: str = None) -> asyncio.Task:
704
+ def create_task(self, coro, *args, name: Optional[str] = None) -> asyncio.Task:
696
705
  """
697
706
  创建一个任务,并将其加入到会话的任务管理队列中。
698
707
 
@@ -743,6 +752,25 @@ class Session:
743
752
 
744
753
  self._tasks = set([t for t in self._tasks if not t.done()])
745
754
 
755
+ @property
756
+ def commandHistory(self) -> List[str]:
757
+ "返回发送到服务器的命令历史。保存的历史命令数量由 Settings.client['history_records'] 决定。默认为500条。"
758
+ return self._command_history
759
+
760
+ def record_command(self, cmd: str):
761
+ """
762
+ 记录一条命令到会话的命令历史中。 **脚本中无需调用。**
763
+ :param cmd: 要记录的命令
764
+ """
765
+ record_count = Settings.client["history_records"]
766
+ if record_count:
767
+ self._command_history.append(cmd)
768
+
769
+ if record_count < 0:
770
+ pass
771
+ elif len(self._command_history) > record_count:
772
+ self._command_history.pop(0)
773
+
746
774
  def write(self, data) -> None:
747
775
  """
748
776
  向服务器写入数据(RAW格式字节数组/字节串)。 **一般不应在脚本中直接调用。**
@@ -774,6 +802,7 @@ class Session:
774
802
  else:
775
803
  self.log.log(f"\x1b[32m{ln}\x1b[0m\n")
776
804
 
805
+ self.record_command(line)
777
806
  cmd = ln + self.newline
778
807
  self.write(cmd.encode(self.encoding, Settings.server["encoding_errors"]))
779
808
 
@@ -783,6 +812,7 @@ class Session:
783
812
  else:
784
813
  self.log.log(f"\x1b[32m{line}\x1b[0m\n")
785
814
 
815
+ self.record_command(line)
786
816
  cmd = line + self.newline
787
817
  self.write(cmd.encode(self.encoding, Settings.server["encoding_errors"]))
788
818
 
@@ -808,6 +838,7 @@ class Session:
808
838
  await asyncio.sleep(wait_time)
809
839
  self.writeline(line)
810
840
  return await awaitable
841
+
811
842
 
812
843
  def exec(self, cmd: str, name = None, *args, **kwargs):
813
844
  r"""
@@ -833,7 +864,7 @@ class Session:
833
864
  session = self.application.sessions[name]
834
865
  session.exec_command(cmd, *args, **kwargs)
835
866
  else:
836
- self.error(f"不存在名称为{name}的会话")
867
+ self.error(Settings.gettext("msg_no_session", name))
837
868
 
838
869
  async def exec_async(self, cmd: str, name = None, *args, **kwargs):
839
870
  """
@@ -848,7 +879,7 @@ class Session:
848
879
  session = self.application.sessions[name]
849
880
  return await session.exec_command_async(cmd, *args, **kwargs)
850
881
  else:
851
- self.error(f"不存在名称为{name}的会话")
882
+ self.error(Settings.gettext("msg_no_session", name))
852
883
 
853
884
  def exec_code(self, cl: CodeLine, *args, **kwargs):
854
885
  """
@@ -876,7 +907,7 @@ class Session:
876
907
  if times > 0:
877
908
  self.create_task(self.handle_num(times, code = cl, *args, **kwargs))
878
909
  else:
879
- self.warning("#{num} {cmd}只能支持正整数!")
910
+ self.warning(Settings.gettext("msg_num_positive"))
880
911
 
881
912
  elif cmd in self.application.sessions.keys():
882
913
  name = cmd
@@ -905,7 +936,7 @@ class Session:
905
936
  else:
906
937
  handler(code = cl, *args, **kwargs)
907
938
  else:
908
- self.warning(f"未识别的命令: {cl.commandText}")
939
+ self.warning(Settings.gettext("msg_cmd_not_recognized", cl.commandText))
909
940
 
910
941
  else:
911
942
  cmdtext, code = cl.expand(self, *args, **kwargs)
@@ -940,7 +971,7 @@ class Session:
940
971
  if times > 0:
941
972
  await self.handle_num(times, code = cl, *args, **kwargs)
942
973
  else:
943
- self.warning("#{num} {cmd}只能支持正整数!")
974
+ self.warning(Settings.gettext("msg_num_positive"))
944
975
 
945
976
  elif cmd in self.application.sessions.keys():
946
977
  name = cmd
@@ -966,7 +997,7 @@ class Session:
966
997
  else:
967
998
  handler(code = cl, *args, **kwargs)
968
999
  else:
969
- self.warning(f"未识别的命令: {cl.commandText}")
1000
+ self.warning(Settings.gettext("msg_cmd_not_recognized", cl.commandText))
970
1001
 
971
1002
  else:
972
1003
  cmdtext, code = cl.expand(self, *args, **kwargs)
@@ -980,29 +1011,43 @@ class Session:
980
1011
 
981
1012
  :param cmdtext: 纯文本命令
982
1013
  """
983
- isNotCmd = True
1014
+ keepEval = True
1015
+ notHandle = True
1016
+
1017
+ # fix bugs, commands filter for enabled and sorted for priority
1018
+ avai_cmds = [cmd for cmd in self._commands.values() if isinstance(cmd, Command) and cmd.enabled]
1019
+ avai_cmds.sort(key = lambda cmd: cmd.priority)
1020
+
984
1021
  for command in self._commands.values():
985
- if isinstance(command, Command) and command.enabled:
986
- state = command.match(cmdtext)
987
- if state.result == Command.SUCCESS:
988
- # 命令的任务名称采用命令id,以便于后续查错
989
- self.create_task(command.execute(cmdtext), name = "task-{0}".format(command.id))
990
- isNotCmd = False
1022
+ state = command.match(cmdtext)
1023
+ if state.result == Command.SUCCESS:
1024
+ notHandle = False
1025
+ # 命令的任务名称采用命令id,以便于后续查错
1026
+ self.create_task(command.execute(cmdtext), name = "task-{0}".format(command.id))
1027
+
1028
+ if not command.keepEval:
1029
+ keepEval = False
991
1030
  break
992
1031
 
993
- # 再判断是否是别名
994
- if isNotCmd:
995
- notAlias = True
996
- for alias in self._aliases.values():
997
- if isinstance(alias, Alias) and alias.enabled:
998
- state = alias.match(cmdtext)
999
- if state.result == Alias.SUCCESS:
1000
- notAlias = False
1032
+ # 若持续匹配,再判断是否是别名
1033
+ if keepEval:
1034
+ # fix bugs, aliases filter for enabled and sorted for priority, and add oneShot, keepEval judge
1035
+ avai_alis = [ali for ali in self._aliases.values() if isinstance(ali, Alias) and ali.enabled]
1036
+ avai_alis.sort(key = lambda ali: ali.priority)
1037
+
1038
+ for alias in avai_alis:
1039
+ state = alias.match(cmdtext)
1040
+ if state.result == Alias.SUCCESS:
1041
+ notHandle = False
1042
+ if alias.oneShot:
1043
+ self.delAlias(alias.id)
1044
+
1045
+ if not alias.keepEval:
1001
1046
  break
1002
1047
 
1003
- # 都不是则是普通命令,直接发送
1004
- if notAlias:
1005
- self.writeline(cmdtext)
1048
+ # 都前面都未被处理,则直接发送
1049
+ if notHandle:
1050
+ self.writeline(cmdtext)
1006
1051
 
1007
1052
  async def exec_text_async(self, cmdtext: str):
1008
1053
  """
@@ -1011,29 +1056,43 @@ class Session:
1011
1056
  异步调用时,该函数要等待对应的代码执行完毕后才会返回。可以用于确保命令执行完毕。
1012
1057
  """
1013
1058
  result = None
1014
- isNotCmd = True
1015
- for command in self._commands.values():
1016
- if isinstance(command, Command) and command.enabled:
1017
- state = command.match(cmdtext)
1018
- if state.result == Command.SUCCESS:
1019
- # 命令的任务名称采用命令id,以便于后续查错
1020
- result = await self.create_task(command.execute(cmdtext), name = "task-{0}".format(command.id))
1021
- isNotCmd = False
1059
+ keepEval = True
1060
+ notHandle = True
1061
+
1062
+ # fix bugs, commands filter for enabled and sorted for priority
1063
+ avai_cmds = [cmd for cmd in self._commands.values() if isinstance(cmd, Command) and cmd.enabled]
1064
+ avai_cmds.sort(key = lambda cmd: cmd.priority)
1065
+
1066
+ for command in avai_cmds:
1067
+ state = command.match(cmdtext)
1068
+ if state.result == Command.SUCCESS:
1069
+ # 命令的任务名称采用命令id,以便于后续查错
1070
+ result = await self.create_task(command.execute(cmdtext), name = "task-{0}".format(command.id))
1071
+ notHandle = False
1072
+ if not command.keepEval:
1073
+ keepEval = False
1022
1074
  break
1023
1075
 
1024
1076
  # 再判断是否是别名
1025
- if isNotCmd:
1026
- notAlias = True
1027
- for alias in self._aliases.values():
1028
- if isinstance(alias, Alias) and alias.enabled:
1029
- state = alias.match(cmdtext)
1030
- if state.result == Alias.SUCCESS:
1031
- notAlias = False
1077
+ if keepEval:
1078
+
1079
+ # fix bugs, aliases filter for enabled and sorted for priority, and add oneShot, keepEval judge
1080
+ avai_alis = [ali for ali in self._aliases.values() if isinstance(ali, Alias) and ali.enabled]
1081
+ avai_alis.sort(key = lambda ali: ali.priority)
1082
+
1083
+ for alias in avai_alis:
1084
+ state = alias.match(cmdtext)
1085
+ if state.result == Alias.SUCCESS:
1086
+ notHandle = False
1087
+ if alias.oneShot:
1088
+ self.delAlias(alias.id)
1089
+
1090
+ if not alias.keepEval:
1032
1091
  break
1033
1092
 
1034
- # 都不是则是普通命令,直接发送
1035
- if notAlias:
1036
- self.writeline(cmdtext)
1093
+ # 若均为处理则是普通命令,直接发送
1094
+ if notHandle:
1095
+ self.writeline(cmdtext)
1037
1096
 
1038
1097
  return result
1039
1098
 
@@ -1106,7 +1165,8 @@ class Session:
1106
1165
 
1107
1166
  若要在脚本中控制断开与服务器的连接,请使用 session.disconnect()
1108
1167
  """
1109
- self._transport.write_eof()
1168
+ if self._transport:
1169
+ self._transport.write_eof()
1110
1170
 
1111
1171
  def getUniqueNumber(self):
1112
1172
  """
@@ -1126,55 +1186,93 @@ class Session:
1126
1186
  """
1127
1187
  return "{0}_{1}".format(prefix, self.getUniqueNumber())
1128
1188
 
1129
- def enableGroup(self, group: str, enabled = True):
1189
+ def enableGroup(self, group: str, enabled = True, subgroup = True, types: Union[Type, Union[Tuple, List]] = (Alias, Trigger, Command, Timer, GMCPTrigger)):
1130
1190
  """
1131
1191
  使能或禁用Group中所有对象, 返回组内各对象个数。
1132
1192
 
1133
1193
  :param group: 组名,即各对象的 group 属性的值
1134
1194
  :param enabled: 使能/禁用开关。为True时表示使能, False为禁用
1195
+ :param subgroup: 是否对子组同时生效,默认为True。子组是指名称在父组名之后的用.xxx命名的组。例如, 组 group1.group2 是 group1 的子组。
1196
+ :param types: 要使能的对象类型,默认为 (Alias, Trigger, Command, Timer, GMCPTrigger)。
1197
+ 可以指定为单个类型,也可以指定为类型列表或元组。
1198
+ 若指定为单个类型,则只使能该类型的对象。
1199
+ 若指定为类型列表或元组,则使能该类型列表或元组中的所有类型的对象。
1135
1200
  :return: 5个整数的列表,依次表示改组内操作的 别名,触发器,命令,定时器,GMCP 的个数
1136
1201
  """
1137
1202
  counts = [0, 0, 0, 0, 0]
1138
- for ali in self._aliases.values():
1139
- if isinstance(ali, Alias) and (ali.group == group):
1140
- ali.enabled = enabled
1141
- counts[0] += 1
1142
-
1143
- for tri in self._triggers.values():
1144
- if isinstance(tri, Trigger) and (tri.group == group):
1145
- tri.enabled = enabled
1146
- counts[1] += 1
1147
-
1148
- for cmd in self._commands.values():
1149
- if isinstance(cmd, Command) and (cmd.group == group):
1150
- cmd.enabled = enabled
1151
- counts[2] += 1
1152
-
1153
- for tmr in self._timers.values():
1154
- if isinstance(tmr, Timer) and (tmr.group == group):
1155
- tmr.enabled = enabled
1156
- counts[3] += 1
1157
-
1158
- for gmcp in self._gmcp.values():
1159
- if isinstance(gmcp, GMCPTrigger) and (gmcp.group == group):
1160
- gmcp.enabled = enabled
1161
- counts[4] += 1
1203
+ if (Alias == types) or (isinstance(types, (list, tuple)) and (Alias in types)):
1204
+ for ali in self._aliases.values():
1205
+ if isinstance(ali, Alias) and ((ali.group == group) or (subgroup and ali.group.startswith(group + "."))):
1206
+ ali.enabled = enabled
1207
+ counts[0] += 1
1208
+
1209
+ if (Trigger == types) or (isinstance(types, (list, tuple)) and (Trigger in types)):
1210
+ for tri in self._triggers.values():
1211
+ if isinstance(tri, Trigger) and ((tri.group == group) or (subgroup and tri.group.startswith(group + "."))):
1212
+ tri.enabled = enabled
1213
+ counts[1] += 1
1214
+
1215
+ if (Command == types) or (isinstance(types, (list, tuple)) and (Command in types)):
1216
+ for cmd in self._commands.values():
1217
+ if isinstance(cmd, Command) and ((cmd.group == group) or (subgroup and cmd.group.startswith(group + "."))):
1218
+ cmd.enabled = enabled
1219
+ counts[2] += 1
1220
+
1221
+ if (Timer == types) or (isinstance(types, (list, tuple)) and (Timer in types)):
1222
+ for tmr in self._timers.values():
1223
+ if isinstance(tmr, Timer) and ((tmr.group == group) or (subgroup and tmr.group.startswith(group + "."))):
1224
+ tmr.enabled = enabled
1225
+ counts[3] += 1
1226
+
1227
+ if (GMCPTrigger == types) or (isinstance(types, (list, tuple)) and (GMCPTrigger in types)):
1228
+ for gmcp in self._gmcp.values():
1229
+ if isinstance(gmcp, GMCPTrigger) and ((gmcp.group == group) or (subgroup and gmcp.group.startswith(group + "."))):
1230
+ gmcp.enabled = enabled
1231
+ counts[4] += 1
1232
+
1233
+ return counts
1234
+
1235
+ def deleteGroup(self, group: str, subgroup = True, types: Union[Type, Union[Tuple, List]] = (Alias, Trigger, Command, Timer, GMCPTrigger)):
1236
+ """
1237
+ 删除Group中所有对象, 返回组内各对象个数。
1238
+
1239
+ :param group: 组名,即各对象的 group 属性的值
1240
+ :param subgroup: 是否对子组同时生效,默认为True。子组是指名称在父组名之后的用.xxx命名的组。例如, 组 group1.group2 是 group1 的子组。
1241
+ :param types: 要删除的对象类型,默认为 (Alias, Trigger, Command, Timer, GMCPTrigger)。
1242
+ 可以指定为单个类型,也可以指定为类型列表或元组。
1243
+ 若指定为单个类型,则只删除该类型的对象。
1244
+ 若指定为类型列表或元组,则删除该类型列表或元组中的所有类型的对象。
1245
+ :return: 5个整数的列表,依次表示改组内操作的 别名,触发器,命令,定时器,GMCP 的个数
1246
+ """
1247
+ counts = [0, 0, 0, 0, 0]
1248
+ if (Alias == types) or (isinstance(types, (list, tuple)) and (Alias in types)):
1249
+ ali_ids = [ali.id for ali in self._aliases.values() if isinstance(ali, Alias) and ((ali.group == group) or (subgroup and ali.group.startswith(group + ".")))]
1250
+ self.delAliases(ali_ids)
1251
+ counts[0] = len(ali_ids)
1252
+
1253
+ if (Trigger == types) or (isinstance(types, (list, tuple)) and (Trigger in types)):
1254
+ tri_ids = [tri.id for tri in self._triggers.values() if isinstance(tri, Trigger) and ((tri.group == group) or (subgroup and tri.group.startswith(group + ".")))]
1255
+ self.delTriggers(tri_ids)
1256
+ counts[1] = len(tri_ids)
1257
+
1258
+ if (Command == types) or (isinstance(types, (list, tuple)) and (Command in types)):
1259
+ cmd_ids = [cmd.id for cmd in self._commands.values() if isinstance(cmd, Command) and ((cmd.group == group) or (subgroup and cmd.group.startswith(group + ".")))]
1260
+ self.delCommands(cmd_ids)
1261
+ counts[2] = len(cmd_ids)
1262
+
1263
+ if (Timer == types) or (isinstance(types, (list, tuple)) and (Timer in types)):
1264
+ tmr_ids = [tmr.id for tmr in self._timers.values() if isinstance(tmr, Timer) and ((tmr.group == group) or (subgroup and tmr.group.startswith(group + ".")))]
1265
+ self.delTimers(tmr_ids)
1266
+ counts[3] = len(tmr_ids)
1267
+
1268
+ if (GMCPTrigger == types) or (isinstance(types, (list, tuple)) and (GMCPTrigger in types)):
1269
+ gmcp_ids = [gmcp.id for gmcp in self._gmcp.values() if isinstance(gmcp, GMCPTrigger) and ((gmcp.group == group) or (subgroup and gmcp.group.startswith(group + ".")))]
1270
+ self.delGMCPs(gmcp_ids)
1271
+ counts[4] = len(gmcp_ids)
1162
1272
 
1163
1273
  return counts
1164
1274
 
1165
- # def _addObjects(self, objs: dict, cls: type):
1166
- # if cls == Alias:
1167
- # self._aliases.update(objs)
1168
- # elif cls == Command:
1169
- # self._commands.update(objs)
1170
- # elif cls == Trigger:
1171
- # self._triggers.update(objs)
1172
- # elif cls == Timer:
1173
- # self._timers.update(objs)
1174
- # elif cls == GMCPTrigger:
1175
- # self._gmcp.update(objs)
1176
-
1177
- def _addObjects(self, objs):
1275
+ def _addObjects(self, objs: Union[Union[List[BaseObject], Tuple[BaseObject]], Dict[str, BaseObject]]):
1178
1276
  if isinstance(objs, list) or isinstance(objs, tuple):
1179
1277
  for item in objs:
1180
1278
  self._addObject(item)
@@ -1183,25 +1281,11 @@ class Session:
1183
1281
  for key, item in objs.items():
1184
1282
  if isinstance(item, BaseObject):
1185
1283
  if key != item.id:
1186
- self.warning(f'对象 {item} 字典键值 {key} 与其id {item.id} 不一致,将丢弃键值,以其id添加到会话中...')
1284
+ self.warning(Settings.gettext("msg_id_not_consistent", item, key, item.id))
1187
1285
 
1188
1286
  self._addObject(item)
1189
1287
 
1190
- # def _addObject(self, obj, cls: type):
1191
- # #if type(obj) == cls:
1192
- # if isinstance(obj, cls):
1193
- # if cls == Alias:
1194
- # self._aliases[obj.id] = obj
1195
- # elif cls == Command:
1196
- # self._commands[obj.id] = obj
1197
- # elif cls == Trigger:
1198
- # self._triggers[obj.id] = obj
1199
- # elif cls == Timer:
1200
- # self._timers[obj.id] = obj
1201
- # elif cls == GMCPTrigger:
1202
- # self._gmcp[obj.id] = obj
1203
-
1204
- def _addObject(self, obj):
1288
+ def _addObject(self, obj: BaseObject):
1205
1289
  if isinstance(obj, Alias):
1206
1290
  self._aliases[obj.id] = obj
1207
1291
  elif isinstance(obj, Command):
@@ -1233,7 +1317,7 @@ class Session:
1233
1317
  """
1234
1318
  self._addObject(obj)
1235
1319
 
1236
- def addObjects(self, objs):
1320
+ def addObjects(self, objs: Union[Union[List[BaseObject], Tuple[BaseObject]], Dict[str, BaseObject]]):
1237
1321
  """
1238
1322
  向会话中增加多个对象,可直接添加 Alias, Trigger, GMCPTrigger, Command, Timer 或它们的子类的元组、列表或者字典(保持兼容性)
1239
1323
 
@@ -1327,7 +1411,7 @@ class Session:
1327
1411
  elif isinstance(obj, (list, tuple, dict)):
1328
1412
  self.delObjects(obj)
1329
1413
 
1330
- def delObjects(self, objs):
1414
+ def delObjects(self, objs: Union[Union[Union[list, tuple], dict], BaseObject]):
1331
1415
  """
1332
1416
  从会话中移除一组对象,可直接删除多个 Alias, Trigger, GMCPTrigger, Command, Timer
1333
1417
 
@@ -1600,7 +1684,7 @@ class Session:
1600
1684
  for gmcp in gmcp_s:
1601
1685
  self.delGMCP(gmcp)
1602
1686
 
1603
- def replace(self, newstr):
1687
+ def replace(self, newstr: str):
1604
1688
  """
1605
1689
  将当前行内容显示替换为newstr。该方法仅在用于触发器的同步处置中才能正确相应
1606
1690
 
@@ -1614,17 +1698,17 @@ class Session:
1614
1698
  ## ###################
1615
1699
  ## 变量 Variables 处理
1616
1700
  ## ###################
1617
- def delVariable(self, name):
1701
+ def delVariable(self, name: str):
1618
1702
  """
1619
1703
  删除一个变量。删除变量是从session管理的变量列表中移除关键字,而不是设置为 None
1620
1704
 
1621
1705
  :param name: 变量名
1622
1706
  """
1623
- assert isinstance(name, str), "name必须是一个字符串"
1707
+ assert isinstance(name, str), Settings.gettext("msg_shall_be_string", "name")
1624
1708
  if name in self._variables.keys():
1625
1709
  self._variables.pop(name)
1626
1710
 
1627
- def setVariable(self, name, value):
1711
+ def setVariable(self, name: str, value: Any):
1628
1712
  """
1629
1713
  设置一个变量的值。可以使用vars快捷点访问器实现同样效果。
1630
1714
 
@@ -1638,10 +1722,10 @@ class Session:
1638
1722
  session.setVariable("myvar1", "the value")
1639
1723
  session.vars.myvar1 = "the value"
1640
1724
  """
1641
- assert isinstance(name, str), "name必须是一个字符串"
1725
+ assert isinstance(name, str), Settings.gettext("msg_shall_be_string", "name")
1642
1726
  self._variables[name] = value
1643
1727
 
1644
- def getVariable(self, name, default = None):
1728
+ def getVariable(self, name: str, default = None):
1645
1729
  """
1646
1730
  获取一个变量的值。可以使用vars快捷点访问器实现类似效果,但vars访问时,默认值总为None。
1647
1731
 
@@ -1657,10 +1741,10 @@ class Session:
1657
1741
  myvar = session.vars.myvar1
1658
1742
  """
1659
1743
  """获取一个变量的值. 当name指定的变量不存在时,返回default"""
1660
- assert isinstance(name, str), "name必须是一个字符串"
1744
+ assert isinstance(name, str), Settings.gettext("msg_shall_be_string", "name")
1661
1745
  return self._variables.get(name, default)
1662
1746
 
1663
- def setVariables(self, names, values):
1747
+ def setVariables(self, names: Union[List[str], Tuple[str]], values: Union[list, tuple]):
1664
1748
  """
1665
1749
  同时设置一组变量的值。要注意,变量名称和值的数量要相同。当不相同时,抛出异常。
1666
1750
 
@@ -1675,15 +1759,15 @@ class Session:
1675
1759
 
1676
1760
  session.setVariables(hp_key, hp_value)
1677
1761
  """
1678
- assert isinstance(names, tuple) or isinstance(names, list), "names命名应为元组或列表,不接受其他类型"
1679
- assert isinstance(values, tuple) or isinstance(values, list), "values值应为元组或列表,不接受其他类型"
1680
- assert (len(names) > 0) and (len(values) > 0) and (len(names) == len(values)), "names与values应不为空,且长度相等"
1762
+ assert isinstance(names, tuple) or isinstance(names, list), Settings.gettext("msg_shall_be_list_or_tuple", "names")
1763
+ assert isinstance(values, tuple) or isinstance(values, list), Settings.gettext("msg_shall_be_list_or_tuple", "values")
1764
+ assert (len(names) > 0) and (len(values) > 0) and (len(names) == len(values)), Settings.gettext("msg_names_and_values")
1681
1765
  for index in range(0, len(names)):
1682
1766
  name = names[index]
1683
1767
  value = values[index]
1684
1768
  self.setVariable(name, value)
1685
1769
 
1686
- def getVariables(self, names):
1770
+ def getVariables(self, names: Union[List[str], Tuple[str]]):
1687
1771
  """
1688
1772
  同时获取一组变量的值。
1689
1773
 
@@ -1695,8 +1779,8 @@ class Session:
1695
1779
 
1696
1780
  qi, jing, neili, jingli = session.getVariables(["qi", "jing", "neili", "jingli"])
1697
1781
  """
1698
- assert isinstance(names, tuple) or isinstance(names, list), "names命名应为元组或列表,不接受其他类型"
1699
- assert len(names) > 0, "names应不为空"
1782
+ assert isinstance(names, tuple) or isinstance(names, list), Settings.gettext("msg_shall_be_list_or_tuple", "names")
1783
+ assert len(names) > 0, Settings.gettext("msg_not_null", "names")
1700
1784
  values = list()
1701
1785
  for name in names:
1702
1786
  value = self.getVariable(name)
@@ -1704,7 +1788,7 @@ class Session:
1704
1788
 
1705
1789
  return tuple(values)
1706
1790
 
1707
- def updateVariables(self, kvdict: dict):
1791
+ def updateVariables(self, kvdict: Dict[str, Any]):
1708
1792
  """
1709
1793
  使用字典更新一组变量的值。若变量不存在将自动添加。
1710
1794
 
@@ -1722,26 +1806,26 @@ class Session:
1722
1806
  ## ###################
1723
1807
  ## 全局变量 Globals 处理
1724
1808
  ## ###################
1725
- def delGlobal(self, name):
1809
+ def delGlobal(self, name: str):
1726
1810
  """
1727
1811
  删除一个全局变量,使用方式与会话变量variable相同
1728
1812
 
1729
1813
  :param name: 全局变量的名称
1730
1814
  """
1731
- assert isinstance(name, str), "name必须是一个字符串"
1815
+ assert isinstance(name, str), Settings.gettext("msg_shall_be_string", "name")
1732
1816
  self.application.del_globals(name)
1733
1817
 
1734
- def setGlobal(self, name, value):
1818
+ def setGlobal(self, name: str, value):
1735
1819
  """
1736
1820
  设置一个全局变量的值,使用方式与会话变量variable相同
1737
1821
 
1738
1822
  :param name: 全局变量的名称
1739
1823
  :param value: 全局变量的值
1740
1824
  """
1741
- assert isinstance(name, str), "name必须是一个字符串"
1825
+ assert isinstance(name, str), Settings.gettext("msg_shall_be_string", "name")
1742
1826
  self.application.set_globals(name, value)
1743
1827
 
1744
- def getGlobal(self, name, default = None):
1828
+ def getGlobal(self, name: str, default = None):
1745
1829
  """
1746
1830
  获取一个全局变量的值,使用方式与会话变量variable相同
1747
1831
 
@@ -1750,7 +1834,7 @@ class Session:
1750
1834
  :return: 全局变量的值,或者 default
1751
1835
  """
1752
1836
 
1753
- assert isinstance(name, str), "name必须是一个字符串"
1837
+ assert isinstance(name, str), Settings.gettext("msg_shall_be_string", "name")
1754
1838
  return self.application.get_globals(name, default)
1755
1839
 
1756
1840
  ## ###################
@@ -1791,7 +1875,7 @@ class Session:
1791
1875
 
1792
1876
  self.writetobuffer("#"*width, newline = True)
1793
1877
 
1794
- def handle_help(self, code: CodeLine = None, *args, **kwargs):
1878
+ def handle_help(self, code: CodeLine, *args, **kwargs):
1795
1879
  '''
1796
1880
  嵌入命令 #help 的执行函数,在当前会话中现实帮助信息。
1797
1881
  当不带参数时, #help会列出所有可用的帮助主题。
@@ -1815,7 +1899,6 @@ class Session:
1815
1899
  - ``#help session``
1816
1900
  显示 #session 命令有关的帮助
1817
1901
  '''
1818
-
1819
1902
  if code.length == 2:
1820
1903
  self._print_all_help()
1821
1904
 
@@ -1832,11 +1915,16 @@ class Session:
1832
1915
  elif topic in self._sys_commands:
1833
1916
  docstring = self._cmds_handler[topic].__doc__
1834
1917
  else:
1835
- docstring = f"未找到主题{topic}, 请确认输入是否正确."
1918
+ docstring = Settings.gettext("msg_topic_not_found", topic)
1836
1919
 
1837
- self.writetobuffer(docstring, True)
1920
+ title = f" HELP: #{topic.upper()} "
1921
+ title_line = "=" * 4 + title + "=" * (self.application.get_width() - get_cwidth(title) - 4)
1922
+ self.writetobuffer("", newline = True)
1923
+ self.writetobuffer(title_line, newline = False)
1924
+ self.writetobuffer(docstring, True) # type: ignore
1925
+ self.writetobuffer("=" * self.application.get_width(), newline = True)
1838
1926
 
1839
- def handle_exit(self, code: CodeLine = None, *args, **kwargs):
1927
+ def handle_exit(self, code: Optional[CodeLine] = None, *args, **kwargs):
1840
1928
  '''
1841
1929
  嵌入命令 #exit 的执行函数,退出 `PyMudApp` 应用。
1842
1930
  该函数不应该在代码中直接调用。
@@ -1850,7 +1938,7 @@ class Session:
1850
1938
 
1851
1939
  self.application.act_exit()
1852
1940
 
1853
- def handle_close(self, code: CodeLine = None, *args, **kwargs):
1941
+ def handle_close(self, code: Optional[CodeLine] = None, *args, **kwargs):
1854
1942
  '''
1855
1943
  嵌入命令 #close 的执行函数,关闭当前会话,并将当前会话从 `PyMudApp` 的会话列表中移除。
1856
1944
  该函数不应该在代码中直接调用。
@@ -1865,7 +1953,7 @@ class Session:
1865
1953
  #self.application.close_session()
1866
1954
  self.application.act_close_session()
1867
1955
 
1868
- async def handle_wait(self, code: CodeLine = None, *args, **kwargs):
1956
+ async def handle_wait(self, code: CodeLine, *args, **kwargs):
1869
1957
  '''
1870
1958
  嵌入命令 #wait / #wa 的执行函数,异步延时等待指定时间,用于多个命令间的延时等待。
1871
1959
  该函数不应该在代码中直接调用。
@@ -1884,13 +1972,13 @@ class Session:
1884
1972
  - #gag
1885
1973
  - #replace
1886
1974
  '''
1887
-
1975
+
1888
1976
  wait_time = code.code[2]
1889
1977
  if wait_time.isnumeric():
1890
1978
  msec = float(wait_time) / 1000.0
1891
1979
  await asyncio.sleep(msec)
1892
1980
 
1893
- def handle_connect(self, code: CodeLine = None, *args, **kwargs):
1981
+ def handle_connect(self, code: Optional[CodeLine] = None, *args, **kwargs):
1894
1982
  '''
1895
1983
  嵌入命令 #connect / #con 的执行函数,连接到远程服务器(仅当远程服务器未连接时有效)。
1896
1984
  该函数不应该在代码中直接调用。
@@ -1905,20 +1993,21 @@ class Session:
1905
1993
  self.open()
1906
1994
 
1907
1995
  else:
1908
- duration = self._protocol.duration
1996
+ duration = 0
1997
+ if self._protocol: duration = self._protocol.duration
1909
1998
  hour = duration // 3600
1910
1999
  min = (duration - 3600 * hour) // 60
1911
2000
  sec = duration % 60
1912
2001
  time_msg = ""
1913
2002
  if hour > 0:
1914
- time_msg += f"{hour} 小时"
2003
+ time_msg += f"{hour} {Settings.gettext('Hour')}"
1915
2004
  if min > 0:
1916
- time_msg += f"{min} "
1917
- time_msg += f"{math.ceil(sec)} "
2005
+ time_msg += f"{min} {Settings.gettext('Minute')}"
2006
+ time_msg += f"{math.ceil(sec)} {Settings.gettext('Second')}"
1918
2007
 
1919
- self.info("已经与服务器连接了 {}".format(time_msg))
2008
+ self.info(Settings.gettext("msg_connection_duration",time_msg))
1920
2009
 
1921
- def handle_disconnect(self, code: CodeLine = None, *args, **kwargs):
2010
+ def handle_disconnect(self, code: Optional[CodeLine] = None, *args, **kwargs):
1922
2011
  '''
1923
2012
  嵌入命令 #disconnect / #dis 的执行函数,断开到远程服务器的连接(仅当远程服务器已连接时有效)。
1924
2013
  该函数不应该在代码中直接调用。
@@ -2078,7 +2167,7 @@ class Session:
2078
2167
 
2079
2168
  return display_lines
2080
2169
 
2081
- def handle_variable(self, code: CodeLine = None, *args, **kwargs):
2170
+ def handle_variable(self, code: CodeLine, *args, **kwargs):
2082
2171
  '''
2083
2172
  嵌入命令 #variable / #var 的执行函数,操作会话变量。
2084
2173
  该命令可以不带参数、带一个参数、两个参数。
@@ -2111,14 +2200,14 @@ class Session:
2111
2200
  elif len(args) == 1:
2112
2201
  if args[0] in self._variables.keys():
2113
2202
  obj = self.getVariable(args[0])
2114
- var_dict = {args[0] : obj}
2203
+ var_dict = DotDict({args[0] : obj})
2115
2204
  lines = self.buildDisplayLines(var_dict, f" VARIABLE [{args[0]}] IN SESSION {self.name} ")
2116
2205
 
2117
2206
  for line in lines:
2118
2207
  self.writetobuffer(line, newline = True)
2119
2208
 
2120
2209
  else:
2121
- self.warning(f"当前session中不存在名称为 {args[0]} 的变量")
2210
+ self.warning(Settings.gettext("msg_no_object", args[0], Settings.gettext("variable")))
2122
2211
 
2123
2212
  elif len(args) == 2:
2124
2213
  val = None
@@ -2128,9 +2217,9 @@ class Session:
2128
2217
  val = args[1]
2129
2218
 
2130
2219
  self.setVariable(args[0], val)
2131
- self.info(f"成功设置变量 {args[0]} 值为 {val}")
2220
+ self.info(Settings.gettext("msg_object_value_setted", Settings.gettext("variable"), args[0], val.__repr__()))
2132
2221
 
2133
- def handle_global(self, code: CodeLine = None, *args, **kwargs):
2222
+ def handle_global(self, code: CodeLine, *args, **kwargs):
2134
2223
  '''
2135
2224
  嵌入命令 #global 的执行函数,操作全局变量(跨会话共享)。
2136
2225
  该命令可以不带参数、带一个参数、两个参数。
@@ -2164,13 +2253,13 @@ class Session:
2164
2253
  if var in self.application.globals.keys():
2165
2254
  # self.info("{0:>20} = {1:<22}".format(var, self.application.get_globals(var).__repr__()), "全局变量")
2166
2255
 
2167
- var_dict = {var : self.application.get_globals(var)}
2256
+ var_dict = DotDict({var : self.application.get_globals(var)})
2168
2257
  lines = self.buildDisplayLines(var_dict, f" GLOBAL VARIABLE [{var}] ")
2169
2258
 
2170
2259
  for line in lines:
2171
2260
  self.writetobuffer(line, newline = True)
2172
2261
  else:
2173
- self.info("全局空间不存在名称为 {} 的变量".format(var), "全局变量")
2262
+ self.warning(Settings.gettext("msg_no_global_object", var, Settings.gettext("variable")))
2174
2263
 
2175
2264
  elif len(args) == 2:
2176
2265
  val = None
@@ -2179,71 +2268,112 @@ class Session:
2179
2268
  except:
2180
2269
  val = args[1]
2181
2270
  self.application.set_globals(args[0], val)
2182
- self.info(f"成功设置全局变量 {args[0]} 值为 {val}")
2271
+ self.info(Settings.gettext("msg_object_value_setted", Settings.gettext("globalvar"), args[0], val.__repr__()))
2272
+
2273
+ @exception
2274
+ def _handle_objs(self, type: Type, objs: dict, *args):
2275
+ if len(args) <= 1:
2276
+ if len(args) == 0:
2277
+ display_objs = objs
2278
+ title = f" {type.__name__.upper()} LIST IN SESSION {self.name} "
2279
+ else:
2280
+ arg = args[0]
2281
+ if arg.startswith(">"):
2282
+ arg = arg[1:]
2283
+ title = f" {type.__name__.upper()} LIST IN GROUP <{arg.upper()}> AND ITS SUBGROUPS IN SESSION {self.name} "
2284
+ display_objs = {obj.id: obj for obj in objs.values() if (obj.group == arg) or obj.group.startswith(f"{arg}.")}
2285
+ elif arg.startswith("="):
2286
+ arg = arg[1:]
2287
+ title = f" {type.__name__.upper()} LIST IN GROUP <{arg.upper()}> IN SESSION {self.name} "
2288
+ display_objs = {obj.id: obj for obj in objs.values() if (obj.group == arg)}
2289
+ else:
2290
+ title = f" {type.__name__.upper()} LIST OF ID <{arg.upper()}> IN SESSION {self.name} "
2291
+ display_objs = {obj.id: obj for obj in objs.values() if (obj.id == arg)}
2183
2292
 
2184
- def _handle_objs(self, name: str, objs: dict, *args):
2185
- if len(args) == 0:
2186
2293
  width = self.application.get_width()
2187
-
2188
- title = f" {name.upper()} LIST IN SESSION {self.name} "
2294
+
2189
2295
  left = (width - len(title)) // 2
2190
2296
  right = width - len(title) - left
2297
+ self.writetobuffer("", newline = True)
2191
2298
  self.writetobuffer("="*left + title + "="*right, newline = True)
2192
2299
 
2193
- for id in sorted(objs.keys()):
2300
+ for id in sorted(display_objs.keys()):
2194
2301
  self.writetobuffer(" %r" % objs[id], newline = True)
2195
2302
 
2196
2303
  self.writetobuffer("="*width, newline = True)
2197
2304
 
2198
- elif len(args) == 1:
2199
- if args[0] in objs.keys():
2200
- obj = objs[args[0]]
2201
- self.info(obj.__detailed__())
2202
- else:
2203
- self.warning(f"当前session中不存在key为 {args[0]} 的 {name}, 请确认后重试.")
2204
-
2205
2305
  elif len(args) == 2:
2206
- # 当第一个参数为对象obj名称时,对对象进行处理
2207
- if args[0] in objs.keys():
2306
+ if args[0].startswith(">"):
2307
+ group = args[0][1:]
2308
+ if args[1] == "on":
2309
+ cnt =self.enableGroup(group, True, True, type)
2310
+ cnt_total = sum(cnt)
2311
+ self.info(Settings.gettext("msg_group_objects_enabled", group, cnt_total, type.__name__))
2312
+ elif args[1] == "off":
2313
+ cnt =self.enableGroup(group, False, True, type)
2314
+ cnt_total = sum(cnt)
2315
+ self.info(Settings.gettext("msg_group_objects_disabled", group, cnt_total, type.__name__))
2316
+ elif args[1] == "del":
2317
+ cnt =self.deleteGroup(group, True, type)
2318
+ cnt_total = sum(cnt)
2319
+ self.info(Settings.gettext("msg_group_objects_deleted", group, cnt_total, type.__name__))
2320
+
2321
+ elif args[0].startswith("="):
2322
+ group = args[0][1:]
2323
+ if args[1] == "on":
2324
+ cnt =self.enableGroup(group, True, False, type)
2325
+ cnt_total = sum(cnt)
2326
+ self.info(Settings.gettext("msg_group_objects_enabled", group, cnt_total, type.__name__))
2327
+ elif args[1] == "off":
2328
+ cnt =self.enableGroup(group, False, False, type)
2329
+ cnt_total = sum(cnt)
2330
+ self.info(Settings.gettext("msg_group_objects_disabled", group, cnt_total, type.__name__))
2331
+ elif args[1] == "del":
2332
+ cnt =self.deleteGroup(group, False, type)
2333
+ cnt_total = sum(cnt)
2334
+ self.info(Settings.gettext("msg_group_objects_deleted", group, cnt_total, type.__name__))
2335
+
2336
+ elif args[0] in objs.keys():
2208
2337
  obj = objs[args[0]]
2209
2338
  if args[1] == "on":
2210
2339
  obj.enabled = True
2211
- self.info(f"对象 {obj} 的使能状态已打开.")
2340
+ self.info(Settings.gettext("msg_object_enabled", obj.__repr__()))
2212
2341
  elif args[1] == "off":
2213
2342
  obj.enabled = False
2214
- self.info(f"对象 {obj} 的使能状态已禁用.")
2343
+ self.info(Settings.gettext("msg_object_disabled", obj.__repr__()))
2215
2344
  elif args[1] == "del":
2345
+ if hasattr(obj, "__unload__"):
2346
+ obj.__unload__()
2347
+ if hasattr(obj, "unload"):
2348
+ obj.unload()
2216
2349
  obj.enabled = False
2217
2350
  objs.pop(args[0])
2218
- self.info(f"对象 {obj} 已从会话中删除.")
2351
+ self.info(Settings.gettext("msg_object_deleted", obj.__repr__()))
2219
2352
  else:
2220
- self.error(f"#{name.lower()}命令的第二个参数仅能接受on/off/del")
2353
+ self.error(Settings.gettext("msg_invalid_param", type.__name__.lower()))
2221
2354
 
2222
- # 当第一个参数为不是对象obj名称时,创建新对象 (此处还有BUG,调试中)
2223
2355
  else:
2224
- #self.warning(f"当前session中不存在key为 {args[0]} 的 {name}, 请确认后重试.")
2225
2356
  pattern, code = args[0], args[1]
2226
2357
  if (len(pattern)>=2) and (pattern[0] == '{') and (pattern[-1] == '}'):
2227
2358
  pattern = pattern[1:-1]
2228
2359
 
2229
- name = name.lower()
2230
- if name == "alias":
2360
+ if type == Alias:
2231
2361
  ali = SimpleAlias(self, pattern, code)
2232
2362
  self.addAlias(ali)
2233
- self.info("创建Alias {} 成功: {}".format(ali.id, ali.__repr__()))
2234
- elif name == "trigger":
2363
+ self.info(Settings.gettext("msg_alias_created", ali.id, ali.__repr__()))
2364
+ elif type == Trigger:
2235
2365
  tri = SimpleTrigger(self, pattern, code)
2236
2366
  self.addTrigger(tri)
2237
- self.info("创建Trigger {} 成功: {}".format(tri.id, tri.__repr__()))
2238
- elif name == "timer":
2367
+ self.info(Settings.gettext("msg_trigger_created", tri.id, tri.__repr__()))
2368
+ elif type == Timer:
2239
2369
  if pattern.isnumeric():
2240
2370
  timeout = float(pattern)
2241
2371
  if timeout > 0:
2242
2372
  ti = SimpleTimer(self, code, timeout = timeout)
2243
2373
  self.addTimer(ti)
2244
- self.info("创建Timer {} 成功: {}".format(ti.id, ti.__repr__()))
2374
+ self.info(Settings.gettext("msg_timer_created", ti.id, ti.__repr__()))
2245
2375
 
2246
- def handle_alias(self, code: CodeLine = None, *args, **kwargs):
2376
+ def handle_alias(self, code: CodeLine, *args, **kwargs):
2247
2377
  r"""
2248
2378
  嵌入命令 #alias / #ali 的执行函数,操作别名。该命令可以不带参数、带一个参数或者两个参数。
2249
2379
  该函数不应该在代码中直接调用。
@@ -2253,10 +2383,14 @@ class Session:
2253
2383
  - #ali {ali_id}: 显示本会话中id为{ali_id}的别名信息
2254
2384
  - #ali {ali_id} {on/off/del}: 使能/禁用/删除本会话中id为{ali_id}的别名
2255
2385
  - #ali {pattern} {code}: 创建一个新别名,匹配为{pattern},匹配时执行{code}
2386
+ - #ali [>=]{groupname}: 显示本会话中组名为{groupname}(当为=时)及其子组(当为>时)的所有别名
2387
+ - #ali [>=]{groupname} {on/off/del}: 使能/禁用/删除本会话中组名为groupname(当为=时)及其子组(当为>时)的所有别名
2256
2388
  - 别名的code中,可以使用%line代表行,%1~%9代表捕获信息
2257
2389
 
2258
2390
  参数:
2259
2391
  :ali_id: 别名Alias的id
2392
+ :>/=: 二者选择其一,标记第二个参数为组名,且指定是否包含子组。当为=时,表示仅指定组,当为>时,表示指定组及其子组。
2393
+ :groupname: 组名,即 alias 的 group 属性
2260
2394
  :on: 使能
2261
2395
  :off: 禁用
2262
2396
  :del: 删除
@@ -2266,9 +2400,13 @@ class Session:
2266
2400
  示例:
2267
2401
  - ``#ali`` : 无参数, 打印列出当前会话中所有的别名清单
2268
2402
  - ``#ali my_ali`` : 一个参数, 列出id为my_ali的Alias对象的详细信息
2403
+ - ``#ali =mygroup`` : 一个参数,列出所有 group 名为 "mygroup" 的 Alias 对象的详细信息
2404
+ - ``#ali >mygroup`` : 一个参数,列出所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Alias 对象的详细信息
2269
2405
  - ``#ali my_ali on`` : 两个参数,启用id为my_ali的Alias对象(enabled = True)
2270
2406
  - ``#ali my_ali off`` : 两个参数, 禁用id为my_ali的Alias对象(enabled = False)
2271
2407
  - ``#ali my_ali del`` : 两个参数,删除id为my_ali的Alias对象
2408
+ - ``#ali =mygroup on`` : 两个参数,启用所有 group 名为 "mygroup" 的 Alias 对象
2409
+ - ``#ali >mygroup off`` : 两个参数,禁用所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Alias 对象
2272
2410
  - ``#ali {^gp\s(.+)$} {get %1 from corpse}`` : 两个参数,新增创建一个Alias对象。使用时, ``gp gold = get gold from corpse``
2273
2411
 
2274
2412
  相关命令:
@@ -2277,9 +2415,9 @@ class Session:
2277
2415
  - #command
2278
2416
  """
2279
2417
 
2280
- self._handle_objs("Alias", self._aliases, *code.code[2:])
2418
+ self._handle_objs(Alias, self._aliases, *code.code[2:])
2281
2419
 
2282
- def handle_timer(self, code: CodeLine = None, *args, **kwargs):
2420
+ def handle_timer(self, code: CodeLine, *args, **kwargs):
2283
2421
  '''
2284
2422
  嵌入命令 #timer / #ti 的执行函数,操作定时器。该命令可以不带参数、带一个参数或者两个参数。
2285
2423
  该函数不应该在代码中直接调用。
@@ -2288,11 +2426,15 @@ class Session:
2288
2426
  - #ti: 显示本会话所有定时器
2289
2427
  - #ti {ti_id}: 显示本会话中id为{ti_id}的定时器信息
2290
2428
  - #ti {ti_id} {on/off/del}: 使能/禁用/删除本会话中id为{ti_id}的定时器
2429
+ - #ti [>=]{groupname}: 显示本会话中组名为{groupname}(当为=时)及其子组(当为>时)的所有定时器
2430
+ - #ti [>=]{groupname} {on/off/del}: 使能/禁用/删除本会话中组名为groupname(当为=时)及其子组(当为>时)的所有定时器
2291
2431
  - #ti {second} {code}: 创建一个新定时器,定时间隔为{second}秒,定时器到时间执行{code}
2292
2432
  - PyMUD支持同时任意多个定时器。
2293
2433
 
2294
2434
  参数:
2295
2435
  :ti_id: 定时器Timer的id
2436
+ :>/=: 二者选择其一,标记第二个参数为组名,且指定是否包含子组。当为=时,表示仅指定组,当为>时,表示指定组及其子组。
2437
+ :groupname: 组名,即 timer 的 group 属性
2296
2438
  :on: 使能
2297
2439
  :off: 禁用
2298
2440
  :del: 删除
@@ -2302,9 +2444,13 @@ class Session:
2302
2444
  示例:
2303
2445
  - ``#ti``: 无参数, 打印列出当前会话中所有的定时器清单
2304
2446
  - ``#ti my_timer``: 一个参数, 列出id为my_timer的Timer对象的详细信息
2447
+ - ``#ti =mygroup``: 一个参数,列出所有 group 名为 "mygroup" 的 Timer 对象的详细信息
2448
+ - ``#ti >mygroup``: 一个参数,列出所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Timer 对象的详细信息
2305
2449
  - ``#ti my_timer on``: 两个参数,启用id为my_timer的Timer对象(enabled = True)
2306
2450
  - ``#ti my_timer off``: 两个参数, 禁用id为my_timer的Timer对象(enabled = False)
2307
2451
  - ``#ti my_timer del``: 两个参数,删除id为my_timer的Timer对象
2452
+ - ``#ti =mygroup on``: 两个参数,启用所有 group 名为 "mygroup" 的 Timer 对象
2453
+ - ``#ti >mygroup off``: 两个参数,禁用所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Timer 对象
2308
2454
  - ``#ti 100 {drink jiudai;#wa 200;eat liang}``: 两个参数,新增创建一个Timer对象。每隔100s,自动执行一次喝酒袋吃干粮。
2309
2455
 
2310
2456
  相关命令:
@@ -2313,9 +2459,9 @@ class Session:
2313
2459
  - #command
2314
2460
  '''
2315
2461
 
2316
- self._handle_objs("Timer", self._timers, *code.code[2:])
2462
+ self._handle_objs(Timer, self._timers, *code.code[2:])
2317
2463
 
2318
- def handle_command(self, code: CodeLine = None, *args, **kwargs):
2464
+ def handle_command(self, code: CodeLine, *args, **kwargs):
2319
2465
  '''
2320
2466
  嵌入命令 #command / #cmd 的执行函数,操作命令。该命令可以不带参数、带一个参数或者两个参数。
2321
2467
  该函数不应该在代码中直接调用。
@@ -2324,10 +2470,14 @@ class Session:
2324
2470
  - #cmd: 显示本会话所有命令(Command及其子类)
2325
2471
  - #cmd {cmd_id}: 显示本会话中id为{cmd_id}的命令信息
2326
2472
  - #cmd {cmd_id} {on/off/del}: 使能/禁用/删除本会话中id为{cmd_id}的命令
2473
+ - #cmd [>=]{groupname}: 显示本会话中组名为{groupname}(当为=时)及其子组(当为>时)的所有命令
2474
+ - #cmd [>=]{groupname} {on/off/del}: 使能/禁用/删除本会话中组名为groupname(当为=时)及其子组(当为>时)的所有命令
2327
2475
  - 由于命令的特殊性,其只能使用脚本代码创建
2328
2476
 
2329
2477
  参数:
2330
2478
  :cmd_id: 命令Command的id
2479
+ :>/=: 二者选择其一,标记第二个参数为组名,且指定是否包含子组。当为=时,表示仅指定组,当为>时,表示指定组及其子组。
2480
+ :groupname: 组名,即 command 的 group 属性
2331
2481
  :on: 使能
2332
2482
  :off: 禁用
2333
2483
  :del: 删除
@@ -2335,9 +2485,13 @@ class Session:
2335
2485
  示例:
2336
2486
  - ``#cmd`` : 无参数, 打印列出当前会话中所有的命令清单
2337
2487
  - ``#cmd my_cmd`` : 一个参数, 列出id为my_cmd的Command对象的详细信息
2488
+ - ``#cmd =mygroup`` : 一个参数,列出所有 group 名为 "mygroup" 的 Command 对象的详细信息
2489
+ - ``#cmd >mygroup`` : 一个参数,列出所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Command 对象的详细信息
2338
2490
  - ``#cmd my_cmd on`` : 两个参数,启用id为my_cmd的Command对象(enabled = True)
2339
2491
  - ``#cmd my_cmd off`` : 两个参数, 禁用id为my_cmd的Command对象(enabled = False)
2340
2492
  - ``#cmd my_cmd del`` : 两个参数,删除id为my_cmd的Command对象
2493
+ - ``#cmd =mygroup on`` : 两个参数,启用所有 group 名为 "mygroup" 的 Command 对象
2494
+ - ``#cmd >mygroup off`` : 两个参数,禁用所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Command 对象
2341
2495
 
2342
2496
  相关命令:
2343
2497
  - #alias
@@ -2345,9 +2499,9 @@ class Session:
2345
2499
  - #timer
2346
2500
  '''
2347
2501
 
2348
- self._handle_objs("Command", self._commands, *code.code[2:])
2502
+ self._handle_objs(Command, self._commands, *code.code[2:])
2349
2503
 
2350
- def handle_trigger(self, code: CodeLine = None, *args, **kwargs):
2504
+ def handle_trigger(self, code: CodeLine, *args, **kwargs):
2351
2505
  '''
2352
2506
  嵌入命令 #trigger / #tri / #action 的执行函数,操作触发器。该命令可以不带参数、带一个参数或者两个参数。
2353
2507
  该函数不应该在代码中直接调用。
@@ -2356,11 +2510,15 @@ class Session:
2356
2510
  - #tri: 显示本会话所有触发器
2357
2511
  - #tri {tri_id}: 显示本会话中id为{tri_id}的触发器信息
2358
2512
  - #tri {tri_id} {on/off/del}: 使能/禁用/删除本会话中id为{tri_id}的触发器
2513
+ - #tri [>=]{groupname}: 显示本会话中组名为{groupname}(当为=时)及其子组(当为>时)的所有触发器
2514
+ - #tri [>=]{groupname} {on/off/del}: 使能/禁用/删除本会话中组名为groupname(当为=时)及其子组(当为>时)的所有触发器
2359
2515
  - #tri {pattern} {code}: 创建一个新触发器,匹配为{pattern},匹配时执行{code}
2360
2516
  - 触发器的code中,可以使用%line代表行,%1~%9代表捕获信息
2361
2517
 
2362
2518
  参数:
2363
2519
  :tri_id: 触发器Trigger的id
2520
+ :[>=]: 二者选择其一,标记第二个参数为组名,且指定是否包含子组。当为=时,表示仅指定组,当为>时,表示指定组及其子组。
2521
+ :groupname: 组名,即 trigger 的 group 属性
2364
2522
  :on: 使能
2365
2523
  :off: 禁用
2366
2524
  :del: 删除
@@ -2370,9 +2528,13 @@ class Session:
2370
2528
  示例:
2371
2529
  - ``#tri``: 无参数, 打印列出当前会话中所有的触发器清单
2372
2530
  - ``#tri my_tri``: 一个参数, 列出id为my_tri的Trigger对象的详细信息
2531
+ - ``#tri =mygroup``: 一个参数,列出所有 group 名为 "mygroup" 的 Trigger 对象的详细信息
2532
+ - ``#tri >mygroup``: 一个参数,列出所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Trigger 对象的详细信息
2373
2533
  - ``#tri my_tri on``: 两个参数,启用id为my_tri的Trigger对象(enabled = True)
2374
2534
  - ``#tri my_tri off``: 两个参数, 禁用id为my_tri的Trigger对象(enabled = False)
2375
2535
  - ``#tri my_tri del``: 两个参数,删除id为my_tri的Trigger对象
2536
+ - ``#tri =mygroup on``: 两个参数,启用所有 group 名为 "mygroup" 的 Trigger 对象
2537
+ - ``#tri >mygroup off``: 两个参数,禁用所有 group 名为 "mygroup" 或以 "mygroup." 开头的下级组(子组,比如 mygroup.subgroup1, mygroup.subgroup2 等)内的 Trigger 对象
2376
2538
  - ``#tri {^[> ]*段誉脚下一个不稳.+} {get duan}``: 两个参数,新增创建一个Trigger对象。当段誉被打倒的时刻把他背起来。
2377
2539
 
2378
2540
  相关命令:
@@ -2381,9 +2543,9 @@ class Session:
2381
2543
  - #command
2382
2544
  '''
2383
2545
 
2384
- self._handle_objs("Trigger", self._triggers, *code.code[2:])
2546
+ self._handle_objs(Trigger, self._triggers, *code.code[2:])
2385
2547
 
2386
- def handle_task(self, code: CodeLine = None, *args, **kwargs):
2548
+ def handle_task(self, code: Optional[CodeLine] = None, *args, **kwargs):
2387
2549
  '''
2388
2550
  嵌入命令 #task 的执行函数,显示当前管理的所有任务清单(仅用于调试)。
2389
2551
  该函数不应该在代码中直接调用。
@@ -2404,24 +2566,28 @@ class Session:
2404
2566
  self.writetobuffer("="*width, newline = True)
2405
2567
 
2406
2568
 
2407
- def handle_ignore(self, code: CodeLine = None, *args, **kwargs):
2569
+ def handle_ignore(self, code: CodeLine, *args, **kwargs):
2408
2570
  '''
2409
2571
  嵌入命令 #ignore / #ig, #t+ / #t- 的执行函数,处理使能/禁用状态。
2410
2572
  该函数不应该在代码中直接调用。
2411
2573
 
2412
2574
  使用:
2413
2575
  - #ig: 切换触发器全局使能/禁用状态
2414
- - #t+ {group}: 使能{group}组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2415
- - #t- {group}: 禁用{group}组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2576
+ - #t+ [>=]{group}: 使能{group}组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2577
+ - #t- [>=]{group}: 禁用{group}组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2416
2578
 
2417
2579
  参数:
2418
- :group: 组名
2580
+ :group: 组名。可以在组名前带 '=', '>' 表示对当前组生效或对当前组及子组同时生效。当省略 >/= 时,相当于 =,即仅对当前组生效。
2419
2581
 
2420
2582
  示例:
2421
2583
  - ``#ig``: 切换全局触发器的使能/禁用状态。为禁用时,状态栏右下角会显示“全局已禁用”
2422
2584
  - ``#t+ mygroup``: 使能名称为 mygroup 的组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2423
2585
  - ``#t- mygroup``: 禁用名称为 mygroup 的组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2586
+ - ``#t+ >mygroup``: 使能名称为 mygroup 的组及子组(所有以 mygroup. 开头的其他组,如 mygroup.subgroup)内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2424
2587
 
2588
+ 注意:
2589
+ 使用#t+/#t-调用时,相当于enableGroup传递的默认参数,即subgroup为True, 且types为所有类型。
2590
+
2425
2591
  相关命令:
2426
2592
  - #trigger
2427
2593
  - #alias
@@ -2432,28 +2598,42 @@ class Session:
2432
2598
  if cmd in ("ig", "ignore"):
2433
2599
  self._ignore = not self._ignore
2434
2600
  if self._ignore:
2435
- self.info("所有触发器使能已全局禁用。")
2601
+ self.info(Settings.gettext("msg_ignore_on"))
2436
2602
  else:
2437
- self.info("不再全局禁用所有触发器使能。")
2603
+ self.info(Settings.gettext("msg_ignore_off"))
2438
2604
  elif cmd == "t+":
2439
2605
  if code.length <= 2:
2440
- self.warning("#T+使能组使用不正确,正确使用示例: #t+ mygroup \n请使用#help ignore进行查询。")
2606
+ self.warning(Settings.gettext("msg_T_plus_incorrect"))
2441
2607
  return
2442
2608
 
2443
2609
  groupname = code.code[2]
2444
- cnts = self.enableGroup(groupname)
2445
- self.info(f"组 {groupname} 中的 {cnts[0]} 个别名,{cnts[1]} 个触发器,{cnts[2]} 个命令,{cnts[3]} 个定时器,{cnts[4]} 个GMCP触发器均已使能。")
2610
+ # 组名支持=, >两种,分别代表仅当前组组,当前组及子组
2611
+ # #t+、t-不指定 >, =时,按 = 处理
2612
+ if groupname.startswith(">"):
2613
+ cnts = self.enableGroup(groupname[1:])
2614
+ elif groupname.startswith("="):
2615
+ cnts = self.enableGroup(groupname[1:], subgroup = False)
2616
+ else:
2617
+ cnts = self.enableGroup(groupname, subgroup = False)
2618
+
2619
+ self.info(Settings.gettext("msg_group_enabled", groupname, *cnts))
2446
2620
 
2447
2621
  elif cmd == "t-":
2448
2622
  if code.length <= 2:
2449
- self.warning("#T-禁用组使用不正确,正确使用示例: #t+ mygroup \n请使用#help ignore进行查询。")
2623
+ self.warning(Settings.gettext("msg_T_minus_incorrect"))
2450
2624
  return
2451
2625
 
2452
2626
  groupname = code.code[2]
2453
- cnts = self.enableGroup(groupname, False)
2454
- self.info(f"组 {groupname} 中的 {cnts[0]} 个别名,{cnts[1]} 个触发器,{cnts[2]} 个命令,{cnts[3]} 个定时器,{cnts[4]} 个GMCP触发器均已禁用。")
2627
+ if groupname.startswith(">"):
2628
+ cnts = self.enableGroup(groupname[1:], False)
2629
+ elif groupname.startswith("="):
2630
+ cnts = self.enableGroup(groupname[1:], False, subgroup = False)
2631
+ else:
2632
+ cnts = self.enableGroup(groupname, False)
2455
2633
 
2456
- def handle_repeat(self, code: CodeLine = None, *args, **kwargs):
2634
+ self.info(Settings.gettext("msg_group_disabled", groupname, *cnts))
2635
+
2636
+ def handle_repeat(self, code: Optional[CodeLine] = None, *args, **kwargs):
2457
2637
  '''
2458
2638
  嵌入命令 #repeat / #rep 的执行函数,重复向session输出上一次人工输入的命令。
2459
2639
  该函数不应该在代码中直接调用。
@@ -2468,9 +2648,9 @@ class Session:
2468
2648
  if self.connected and self.last_command:
2469
2649
  self.exec_command(self.last_command)
2470
2650
  else:
2471
- self.info("当前会话没有连接或没有键入过指令,repeat无效")
2651
+ self.info(Settings.gettext("msg_repeat_invalid"))
2472
2652
 
2473
- async def handle_num(self, times, code: CodeLine = None, *args, **kwargs):
2653
+ async def handle_num(self, times, code: CodeLine, *args, **kwargs):
2474
2654
  '''
2475
2655
  嵌入命令 #{num} 的执行函数,重复执行多次命令。
2476
2656
  该函数不应该在代码中直接调用。
@@ -2499,7 +2679,7 @@ class Session:
2499
2679
  for i in range(0, times):
2500
2680
  await cmd.async_execute(self, *args, **kwargs)
2501
2681
 
2502
- def handle_gmcp(self, code: CodeLine = None, *args, **kwargs):
2682
+ def handle_gmcp(self, code: CodeLine, *args, **kwargs):
2503
2683
  '''
2504
2684
  嵌入命令 #gmcp 的执行函数,操作GMCPTrigger。该命令可以不带参数、带一个参数或者两个参数。
2505
2685
  该函数不应该在代码中直接调用。
@@ -2508,10 +2688,14 @@ class Session:
2508
2688
  - #gmcp: 显示本会话所有GMCPTrigger
2509
2689
  - #gmcp {gmcp_key}: 显示本会话中name为{gmcp_key}的GMCPTrigger信息
2510
2690
  - #gmcp {gmcp_key} {on/off/del}: 使能/禁用/删除本会话中name为{gmcp_key}的GMCPTrigger
2691
+ - #gmcp [>=]{groupname}: 显示本会话中组名为{groupname}(当为=时)及其子组(当为>时)的所有GMCPTrigger
2692
+ - #gmcp [>=]{groupname} {on/off/del}: 使能/禁用/删除本会话中group为{groupname}的GMCPTrigger
2511
2693
  - 由于GMCPTrigger的特殊性,其只能使用脚本代码创建
2512
2694
 
2513
2695
  参数:
2514
2696
  :gmcp_key: GMCPTrigger的关键字name
2697
+ :>/=: 二者选择其一,标记第二个参数为组名,且指定是否包含子组。当为=时,表示仅指定组,当为>时,表示指定组及其子组。
2698
+ :groupname: 组名,即 gmcp 的 group 属性。虽然GMCP大概率不会使用group,但仍支持此功能。
2515
2699
  :on: 使能
2516
2700
  :off: 禁用
2517
2701
  :del: 删除
@@ -2529,9 +2713,9 @@ class Session:
2529
2713
  - #timer
2530
2714
  '''
2531
2715
 
2532
- self._handle_objs("GMCPs", self._gmcp, *code.code[2:])
2716
+ self._handle_objs(GMCPTrigger, self._gmcp, *code.code[2:])
2533
2717
 
2534
- def handle_message(self, code: CodeLine = None, *args, **kwargs):
2718
+ def handle_message(self, code: CodeLine, *args, **kwargs):
2535
2719
  '''
2536
2720
  嵌入命令 #message / #mess 的执行函数,弹出对话框显示给定信息。
2537
2721
  该函数不应该在代码中直接调用。
@@ -2547,14 +2731,14 @@ class Session:
2547
2731
  - ``#mess %line`` : 使用弹出窗口显示系统变量%line的值
2548
2732
  '''
2549
2733
 
2550
- title = "来自会话 {} 的消息".format(self.name)
2734
+ title = Settings.gettext("msg_window_title", self.name)
2551
2735
 
2552
2736
  new_cmd_text, new_code = code.expand(self, *args, **kwargs)
2553
2737
  index = new_cmd_text.find(" ")
2554
2738
  self.application.show_message(title, new_cmd_text[index:], False)
2555
2739
 
2556
2740
 
2557
- def handle_all(self, code: CodeLine = None, *args, **kwargs):
2741
+ def handle_all(self, code: CodeLine, *args, **kwargs):
2558
2742
  '''
2559
2743
  嵌入命令 #all 的执行函数,向所有会话发送统一命令。
2560
2744
  该函数不应该在代码中直接调用。
@@ -2657,7 +2841,13 @@ class Session:
2657
2841
  def _load_module(self, module_name):
2658
2842
  "加载指定名称模块"
2659
2843
  try:
2660
- if module_name not in self._modules.keys():
2844
+ if module_name in self.application.plugins.keys():
2845
+ plugin = self.application.plugins[module_name]
2846
+ if isinstance(plugin, Plugin):
2847
+ plugin.onSessionCreate(self)
2848
+ self.info(Settings.gettext("msg_plugin_loaded", module_name))
2849
+
2850
+ elif module_name not in self._modules.keys():
2661
2851
  self._modules[module_name] = ModuleInfo(module_name, self)
2662
2852
 
2663
2853
  else:
@@ -2667,8 +2857,8 @@ class Session:
2667
2857
 
2668
2858
  except Exception as e:
2669
2859
  import traceback
2670
- self.error(f"模块 {module_name} 加载失败,异常为 {e}, 类型为 {type(e)}.")
2671
- self.error(f"异常追踪为: {traceback.format_exc()}")
2860
+ self.error(Settings.gettext("msg_module_load_fail", module_name, e, type(e)))
2861
+ self.error(Settings.gettext("msg_exception_traceback", traceback.format_exc()))
2672
2862
 
2673
2863
  def unload_module(self, module_names):
2674
2864
  """
@@ -2695,8 +2885,14 @@ class Session:
2695
2885
  if isinstance(mod, ModuleInfo):
2696
2886
  mod.unload()
2697
2887
 
2888
+ elif module_name in self.application.plugins.keys():
2889
+ plugin = self.application.plugins[module_name]
2890
+ if isinstance(plugin, Plugin):
2891
+ plugin.onSessionDestroy(self)
2892
+ self.info(Settings.gettext("msg_plugin_unloaded", module_name))
2893
+
2698
2894
  else:
2699
- self.warning(f"指定模块名称 {module_name} 并未加载.")
2895
+ self.warning(Settings.gettext("msg_module_not_loaded", module_name))
2700
2896
 
2701
2897
  def reload_module(self, module_names = None):
2702
2898
  """
@@ -2711,7 +2907,7 @@ class Session:
2711
2907
  if isinstance(module, ModuleInfo):
2712
2908
  module.reload()
2713
2909
 
2714
- self.info(f"所有配置模块全部重新加载完成.")
2910
+ self.info(Settings.gettext("msg_all_module_reloaded"))
2715
2911
 
2716
2912
  elif isinstance(module_names, (list, tuple)):
2717
2913
  for mod in module_names:
@@ -2721,7 +2917,7 @@ class Session:
2721
2917
  if isinstance(module, ModuleInfo):
2722
2918
  module.reload()
2723
2919
  else:
2724
- self.warning(f"指定模块名称 {mod} 并未加载,无法重新加载.")
2920
+ self.warning(Settings.gettext("msg_module_not_loaded", mod))
2725
2921
 
2726
2922
  elif isinstance(module_names, str):
2727
2923
  if module_names in self._modules.keys():
@@ -2729,10 +2925,10 @@ class Session:
2729
2925
  if isinstance(module, ModuleInfo):
2730
2926
  module.reload()
2731
2927
  else:
2732
- self.warning(f"指定模块名称 {module_names} 并未加载,无法重新加载.")
2928
+ self.warning(Settings.gettext("msg_module_not_loaded", module_names))
2733
2929
 
2734
2930
 
2735
- def handle_load(self, code: CodeLine = None, *args, **kwargs):
2931
+ def handle_load(self, code: CodeLine, *args, **kwargs):
2736
2932
  '''
2737
2933
  嵌入命令 #load 的执行函数,为当前会话执行模块加载操作。当要加载多个模块时,使用空格或英文逗号隔开。
2738
2934
  该函数不应该在代码中直接调用。
@@ -2761,7 +2957,7 @@ class Session:
2761
2957
  modules = ",".join(code.code[2:]).split(",")
2762
2958
  self.load_module(modules)
2763
2959
 
2764
- def handle_reload(self, code: CodeLine = None, *args, **kwargs):
2960
+ def handle_reload(self, code: Optional[CodeLine] = None, *args, **kwargs):
2765
2961
  '''
2766
2962
  嵌入命令 #reload 的执行函数,重新加载模块/插件。
2767
2963
  该函数不应该在代码中直接调用。
@@ -2811,11 +3007,11 @@ class Session:
2811
3007
 
2812
3008
  elif mod in self.plugins.keys():
2813
3009
  self.application.reload_plugin(self.plugins[mod])
2814
- self.info(f'插件 {mod} 重新加载完成!')
3010
+ self.info(Settings.gettext("msg_plugins_reloaded", mod))
2815
3011
  else:
2816
- self.warning(f"指定名称 {mod} 既未找到模块,也未找到插件,重新加载失败..")
3012
+ self.warning(Settings.gettext("msg_name_not_found", mod))
2817
3013
 
2818
- def handle_unload(self, code: CodeLine = None, *args, **kwargs):
3014
+ def handle_unload(self, code: CodeLine, *args, **kwargs):
2819
3015
  '''
2820
3016
  嵌入命令 #unload 的执行函数,卸载模块。
2821
3017
  该函数不应该在代码中直接调用。
@@ -2851,7 +3047,7 @@ class Session:
2851
3047
  modules = ",".join(args).split(",")
2852
3048
  self.unload_module(modules)
2853
3049
 
2854
- def handle_modules(self, code: CodeLine = None, *args, **kwargs):
3050
+ def handle_modules(self, code: CodeLine, *args, **kwargs):
2855
3051
  '''
2856
3052
  嵌入命令 #modules / #mods 的执行函数,显示加载模块清单。该命令不带参数。
2857
3053
  该函数不应该在代码中直接调用。
@@ -2870,9 +3066,9 @@ class Session:
2870
3066
  if len(args) == 0:
2871
3067
  count = len(self._modules.keys())
2872
3068
  if count == 0:
2873
- self.info("当前会话并未加载任何模块。", "MODULES")
3069
+ self.info(Settings.gettext("msg_no_module"), "MODULES")
2874
3070
  else:
2875
- self.info(f"当前会话已加载 {count} 个模块,包括(按加载顺序排列):{list(self._modules.keys())}", "MODULES")
3071
+ self.info(Settings.gettext("msg_module_list", count, list(self._modules.keys()).__repr__()))
2876
3072
 
2877
3073
  elif len(args) >= 1:
2878
3074
  modules = ",".join(args).split(",")
@@ -2881,14 +3077,14 @@ class Session:
2881
3077
  module = self._modules[mod]
2882
3078
  if isinstance(module, ModuleInfo):
2883
3079
  if module.ismainmodule:
2884
- self.info(f"模块 {module.name} 中包含的配置包括: {', '.join(module.config.keys())}")
3080
+ self.info(Settings.gettext("msg_module_configurations", module.name, ",".join(module.config.keys())))
2885
3081
  else:
2886
- self.info(f"模块 {module.name} 为子模块,不包含配置。")
3082
+ self.info(Settings.gettext("msg_submodule_no_config"))
2887
3083
 
2888
3084
  else:
2889
- self.info(f"本会话中不存在指定名称 {mod} 的模块,可能是尚未加载到本会话中")
3085
+ self.info(Settings.gettext("msg_module_not_loaded", mod))
2890
3086
 
2891
- def handle_reset(self, code: CodeLine = None, *args, **kwargs):
3087
+ def handle_reset(self, code: CodeLine, *args, **kwargs):
2892
3088
  '''
2893
3089
  嵌入命令 #reset 的执行函数,复位全部脚本。该命令不带参数。
2894
3090
  复位操作将复位所有的触发器、命令、未完成的任务,并清空所有触发器、命令、别名、变量。
@@ -2905,7 +3101,7 @@ class Session:
2905
3101
 
2906
3102
  self.reset()
2907
3103
 
2908
- def handle_save(self, code: CodeLine = None, *args, **kwargs):
3104
+ def handle_save(self, code: Optional[CodeLine] = None, *args, **kwargs):
2909
3105
  '''
2910
3106
  嵌入命令 #save 的执行函数,保存当前会话变量(系统变量和临时变量除外)至文件。该命令不带参数。
2911
3107
  系统变量包括 %line, %copy 和 %raw 三个,临时变量是指变量名已下划线开头的变量
@@ -2924,7 +3120,11 @@ class Session:
2924
3120
  - #variable
2925
3121
  '''
2926
3122
 
2927
- file = f"{self.name}.mud"
3123
+ muddir = Path.cwd().joinpath('save')
3124
+ if not muddir.exists() or not muddir.is_dir():
3125
+ muddir.mkdir()
3126
+
3127
+ file = muddir.joinpath(f"{self.name}.mud")
2928
3128
 
2929
3129
  with open(file, "wb") as fp:
2930
3130
  saved = dict()
@@ -2937,9 +3137,9 @@ class Session:
2937
3137
  saved.pop("%raw", None)
2938
3138
  saved.pop("%copy", None)
2939
3139
  pickle.dump(saved, fp)
2940
- self.info(f"会话变量信息已保存到{file}")
3140
+ self.info(Settings.gettext("msg_variables_saved", file))
2941
3141
 
2942
- def handle_clear(self, code: CodeLine = None, *args, **kwargs):
3142
+ def handle_clear(self, code: CodeLine, *args, **kwargs):
2943
3143
  '''
2944
3144
  嵌入命令 #clear / #cls 的执行函数,清空当前会话缓冲与显示。
2945
3145
  该函数不应该在代码中直接调用。
@@ -2950,28 +3150,32 @@ class Session:
2950
3150
 
2951
3151
  self.buffer.text = ""
2952
3152
 
2953
- def handle_test(self, code: CodeLine = None, *args, **kwargs):
3153
+ @exception
3154
+ def handle_test(self, code: CodeLine, *args, **kwargs):
2954
3155
  '''
2955
- 嵌入命令 #test / #show 的执行函数,触发器测试命令。类似于zmud的#show命令。
3156
+ 嵌入命令 #test / #show / #echo 的执行函数,触发器测试命令。类似于zmud的#show命令。
2956
3157
  该函数不应该在代码中直接调用。
2957
3158
 
2958
3159
  使用:
2959
- - #test {some_text}: 测试服务器收到{some_text}时的触发器响应情况。此时,触发器不会真的响应。
2960
- - #tt {some_test}: 与#test 的差异是,若存在匹配的触发器,无论其是否被使能,该触发器均会实际响应。
3160
+ - #show {some_text}: 测试收到服务器{some_text}时的触发器响应情况。此时,触发器不会真的响应。
3161
+ - #test {some_test}: 与#show 的差异是,若存在匹配的触发器,无论其是否被使能,该触发器均会实际响应。
3162
+ - #echo {some_text}: 模拟收到服务器 {some_text}的情况,触发器按正常情况响应,但不会显示测试结果。
2961
3163
 
2962
3164
  示例:
2963
- - ``#test 你深深吸了口气,站了起来。`` : 模拟服务器收到“你深深吸了口气,站了起来。”时的情况进行触发测试(仅显示触发测试情况)
3165
+ - ``#show 你深深吸了口气,站了起来。`` : 模拟收到服务器“你深深吸了口气,站了起来。”时的情况进行触发测试(仅显示触发测试情况)
2964
3166
  - ``#test %copy``: 复制一句话,模拟服务器再次收到复制的这句内容时的情况进行触发器测试
2965
- - ``#test 你深深吸了口气,站了起来。`` : 模拟服务器收到“你深深吸了口气,站了起来。”时的情况进行触发测试(会实际导致触发器动作)
3167
+ - ``#test 你深深吸了口气,站了起来。`` : 模拟收到服务器“你深深吸了口气,站了起来。”时的情况进行触发测试(会实际导致触发器动作)
3168
+ - ``#echo 你深深吸了口气,站了起来。`` : 模拟收到服务器“你深深吸了口气,站了起来。”时的情况进行触发测试(不会显示测试结果)
2966
3169
 
2967
3170
  注意:
2968
- - #test命令测试触发器时,触发器不会真的响应。
2969
- - #tt命令测试触发器时,触发器无论是否使能,均会真的响应。
3171
+ - #show命令测试触发器时,触发器不会真的响应。
3172
+ - #test命令测试触发器时,触发器无论是否使能,均会真的响应。
3173
+ - #echo命令可以用来人工激发触发器。
2970
3174
  '''
2971
3175
  cmd = code.code[1].lower()
2972
- docallback = False
2973
- if cmd == "test":
2974
- docallback = True
3176
+ docallback = True
3177
+ if cmd == "show":
3178
+ docallback = False
2975
3179
 
2976
3180
  new_cmd_text, new_code = code.expand(self, *args, **kwargs)
2977
3181
  line = new_cmd_text[6:] # 取出#test 之后的所有内容
@@ -2997,6 +3201,10 @@ class Session:
2997
3201
  tris_disabled.sort(key = lambda tri: tri.priority)
2998
3202
 
2999
3203
  for raw_line in lines:
3204
+ # echo 模式下,直接将原始数据输出到窗口,并进行触发测试
3205
+ if cmd == "echo":
3206
+ self.writetobuffer(raw_line, True)
3207
+
3000
3208
  tri_line = self.getPlainText(raw_line)
3001
3209
 
3002
3210
  block = False
@@ -3006,67 +3214,76 @@ class Session:
3006
3214
  else:
3007
3215
  state = tri.match(tri_line, docallback = docallback)
3008
3216
 
3009
- if state.result == Trigger.SUCCESS:
3217
+ if state and (state.result == Trigger.SUCCESS):
3010
3218
  triggered_enabled += 1
3011
3219
  if not block:
3012
3220
  triggered += 1
3013
- # info_enabled.append(f" {Settings.INFO_STYLE}{tri.__detailed__()} 正常触发。{Settings.CLR_STYLE}")
3014
- # info_enabled.append(f" {Settings.INFO_STYLE}捕获:{state.wildcards}{Settings.CLR_STYLE}")
3015
- info_enabled.append(f" {tri.__detailed__()} 正常触发。")
3016
- info_enabled.append(f" 捕获:{state.wildcards}")
3221
+ info_enabled.append(Settings.gettext("msg_tri_triggered", tri.__detailed__()))
3222
+ info_enabled.append(Settings.gettext("msg_tri_wildcards", state.wildcards))
3017
3223
 
3018
3224
  if not tri.keepEval: # 非持续匹配的trigger,匹配成功后停止检测后续Trigger
3019
- info_enabled.append(f" {Settings.WARN_STYLE}该触发器未开启keepEval, 会阻止后续触发器。{Settings.CLR_STYLE}")
3020
- #info_enabled.append(f" 该触发器未开启keepEval, 会阻止后续触发器。")
3225
+ info_enabled.append(Settings.gettext("msg_tri_prevent", Settings.WARN_STYLE, Settings.CLR_STYLE))
3226
+ #info_enabled.append(f" {Settings.WARN_STYLE}该触发器未开启keepEval, 会阻止后续触发器。{Settings.CLR_STYLE}")
3021
3227
  block = True
3022
3228
  else:
3023
- info_enabled.append(f" {Settings.WARN_STYLE}{tri.__detailed__()} 可以触发,但由于优先级与keepEval设定,触发器不会触发。{Settings.CLR_STYLE}")
3024
- #info_enabled.append(f" {tri.__detailed__()} 可以触发,但由于优先级与keepEval设定,触发器不会触发。")
3229
+ info_enabled.append(Settings.gettext("msg_tri_ignored", tri.__detailed__(), Settings.WARN_STYLE, Settings.CLR_STYLE))
3230
+ # info_enabled.append(f" {Settings.WARN_STYLE}{tri.__detailed__()} 可以触发,但由于优先级与keepEval设定,触发器不会触发。{Settings.CLR_STYLE}")
3025
3231
 
3026
-
3027
- for tri in tris_disabled:
3028
- if tri.raw:
3029
- state = tri.match(raw_line, docallback = docallback)
3030
- else:
3031
- state = tri.match(tri_line, docallback = docallback)
3032
-
3033
- if state.result == Trigger.SUCCESS:
3034
- triggered_disabled += 1
3035
- # info_disabled.append(f" {Settings.INFO_STYLE}{tri.__detailed__()} 可以匹配触发。{Settings.CLR_STYLE}")
3036
- info_disabled.append(f" {tri.__detailed__()} 可以匹配触发。")
3232
+ if cmd != "echo":
3233
+ for tri in tris_disabled:
3234
+ if tri.raw:
3235
+ state = tri.match(raw_line, docallback = docallback)
3236
+ else:
3237
+ state = tri.match(tri_line, docallback = docallback)
3238
+
3239
+ if state and (state.result == Trigger.SUCCESS):
3240
+ triggered_disabled += 1
3241
+ #info_disabled.append(f" {tri.__detailed__()} 可以匹配触发。")
3242
+ info_disabled.append(Settings.gettext("msg_tri_matched", tri.__detailed__()))
3243
+
3244
+ if triggered_enabled + triggered_disabled == 0:
3245
+ info_all.append("")
3246
+
3247
+ if cmd != "echo":
3248
+ if triggered_enabled == 0:
3249
+ info_enabled.insert(0, Settings.gettext("msg_enabled_summary_0", Settings.INFO_STYLE))
3250
+ #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,没有可以触发的。")
3251
+ elif triggered < triggered_enabled:
3252
+ info_enabled.insert(0, Settings.gettext("msg_enabled_summary_1", Settings.INFO_STYLE, triggered_enabled, triggered, triggered_enabled - triggered))
3253
+ #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个可以触发,实际触发 {triggered} 个,另有 {triggered_enabled - triggered} 个由于 keepEval 原因实际不会触发。")
3254
+ else:
3255
+ info_enabled.insert(0, Settings.gettext("msg_enabled_summary_2", Settings.INFO_STYLE, triggered_enabled))
3256
+ #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个全部可以被正常触发。")
3037
3257
 
3258
+ if triggered_disabled > 0:
3259
+ info_disabled.insert(0, Settings.gettext("msg_disabled_summary_0", Settings.INFO_STYLE, triggered_disabled))
3260
+ #info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能的触发器中,共有 {triggered_disabled} 个可以匹配。")
3261
+ else:
3262
+ info_disabled.insert(0, Settings.gettext("msg_disabled_summary_1", Settings.INFO_STYLE))
3263
+ #info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能触发器,没有可以匹配的。")
3264
+
3265
+ info_all.append("")
3038
3266
  if triggered_enabled + triggered_disabled == 0:
3039
- info_all.append("")
3040
-
3041
- if triggered_enabled == 0:
3042
- info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,没有可以触发的。")
3043
- elif triggered < triggered_enabled:
3044
- info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个可以触发,实际触发 {triggered} 个,另有 {triggered_enabled - triggered} 个由于 keepEval 原因实际不会触发。")
3045
- else:
3046
- info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个全部可以被正常触发。")
3047
-
3048
- if triggered_disabled > 0:
3049
- info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能的触发器中,共有 {triggered_disabled} 个可以匹配。")
3050
- else:
3051
- info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能触发器,没有可以匹配的。")
3052
-
3053
- info_all.append("")
3054
- if triggered_enabled + triggered_disabled == 0:
3055
- #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3056
- info_all.append(f" 测试内容: {line}")
3057
- info_all.append(f" 测试结果: 没有可以匹配的触发器。")
3058
- else:
3059
- #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3060
- info_all.append(f" 测试内容: {line}")
3061
- info_all.append(f" 测试结果: 有{triggered}个触发器可以被正常触发,一共有{triggered_enabled + triggered_disabled}个满足匹配触发要求。")
3062
- info_all.extend(info_enabled)
3063
- info_all.extend(info_disabled)
3064
-
3065
- title = f"触发器测试 - {'响应模式' if docallback else '测试模式'}"
3066
- self.info("\n".join(info_all), title)
3067
- #self.info("PYMUD 触发器测试 完毕")
3267
+ #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3268
+ info_all.append(Settings.gettext("msg_test_summary_0", line))
3269
+ info_all.append(Settings.gettext("msg_test_summary_1"))
3270
+ #info_all.append(f" 测试内容: {line}")
3271
+ #info_all.append(f" 测试结果: 没有可以匹配的触发器。")
3272
+ else:
3273
+ #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3274
+ info_all.append(Settings.gettext("msg_test_summary_0", line))
3275
+ info_all.append(Settings.gettext("msg_test_summary_2", triggered, triggered_enabled + triggered_disabled))
3276
+ #info_all.append(f" 测试内容: {line}")
3277
+ #info_all.append(f" 测试结果: 有{triggered}个触发器可以被正常触发,一共有{triggered_enabled + triggered_disabled}个满足匹配触发要求。")
3278
+ info_all.extend(info_enabled)
3279
+ info_all.extend(info_disabled)
3280
+
3281
+ title = Settings.gettext("msg_test_title", Settings.gettext("msg_triggered_mode") if docallback else Settings.gettext("msg_matched_mode"))
3282
+ #title = f"触发器测试 - {'响应模式' if docallback else '测试模式'}"
3283
+ self.info("\n".join(info_all), title)
3284
+ #self.info("PYMUD 触发器测试 完毕")
3068
3285
 
3069
- def handle_plugins(self, code: CodeLine = None, *args, **kwargs):
3286
+ def handle_plugins(self, code: CodeLine, *args, **kwargs):
3070
3287
  '''
3071
3288
  嵌入命令 #plugins 的执行函数,显示插件信息。该命令可以不带参数、带一个参数。
3072
3289
  该函数不应该在代码中直接调用。
@@ -3084,20 +3301,24 @@ class Session:
3084
3301
  if len(args) == 0:
3085
3302
  count = len(self.plugins.keys())
3086
3303
  if count == 0:
3087
- self.info("PYMUD当前并未加载任何插件。", "PLUGINS")
3304
+ self.info(Settings.gettext("msg_no_plugins"), "PLUGINS")
3305
+ #self.info("PYMUD当前并未加载任何插件。", "PLUGINS")
3088
3306
  else:
3089
- self.info(f"PYMUD当前已加载 {count} 个插件,分别为:", "PLUGINS")
3307
+ self.info(Settings.gettext("msg_plugins_list", count), "PLUGINS")
3308
+ #self.info(f"PYMUD当前已加载 {count} 个插件,分别为:", "PLUGINS")
3090
3309
  for name, plugin in self.plugins.items():
3091
- self.info(f"{plugin.desc['DESCRIPTION']}, 版本 {plugin.desc['VERSION']} 作者 {plugin.desc['AUTHOR']} 发布日期 {plugin.desc['RELEASE_DATE']}", f"PLUGIN {name}")
3310
+ self.info(Settings.gettext("msg_plugins_info", plugin.desc['DESCRIPTION'], plugin.desc['VERSION'], plugin.desc['AUTHOR'], plugin.desc['RELEASE_DATE']), f"PLUGIN {name}")
3311
+ #self.info(f"{plugin.desc['DESCRIPTION']}, 版本 {plugin.desc['VERSION']} 作者 {plugin.desc['AUTHOR']} 发布日期 {plugin.desc['RELEASE_DATE']}", f"PLUGIN {name}")
3092
3312
 
3093
3313
  elif len(args) == 1:
3094
3314
  name = args[0]
3095
3315
  if name in self.plugins.keys():
3096
3316
  plugin = self.plugins[name]
3097
- self.info(f"{plugin.desc['DESCRIPTION']}, 版本 {plugin.desc['VERSION']} 作者 {plugin.desc['AUTHOR']} 发布日期 {plugin.desc['RELEASE_DATE']}", f"PLUGIN {name}")
3317
+ self.info(Settings.gettext("msg_plugins_info", plugin.desc['DESCRIPTION'], plugin.desc['VERSION'], plugin.desc['AUTHOR'], plugin.desc['RELEASE_DATE']), f"PLUGIN {name}")
3318
+ #self.info(f"{plugin.desc['DESCRIPTION']}, 版本 {plugin.desc['VERSION']} 作者 {plugin.desc['AUTHOR']} 发布日期 {plugin.desc['RELEASE_DATE']}", f"PLUGIN {name}")
3098
3319
  self.writetobuffer(plugin.help, True)
3099
3320
 
3100
- def handle_replace(self, code: CodeLine = None, *args, **kwargs):
3321
+ def handle_replace(self, code: CodeLine, *args, **kwargs):
3101
3322
  '''
3102
3323
  嵌入命令 #replace 的执行函数,修改显示内容,将当前行原本显示内容替换为msg显示。不需要增加换行符。
3103
3324
  该函数不应该在代码中直接调用。
@@ -3121,7 +3342,7 @@ class Session:
3121
3342
  new_text, new_code = code.expand(self, *args, **kwargs)
3122
3343
  self.replace(new_text[9:])
3123
3344
 
3124
- def handle_gag(self, code: CodeLine = None, *args, **kwargs):
3345
+ def handle_gag(self, code: Optional[CodeLine] = None, *args, **kwargs):
3125
3346
  '''
3126
3347
  嵌入命令 #gag 的执行函数,在主窗口中不显示当前行内容,一般用于触发器中。
3127
3348
  该函数不应该在代码中直接调用。
@@ -3138,7 +3359,7 @@ class Session:
3138
3359
 
3139
3360
  self.display_line = ""
3140
3361
 
3141
- def handle_py(self, code: CodeLine = None, *args, **kwargs):
3362
+ def handle_py(self, code: CodeLine, *args, **kwargs):
3142
3363
  '''
3143
3364
  嵌入命令 #py 的执行函数,执行 Python 语句。
3144
3365
  该函数不应该在代码中直接调用。
@@ -3155,9 +3376,9 @@ class Session:
3155
3376
  try:
3156
3377
  exec(code.commandText[4:])
3157
3378
  except Exception as e:
3158
- self.error(f"Python执行错误:{e}")
3379
+ self.error(Settings.gettext("msg_py_exception", e))
3159
3380
 
3160
- def handle_info(self, code: CodeLine = None, *args, **kwargs):
3381
+ def handle_info(self, code: CodeLine, *args, **kwargs):
3161
3382
  '''
3162
3383
  嵌入命令 #info 的执行函数,使用 session.info 输出一行,主要用于测试。
3163
3384
  该函数不应该在代码中直接调用。
@@ -3173,7 +3394,7 @@ class Session:
3173
3394
  new_text, new_code = code.expand(self, *args, **kwargs)
3174
3395
  self.info(new_text[6:])
3175
3396
 
3176
- def handle_warning(self, code: CodeLine = None, *args, **kwargs):
3397
+ def handle_warning(self, code: CodeLine, *args, **kwargs):
3177
3398
  '''
3178
3399
  嵌入命令 #warning 的执行函数,使用 session.warning 输出一行,主要用于测试。
3179
3400
  该函数不应该在代码中直接调用。
@@ -3189,7 +3410,7 @@ class Session:
3189
3410
  new_text, new_code = code.expand(self, *args, **kwargs)
3190
3411
  self.warning(new_text[9:])
3191
3412
 
3192
- def handle_error(self, code: CodeLine = None, *args, **kwargs):
3413
+ def handle_error(self, code: CodeLine, *args, **kwargs):
3193
3414
  '''
3194
3415
  嵌入命令 #error 的执行函数,使用 session.error 输出一行,主要用于测试。
3195
3416
  该函数不应该在代码中直接调用。
@@ -3205,21 +3426,12 @@ class Session:
3205
3426
  new_text, new_code = code.expand(self, *args, **kwargs)
3206
3427
  self.error(new_text[7:])
3207
3428
 
3208
- def info2(self, msg, title = "消息", style = Settings.INFO_STYLE):
3429
+ def info2(self, msg, title = None, style = Settings.INFO_STYLE):
3430
+ title = title or Settings.gettext("title_msg")
3209
3431
  msg = f"{msg}"
3210
-
3211
- # if Settings.client["newline"] in msg:
3212
- # new_lines = list()
3213
- # msg_lines = msg.split(Settings.client["newline"])
3214
- # for line in msg_lines:
3215
- # new_lines.append("{}{}".format(style, line))
3216
-
3217
- # msg = Settings.client["newline"].join(new_lines)
3218
-
3219
- # 将颜色跨行显示移动到了MudFormatProcessor中,此处无需再处理(不行,还得恢复)
3220
3432
  self.writetobuffer("{}〔{}〕{}{}".format(style, title, msg, Settings.CLR_STYLE), newline = True)
3221
3433
 
3222
- def info(self, msg, title = "提示", style = Settings.INFO_STYLE):
3434
+ def info(self, msg, title = None, style = Settings.INFO_STYLE):
3223
3435
  """
3224
3436
  使用默认的INFO_STYLE(绿色)输出信息,并自动换行。信息格式类似 [title] msg
3225
3437
 
@@ -3227,9 +3439,10 @@ class Session:
3227
3439
  :param title: 要显示在前面的标题,不指定时默认为 PYMUD INFO
3228
3440
  :param style: 要输出信息的格式(ANSI), 默认为 INFO_STYLE, \x1b[32m
3229
3441
  """
3442
+ title = title or Settings.gettext("title_info")
3230
3443
  self.info2(msg, title, style)
3231
3444
 
3232
- def warning(self, msg, title = "警告", style = Settings.WARN_STYLE):
3445
+ def warning(self, msg, title = None, style = Settings.WARN_STYLE):
3233
3446
  """
3234
3447
  使用默认的WARN_STYLE(黄色)输出信息,并自动换行。信息格式类似 [title] msg
3235
3448
 
@@ -3237,9 +3450,10 @@ class Session:
3237
3450
  :param title: 要显示在前面的标题,不指定时默认为 PYMUD WARNING
3238
3451
  :param style: 要输出信息的格式(ANSI), 默认为 WARN_STYLE, \x1b[33m
3239
3452
  """
3453
+ title = title or Settings.gettext("title_warning")
3240
3454
  self.info2(msg, title, style)
3241
3455
 
3242
- def error(self, msg, title = "错误", style = Settings.ERR_STYLE):
3456
+ def error(self, msg, title = None, style = Settings.ERR_STYLE):
3243
3457
  """
3244
3458
  使用默认的ERR_STYLE(红色)输出信息,并自动换行。信息格式类似 [title] msg
3245
3459
 
@@ -3247,9 +3461,10 @@ class Session:
3247
3461
  :param title: 要显示在前面的标题,不指定时默认为 PYMUD ERROR
3248
3462
  :param style: 要输出信息的格式(ANSI), 默认为 ERR_STYLE, \x1b[31m
3249
3463
  """
3464
+ title = title or Settings.gettext("title_error")
3250
3465
  self.info2(msg, title, style)
3251
3466
 
3252
- def handle_log(self, code: CodeLine = None, *args, **kwargs):
3467
+ def handle_log(self, code: CodeLine, *args, **kwargs):
3253
3468
  '''
3254
3469
  嵌入命令 #log 的执行函数,控制当前会话的记录状态。
3255
3470
  该函数不应该在代码中直接调用。
@@ -3292,16 +3507,21 @@ class Session:
3292
3507
  if len(args) == 0:
3293
3508
  session_loggers = set(self._loggers.keys())
3294
3509
  app_loggers = set(self.application.loggers.keys()).difference(session_loggers)
3295
- self.info("本会话中的记录器情况:")
3510
+
3511
+ self.info(Settings.gettext("msg_log_title"))
3512
+ #self.info("本会话中的记录器情况:")
3296
3513
  for name in session_loggers:
3297
3514
  logger = self.application.loggers[name]
3298
- self.info(f"记录器 {logger.name}, 当前状态: {'开启' if logger.enabled else '关闭'}, 文件模式: {logger.mode}, 记录模式: {'ANSI' if logger.raw else '纯文本'}")
3515
+ self.info(f"{Settings.gettext('logger')} {logger.name}, {Settings.gettext('logger_status')}: {Settings.gettext('enabled') if logger.enabled else Settings.gettext('disabled')}, {Settings.gettext('file_mode')}: {logger.mode}, {Settings.gettext('logger_mode')}: {Settings.gettext('ANSI') if logger.raw else Settings.gettext('plain_text')}")
3516
+ #self.info(f"记录器 {logger.name}, 当前状态: {'开启' if logger.enabled else '关闭'}, 文件模式: {logger.mode}, 记录模式: {'ANSI' if logger.raw else '纯文本'}")
3299
3517
 
3300
3518
  if len(app_loggers) > 0:
3301
- self.info("本应用其他会话中的记录器情况:")
3519
+ self.info(Settings.gettext("msg_log_title2"))
3520
+ #self.info("本应用其他会话中的记录器情况:")
3302
3521
  for name in app_loggers:
3303
3522
  logger = self.application.loggers[name]
3304
- self.info(f"记录器 {logger.name}, 当前状态: {'开启' if logger.enabled else '关闭'}, 文件模式: {logger.mode}, 记录模式: {'ANSI' if logger.raw else '纯文本'}")
3523
+ self.info(f"{Settings.gettext('logger')} {logger.name}, {Settings.gettext('logger_status')}: {Settings.gettext('enabled') if logger.enabled else Settings.gettext('disabled')}, {Settings.gettext('file_mode')}: {logger.mode}, {Settings.gettext('logger_mode')}: {Settings.gettext('ANSI') if logger.raw else Settings.gettext('plain_text')}")
3524
+ #self.info(f"记录器 {logger.name}, 当前状态: {'开启' if logger.enabled else '关闭'}, 文件模式: {logger.mode}, 记录模式: {'ANSI' if logger.raw else '纯文本'}")
3305
3525
 
3306
3526
  else:
3307
3527
  name = self.name
@@ -3311,24 +3531,30 @@ class Session:
3311
3531
  if (args[0] == "start"):
3312
3532
  if "-n" in args:
3313
3533
  mode = "n"
3314
- mode_name = '新建'
3534
+ #mode_name = '新建'
3535
+ mode_name = Settings.gettext("filemode_new")
3315
3536
  elif "-w" in args:
3316
3537
  mode = "w"
3317
- mode_name = '覆写'
3538
+ #mode_name = '覆写'
3539
+ mode_name = Settings.gettext("filemode_overwrite")
3318
3540
  else:
3319
3541
  mode = "a"
3320
- mode_name = '添加'
3542
+ #mode_name = '追加'
3543
+ mode_name = Settings.gettext("filemode_append")
3321
3544
 
3322
3545
  raw = True if "-r" in args else False
3323
- raw_name = '原始ANSI' if raw else '纯文本'
3546
+ #raw_name = '原始ANSI' if raw else '纯文本'
3547
+ raw_name = Settings.gettext("ANSI") if raw else Settings.gettext("plain_text")
3324
3548
 
3325
3549
  logger = self.getLogger(name = name, mode = mode, raw = raw)
3326
3550
  logger.enabled = True
3327
3551
 
3328
- self.info(f"{datetime.datetime.now()}: 记录器{name}以{mode_name}文件模式以及{raw_name}记录模式开启。")
3552
+ #self.info(f"{datetime.datetime.now()}: 记录器{name}以{mode_name}文件模式以及{raw_name}记录模式开启。")
3553
+ self.info(Settings.gettext("msg_logger_enabled", datetime.datetime.now(), name, mode_name, raw_name))
3329
3554
 
3330
3555
  elif (args[0] == "stop"):
3331
- self.info(f"{datetime.datetime.now()}: 记录器{name}记录已关闭。")
3556
+ #self.info(f"{datetime.datetime.now()}: 记录器{name}记录已关闭。")
3557
+ self.info(Settings.gettext("msg_logger_disabled", datetime.datetime.now(), name))
3332
3558
  self.log.enabled = False
3333
3559
 
3334
3560
  elif (args[0] == "show"):
@@ -3345,7 +3571,7 @@ class Session:
3345
3571
  self.application.logFileShown = filepath
3346
3572
  self.application.showLogInTab()
3347
3573
  else:
3348
- self.warning(f'指定记录文件 {file} 不存在!')
3574
+ self.warning(Settings.gettext("msg_logfile_not_exists", file))
3349
3575
 
3350
3576
  else:
3351
3577
  self.application.show_logSelectDialog()