-
Notifications
You must be signed in to change notification settings - Fork 0
新增訊息閱讀器功能 (NVDA+Windows+J) #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+501
−1
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
df89e76
新增訊息閱讀器功能 (NVDA+Windows+J)
keyang556 a74fe36
_chatParser: 支援多行訊息(Shift+Enter 換行)
keyang556 e75d6ea
修正訊息閱讀器暫存檔路徑,避免鎖定插件資料夾
keyang556 d8c4ac2
訊息閱讀器關閉時自動刪除暫存檔
keyang556 26fc287
儲存前預先刪除暫存檔,避免出現取代確認對話框
keyang556 47265eb
改善訊息閱讀器:並發保護、起始位置、Escape 處理
keyang556 cb3509f
訊息閱讀器重複觸發時給予使用者回饋
keyang556 1ea2f51
訊息閱讀器加入單例防護,避免開啟多個視窗
keyang556 f4f6435
將 _messageReader.py 中的硬編碼中文字串改為 _() 包裹
keyang556 2387fbb
修正關閉後再次開啟訊息閱讀器會引發 RuntimeError 的問題
keyang556 69d8b97
修正 _messageReader.py 中 TypeError: 'module' object is not callable
keyang556 d9b94a0
修正 line.py 第 5969 行縮排錯誤 (IndentationError)
keyang556 ab8406d
Update addon/appModules/line.py
keyang556 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||
| import re | ||||||
|
|
||||||
| _DATE_RE = re.compile(r'^\d{4}\.\d{2}\.\d{2} .+$') | ||||||
| _MSG_RE = re.compile(r'^(\d{2}:\d{2}) (\S+?) (.+)$') | ||||||
| _RECALL_RE = re.compile(r'^(\d{2}:\d{2}) (\S+?)已收回訊息$') | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
若 LINE 匯出格式為 建議修正為:
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: addon/appModules/_chatParser.py
Line: 5
Comment:
**`_RECALL_RE` 在訊息名稱與「已收回訊息」之間缺少空白**
`_MSG_RE` 使用空白分隔三個欄位(時間、名稱、內容),格式為 `HH:MM NAME content`。但 `_RECALL_RE` 在名稱與「已收回訊息」之間沒有空白,格式為 `HH:MM NAME已收回訊息`。
若 LINE 匯出格式為 `HH:MM NAME 已收回訊息`(名稱後有空白),`_RECALL_RE` 無法匹配,此行會進入 `_MSG_RE` 匹配,將「已收回訊息」視為普通訊息內容,失去語意標記。
建議修正為:
```suggestion
_RECALL_RE = re.compile(r'^(\d{2}:\d{2}) (\S+?) 已收回訊息$')
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||
|
|
||||||
|
|
||||||
| def parseChatFile(filePath): | ||||||
| """Parse a LINE chat export text file. | ||||||
|
|
||||||
| Returns a list of dicts with keys: name, content, time. | ||||||
| Display format: name content time | ||||||
| Continuation lines (Shift+Enter multi-line messages) are appended to the | ||||||
| previous message's content. | ||||||
| """ | ||||||
| messages = [] | ||||||
| with open(filePath, 'r', encoding='utf-8') as f: | ||||||
| for line in f: | ||||||
| line = line.rstrip('\r\n') | ||||||
| if not line: | ||||||
| continue | ||||||
| if _DATE_RE.match(line): | ||||||
| continue | ||||||
| m = _RECALL_RE.match(line) | ||||||
| if m: | ||||||
| messages.append({ | ||||||
| 'time': m.group(1), | ||||||
| 'name': m.group(2), | ||||||
| 'content': '已收回訊息', | ||||||
| }) | ||||||
| continue | ||||||
| m = _MSG_RE.match(line) | ||||||
| if m: | ||||||
| messages.append({ | ||||||
| 'time': m.group(1), | ||||||
| 'name': m.group(2), | ||||||
| 'content': m.group(3), | ||||||
| }) | ||||||
greptile-apps[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| continue | ||||||
| # Continuation line (Shift+Enter multi-line message) | ||||||
| if messages: | ||||||
| messages[-1]['content'] += '\n' + line | ||||||
| return messages | ||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| import wx | ||
| import gui | ||
| from logHandler import log | ||
|
|
||
|
|
||
| class MessageReaderDialog(wx.Dialog): | ||
| """A dialog for reading LINE chat messages with up/down arrow navigation. | ||
|
|
||
| Each message is displayed as: name content time | ||
| Up arrow moves to the previous message, down arrow moves to the next. | ||
| """ | ||
|
|
||
| def __init__(self, messages, title=None, cleanupPath=None): | ||
| if title is None: | ||
| title = _("訊息閱讀器") | ||
| super().__init__( | ||
| gui.mainFrame, | ||
| title=title, | ||
| style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, | ||
| ) | ||
| self._messages = messages | ||
| self._pos = 0 if messages else -1 | ||
| self._cleanupPath = cleanupPath | ||
|
|
||
| panel = wx.Panel(self) | ||
| sizer = wx.BoxSizer(wx.VERTICAL) | ||
|
|
||
| self._totalLabel = wx.StaticText(panel, label="") | ||
| sizer.Add(self._totalLabel, 0, wx.ALL, 5) | ||
|
|
||
| self._textCtrl = wx.TextCtrl( | ||
| panel, | ||
| style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2, | ||
| size=(500, 100), | ||
| ) | ||
| sizer.Add(self._textCtrl, 1, wx.EXPAND | wx.ALL, 5) | ||
|
|
||
| closeBtn = wx.Button(panel, wx.ID_CLOSE, _("關閉(&C)")) | ||
| sizer.Add(closeBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5) | ||
|
|
||
| panel.SetSizer(sizer) | ||
| sizer.Fit(self) | ||
|
|
||
| self._textCtrl.Bind(wx.EVT_KEY_DOWN, self._onKeyDown) | ||
| closeBtn.Bind(wx.EVT_BUTTON, self._onClose) | ||
| self.Bind(wx.EVT_CLOSE, self._onClose) | ||
| self.Bind(wx.EVT_CHAR_HOOK, self._onCharHook) | ||
|
|
||
| self._updateDisplay() | ||
| self._textCtrl.SetFocus() | ||
|
|
||
| def _formatMessage(self, msg): | ||
| return f"{msg['name']} {msg['content']} {msg['time']}" | ||
|
|
||
| def _updateDisplay(self): | ||
| if not self._messages or self._pos < 0: | ||
| self._textCtrl.SetValue(_("沒有訊息")) | ||
| self._totalLabel.SetLabel("") | ||
| return | ||
| msg = self._messages[self._pos] | ||
| text = self._formatMessage(msg) | ||
| self._textCtrl.SetValue(text) | ||
| self._totalLabel.SetLabel( | ||
| f"{self._pos + 1} / {len(self._messages)}" | ||
| ) | ||
| self._speakMessage(text) | ||
|
|
||
| def _speakMessage(self, text): | ||
| try: | ||
| import speech | ||
| speech.cancelSpeech() | ||
| speech.speakMessage(text) | ||
| except Exception: | ||
| pass | ||
|
|
||
| def _onKeyDown(self, evt): | ||
| keyCode = evt.GetKeyCode() | ||
| if keyCode == wx.WXK_UP: | ||
| self._movePrevious() | ||
| elif keyCode == wx.WXK_DOWN: | ||
| self._moveNext() | ||
| else: | ||
| evt.Skip() | ||
|
|
||
| def _onCharHook(self, evt): | ||
| keyCode = evt.GetKeyCode() | ||
| if keyCode == wx.WXK_ESCAPE: | ||
| self.Close() | ||
| return | ||
| evt.Skip() | ||
|
|
||
| def _movePrevious(self): | ||
| if not self._messages: | ||
| return | ||
| if self._pos > 0: | ||
| self._pos -= 1 | ||
| self._updateDisplay() | ||
| else: | ||
| self._speakMessage(_("已經是第一則訊息")) | ||
|
|
||
| def _moveNext(self): | ||
| if not self._messages: | ||
| return | ||
| if self._pos < len(self._messages) - 1: | ||
| self._pos += 1 | ||
| self._updateDisplay() | ||
| else: | ||
| self._speakMessage(_("已經是最後一則訊息")) | ||
|
|
||
| def _onClose(self, evt): | ||
| global _readerDlg | ||
| _readerDlg = None # Allow future invocations to create a new instance | ||
| # Clean up temp file if specified | ||
| if self._cleanupPath: | ||
| try: | ||
| import os | ||
| if os.path.isfile(self._cleanupPath): | ||
| os.remove(self._cleanupPath) | ||
| log.debug(f"Deleted temp chat export: {self._cleanupPath}") | ||
| except Exception as e: | ||
| log.warning(f"Failed to delete temp chat export: {e}") | ||
| self.Destroy() | ||
greptile-apps[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| _readerDlg = None # module-level singleton sentinel | ||
|
|
||
|
|
||
| def openMessageReader(messages, title=None, cleanupPath=None): | ||
| """Open the message reader dialog on the main GUI thread. | ||
|
|
||
| Args: | ||
| messages: List of parsed message dicts | ||
| title: Dialog window title | ||
| cleanupPath: Optional file path to delete when dialog closes | ||
| """ | ||
| def _show(): | ||
| global _readerDlg | ||
| if _readerDlg and _readerDlg.IsShown(): | ||
| _readerDlg.Raise() | ||
| return | ||
| _readerDlg = MessageReaderDialog(messages, title=title, cleanupPath=cleanupPath) | ||
| _readerDlg.Show() | ||
| _readerDlg.Raise() | ||
| wx.CallAfter(_show) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LINE 桌面版的聊天記錄匯出格式通常以 Tab(
\t) 分隔時間、名稱與訊息內容,例如:目前
_MSG_RE與_RECALL_RE皆以空白(' ')作為欄位分隔符號。若匯出格式確為 Tab 分隔,這兩個 regex 將無法正確解析任何訊息,導致parseChatFile永遠回傳空列表,使用者只會看到「聊天紀錄中沒有訊息」。建議確認實際匯出格式後,視需要將空白改為
\t:另外,若使用空白分隔,含有空白的使用者名稱(如
John Smith)只會將第一個詞解析為名稱,其餘詞彙會被歸入訊息內容,導致顯示錯誤。Prompt To Fix With AI