From 6e0bbc98200b5ccfc7bdbeefd589ae44cdabeb91 Mon Sep 17 00:00:00 2001 From: yorklin Date: Wed, 28 Jan 2026 15:21:43 +0800 Subject: [PATCH 1/8] =?UTF-8?q?update=201.=20=E6=96=B0=E5=A2=9E=E5=90=83?= =?UTF-8?q?=E6=96=99=E7=90=86=E5=BE=AA=E7=92=B0=E3=80=82=202.=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=AF=84=E4=BF=A1=E5=8A=9F=E8=83=BD=E3=80=82=203.=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=89=A9=E5=93=81=E6=90=8D=E5=A3=9E=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E3=80=82=204.=20=E8=A8=AD=E5=AE=9A=E6=87=89=E7=94=A8?= =?UTF-8?q?=E4=BE=86=E6=BA=90=EF=BC=8C=E6=96=B9=E4=BE=BF=E4=BF=A1=E4=BB=B6?= =?UTF-8?q?=E9=81=8E=E6=BF=BE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 178 +++++++++++++++ src/CgLogListener/CgLogListener.csproj | 22 +- src/CgLogListener/CgLogListenerControls.cs | 31 ++- src/CgLogListener/FormMain.Designer.cs | 191 ++++++++++++---- src/CgLogListener/FormMain.cs | 225 +++++++++++++++---- src/CgLogListener/FormMain.resx | 7 +- src/CgLogListener/INotifyMessage.cs | 2 +- src/CgLogListener/MailHelper.cs | 116 ++++++++++ src/CgLogListener/NotifyResult.cs | 21 ++ src/CgLogListener/Settings.cs | 121 ++++++++-- src/CgLogListener/TipNotifyOptions.cs | 50 +++++ src/DiscordNotifier/DiscordNotifier.csproj | 142 +++++++----- src/TelegramNotifier/TelegramNotifier.csproj | 8 +- 13 files changed, 923 insertions(+), 191 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/CgLogListener/MailHelper.cs create mode 100644 src/CgLogListener/NotifyResult.cs create mode 100644 src/CgLogListener/TipNotifyOptions.cs diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e2f6330 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,178 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build Commands + +```bash +# Build entire solution +msbuild src/CgLogListener.sln + +# Build specific project +msbuild src/CgLogListener/CgLogListener.csproj + +# Build Release version +msbuild src/CgLogListener.sln /p:Configuration=Release + +# Restore NuGet packages (if needed) +nuget restore src/CgLogListener.sln +``` + +## Architecture + +This is a Windows Forms application (.NET Framework 4.6) that monitors game log files and sends notifications when keywords are detected. + +### Project Structure + +``` +src/ +├── CgLogListener/ # Main WinForms application +│ ├── FormMain.cs # Main UI, event handling, notification triggers +│ ├── FormMain.Designer.cs # UI layout (auto-generated, can be manually edited) +│ ├── CgLogHandler.cs # FileSystemWatcher for log files (BIG5 encoding) +│ ├── CgLogListenerControls.cs # Custom controls (CgLogListenerCheckBox, CgLogListenerListBox) +│ ├── Settings.cs # Singleton, reads/writes settings.ini +│ ├── TipNotifyOptions.cs # Data class for keyword settings (enabled, playSound, sendMail) +│ ├── NotifyResult.cs # Return type for INotifyMessage.Notify() +│ ├── INotifyMessage.cs # Interface for notification controls +│ └── MailHelper.cs # SMTP email sending, reads mail.ini +├── DiscordNotifier/ # External notifier (Console App) +├── TelegramNotifier/ # External notifier (Console App) +└── LineNotifier/ # External notifier (Console App) +``` + +### Core Interfaces + +```csharp +// INotifyMessage.cs +public interface INotifyMessage +{ + NotifyResult Notify(string message); +} + +// NotifyResult.cs +public class NotifyResult +{ + public bool IsMatch { get; set; } + public bool PlaySound { get; set; } + public bool SendMail { get; set; } +} + +// TipNotifyOptions.cs - stored in settings.ini as "enabled,playSound,sendMail" (e.g., "1,1,0") +public class TipNotifyOptions +{ + public bool Enabled { get; set; } + public bool PlaySound { get; set; } + public bool SendMail { get; set; } +} +``` + +### Notification Flow + +``` +Log file changes → CgLogHandler.OnNewLog event + ↓ +FormMain.Watcher_OnNewLog(log) + ↓ +1. Check 吃料理通知 (cooking reminder with timer) + ↓ +2. Loop through panel1.Controls.OfType() + - CgLogListenerCheckBox (standard keywords with regex) + - CgLogListenerListBox (custom keywords with contains) + ↓ +3. If result.IsMatch: + - Show BalloonTip notification + - Play sound.wav (if result.PlaySound) + - Send email via MailHelper (if result.SendMail) + - Call external notifiers (if CustomNotify enabled) +``` + +### UI Components + +| Control | Type | Function | +|---------|------|----------| +| cgLogListenerCheckBox1-6 | CgLogListenerCheckBox | Standard keyword checkboxes with RegexPattern | +| Dynamic 🔊/✉ checkboxes | CheckBox | Per-keyword sound/mail toggles (created in SetupStandardTips) | +| cgLogListenerListBox | CgLogListenerListBox | Custom keywords list | +| btnAddCus / btnDelCus | Button | Add/remove custom keywords | +| cgLogListenerTrackBar | TrackBar | Sound volume (0-10) | +| checkBox1 | CheckBox | Custom Notify (external notifier) | +| chkCookingReminder | CheckBox | Cooking reminder toggle | +| txtCookingInterval | TextBox | Cooking reminder interval (seconds) | +| timerCooking | Timer | Cooking reminder timer | + +### Standard Keywords (RegexPattern) + +| NameInSetting | Pattern | Description | +|---------------|---------|-------------| +| Health | `在工作時不小心受傷了。` | 採集受傷通知 | +| ItemFull | `物品欄沒有空位。` | 道具滿通知 | +| MP0 | `魔力不足。` | 魔力不足通知 | +| PlayerJoin | `加入了(你\|您)的隊伍。` | 被加入隊伍通知 | +| Sell | `您順利賣掉了一個.*,(收入\|獲得).*魔幣!` | 擺攤售出通知 | +| ReMaze | `你感覺到一股不可思議的力量,而『.*』好像快(要?)消失了。` | 迷宮重組通知 | + +### Cooking Reminder (吃料理通知) + +- **Trigger**: Detects `恢復了\d+魔力` in log +- **Behavior**: Resets timer on each detection, alerts when timer expires +- **Flow**: + ``` + [✓] 吃料理通知 [180] 秒 + ↓ + Log: "恢復了123魔力" detected + ↓ + Timer reset, starts counting down + ↓ + 180 seconds later → BalloonTip + sound.wav + ``` + +### Configuration Files + +| File | Format | Purpose | +|------|--------|---------| +| `settings.ini` | INI | Main settings | +| `mail.ini` | INI | SMTP credentials (host, port, username, password, from, to) | + +**settings.ini structure:** +```ini +[base] +CgLogPath=C:\Game\CrossGate +SoundVol=5 +CustomNotify=0 +CustomNotifier= + +[standard tips] +Health=1,1,0 # enabled=1, playSound=1, sendMail=0 +ItemFull=1,1,1 # enabled=1, playSound=1, sendMail=1 + +[custom tips] +關鍵字=1,1,0 +關鍵字|排除詞=1,1,0 # keyword with exclusion +``` + +**mail.ini structure:** +```ini +[smtp] +host=smtp.gmail.com +port=587 +enableSsl=1 +username= +password= +from=your-email@gmail.com +to=recipient@example.com +``` + +### Adding a New Standard Keyword + +1. Add `CgLogListenerCheckBox` in `FormMain.Designer.cs` +2. Set `NameInSetting` and `RegexPattern` properties +3. Add to `standardCheckBoxes` array in `FormMain.cs` `SetupStandardTips()` + +### Adding a New External Notifier + +1. Create new Console App project targeting .NET Framework 4.6 +2. Add `ini-parser` NuGet package +3. Read config from `{NotifierName}.ini` in same directory as exe +4. Accept message as `args[0]` +5. User configures path in `settings.ini` under `CustomNotifier` diff --git a/src/CgLogListener/CgLogListener.csproj b/src/CgLogListener/CgLogListener.csproj index 7685266..5587725 100644 --- a/src/CgLogListener/CgLogListener.csproj +++ b/src/CgLogListener/CgLogListener.csproj @@ -12,7 +12,7 @@ 512 false - publish\ + C:\Users\aiet\Desktop\TEST\ true Disk false @@ -22,9 +22,11 @@ false false true - 0 + true + 1 2.1.0.%2a false + true true @@ -51,6 +53,18 @@ icon.ico + + 76921EC744D0212480757F6181BD1C3D8ADB9734 + + + CgLogListener_TemporaryKey.pfx + + + true + + + true + ..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll @@ -96,7 +110,10 @@ Resource.resx + + + @@ -116,6 +133,7 @@ Resource.Designer.cs + diff --git a/src/CgLogListener/CgLogListenerControls.cs b/src/CgLogListener/CgLogListenerControls.cs index 9badeb0..507babf 100644 --- a/src/CgLogListener/CgLogListenerControls.cs +++ b/src/CgLogListener/CgLogListenerControls.cs @@ -12,18 +12,18 @@ public class CgLogListenerCheckBox : CheckBox, INotifyMessage public string NameInSetting { get; set; } public string RegexPattern { get; set; } - public bool Notify(string message) + public NotifyResult Notify(string message) { var settings = Settings.GetInstance(); - if (settings.StandardTips.TryGetValue(NameInSetting, out bool value) && - value && + if (settings.StandardTips.TryGetValue(NameInSetting, out TipNotifyOptions options) && + options.Enabled && Regex.IsMatch(message, RegexPattern)) { - return true; + return NotifyResult.Match(options.PlaySound, options.SendMail); } - return false; + return NotifyResult.NoMatch; } } @@ -36,29 +36,36 @@ public class CgLogListenerListBox : ListBox, INotifyMessage { public NotifyIcon NotifyIcon { get; set; } - public bool Notify(string message) + public NotifyResult Notify(string message) { var settings = Settings.GetInstance(); - foreach (var s in settings.CustomizeTips) + foreach (var kv in settings.CustomizeTips) { - var split = s.Split('|'); + var keyword = kv.Key; + var options = kv.Value; + + if (!options.Enabled) continue; + + var split = keyword.Split('|'); if (message.Contains(split[0])) { if (split.Length > 1) { var exps = split[1].Split(','); - - return !exps.Any(x => message.Contains(x));// !message.Contains(split[1]); + if (!exps.Any(x => message.Contains(x))) + { + return NotifyResult.Match(options.PlaySound, options.SendMail); + } } else { - return true; + return NotifyResult.Match(options.PlaySound, options.SendMail); } } } - return false; + return NotifyResult.NoMatch; } } } diff --git a/src/CgLogListener/FormMain.Designer.cs b/src/CgLogListener/FormMain.Designer.cs index f8daf15..4ded9ab 100644 --- a/src/CgLogListener/FormMain.Designer.cs +++ b/src/CgLogListener/FormMain.Designer.cs @@ -1,4 +1,4 @@ -namespace CgLogListener +namespace CgLogListener { partial class FormMain { @@ -38,10 +38,15 @@ private void InitializeComponent() this.toolExit = new System.Windows.Forms.ToolStripMenuItem(); this.txtCgLogPath = new System.Windows.Forms.TextBox(); this.btnSelectLogPath = new System.Windows.Forms.Button(); + this.lblAppName = new System.Windows.Forms.Label(); + this.txtAppName = new System.Windows.Forms.TextBox(); this.panel1 = new System.Windows.Forms.Panel(); + this.chkCookingReminder = new System.Windows.Forms.CheckBox(); + this.txtCookingInterval = new System.Windows.Forms.TextBox(); + this.lblCookingUnit = new System.Windows.Forms.Label(); this.checkBox1 = new System.Windows.Forms.CheckBox(); this.cgLogListenerTrackBar = new CgLogListener.CgLogListenerTrackBar(); - this.cgLogListenerSettingCheckBox1 = new CgLogListener.CgLogListenerCheckBox(); + this.lblSoundVol = new System.Windows.Forms.Label(); this.cgLogListenerCheckBox6 = new CgLogListener.CgLogListenerCheckBox(); this.cgLogListenerCheckBox5 = new CgLogListener.CgLogListenerCheckBox(); this.cgLogListenerCheckBox4 = new CgLogListener.CgLogListenerCheckBox(); @@ -52,8 +57,10 @@ private void InitializeComponent() this.btnAddCus = new System.Windows.Forms.Button(); this.cgLogListenerCheckBox2 = new CgLogListener.CgLogListenerCheckBox(); this.cgLogListenerCheckBox1 = new CgLogListener.CgLogListenerCheckBox(); + this.timerCooking = new System.Windows.Forms.Timer(this.components); this.btnExit = new System.Windows.Forms.Button(); this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + this.cgLogListenerCheckBox7 = new CgLogListener.CgLogListenerCheckBox(); this.notifyIconContextMenu.SuspendLayout(); this.panel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.cgLogListenerTrackBar)).BeginInit(); @@ -78,46 +85,49 @@ private void InitializeComponent() this.toolExit}); this.notifyIconContextMenu.Name = "notifyIconContextMenu"; this.notifyIconContextMenu.ShowImageMargin = false; - this.notifyIconContextMenu.Size = new System.Drawing.Size(86, 76); + this.notifyIconContextMenu.Size = new System.Drawing.Size(99, 82); // // toolOpen // this.toolOpen.Name = "toolOpen"; - this.toolOpen.Size = new System.Drawing.Size(85, 22); + this.toolOpen.Size = new System.Drawing.Size(98, 24); this.toolOpen.Text = "開啟"; this.toolOpen.Click += new System.EventHandler(this.ToolOpen_Click); // // toolMinsize // this.toolMinsize.Name = "toolMinsize"; - this.toolMinsize.Size = new System.Drawing.Size(85, 22); + this.toolMinsize.Size = new System.Drawing.Size(98, 24); this.toolMinsize.Text = "最小化"; this.toolMinsize.Click += new System.EventHandler(this.ToolMinsize_Click); // // toolSep1 // this.toolSep1.Name = "toolSep1"; - this.toolSep1.Size = new System.Drawing.Size(82, 6); + this.toolSep1.Size = new System.Drawing.Size(95, 6); // // toolExit // this.toolExit.Name = "toolExit"; - this.toolExit.Size = new System.Drawing.Size(85, 22); + this.toolExit.Size = new System.Drawing.Size(98, 24); this.toolExit.Text = "結束"; this.toolExit.Click += new System.EventHandler(this.ToolExit_Click); // // txtCgLogPath // + this.txtCgLogPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.txtCgLogPath.Location = new System.Drawing.Point(11, 9); this.txtCgLogPath.Margin = new System.Windows.Forms.Padding(2); this.txtCgLogPath.Name = "txtCgLogPath"; this.txtCgLogPath.ReadOnly = true; - this.txtCgLogPath.Size = new System.Drawing.Size(219, 22); + this.txtCgLogPath.Size = new System.Drawing.Size(375, 25); this.txtCgLogPath.TabIndex = 1; // // btnSelectLogPath // - this.btnSelectLogPath.Location = new System.Drawing.Point(234, 8); + this.btnSelectLogPath.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSelectLogPath.Location = new System.Drawing.Point(390, 8); this.btnSelectLogPath.Margin = new System.Windows.Forms.Padding(2); this.btnSelectLogPath.Name = "btnSelectLogPath"; this.btnSelectLogPath.Size = new System.Drawing.Size(53, 22); @@ -126,11 +136,38 @@ private void InitializeComponent() this.btnSelectLogPath.UseVisualStyleBackColor = true; this.btnSelectLogPath.Click += new System.EventHandler(this.BtnSelectLogPath_Click); // + // lblAppName + // + this.lblAppName.AutoSize = true; + this.lblAppName.Location = new System.Drawing.Point(11, 38); + this.lblAppName.Name = "lblAppName"; + this.lblAppName.Size = new System.Drawing.Size(82, 15); + this.lblAppName.TabIndex = 20; + this.lblAppName.Text = "應用名稱:"; + // + // txtAppName + // + this.txtAppName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtAppName.Location = new System.Drawing.Point(98, 35); + this.txtAppName.Margin = new System.Windows.Forms.Padding(2); + this.txtAppName.Name = "txtAppName"; + this.txtAppName.Size = new System.Drawing.Size(335, 25); + this.txtAppName.TabIndex = 21; + this.txtAppName.Leave += new System.EventHandler(this.TxtAppName_Leave); + // // panel1 // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.Controls.Add(this.cgLogListenerCheckBox7); + this.panel1.Controls.Add(this.chkCookingReminder); + this.panel1.Controls.Add(this.txtCookingInterval); + this.panel1.Controls.Add(this.lblCookingUnit); this.panel1.Controls.Add(this.checkBox1); this.panel1.Controls.Add(this.cgLogListenerTrackBar); - this.panel1.Controls.Add(this.cgLogListenerSettingCheckBox1); + this.panel1.Controls.Add(this.lblSoundVol); this.panel1.Controls.Add(this.cgLogListenerCheckBox6); this.panel1.Controls.Add(this.cgLogListenerCheckBox5); this.panel1.Controls.Add(this.cgLogListenerCheckBox4); @@ -141,18 +178,47 @@ private void InitializeComponent() this.panel1.Controls.Add(this.btnAddCus); this.panel1.Controls.Add(this.cgLogListenerCheckBox2); this.panel1.Controls.Add(this.cgLogListenerCheckBox1); - this.panel1.Location = new System.Drawing.Point(11, 38); + this.panel1.Location = new System.Drawing.Point(11, 62); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(276, 255); + this.panel1.Size = new System.Drawing.Size(433, 266); this.panel1.TabIndex = 6; // + // chkCookingReminder + // + this.chkCookingReminder.AutoSize = true; + this.chkCookingReminder.Location = new System.Drawing.Point(2, 225); + this.chkCookingReminder.Name = "chkCookingReminder"; + this.chkCookingReminder.Size = new System.Drawing.Size(104, 19); + this.chkCookingReminder.TabIndex = 15; + this.chkCookingReminder.Text = "吃料理通知"; + this.chkCookingReminder.UseVisualStyleBackColor = true; + this.chkCookingReminder.CheckedChanged += new System.EventHandler(this.ChkCookingReminder_CheckedChanged); + // + // txtCookingInterval + // + this.txtCookingInterval.Location = new System.Drawing.Point(112, 223); + this.txtCookingInterval.Name = "txtCookingInterval"; + this.txtCookingInterval.Size = new System.Drawing.Size(40, 25); + this.txtCookingInterval.TabIndex = 16; + this.txtCookingInterval.Text = "180"; + this.txtCookingInterval.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // lblCookingUnit + // + this.lblCookingUnit.AutoSize = true; + this.lblCookingUnit.Location = new System.Drawing.Point(132, 226); + this.lblCookingUnit.Name = "lblCookingUnit"; + this.lblCookingUnit.Size = new System.Drawing.Size(22, 15); + this.lblCookingUnit.TabIndex = 17; + this.lblCookingUnit.Text = "秒"; + // // checkBox1 // this.checkBox1.AutoSize = true; - this.checkBox1.Location = new System.Drawing.Point(2, 230); + this.checkBox1.Location = new System.Drawing.Point(2, 205); this.checkBox1.Name = "checkBox1"; - this.checkBox1.Size = new System.Drawing.Size(93, 16); + this.checkBox1.Size = new System.Drawing.Size(113, 19); this.checkBox1.TabIndex = 13; this.checkBox1.Text = "Custom Notify"; this.checkBox1.UseVisualStyleBackColor = true; @@ -161,24 +227,21 @@ private void InitializeComponent() // this.cgLogListenerTrackBar.AutoSize = false; this.cgLogListenerTrackBar.LargeChange = 1; - this.cgLogListenerTrackBar.Location = new System.Drawing.Point(2, 197); + this.cgLogListenerTrackBar.Location = new System.Drawing.Point(50, 172); this.cgLogListenerTrackBar.Name = "cgLogListenerTrackBar"; this.cgLogListenerTrackBar.NameInSetting = "SoundVol"; - this.cgLogListenerTrackBar.Size = new System.Drawing.Size(108, 27); + this.cgLogListenerTrackBar.Size = new System.Drawing.Size(70, 27); this.cgLogListenerTrackBar.TabIndex = 12; this.cgLogListenerTrackBar.Value = 5; // - // cgLogListenerSettingCheckBox1 + // lblSoundVol // - this.cgLogListenerSettingCheckBox1.AutoSize = true; - this.cgLogListenerSettingCheckBox1.Location = new System.Drawing.Point(2, 172); - this.cgLogListenerSettingCheckBox1.Name = "cgLogListenerSettingCheckBox1"; - this.cgLogListenerSettingCheckBox1.NameInSetting = "PlaySound"; - this.cgLogListenerSettingCheckBox1.RegexPattern = null; - this.cgLogListenerSettingCheckBox1.Size = new System.Drawing.Size(72, 16); - this.cgLogListenerSettingCheckBox1.TabIndex = 8; - this.cgLogListenerSettingCheckBox1.Text = "播放音效"; - this.cgLogListenerSettingCheckBox1.UseVisualStyleBackColor = true; + this.lblSoundVol.AutoSize = true; + this.lblSoundVol.Location = new System.Drawing.Point(2, 177); + this.lblSoundVol.Name = "lblSoundVol"; + this.lblSoundVol.Size = new System.Drawing.Size(52, 15); + this.lblSoundVol.TabIndex = 14; + this.lblSoundVol.Text = "音量:"; // // cgLogListenerCheckBox6 // @@ -187,7 +250,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox6.Name = "cgLogListenerCheckBox6"; this.cgLogListenerCheckBox6.NameInSetting = "ReMaze"; this.cgLogListenerCheckBox6.RegexPattern = "你感覺到一股不可思議的力量,而『.*』好像快(要?)消失了。"; - this.cgLogListenerCheckBox6.Size = new System.Drawing.Size(96, 16); + this.cgLogListenerCheckBox6.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox6.TabIndex = 8; this.cgLogListenerCheckBox6.Text = "迷宮重組通知"; this.cgLogListenerCheckBox6.UseVisualStyleBackColor = true; @@ -199,7 +262,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox5.Name = "cgLogListenerCheckBox5"; this.cgLogListenerCheckBox5.NameInSetting = "Sell"; this.cgLogListenerCheckBox5.RegexPattern = "您順利賣掉了一個.*,(收入|獲得).*魔幣!"; - this.cgLogListenerCheckBox5.Size = new System.Drawing.Size(96, 16); + this.cgLogListenerCheckBox5.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox5.TabIndex = 8; this.cgLogListenerCheckBox5.Text = "擺攤售出通知"; this.cgLogListenerCheckBox5.UseVisualStyleBackColor = true; @@ -211,7 +274,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox4.Name = "cgLogListenerCheckBox4"; this.cgLogListenerCheckBox4.NameInSetting = "PlayerJoin"; this.cgLogListenerCheckBox4.RegexPattern = "加入了(你|您)的隊伍。"; - this.cgLogListenerCheckBox4.Size = new System.Drawing.Size(108, 16); + this.cgLogListenerCheckBox4.Size = new System.Drawing.Size(134, 19); this.cgLogListenerCheckBox4.TabIndex = 8; this.cgLogListenerCheckBox4.Text = "被加入隊伍通知"; this.cgLogListenerCheckBox4.UseVisualStyleBackColor = true; @@ -223,34 +286,38 @@ private void InitializeComponent() this.cgLogListenerCheckBox3.Name = "cgLogListenerCheckBox3"; this.cgLogListenerCheckBox3.NameInSetting = "MP0"; this.cgLogListenerCheckBox3.RegexPattern = "魔力不足。"; - this.cgLogListenerCheckBox3.Size = new System.Drawing.Size(96, 16); + this.cgLogListenerCheckBox3.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox3.TabIndex = 8; this.cgLogListenerCheckBox3.Text = "魔力不足通知"; this.cgLogListenerCheckBox3.UseVisualStyleBackColor = true; // // label1 // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(126, 2); + this.label1.Location = new System.Drawing.Point(275, 2); this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(65, 12); + this.label1.Size = new System.Drawing.Size(82, 15); this.label1.TabIndex = 11; this.label1.Text = "自訂關鍵字"; // // cgLogListenerListBox // + this.cgLogListenerListBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); this.cgLogListenerListBox.FormattingEnabled = true; - this.cgLogListenerListBox.ItemHeight = 12; - this.cgLogListenerListBox.Location = new System.Drawing.Point(127, 20); + this.cgLogListenerListBox.ItemHeight = 15; + this.cgLogListenerListBox.Location = new System.Drawing.Point(275, 20); this.cgLogListenerListBox.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); this.cgLogListenerListBox.Name = "cgLogListenerListBox"; this.cgLogListenerListBox.NotifyIcon = this.notifyIcon; - this.cgLogListenerListBox.Size = new System.Drawing.Size(147, 148); + this.cgLogListenerListBox.Size = new System.Drawing.Size(147, 169); this.cgLogListenerListBox.TabIndex = 9; // // btnDelCus // - this.btnDelCus.Location = new System.Drawing.Point(178, 181); + this.btnDelCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnDelCus.Location = new System.Drawing.Point(326, 221); this.btnDelCus.Margin = new System.Windows.Forms.Padding(2); this.btnDelCus.Name = "btnDelCus"; this.btnDelCus.Size = new System.Drawing.Size(47, 22); @@ -261,7 +328,8 @@ private void InitializeComponent() // // btnAddCus // - this.btnAddCus.Location = new System.Drawing.Point(127, 181); + this.btnAddCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnAddCus.Location = new System.Drawing.Point(275, 221); this.btnAddCus.Margin = new System.Windows.Forms.Padding(2); this.btnAddCus.Name = "btnAddCus"; this.btnAddCus.Size = new System.Drawing.Size(47, 22); @@ -278,7 +346,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox2.Name = "cgLogListenerCheckBox2"; this.cgLogListenerCheckBox2.NameInSetting = "ItemFull"; this.cgLogListenerCheckBox2.RegexPattern = "物品欄沒有空位。"; - this.cgLogListenerCheckBox2.Size = new System.Drawing.Size(84, 16); + this.cgLogListenerCheckBox2.Size = new System.Drawing.Size(104, 19); this.cgLogListenerCheckBox2.TabIndex = 1; this.cgLogListenerCheckBox2.Text = "道具滿通知"; this.cgLogListenerCheckBox2.UseVisualStyleBackColor = true; @@ -291,15 +359,20 @@ private void InitializeComponent() this.cgLogListenerCheckBox1.Name = "cgLogListenerCheckBox1"; this.cgLogListenerCheckBox1.NameInSetting = "Health"; this.cgLogListenerCheckBox1.RegexPattern = "在工作時不小心受傷了。"; - this.cgLogListenerCheckBox1.Size = new System.Drawing.Size(96, 16); + this.cgLogListenerCheckBox1.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox1.TabIndex = 1; this.cgLogListenerCheckBox1.Text = "採集受傷通知"; this.cgLogListenerCheckBox1.UseVisualStyleBackColor = true; // + // timerCooking + // + this.timerCooking.Interval = 180000; + this.timerCooking.Tick += new System.EventHandler(this.TimerCooking_Tick); + // // btnExit // - this.btnExit.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.btnExit.Location = new System.Drawing.Point(198, 300); + this.btnExit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnExit.Location = new System.Drawing.Point(355, 335); this.btnExit.Margin = new System.Windows.Forms.Padding(2); this.btnExit.Name = "btnExit"; this.btnExit.Size = new System.Drawing.Size(89, 22); @@ -310,29 +383,43 @@ private void InitializeComponent() // // linkLabel1 // + this.linkLabel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.linkLabel1.AutoSize = true; - this.linkLabel1.Location = new System.Drawing.Point(9, 311); + this.linkLabel1.Location = new System.Drawing.Point(9, 340); this.linkLabel1.Name = "linkLabel1"; - this.linkLabel1.Size = new System.Drawing.Size(65, 12); + this.linkLabel1.Size = new System.Drawing.Size(82, 15); this.linkLabel1.TabIndex = 7; this.linkLabel1.TabStop = true; this.linkLabel1.Text = "關於本程式"; this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel1_LinkClicked); // + // cgLogListenerCheckBox7 + // + this.cgLogListenerCheckBox7.AutoSize = true; + this.cgLogListenerCheckBox7.Location = new System.Drawing.Point(2, 147); + this.cgLogListenerCheckBox7.Name = "cgLogListenerCheckBox7"; + this.cgLogListenerCheckBox7.NameInSetting = "ReMaze"; + this.cgLogListenerCheckBox7.RegexPattern = "壞掉了"; + this.cgLogListenerCheckBox7.Size = new System.Drawing.Size(119, 19); + this.cgLogListenerCheckBox7.TabIndex = 18; + this.cgLogListenerCheckBox7.Text = "物品損壞通知"; + this.cgLogListenerCheckBox7.UseVisualStyleBackColor = true; + // // FormMain // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(298, 333); + this.ClientSize = new System.Drawing.Size(455, 365); this.Controls.Add(this.linkLabel1); this.Controls.Add(this.panel1); + this.Controls.Add(this.lblAppName); + this.Controls.Add(this.txtAppName); this.Controls.Add(this.btnSelectLogPath); this.Controls.Add(this.txtCgLogPath); this.Controls.Add(this.btnExit); this.Font = new System.Drawing.Font("新細明體", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); - this.MaximizeBox = false; + this.MinimumSize = new System.Drawing.Size(360, 325); this.Name = "FormMain"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "魔力Log監視"; @@ -354,6 +441,8 @@ private void InitializeComponent() private System.Windows.Forms.NotifyIcon notifyIcon; private System.Windows.Forms.TextBox txtCgLogPath; private System.Windows.Forms.Button btnSelectLogPath; + private System.Windows.Forms.Label lblAppName; + private System.Windows.Forms.TextBox txtAppName; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button btnDelCus; private System.Windows.Forms.Button btnAddCus; @@ -371,10 +460,14 @@ private void InitializeComponent() private CgLogListenerCheckBox cgLogListenerCheckBox4; private CgLogListenerCheckBox cgLogListenerCheckBox5; private System.Windows.Forms.LinkLabel linkLabel1; - private CgLogListenerCheckBox cgLogListenerSettingCheckBox1; private CgLogListenerCheckBox cgLogListenerCheckBox6; private CgLogListenerTrackBar cgLogListenerTrackBar; private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.Label lblSoundVol; + private System.Windows.Forms.CheckBox chkCookingReminder; + private System.Windows.Forms.TextBox txtCookingInterval; + private System.Windows.Forms.Label lblCookingUnit; + private System.Windows.Forms.Timer timerCooking; + private CgLogListenerCheckBox cgLogListenerCheckBox7; } } - diff --git a/src/CgLogListener/FormMain.cs b/src/CgLogListener/FormMain.cs index cecb49b..a24f788 100644 --- a/src/CgLogListener/FormMain.cs +++ b/src/CgLogListener/FormMain.cs @@ -1,7 +1,10 @@ -using System; +using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Web; using System.Windows.Forms; using System.Windows.Media; @@ -13,6 +16,8 @@ public partial class FormMain : Form private Settings settings; private CgLogHandler watcher; private readonly MediaPlayer mp = new MediaPlayer(); + private readonly Dictionary soundCheckBoxes = new Dictionary(); + private readonly Dictionary mailCheckBoxes = new Dictionary(); public FormMain() { @@ -28,6 +33,10 @@ private void FrmMain_Load(object sender, EventArgs e) { settings = Settings.GetInstance(); + // 設定通知標題 + txtAppName.Text = settings.AppName; + UpdateAppTitle(); + if (string.IsNullOrEmpty(settings.CgLogPath)) { string cgLogPath = settings.CgLogPath; @@ -43,7 +52,6 @@ private void FrmMain_Load(object sender, EventArgs e) if (!Directory.Exists(settings.CgLogPath) || !CgLogHandler.ValidationPath(settings.CgLogPath)) { - // the dir path invalid, set to default and exit settings.SetCgLogPath(string.Empty); MessageBox.Show(this, "設定檔路徑錯誤, 請重新啟動", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); this.Close(); @@ -52,56 +60,107 @@ private void FrmMain_Load(object sender, EventArgs e) BindWatcher(); - // set playsound check - cgLogListenerSettingCheckBox1.Checked = settings.PlaySound; - // set playsound vol cgLogListenerTrackBar.Value = settings.SoundVol; // set line notify checkBox1.Checked = settings.CustomNotify; - // set default tips check - foreach (var chk in panel1.Controls.OfType()) - { - // skip playsound - if (chk == cgLogListenerSettingCheckBox1) { continue; } + // 設定標準關鍵字及其音效/郵件選項 + SetupStandardTips(); - settings.StandardTips.TryGetValue(chk.NameInSetting, out bool isEnable); - chk.Checked = isEnable; - } + // 設定自訂關鍵字 + SetupCustomTips(); - // set custom tips items - settings.CustomizeTips - .ForEach(s => - { - if (!string.IsNullOrEmpty(s)) - { - cgLogListenerListBox.Items.Add(s); - } - }); - - cgLogListenerCheckBox1.CheckedChanged += CgLogListenerCheckBox_CheckedChanged; - cgLogListenerCheckBox2.CheckedChanged += CgLogListenerCheckBox_CheckedChanged; - cgLogListenerCheckBox3.CheckedChanged += CgLogListenerCheckBox_CheckedChanged; - cgLogListenerCheckBox4.CheckedChanged += CgLogListenerCheckBox_CheckedChanged; - cgLogListenerCheckBox5.CheckedChanged += CgLogListenerCheckBox_CheckedChanged; - cgLogListenerCheckBox6.CheckedChanged += CgLogListenerCheckBox_CheckedChanged; - cgLogListenerSettingCheckBox1.CheckedChanged += CgLogListenerSettingCheckBox1_CheckedChanged; cgLogListenerTrackBar.ValueChanged += CgLogListenerTrackBar_ValueChanged; checkBox1.CheckedChanged += CheckBox1_CheckedChanged; } - private void CgLogListenerCheckBox_CheckedChanged(object sender, EventArgs e) + private void SetupStandardTips() { - var chk = (CgLogListenerCheckBox)sender; - settings.SetStandardTip(chk.NameInSetting, chk.Checked); + var standardCheckBoxes = new[] + { + cgLogListenerCheckBox1, + cgLogListenerCheckBox2, + cgLogListenerCheckBox3, + cgLogListenerCheckBox4, + cgLogListenerCheckBox5, + cgLogListenerCheckBox6, + cgLogListenerCheckBox7 + }; + + // 固定位置讓 🔊/✉ checkbox 對齊 + const int soundCheckBoxX = 155; + const int mailCheckBoxX = 210; + + foreach (var chk in standardCheckBoxes) + { + var nameInSetting = chk.NameInSetting; + + // 取得或建立設定 + if (!settings.StandardTips.TryGetValue(nameInSetting, out TipNotifyOptions options)) + { + options = new TipNotifyOptions(); + settings.SetStandardTip(nameInSetting, options); + } + + // 設定主 checkbox + chk.Checked = options.Enabled; + chk.CheckedChanged += (s, ev) => + { + var cb = (CgLogListenerCheckBox)s; + settings.SetStandardTipEnabled(cb.NameInSetting, cb.Checked); + }; + + // 動態建立音效 checkbox + var soundChk = new CheckBox + { + Text = "🔊", + AutoSize = true, + Location = new Point(soundCheckBoxX, chk.Top), + Checked = options.PlaySound, + Font = new Font("Segoe UI Emoji", 8) + }; + soundChk.CheckedChanged += (s, ev) => + { + settings.SetStandardTipPlaySound(nameInSetting, ((CheckBox)s).Checked); + }; + panel1.Controls.Add(soundChk); + soundCheckBoxes[nameInSetting] = soundChk; + + // 動態建立郵件 checkbox + var mailChk = new CheckBox + { + Text = "✉", + AutoSize = true, + Location = new Point(mailCheckBoxX, chk.Top), + Checked = options.SendMail, + Font = new Font("Segoe UI Emoji", 8) + }; + mailChk.CheckedChanged += (s, ev) => + { + var isChecked = ((CheckBox)s).Checked; + if (isChecked && !MailHelper.IsConfigured()) + { + MailHelper.GenerateDefaultConfig(); + MessageBox.Show(this, "請先設定 mail.ini 檔案中的 SMTP 資訊", "郵件設定", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + settings.SetStandardTipSendMail(nameInSetting, isChecked); + }; + panel1.Controls.Add(mailChk); + mailCheckBoxes[nameInSetting] = mailChk; + } } - private void CgLogListenerSettingCheckBox1_CheckedChanged(object sender, EventArgs e) + private void SetupCustomTips() { - var chk = (CgLogListenerCheckBox)sender; - settings.SetPlaySound(chk.Checked); + foreach (var kv in settings.CustomizeTips) + { + if (!string.IsNullOrEmpty(kv.Key)) + { + cgLogListenerListBox.Items.Add(kv.Key); + } + } } private void CgLogListenerTrackBar_ValueChanged(object sender, EventArgs e) @@ -160,14 +219,31 @@ void BindWatcher() void Watcher_OnNewLog(string log) { + // 吃料理監聽:偵測到「恢復了XXX魔力」就重置計時器 + if (chkCookingReminder.Checked && Regex.IsMatch(log, @"恢復了\d+魔力")) + { + Invoke((Action)delegate + { + // 重置計時器 + timerCooking.Stop(); + if (int.TryParse(txtCookingInterval.Text, out int seconds) && seconds > 0) + { + timerCooking.Interval = seconds * 1000; + timerCooking.Start(); + } + }); + } + foreach (var n in panel1.Controls.OfType()) { - if (n.Notify(log)) + var result = n.Notify(log); + if (result.IsMatch) { notifyIcon.ShowBalloonTip(1, notifyIcon.BalloonTipTitle, log, ToolTipIcon.None); + // 根據該關鍵字的設定決定是否播放音效 const string soundName = "sound.wav"; - if (settings.PlaySound && File.Exists(soundName)) + if (result.PlaySound && File.Exists(soundName)) { Invoke((Action)delegate { @@ -178,13 +254,24 @@ void Watcher_OnNewLog(string log) }); } + // 根據該關鍵字的設定決定是否發送郵件 + if (result.SendMail) + { + try + { + MailHelper.SendMail("魔力Log監視通知", log); + } + catch { } + } + + // Custom Notifier (全域設定) if (settings.CustomNotify) { foreach (var notifier in settings.CustomNotifier.Split(',')) { try { - ProcessStartInfo p = new ProcessStartInfo(notifier, $"\"{log}\"") + ProcessStartInfo p = new ProcessStartInfo(notifier, $"\"[{settings.AppName}] {log}\"") { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true @@ -195,7 +282,6 @@ void Watcher_OnNewLog(string log) } } - // break if was trigger break; } } @@ -209,7 +295,7 @@ private void BtnAddCus_Click(object sender, EventArgs e) return; } - settings.AddCustmizeTip(value); + settings.AddCustomizeTip(value, new TipNotifyOptions(true, true, false)); cgLogListenerListBox.Items.Add(value); } @@ -221,7 +307,7 @@ private void BtnDelCus_Click(object sender, EventArgs e) } var selectItem = (string)cgLogListenerListBox.SelectedItem; - settings.RemoveCustmizeTip(selectItem); + settings.RemoveCustomizeTip(selectItem); cgLogListenerListBox.Items.Remove(selectItem); } @@ -240,6 +326,61 @@ private void CheckBox1_CheckedChanged(object sender, EventArgs e) } } + private void TxtAppName_Leave(object sender, EventArgs e) + { + var newAppName = txtAppName.Text.Trim(); + if (string.IsNullOrEmpty(newAppName)) + { + newAppName = "CgLogListener"; + txtAppName.Text = newAppName; + } + settings.SetAppName(newAppName); + UpdateAppTitle(); + } + + private void UpdateAppTitle() + { + var appTitle = $"[{settings.AppName}] 魔力Log監視"; + notifyIcon.BalloonTipTitle = appTitle; + notifyIcon.Text = appTitle; + this.Text = appTitle; + } + + private void ChkCookingReminder_CheckedChanged(object sender, EventArgs e) + { + if (chkCookingReminder.Checked) + { + if (!int.TryParse(txtCookingInterval.Text, out int seconds) || seconds <= 0) + { + MessageBox.Show("請輸入有效的秒數", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning); + chkCookingReminder.Checked = false; + } + // 啟用功能,等偵測到「恢復了XXX魔力」才開始計時 + } + else + { + timerCooking.Stop(); + } + } + + private void TimerCooking_Tick(object sender, EventArgs e) + { + notifyIcon.ShowBalloonTip(3000, $"[{settings.AppName}] 吃料理通知", "三分鐘到了,吃料理~", ToolTipIcon.Info); + + // 播放音效 + const string soundName = "sound.wav"; + if (File.Exists(soundName)) + { + Invoke((Action)delegate + { + mp.Stop(); + mp.Open(new Uri(new FileInfo(soundName).FullName)); + mp.Volume = settings.SoundVol / 10d; + mp.Play(); + }); + } + } + #region notifyIcon, window minsize and exit ... private void NotifyIcon_DoubleClick(object sender, EventArgs e) diff --git a/src/CgLogListener/FormMain.resx b/src/CgLogListener/FormMain.resx index 789ad64..bb9c6d4 100644 --- a/src/CgLogListener/FormMain.resx +++ b/src/CgLogListener/FormMain.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 17, 17 + 173, 17 - 147, 17 + 302, 17 @@ -148,4 +148,7 @@ AADgBwAA+B8AAA== + + 17, 17 + \ No newline at end of file diff --git a/src/CgLogListener/INotifyMessage.cs b/src/CgLogListener/INotifyMessage.cs index ce139db..67ab6ee 100644 --- a/src/CgLogListener/INotifyMessage.cs +++ b/src/CgLogListener/INotifyMessage.cs @@ -7,6 +7,6 @@ namespace CgLogListener { public interface INotifyMessage { - bool Notify(string message); + NotifyResult Notify(string message); } } diff --git a/src/CgLogListener/MailHelper.cs b/src/CgLogListener/MailHelper.cs new file mode 100644 index 0000000..59cb411 --- /dev/null +++ b/src/CgLogListener/MailHelper.cs @@ -0,0 +1,116 @@ +using IniParser; +using System; +using System.IO; +using System.Net; +using System.Net.Mail; + +namespace CgLogListener +{ + public static class MailHelper + { + private const string MailConfigFileName = "mail.ini"; + + public static bool IsConfigured() + { + if (!File.Exists(MailConfigFileName)) + { + return false; + } + + try + { + var ini = new FileIniDataParser().ReadFile(MailConfigFileName); + return !string.IsNullOrEmpty(ini["smtp"]["host"]) && + !string.IsNullOrEmpty(ini["smtp"]["from"]) && + !string.IsNullOrEmpty(ini["smtp"]["to"]); + } + catch + { + return false; + } + } + + public static void SendMail(string subject, string body) + { + if (!File.Exists(MailConfigFileName)) + { + return; + } + + try + { + var appName = Settings.GetInstance().AppName; + var fullSubject = $"[{appName}] {subject}"; + + var ini = new FileIniDataParser().ReadFile(MailConfigFileName); + var smtpSection = ini["smtp"]; + + var host = smtpSection["host"]; + var portStr = smtpSection["port"]; + var username = smtpSection["username"]; + var password = smtpSection["password"]; + var from = smtpSection["from"]; + var to = smtpSection["to"]; + var enableSslStr = smtpSection["enableSsl"]; + + if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(from) || string.IsNullOrEmpty(to)) + { + return; + } + + int port = 587; + if (!string.IsNullOrEmpty(portStr)) + { + int.TryParse(portStr, out port); + } + + bool enableSsl = true; + if (!string.IsNullOrEmpty(enableSslStr)) + { + enableSsl = enableSslStr == "1" || enableSslStr.ToLower() == "true"; + } + + using (var client = new SmtpClient(host, port)) + { + client.EnableSsl = enableSsl; + + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) + { + client.Credentials = new NetworkCredential(username, password); + } + + var message = new MailMessage(from, to, fullSubject, body); + client.Send(message); + } + } + catch + { + // 發送失敗時靜默處理,避免影響主程式 + } + } + + public static void GenerateDefaultConfig() + { + if (File.Exists(MailConfigFileName)) + { + return; + } + + var content = @"[smtp] +; SMTP 伺服器設定 +host=smtp.gmail.com +port=587 +enableSsl=1 + +; 認證資訊 (如果 SMTP 需要認證) +username= +password= + +; 寄件者與收件者 +from=your-email@gmail.com +to=recipient@example.com +"; + File.WriteAllText(MailConfigFileName, content); + } + } +} diff --git a/src/CgLogListener/NotifyResult.cs b/src/CgLogListener/NotifyResult.cs new file mode 100644 index 0000000..63a0706 --- /dev/null +++ b/src/CgLogListener/NotifyResult.cs @@ -0,0 +1,21 @@ +namespace CgLogListener +{ + public class NotifyResult + { + public bool IsMatch { get; set; } + public bool PlaySound { get; set; } + public bool SendMail { get; set; } + + public static NotifyResult NoMatch => new NotifyResult { IsMatch = false }; + + public static NotifyResult Match(bool playSound, bool sendMail) + { + return new NotifyResult + { + IsMatch = true, + PlaySound = playSound, + SendMail = sendMail + }; + } + } +} diff --git a/src/CgLogListener/Settings.cs b/src/CgLogListener/Settings.cs index ad0c2ec..508bceb 100644 --- a/src/CgLogListener/Settings.cs +++ b/src/CgLogListener/Settings.cs @@ -1,4 +1,4 @@ -using IniParser; +using IniParser; using IniParser.Model; using System; using System.Collections.Generic; @@ -14,15 +14,16 @@ public sealed class Settings const string settingsFileName = "settings.ini"; const string settingsBaseSection = "base"; const string settingsStandardTipsSection = "standard tips"; + const string settingsCustomTipsSection = "custom tips"; const string custmizeFileName = "custmize.dat"; + public string AppName { get; private set; } public bool CustomNotify { get; private set; } public string CustomNotifier { get; private set; } - public bool PlaySound { get; private set; } public int SoundVol { get; private set; } public string CgLogPath { get; private set; } - public Dictionary StandardTips { get; private set; } = new Dictionary(); - public List CustomizeTips { get; private set; } = new List(); + public Dictionary StandardTips { get; private set; } = new Dictionary(); + public Dictionary CustomizeTips { get; private set; } = new Dictionary(); public static Settings GetInstance() { @@ -39,11 +40,9 @@ public void Load() { if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), settingsFileName))) { - // gen new conf file GenConfigFile(); } - // load settings LoadSettings(); } @@ -53,24 +52,39 @@ private void LoadSettings() var iniData = fileIniDataParser.ReadFile(settingsFileName); var baseData = iniData[settingsBaseSection]; + AppName = baseData[nameof(AppName)] ?? "CgLogListener"; + if (string.IsNullOrEmpty(AppName)) AppName = "CgLogListener"; CgLogPath = baseData[nameof(CgLogPath)]; - PlaySound = baseData[nameof(PlaySound)] == "1"; - SoundVol = int.Parse(baseData[nameof(SoundVol)]); + SoundVol = int.Parse(baseData[nameof(SoundVol)] ?? "5"); CustomNotify = baseData[nameof(CustomNotify)] == "1"; CustomNotifier = baseData[nameof(CustomNotifier)]; + // 載入標準關鍵字設定 var standardTipData = iniData[settingsStandardTipsSection]; foreach (var kd in standardTipData) { - StandardTips.Add(kd.KeyName, kd.Value == "1"); + StandardTips[kd.KeyName] = TipNotifyOptions.Parse(kd.Value); } + // 載入自訂關鍵字設定 + var customTipData = iniData[settingsCustomTipsSection]; + foreach (var kd in customTipData) + { + CustomizeTips[kd.KeyName] = TipNotifyOptions.Parse(kd.Value); + } + + // 相容舊版:讀取舊的 custmize.dat if (File.Exists(custmizeFileName)) { foreach (var s in File.ReadAllLines(custmizeFileName)) { - CustomizeTips.Add(s); + if (!string.IsNullOrEmpty(s) && !CustomizeTips.ContainsKey(s)) + { + CustomizeTips[s] = new TipNotifyOptions(true, true, false); + } } + // 遷移後刪除舊檔 + try { File.Delete(custmizeFileName); } catch { } } } @@ -78,8 +92,8 @@ private void GenConfigFile() { var iniData = new IniData(); var baseSection = iniData[settingsBaseSection]; + baseSection[nameof(AppName)] = "CgLogListener"; baseSection[nameof(CgLogPath)] = string.Empty; - baseSection[nameof(PlaySound)] = "1"; baseSection[nameof(SoundVol)] = "5"; baseSection[nameof(CustomNotify)] = "0"; @@ -93,8 +107,8 @@ private void UpdateConfig() var iniData = new IniData(); var baseSection = iniData[settingsBaseSection]; + baseSection[nameof(AppName)] = AppName; baseSection[nameof(CgLogPath)] = CgLogPath; - baseSection[nameof(PlaySound)] = PlaySound ? "1" : "0"; baseSection[nameof(SoundVol)] = SoundVol.ToString(); baseSection[nameof(CustomNotify)] = CustomNotify ? "1" : "0"; baseSection[nameof(CustomNotifier)] = CustomNotifier; @@ -102,12 +116,16 @@ private void UpdateConfig() var standardTipData = iniData[settingsStandardTipsSection]; foreach (var kv in StandardTips) { - standardTipData[kv.Key] = kv.Value ? "1" : "0"; + standardTipData[kv.Key] = kv.Value.ToString(); } - fileIniDataParser.WriteFile(settingsFileName, iniData); + var customTipData = iniData[settingsCustomTipsSection]; + foreach (var kv in CustomizeTips) + { + customTipData[kv.Key] = kv.Value.ToString(); + } - File.WriteAllLines(custmizeFileName, CustomizeTips); + fileIniDataParser.WriteFile(settingsFileName, iniData); } internal void SetCgLogPath(string cgLogPath) @@ -116,15 +134,39 @@ internal void SetCgLogPath(string cgLogPath) UpdateConfig(); } - internal void SetStandardTip(string nameInSetting, bool @checked) + internal void SetStandardTip(string nameInSetting, TipNotifyOptions options) + { + StandardTips[nameInSetting] = options; + UpdateConfig(); + } + + internal void SetStandardTipEnabled(string nameInSetting, bool enabled) + { + if (!StandardTips.ContainsKey(nameInSetting)) + { + StandardTips[nameInSetting] = new TipNotifyOptions(); + } + StandardTips[nameInSetting].Enabled = enabled; + UpdateConfig(); + } + + internal void SetStandardTipPlaySound(string nameInSetting, bool playSound) { - StandardTips[nameInSetting] = @checked; + if (!StandardTips.ContainsKey(nameInSetting)) + { + StandardTips[nameInSetting] = new TipNotifyOptions(); + } + StandardTips[nameInSetting].PlaySound = playSound; UpdateConfig(); } - internal void SetPlaySound(bool @checked) + internal void SetStandardTipSendMail(string nameInSetting, bool sendMail) { - PlaySound = @checked; + if (!StandardTips.ContainsKey(nameInSetting)) + { + StandardTips[nameInSetting] = new TipNotifyOptions(); + } + StandardTips[nameInSetting].SendMail = sendMail; UpdateConfig(); } @@ -134,18 +176,45 @@ internal void SetSoundVol(int value) UpdateConfig(); } - internal void AddCustmizeTip(string value) + internal void AddCustomizeTip(string keyword, TipNotifyOptions options) { - CustomizeTips.Add(value); + CustomizeTips[keyword] = options; UpdateConfig(); } - internal void RemoveCustmizeTip(string value) + internal void RemoveCustomizeTip(string keyword) { - CustomizeTips.Remove(value); + CustomizeTips.Remove(keyword); UpdateConfig(); } + internal void SetCustomizeTipEnabled(string keyword, bool enabled) + { + if (CustomizeTips.ContainsKey(keyword)) + { + CustomizeTips[keyword].Enabled = enabled; + UpdateConfig(); + } + } + + internal void SetCustomizeTipPlaySound(string keyword, bool playSound) + { + if (CustomizeTips.ContainsKey(keyword)) + { + CustomizeTips[keyword].PlaySound = playSound; + UpdateConfig(); + } + } + + internal void SetCustomizeTipSendMail(string keyword, bool sendMail) + { + if (CustomizeTips.ContainsKey(keyword)) + { + CustomizeTips[keyword].SendMail = sendMail; + UpdateConfig(); + } + } + internal void SetCustomNotifier(string value) { CustomNotifier = value; @@ -157,5 +226,11 @@ internal void SetCustomNotify(bool value) CustomNotify = value; UpdateConfig(); } + + internal void SetAppName(string value) + { + AppName = string.IsNullOrEmpty(value) ? "CgLogListener" : value; + UpdateConfig(); + } } } diff --git a/src/CgLogListener/TipNotifyOptions.cs b/src/CgLogListener/TipNotifyOptions.cs new file mode 100644 index 0000000..3a3ed1d --- /dev/null +++ b/src/CgLogListener/TipNotifyOptions.cs @@ -0,0 +1,50 @@ +namespace CgLogListener +{ + public class TipNotifyOptions + { + public bool Enabled { get; set; } + public bool PlaySound { get; set; } + public bool SendMail { get; set; } + + public TipNotifyOptions() + { + Enabled = false; + PlaySound = true; + SendMail = false; + } + + public TipNotifyOptions(bool enabled, bool playSound, bool sendMail) + { + Enabled = enabled; + PlaySound = playSound; + SendMail = sendMail; + } + + /// + /// 從 INI 字串解析 (格式: "enabled,playSound,sendMail" 如 "1,1,0") + /// + public static TipNotifyOptions Parse(string value) + { + if (string.IsNullOrEmpty(value)) + { + return new TipNotifyOptions(); + } + + var parts = value.Split(','); + return new TipNotifyOptions + { + Enabled = parts.Length > 0 && parts[0] == "1", + PlaySound = parts.Length > 1 && parts[1] == "1", + SendMail = parts.Length > 2 && parts[2] == "1" + }; + } + + /// + /// 轉成 INI 字串 (格式: "enabled,playSound,sendMail") + /// + public override string ToString() + { + return $"{(Enabled ? "1" : "0")},{(PlaySound ? "1" : "0")},{(SendMail ? "1" : "0")}"; + } + } +} diff --git a/src/DiscordNotifier/DiscordNotifier.csproj b/src/DiscordNotifier/DiscordNotifier.csproj index 000c25d..91af799 100644 --- a/src/DiscordNotifier/DiscordNotifier.csproj +++ b/src/DiscordNotifier/DiscordNotifier.csproj @@ -1,61 +1,87 @@ - - - Debug - AnyCPU - {7F0CAD47-7583-479B-8BD2-7901CD5B81CB} - Exe - DiscordNotifier - DiscordNotifier - v4.6 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll - - - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - + + + Debug + AnyCPU + {7F0CAD47-7583-479B-8BD2-7901CD5B81CB} + Exe + DiscordNotifier + DiscordNotifier + v4.6 + 512 + true + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\CgLogListener\bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + False + .NET Framework 3.5 SP1 + false + + + \ No newline at end of file diff --git a/src/TelegramNotifier/TelegramNotifier.csproj b/src/TelegramNotifier/TelegramNotifier.csproj index 6b52c63..ca34a28 100644 --- a/src/TelegramNotifier/TelegramNotifier.csproj +++ b/src/TelegramNotifier/TelegramNotifier.csproj @@ -27,7 +27,7 @@ AnyCPU pdbonly true - bin\Release\ + ..\CgLogListener\bin\Release\ TRACE prompt 4 @@ -52,8 +52,12 @@ - + + + PreserveNewest + + \ No newline at end of file From db77bf0355ccdf73dfb648896209331913d201eb Mon Sep 17 00:00:00 2001 From: yorklin Date: Wed, 28 Jan 2026 16:34:03 +0800 Subject: [PATCH 2/8] =?UTF-8?q?update=20=E4=BF=AE=E6=AD=A3=E7=B9=AB?= =?UTF-8?q?=E7=B5=90=E9=8C=AF=E8=AA=A4=E7=9A=84=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CgLogListener/FormMain.Designer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CgLogListener/FormMain.Designer.cs b/src/CgLogListener/FormMain.Designer.cs index 4ded9ab..bb6f9d7 100644 --- a/src/CgLogListener/FormMain.Designer.cs +++ b/src/CgLogListener/FormMain.Designer.cs @@ -394,11 +394,11 @@ private void InitializeComponent() this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel1_LinkClicked); // // cgLogListenerCheckBox7 - // + // this.cgLogListenerCheckBox7.AutoSize = true; this.cgLogListenerCheckBox7.Location = new System.Drawing.Point(2, 147); this.cgLogListenerCheckBox7.Name = "cgLogListenerCheckBox7"; - this.cgLogListenerCheckBox7.NameInSetting = "ReMaze"; + this.cgLogListenerCheckBox7.NameInSetting = "ItemBroken"; this.cgLogListenerCheckBox7.RegexPattern = "壞掉了"; this.cgLogListenerCheckBox7.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox7.TabIndex = 18; From 895251237341223788fb804054d1936d65aa2821 Mon Sep 17 00:00:00 2001 From: yorklin020 <86771074+yorklin020@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:43:42 +0800 Subject: [PATCH 3/8] Update README with new features and settings Added new features including SMTP mail settings, item damage notifications, application source settings, and feeding detection reminders. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index b0a7c58..9d5302f 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,10 @@ CgLogListener 目錄下可替換wav + +York +20260128 +1. 增加 smtp mailsettings (暫時只使用 gmail 進行測試)。 +2. 新增物品損壞通知。 +3. 設定應用來源,方便信件過濾。 +4. 新增吃料偵測,秒數循環提醒。 From 14df9cb755c2a6a273a0eadde63718698aa4ed6b Mon Sep 17 00:00:00 2001 From: yorklin Date: Wed, 28 Jan 2026 16:54:00 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E5=80=8B=E4=BA=BA?= =?UTF-8?q?=E8=B7=AF=E5=BE=91=E8=B3=87=E8=A8=8A=E8=88=87=E6=8E=92=E9=99=A4?= =?UTF-8?q?=20CLAUDE.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 將 PublishUrl 改為相對路徑 publish\ - 在 .gitignore 中排除 CLAUDE.md - 從 git 追蹤中移除 CLAUDE.md Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 5 +- CLAUDE.md | 178 ------------------------- src/CgLogListener/CgLogListener.csproj | 2 +- 3 files changed, 5 insertions(+), 180 deletions(-) delete mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 3c4efe2..fc68272 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,7 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc + +# Claude Code project-specific instructions +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index e2f6330..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,178 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Build Commands - -```bash -# Build entire solution -msbuild src/CgLogListener.sln - -# Build specific project -msbuild src/CgLogListener/CgLogListener.csproj - -# Build Release version -msbuild src/CgLogListener.sln /p:Configuration=Release - -# Restore NuGet packages (if needed) -nuget restore src/CgLogListener.sln -``` - -## Architecture - -This is a Windows Forms application (.NET Framework 4.6) that monitors game log files and sends notifications when keywords are detected. - -### Project Structure - -``` -src/ -├── CgLogListener/ # Main WinForms application -│ ├── FormMain.cs # Main UI, event handling, notification triggers -│ ├── FormMain.Designer.cs # UI layout (auto-generated, can be manually edited) -│ ├── CgLogHandler.cs # FileSystemWatcher for log files (BIG5 encoding) -│ ├── CgLogListenerControls.cs # Custom controls (CgLogListenerCheckBox, CgLogListenerListBox) -│ ├── Settings.cs # Singleton, reads/writes settings.ini -│ ├── TipNotifyOptions.cs # Data class for keyword settings (enabled, playSound, sendMail) -│ ├── NotifyResult.cs # Return type for INotifyMessage.Notify() -│ ├── INotifyMessage.cs # Interface for notification controls -│ └── MailHelper.cs # SMTP email sending, reads mail.ini -├── DiscordNotifier/ # External notifier (Console App) -├── TelegramNotifier/ # External notifier (Console App) -└── LineNotifier/ # External notifier (Console App) -``` - -### Core Interfaces - -```csharp -// INotifyMessage.cs -public interface INotifyMessage -{ - NotifyResult Notify(string message); -} - -// NotifyResult.cs -public class NotifyResult -{ - public bool IsMatch { get; set; } - public bool PlaySound { get; set; } - public bool SendMail { get; set; } -} - -// TipNotifyOptions.cs - stored in settings.ini as "enabled,playSound,sendMail" (e.g., "1,1,0") -public class TipNotifyOptions -{ - public bool Enabled { get; set; } - public bool PlaySound { get; set; } - public bool SendMail { get; set; } -} -``` - -### Notification Flow - -``` -Log file changes → CgLogHandler.OnNewLog event - ↓ -FormMain.Watcher_OnNewLog(log) - ↓ -1. Check 吃料理通知 (cooking reminder with timer) - ↓ -2. Loop through panel1.Controls.OfType() - - CgLogListenerCheckBox (standard keywords with regex) - - CgLogListenerListBox (custom keywords with contains) - ↓ -3. If result.IsMatch: - - Show BalloonTip notification - - Play sound.wav (if result.PlaySound) - - Send email via MailHelper (if result.SendMail) - - Call external notifiers (if CustomNotify enabled) -``` - -### UI Components - -| Control | Type | Function | -|---------|------|----------| -| cgLogListenerCheckBox1-6 | CgLogListenerCheckBox | Standard keyword checkboxes with RegexPattern | -| Dynamic 🔊/✉ checkboxes | CheckBox | Per-keyword sound/mail toggles (created in SetupStandardTips) | -| cgLogListenerListBox | CgLogListenerListBox | Custom keywords list | -| btnAddCus / btnDelCus | Button | Add/remove custom keywords | -| cgLogListenerTrackBar | TrackBar | Sound volume (0-10) | -| checkBox1 | CheckBox | Custom Notify (external notifier) | -| chkCookingReminder | CheckBox | Cooking reminder toggle | -| txtCookingInterval | TextBox | Cooking reminder interval (seconds) | -| timerCooking | Timer | Cooking reminder timer | - -### Standard Keywords (RegexPattern) - -| NameInSetting | Pattern | Description | -|---------------|---------|-------------| -| Health | `在工作時不小心受傷了。` | 採集受傷通知 | -| ItemFull | `物品欄沒有空位。` | 道具滿通知 | -| MP0 | `魔力不足。` | 魔力不足通知 | -| PlayerJoin | `加入了(你\|您)的隊伍。` | 被加入隊伍通知 | -| Sell | `您順利賣掉了一個.*,(收入\|獲得).*魔幣!` | 擺攤售出通知 | -| ReMaze | `你感覺到一股不可思議的力量,而『.*』好像快(要?)消失了。` | 迷宮重組通知 | - -### Cooking Reminder (吃料理通知) - -- **Trigger**: Detects `恢復了\d+魔力` in log -- **Behavior**: Resets timer on each detection, alerts when timer expires -- **Flow**: - ``` - [✓] 吃料理通知 [180] 秒 - ↓ - Log: "恢復了123魔力" detected - ↓ - Timer reset, starts counting down - ↓ - 180 seconds later → BalloonTip + sound.wav - ``` - -### Configuration Files - -| File | Format | Purpose | -|------|--------|---------| -| `settings.ini` | INI | Main settings | -| `mail.ini` | INI | SMTP credentials (host, port, username, password, from, to) | - -**settings.ini structure:** -```ini -[base] -CgLogPath=C:\Game\CrossGate -SoundVol=5 -CustomNotify=0 -CustomNotifier= - -[standard tips] -Health=1,1,0 # enabled=1, playSound=1, sendMail=0 -ItemFull=1,1,1 # enabled=1, playSound=1, sendMail=1 - -[custom tips] -關鍵字=1,1,0 -關鍵字|排除詞=1,1,0 # keyword with exclusion -``` - -**mail.ini structure:** -```ini -[smtp] -host=smtp.gmail.com -port=587 -enableSsl=1 -username= -password= -from=your-email@gmail.com -to=recipient@example.com -``` - -### Adding a New Standard Keyword - -1. Add `CgLogListenerCheckBox` in `FormMain.Designer.cs` -2. Set `NameInSetting` and `RegexPattern` properties -3. Add to `standardCheckBoxes` array in `FormMain.cs` `SetupStandardTips()` - -### Adding a New External Notifier - -1. Create new Console App project targeting .NET Framework 4.6 -2. Add `ini-parser` NuGet package -3. Read config from `{NotifierName}.ini` in same directory as exe -4. Accept message as `args[0]` -5. User configures path in `settings.ini` under `CustomNotifier` diff --git a/src/CgLogListener/CgLogListener.csproj b/src/CgLogListener/CgLogListener.csproj index 5587725..26029f2 100644 --- a/src/CgLogListener/CgLogListener.csproj +++ b/src/CgLogListener/CgLogListener.csproj @@ -12,7 +12,7 @@ 512 false - C:\Users\aiet\Desktop\TEST\ + publish\ true Disk false From c6e6334d7ad3c6863cd5b5a58712eff59c447a49 Mon Sep 17 00:00:00 2001 From: yorklin Date: Mon, 2 Feb 2026 14:49:24 +0800 Subject: [PATCH 5/8] =?UTF-8?q?update=201.=20=E8=AA=BF=E6=95=B4=E7=A7=92?= =?UTF-8?q?=E6=95=B8=E9=80=9A=E7=9F=A5=E5=8A=9F=E8=83=BD=E5=8F=AF=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E7=BE=A9=E7=AF=A9=E9=81=B8=E8=A6=8F=E5=89=87=E8=88=87?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E8=A8=8A=E6=81=AF=E3=80=82=202.=20=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E7=BE=A9=E6=96=87=E5=AD=97=E5=A2=9E=E5=8A=A0=E9=9F=B3?= =?UTF-8?q?=E6=95=88=E3=80=81mail=20=E7=9A=84=E9=80=9A=E7=9F=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82=203.=20=E6=87=89=E7=94=A8=E7=A8=8B=E5=BC=8F?= =?UTF-8?q?=20name=20=E7=9A=84=E5=84=B2=E5=AD=98=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CgLogListener/CgLogListenerControls.cs | 20 ++++- src/CgLogListener/FormMain.Designer.cs | 98 ++++++++++++++++------ src/CgLogListener/FormMain.cs | 63 ++++++++++++-- src/CgLogListener/FormPrompt.Designer.cs | 64 +++++++++++++- src/CgLogListener/FormPrompt.cs | 13 +++ src/CgLogListener/TipNotifyOptions.cs | 14 ++-- 6 files changed, 232 insertions(+), 40 deletions(-) diff --git a/src/CgLogListener/CgLogListenerControls.cs b/src/CgLogListener/CgLogListenerControls.cs index 507babf..0a5f448 100644 --- a/src/CgLogListener/CgLogListenerControls.cs +++ b/src/CgLogListener/CgLogListenerControls.cs @@ -47,8 +47,26 @@ public NotifyResult Notify(string message) if (!options.Enabled) continue; var split = keyword.Split('|'); + var pattern = split[0]; - if (message.Contains(split[0])) + bool isMatch; + if (options.IsRegex) + { + try + { + isMatch = Regex.IsMatch(message, pattern); + } + catch + { + isMatch = false; + } + } + else + { + isMatch = message.Contains(pattern); + } + + if (isMatch) { if (split.Length > 1) { diff --git a/src/CgLogListener/FormMain.Designer.cs b/src/CgLogListener/FormMain.Designer.cs index bb6f9d7..afcc220 100644 --- a/src/CgLogListener/FormMain.Designer.cs +++ b/src/CgLogListener/FormMain.Designer.cs @@ -41,9 +41,13 @@ private void InitializeComponent() this.lblAppName = new System.Windows.Forms.Label(); this.txtAppName = new System.Windows.Forms.TextBox(); this.panel1 = new System.Windows.Forms.Panel(); + this.cgLogListenerCheckBox7 = new CgLogListener.CgLogListenerCheckBox(); this.chkCookingReminder = new System.Windows.Forms.CheckBox(); this.txtCookingInterval = new System.Windows.Forms.TextBox(); this.lblCookingUnit = new System.Windows.Forms.Label(); + this.txtCookingPattern = new System.Windows.Forms.TextBox(); + this.txtCookingMessage = new System.Windows.Forms.TextBox(); + this.btnCookingDefault = new System.Windows.Forms.Button(); this.checkBox1 = new System.Windows.Forms.CheckBox(); this.cgLogListenerTrackBar = new CgLogListener.CgLogListenerTrackBar(); this.lblSoundVol = new System.Windows.Forms.Label(); @@ -58,9 +62,9 @@ private void InitializeComponent() this.cgLogListenerCheckBox2 = new CgLogListener.CgLogListenerCheckBox(); this.cgLogListenerCheckBox1 = new CgLogListener.CgLogListenerCheckBox(); this.timerCooking = new System.Windows.Forms.Timer(this.components); + this.btnSaveAppName = new System.Windows.Forms.Button(); this.btnExit = new System.Windows.Forms.Button(); this.linkLabel1 = new System.Windows.Forms.LinkLabel(); - this.cgLogListenerCheckBox7 = new CgLogListener.CgLogListenerCheckBox(); this.notifyIconContextMenu.SuspendLayout(); this.panel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.cgLogListenerTrackBar)).BeginInit(); @@ -152,7 +156,7 @@ private void InitializeComponent() this.txtAppName.Location = new System.Drawing.Point(98, 35); this.txtAppName.Margin = new System.Windows.Forms.Padding(2); this.txtAppName.Name = "txtAppName"; - this.txtAppName.Size = new System.Drawing.Size(335, 25); + this.txtAppName.Size = new System.Drawing.Size(288, 25); this.txtAppName.TabIndex = 21; this.txtAppName.Leave += new System.EventHandler(this.TxtAppName_Leave); // @@ -165,6 +169,9 @@ private void InitializeComponent() this.panel1.Controls.Add(this.chkCookingReminder); this.panel1.Controls.Add(this.txtCookingInterval); this.panel1.Controls.Add(this.lblCookingUnit); + this.panel1.Controls.Add(this.txtCookingPattern); + this.panel1.Controls.Add(this.txtCookingMessage); + this.panel1.Controls.Add(this.btnCookingDefault); this.panel1.Controls.Add(this.checkBox1); this.panel1.Controls.Add(this.cgLogListenerTrackBar); this.panel1.Controls.Add(this.lblSoundVol); @@ -181,9 +188,21 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(11, 62); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(433, 266); + this.panel1.Size = new System.Drawing.Size(433, 315); this.panel1.TabIndex = 6; // + // cgLogListenerCheckBox7 + // + this.cgLogListenerCheckBox7.AutoSize = true; + this.cgLogListenerCheckBox7.Location = new System.Drawing.Point(2, 147); + this.cgLogListenerCheckBox7.Name = "cgLogListenerCheckBox7"; + this.cgLogListenerCheckBox7.NameInSetting = "ItemBroken"; + this.cgLogListenerCheckBox7.RegexPattern = "壞掉了"; + this.cgLogListenerCheckBox7.Size = new System.Drawing.Size(119, 19); + this.cgLogListenerCheckBox7.TabIndex = 18; + this.cgLogListenerCheckBox7.Text = "物品損壞通知"; + this.cgLogListenerCheckBox7.UseVisualStyleBackColor = true; + // // chkCookingReminder // this.chkCookingReminder.AutoSize = true; @@ -207,12 +226,38 @@ private void InitializeComponent() // lblCookingUnit // this.lblCookingUnit.AutoSize = true; - this.lblCookingUnit.Location = new System.Drawing.Point(132, 226); + this.lblCookingUnit.Location = new System.Drawing.Point(155, 226); this.lblCookingUnit.Name = "lblCookingUnit"; this.lblCookingUnit.Size = new System.Drawing.Size(22, 15); this.lblCookingUnit.TabIndex = 17; this.lblCookingUnit.Text = "秒"; // + // txtCookingPattern + // + this.txtCookingPattern.Location = new System.Drawing.Point(2, 248); + this.txtCookingPattern.Name = "txtCookingPattern"; + this.txtCookingPattern.Size = new System.Drawing.Size(254, 25); + this.txtCookingPattern.TabIndex = 19; + this.txtCookingPattern.Text = "恢復了\\d+魔力"; + // + // txtCookingMessage + // + this.txtCookingMessage.Location = new System.Drawing.Point(2, 275); + this.txtCookingMessage.Name = "txtCookingMessage"; + this.txtCookingMessage.Size = new System.Drawing.Size(200, 25); + this.txtCookingMessage.TabIndex = 20; + this.txtCookingMessage.Text = "時間到了,吃料理~"; + // + // btnCookingDefault + // + this.btnCookingDefault.Location = new System.Drawing.Point(206, 274); + this.btnCookingDefault.Name = "btnCookingDefault"; + this.btnCookingDefault.Size = new System.Drawing.Size(50, 22); + this.btnCookingDefault.TabIndex = 21; + this.btnCookingDefault.Text = "預設"; + this.btnCookingDefault.UseVisualStyleBackColor = true; + this.btnCookingDefault.Click += new System.EventHandler(this.BtnCookingDefault_Click); + // // checkBox1 // this.checkBox1.AutoSize = true; @@ -222,6 +267,7 @@ private void InitializeComponent() this.checkBox1.TabIndex = 13; this.checkBox1.Text = "Custom Notify"; this.checkBox1.UseVisualStyleBackColor = true; + this.checkBox1.CheckedChanged += new System.EventHandler(this.CheckBox1_CheckedChanged); // // cgLogListenerTrackBar // @@ -303,8 +349,7 @@ private void InitializeComponent() // // cgLogListenerListBox // - this.cgLogListenerListBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Right))); + this.cgLogListenerListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.cgLogListenerListBox.FormattingEnabled = true; this.cgLogListenerListBox.ItemHeight = 15; this.cgLogListenerListBox.Location = new System.Drawing.Point(275, 20); @@ -316,8 +361,8 @@ private void InitializeComponent() // // btnDelCus // - this.btnDelCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.btnDelCus.Location = new System.Drawing.Point(326, 221); + this.btnDelCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnDelCus.Location = new System.Drawing.Point(326, 194); this.btnDelCus.Margin = new System.Windows.Forms.Padding(2); this.btnDelCus.Name = "btnDelCus"; this.btnDelCus.Size = new System.Drawing.Size(47, 22); @@ -328,8 +373,8 @@ private void InitializeComponent() // // btnAddCus // - this.btnAddCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.btnAddCus.Location = new System.Drawing.Point(275, 221); + this.btnAddCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnAddCus.Location = new System.Drawing.Point(275, 194); this.btnAddCus.Margin = new System.Windows.Forms.Padding(2); this.btnAddCus.Name = "btnAddCus"; this.btnAddCus.Size = new System.Drawing.Size(47, 22); @@ -369,6 +414,18 @@ private void InitializeComponent() this.timerCooking.Interval = 180000; this.timerCooking.Tick += new System.EventHandler(this.TimerCooking_Tick); // + // btnSaveAppName + // + this.btnSaveAppName.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSaveAppName.Location = new System.Drawing.Point(390, 34); + this.btnSaveAppName.Margin = new System.Windows.Forms.Padding(2); + this.btnSaveAppName.Name = "btnSaveAppName"; + this.btnSaveAppName.Size = new System.Drawing.Size(53, 22); + this.btnSaveAppName.TabIndex = 22; + this.btnSaveAppName.Text = "儲存"; + this.btnSaveAppName.UseVisualStyleBackColor = true; + this.btnSaveAppName.Click += new System.EventHandler(this.BtnSaveAppName_Click); + // // btnExit // this.btnExit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); @@ -385,7 +442,7 @@ private void InitializeComponent() // this.linkLabel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.linkLabel1.AutoSize = true; - this.linkLabel1.Location = new System.Drawing.Point(9, 340); + this.linkLabel1.Location = new System.Drawing.Point(8, 391); this.linkLabel1.Name = "linkLabel1"; this.linkLabel1.Size = new System.Drawing.Size(82, 15); this.linkLabel1.TabIndex = 7; @@ -393,26 +450,15 @@ private void InitializeComponent() this.linkLabel1.Text = "關於本程式"; this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel1_LinkClicked); // - // cgLogListenerCheckBox7 - // - this.cgLogListenerCheckBox7.AutoSize = true; - this.cgLogListenerCheckBox7.Location = new System.Drawing.Point(2, 147); - this.cgLogListenerCheckBox7.Name = "cgLogListenerCheckBox7"; - this.cgLogListenerCheckBox7.NameInSetting = "ItemBroken"; - this.cgLogListenerCheckBox7.RegexPattern = "壞掉了"; - this.cgLogListenerCheckBox7.Size = new System.Drawing.Size(119, 19); - this.cgLogListenerCheckBox7.TabIndex = 18; - this.cgLogListenerCheckBox7.Text = "物品損壞通知"; - this.cgLogListenerCheckBox7.UseVisualStyleBackColor = true; - // // FormMain // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(455, 365); + this.ClientSize = new System.Drawing.Size(455, 415); this.Controls.Add(this.linkLabel1); this.Controls.Add(this.panel1); this.Controls.Add(this.lblAppName); + this.Controls.Add(this.btnSaveAppName); this.Controls.Add(this.txtAppName); this.Controls.Add(this.btnSelectLogPath); this.Controls.Add(this.txtCgLogPath); @@ -467,7 +513,11 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox chkCookingReminder; private System.Windows.Forms.TextBox txtCookingInterval; private System.Windows.Forms.Label lblCookingUnit; + private System.Windows.Forms.TextBox txtCookingPattern; + private System.Windows.Forms.TextBox txtCookingMessage; + private System.Windows.Forms.Button btnCookingDefault; private System.Windows.Forms.Timer timerCooking; private CgLogListenerCheckBox cgLogListenerCheckBox7; + private System.Windows.Forms.Button btnSaveAppName; } } diff --git a/src/CgLogListener/FormMain.cs b/src/CgLogListener/FormMain.cs index a24f788..bab64fe 100644 --- a/src/CgLogListener/FormMain.cs +++ b/src/CgLogListener/FormMain.cs @@ -217,10 +217,24 @@ void BindWatcher() watcher.OnNewLog += Watcher_OnNewLog; } + private const string DefaultCookingPattern = @"恢復了\d+魔力"; + private const string DefaultCookingMessage = "時間到了,吃料理~"; + void Watcher_OnNewLog(string log) { - // 吃料理監聽:偵測到「恢復了XXX魔力」就重置計時器 - if (chkCookingReminder.Checked && Regex.IsMatch(log, @"恢復了\d+魔力")) + // 吃料理監聽:使用自定義 Regex pattern 偵測 + var cookingPattern = string.IsNullOrWhiteSpace(txtCookingPattern.Text) + ? DefaultCookingPattern + : txtCookingPattern.Text; + + bool cookingMatch = false; + try + { + cookingMatch = chkCookingReminder.Checked && Regex.IsMatch(log, cookingPattern); + } + catch { } + + if (cookingMatch) { Invoke((Action)delegate { @@ -289,13 +303,13 @@ void Watcher_OnNewLog(string log) private void BtnAddCus_Click(object sender, EventArgs e) { - if (FormPrompt.ShowDialog(this, out string value) != DialogResult.OK || + if (FormPrompt.ShowDialog(this, out string value, out TipNotifyOptions options) != DialogResult.OK || string.IsNullOrEmpty(value)) { return; } - settings.AddCustomizeTip(value, new TipNotifyOptions(true, true, false)); + settings.AddCustomizeTip(value, options); cgLogListenerListBox.Items.Add(value); } @@ -327,6 +341,11 @@ private void CheckBox1_CheckedChanged(object sender, EventArgs e) } private void TxtAppName_Leave(object sender, EventArgs e) + { + // 不再自動儲存,由按鈕觸發 + } + + private void BtnSaveAppName_Click(object sender, EventArgs e) { var newAppName = txtAppName.Text.Trim(); if (string.IsNullOrEmpty(newAppName)) @@ -354,8 +373,23 @@ private void ChkCookingReminder_CheckedChanged(object sender, EventArgs e) { MessageBox.Show("請輸入有效的秒數", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning); chkCookingReminder.Checked = false; + return; + } + + // 驗證 Regex pattern 是否有效 + var pattern = string.IsNullOrWhiteSpace(txtCookingPattern.Text) + ? DefaultCookingPattern + : txtCookingPattern.Text; + try + { + Regex.IsMatch("test", pattern); + } + catch + { + MessageBox.Show("Regex 格式錯誤", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning); + chkCookingReminder.Checked = false; + return; } - // 啟用功能,等偵測到「恢復了XXX魔力」才開始計時 } else { @@ -363,9 +397,19 @@ private void ChkCookingReminder_CheckedChanged(object sender, EventArgs e) } } + private void BtnCookingDefault_Click(object sender, EventArgs e) + { + txtCookingPattern.Text = DefaultCookingPattern; + txtCookingMessage.Text = DefaultCookingMessage; + } + private void TimerCooking_Tick(object sender, EventArgs e) { - notifyIcon.ShowBalloonTip(3000, $"[{settings.AppName}] 吃料理通知", "三分鐘到了,吃料理~", ToolTipIcon.Info); + var message = string.IsNullOrWhiteSpace(txtCookingMessage.Text) + ? DefaultCookingMessage + : txtCookingMessage.Text; + + notifyIcon.ShowBalloonTip(3000, $"[{settings.AppName}] 吃料理通知", message, ToolTipIcon.Info); // 播放音效 const string soundName = "sound.wav"; @@ -432,6 +476,13 @@ private void FormMain_Resize(object sender, EventArgs e) private void FormMain_FormClosing(object sender, FormClosingEventArgs e) { + // 確保 AppName 在關閉前儲存 + var newAppName = txtAppName.Text.Trim(); + if (!string.IsNullOrEmpty(newAppName) && newAppName != settings.AppName) + { + settings.SetAppName(newAppName); + } + watcher?.Dispose(); notifyIcon?.Dispose(); } diff --git a/src/CgLogListener/FormPrompt.Designer.cs b/src/CgLogListener/FormPrompt.Designer.cs index e57dc3b..2cb2b90 100644 --- a/src/CgLogListener/FormPrompt.Designer.cs +++ b/src/CgLogListener/FormPrompt.Designer.cs @@ -34,6 +34,10 @@ private void InitializeComponent() this.btnCancel = new System.Windows.Forms.Button(); this.label2 = new System.Windows.Forms.Label(); this.txtExp = new System.Windows.Forms.TextBox(); + this.chkEnabled = new System.Windows.Forms.CheckBox(); + this.chkPlaySound = new System.Windows.Forms.CheckBox(); + this.chkSendMail = new System.Windows.Forms.CheckBox(); + this.chkIsRegex = new System.Windows.Forms.CheckBox(); this.SuspendLayout(); // // btnOk @@ -88,15 +92,63 @@ private void InitializeComponent() this.txtExp.Name = "txtExp"; this.txtExp.Size = new System.Drawing.Size(165, 21); this.txtExp.TabIndex = 2; - // + // + // chkEnabled + // + this.chkEnabled.AutoSize = true; + this.chkEnabled.Checked = true; + this.chkEnabled.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkEnabled.Location = new System.Drawing.Point(14, 61); + this.chkEnabled.Name = "chkEnabled"; + this.chkEnabled.Size = new System.Drawing.Size(52, 19); + this.chkEnabled.TabIndex = 5; + this.chkEnabled.Text = "啟用"; + this.chkEnabled.UseVisualStyleBackColor = true; + // + // chkPlaySound + // + this.chkPlaySound.AutoSize = true; + this.chkPlaySound.Checked = true; + this.chkPlaySound.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkPlaySound.Location = new System.Drawing.Point(72, 61); + this.chkPlaySound.Name = "chkPlaySound"; + this.chkPlaySound.Size = new System.Drawing.Size(52, 19); + this.chkPlaySound.TabIndex = 6; + this.chkPlaySound.Text = "音效"; + this.chkPlaySound.UseVisualStyleBackColor = true; + // + // chkSendMail + // + this.chkSendMail.AutoSize = true; + this.chkSendMail.Location = new System.Drawing.Point(130, 61); + this.chkSendMail.Name = "chkSendMail"; + this.chkSendMail.Size = new System.Drawing.Size(52, 19); + this.chkSendMail.TabIndex = 7; + this.chkSendMail.Text = "寄信"; + this.chkSendMail.UseVisualStyleBackColor = true; + // + // chkIsRegex + // + this.chkIsRegex.AutoSize = true; + this.chkIsRegex.Location = new System.Drawing.Point(188, 61); + this.chkIsRegex.Name = "chkIsRegex"; + this.chkIsRegex.Size = new System.Drawing.Size(58, 19); + this.chkIsRegex.TabIndex = 8; + this.chkIsRegex.Text = "Regex"; + this.chkIsRegex.UseVisualStyleBackColor = true; + // // FormPrompt - // + // this.AcceptButton = this.btnOk; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.btnCancel; - this.ClientSize = new System.Drawing.Size(368, 64); + this.ClientSize = new System.Drawing.Size(368, 90); this.ControlBox = false; + this.Controls.Add(this.chkIsRegex); + this.Controls.Add(this.chkSendMail); + this.Controls.Add(this.chkPlaySound); + this.Controls.Add(this.chkEnabled); this.Controls.Add(this.txtExp); this.Controls.Add(this.label2); this.Controls.Add(this.txtValue); @@ -111,7 +163,7 @@ private void InitializeComponent() this.ShowIcon = false; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = " "; + this.Text = "新增自訂關鍵字"; this.ResumeLayout(false); this.PerformLayout(); @@ -125,5 +177,9 @@ private void InitializeComponent() private System.Windows.Forms.Button btnCancel; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox txtExp; + private System.Windows.Forms.CheckBox chkEnabled; + private System.Windows.Forms.CheckBox chkPlaySound; + private System.Windows.Forms.CheckBox chkSendMail; + private System.Windows.Forms.CheckBox chkIsRegex; } } \ No newline at end of file diff --git a/src/CgLogListener/FormPrompt.cs b/src/CgLogListener/FormPrompt.cs index 8436e84..c4fcaa6 100644 --- a/src/CgLogListener/FormPrompt.cs +++ b/src/CgLogListener/FormPrompt.cs @@ -18,6 +18,12 @@ public partial class FormPrompt : Form } public static DialogResult ShowDialog(IWin32Window owner, out string value) + { + var result = ShowDialog(owner, out value, out _); + return result; + } + + public static DialogResult ShowDialog(IWin32Window owner, out string value, out TipNotifyOptions options) { FormPrompt f = new FormPrompt(); DialogResult result = f.ShowDialog(owner); @@ -27,6 +33,13 @@ public static DialogResult ShowDialog(IWin32Window owner, out string value) value += $"|{f.txtExp.Text}"; } + options = new TipNotifyOptions( + f.chkEnabled.Checked, + f.chkPlaySound.Checked, + f.chkSendMail.Checked, + f.chkIsRegex.Checked + ); + return result; } diff --git a/src/CgLogListener/TipNotifyOptions.cs b/src/CgLogListener/TipNotifyOptions.cs index 3a3ed1d..4b1cfd5 100644 --- a/src/CgLogListener/TipNotifyOptions.cs +++ b/src/CgLogListener/TipNotifyOptions.cs @@ -5,23 +5,26 @@ public class TipNotifyOptions public bool Enabled { get; set; } public bool PlaySound { get; set; } public bool SendMail { get; set; } + public bool IsRegex { get; set; } public TipNotifyOptions() { Enabled = false; PlaySound = true; SendMail = false; + IsRegex = false; } - public TipNotifyOptions(bool enabled, bool playSound, bool sendMail) + public TipNotifyOptions(bool enabled, bool playSound, bool sendMail, bool isRegex = false) { Enabled = enabled; PlaySound = playSound; SendMail = sendMail; + IsRegex = isRegex; } /// - /// 從 INI 字串解析 (格式: "enabled,playSound,sendMail" 如 "1,1,0") + /// 從 INI 字串解析 (格式: "enabled,playSound,sendMail,isRegex" 如 "1,1,0,0") /// public static TipNotifyOptions Parse(string value) { @@ -35,16 +38,17 @@ public static TipNotifyOptions Parse(string value) { Enabled = parts.Length > 0 && parts[0] == "1", PlaySound = parts.Length > 1 && parts[1] == "1", - SendMail = parts.Length > 2 && parts[2] == "1" + SendMail = parts.Length > 2 && parts[2] == "1", + IsRegex = parts.Length > 3 && parts[3] == "1" }; } /// - /// 轉成 INI 字串 (格式: "enabled,playSound,sendMail") + /// 轉成 INI 字串 (格式: "enabled,playSound,sendMail,isRegex") /// public override string ToString() { - return $"{(Enabled ? "1" : "0")},{(PlaySound ? "1" : "0")},{(SendMail ? "1" : "0")}"; + return $"{(Enabled ? "1" : "0")},{(PlaySound ? "1" : "0")},{(SendMail ? "1" : "0")},{(IsRegex ? "1" : "0")}"; } } } From ff109049c8ffb2ac367b6aa92ef2d511482c2f01 Mon Sep 17 00:00:00 2001 From: yorklin Date: Tue, 3 Feb 2026 13:43:56 +0800 Subject: [PATCH 6/8] =?UTF-8?q?update=201.=20=E4=BF=AE=E6=94=B9=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E7=BE=A9=E9=80=9A=E7=9F=A5=E8=B7=AF=E5=BE=91=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=80=82=202.=20=E9=80=9A=E7=9F=A5=E5=AE=A2=E8=A3=BD?= =?UTF-8?q?=E5=8C=96=E3=80=82=203.=20=E8=87=AA=E8=A8=82=E9=97=9C=E9=8D=B5?= =?UTF-8?q?=E5=AD=97=E5=8F=AF=E4=BF=AE=E6=94=B9=E3=80=82=204.=20=E6=97=A2?= =?UTF-8?q?=E6=9C=89=E5=9B=BA=E5=AE=9A=207=20=E9=A0=85=EF=BC=8C=E5=8F=AF?= =?UTF-8?q?=E8=87=AA=E7=94=B1=E5=BE=9E=20setting=20=E4=B8=AD=E8=87=AA?= =?UTF-8?q?=E8=A1=8C=E8=AA=BF=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CgLogListener/CgLogListenerControls.cs | 6 +- src/CgLogListener/FormMain.Designer.cs | 123 +++++++++++++------- src/CgLogListener/FormMain.cs | 125 ++++++++++++++++++--- src/CgLogListener/FormPrompt.Designer.cs | 15 ++- src/CgLogListener/FormPrompt.cs | 44 +++++++- src/CgLogListener/NotifyResult.cs | 6 +- src/CgLogListener/Settings.cs | 21 +++- src/CgLogListener/TipNotifyOptions.cs | 48 +++++++- 8 files changed, 314 insertions(+), 74 deletions(-) diff --git a/src/CgLogListener/CgLogListenerControls.cs b/src/CgLogListener/CgLogListenerControls.cs index 0a5f448..807fcaf 100644 --- a/src/CgLogListener/CgLogListenerControls.cs +++ b/src/CgLogListener/CgLogListenerControls.cs @@ -20,7 +20,7 @@ public NotifyResult Notify(string message) options.Enabled && Regex.IsMatch(message, RegexPattern)) { - return NotifyResult.Match(options.PlaySound, options.SendMail); + return NotifyResult.Match(options.PlaySound, options.SendMail, options.CustomNotify); } return NotifyResult.NoMatch; @@ -73,12 +73,12 @@ public NotifyResult Notify(string message) var exps = split[1].Split(','); if (!exps.Any(x => message.Contains(x))) { - return NotifyResult.Match(options.PlaySound, options.SendMail); + return NotifyResult.Match(options.PlaySound, options.SendMail, options.CustomNotify); } } else { - return NotifyResult.Match(options.PlaySound, options.SendMail); + return NotifyResult.Match(options.PlaySound, options.SendMail, options.CustomNotify); } } } diff --git a/src/CgLogListener/FormMain.Designer.cs b/src/CgLogListener/FormMain.Designer.cs index afcc220..3fc34cc 100644 --- a/src/CgLogListener/FormMain.Designer.cs +++ b/src/CgLogListener/FormMain.Designer.cs @@ -48,7 +48,6 @@ private void InitializeComponent() this.txtCookingPattern = new System.Windows.Forms.TextBox(); this.txtCookingMessage = new System.Windows.Forms.TextBox(); this.btnCookingDefault = new System.Windows.Forms.Button(); - this.checkBox1 = new System.Windows.Forms.CheckBox(); this.cgLogListenerTrackBar = new CgLogListener.CgLogListenerTrackBar(); this.lblSoundVol = new System.Windows.Forms.Label(); this.cgLogListenerCheckBox6 = new CgLogListener.CgLogListenerCheckBox(); @@ -58,9 +57,13 @@ private void InitializeComponent() this.label1 = new System.Windows.Forms.Label(); this.cgLogListenerListBox = new CgLogListener.CgLogListenerListBox(); this.btnDelCus = new System.Windows.Forms.Button(); + this.btnEditCus = new System.Windows.Forms.Button(); this.btnAddCus = new System.Windows.Forms.Button(); this.cgLogListenerCheckBox2 = new CgLogListener.CgLogListenerCheckBox(); this.cgLogListenerCheckBox1 = new CgLogListener.CgLogListenerCheckBox(); + this.txtCustomNotifier = new System.Windows.Forms.TextBox(); + this.lblCustomNotifier = new System.Windows.Forms.Label(); + this.btnSelectNotifier = new System.Windows.Forms.Button(); this.timerCooking = new System.Windows.Forms.Timer(this.components); this.btnSaveAppName = new System.Windows.Forms.Button(); this.btnExit = new System.Windows.Forms.Button(); @@ -143,20 +146,20 @@ private void InitializeComponent() // lblAppName // this.lblAppName.AutoSize = true; - this.lblAppName.Location = new System.Drawing.Point(11, 38); + this.lblAppName.Location = new System.Drawing.Point(11, 72); this.lblAppName.Name = "lblAppName"; - this.lblAppName.Size = new System.Drawing.Size(82, 15); + this.lblAppName.Size = new System.Drawing.Size(71, 15); this.lblAppName.TabIndex = 20; - this.lblAppName.Text = "應用名稱:"; + this.lblAppName.Text = "應用名稱:"; // // txtAppName // this.txtAppName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.txtAppName.Location = new System.Drawing.Point(98, 35); + this.txtAppName.Location = new System.Drawing.Point(85, 68); this.txtAppName.Margin = new System.Windows.Forms.Padding(2); this.txtAppName.Name = "txtAppName"; - this.txtAppName.Size = new System.Drawing.Size(288, 25); + this.txtAppName.Size = new System.Drawing.Size(299, 25); this.txtAppName.TabIndex = 21; this.txtAppName.Leave += new System.EventHandler(this.TxtAppName_Leave); // @@ -172,7 +175,6 @@ private void InitializeComponent() this.panel1.Controls.Add(this.txtCookingPattern); this.panel1.Controls.Add(this.txtCookingMessage); this.panel1.Controls.Add(this.btnCookingDefault); - this.panel1.Controls.Add(this.checkBox1); this.panel1.Controls.Add(this.cgLogListenerTrackBar); this.panel1.Controls.Add(this.lblSoundVol); this.panel1.Controls.Add(this.cgLogListenerCheckBox6); @@ -182,10 +184,11 @@ private void InitializeComponent() this.panel1.Controls.Add(this.label1); this.panel1.Controls.Add(this.cgLogListenerListBox); this.panel1.Controls.Add(this.btnDelCus); + this.panel1.Controls.Add(this.btnEditCus); this.panel1.Controls.Add(this.btnAddCus); this.panel1.Controls.Add(this.cgLogListenerCheckBox2); this.panel1.Controls.Add(this.cgLogListenerCheckBox1); - this.panel1.Location = new System.Drawing.Point(11, 62); + this.panel1.Location = new System.Drawing.Point(11, 100); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(433, 315); @@ -196,8 +199,8 @@ private void InitializeComponent() this.cgLogListenerCheckBox7.AutoSize = true; this.cgLogListenerCheckBox7.Location = new System.Drawing.Point(2, 147); this.cgLogListenerCheckBox7.Name = "cgLogListenerCheckBox7"; - this.cgLogListenerCheckBox7.NameInSetting = "ItemBroken"; - this.cgLogListenerCheckBox7.RegexPattern = "壞掉了"; + this.cgLogListenerCheckBox7.NameInSetting = "7"; + this.cgLogListenerCheckBox7.RegexPattern = "^(?!.*\\d).*壞掉了.*$"; this.cgLogListenerCheckBox7.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox7.TabIndex = 18; this.cgLogListenerCheckBox7.Text = "物品損壞通知"; @@ -206,27 +209,28 @@ private void InitializeComponent() // chkCookingReminder // this.chkCookingReminder.AutoSize = true; - this.chkCookingReminder.Location = new System.Drawing.Point(2, 225); + this.chkCookingReminder.Location = new System.Drawing.Point(1, 205); this.chkCookingReminder.Name = "chkCookingReminder"; this.chkCookingReminder.Size = new System.Drawing.Size(104, 19); this.chkCookingReminder.TabIndex = 15; - this.chkCookingReminder.Text = "吃料理通知"; + this.chkCookingReminder.Text = "計時器通知"; this.chkCookingReminder.UseVisualStyleBackColor = true; this.chkCookingReminder.CheckedChanged += new System.EventHandler(this.ChkCookingReminder_CheckedChanged); // // txtCookingInterval // - this.txtCookingInterval.Location = new System.Drawing.Point(112, 223); + this.txtCookingInterval.Location = new System.Drawing.Point(109, 203); this.txtCookingInterval.Name = "txtCookingInterval"; this.txtCookingInterval.Size = new System.Drawing.Size(40, 25); this.txtCookingInterval.TabIndex = 16; this.txtCookingInterval.Text = "180"; this.txtCookingInterval.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.txtCookingInterval.TextChanged += new System.EventHandler(this.txtCookingInterval_TextChanged); // // lblCookingUnit // this.lblCookingUnit.AutoSize = true; - this.lblCookingUnit.Location = new System.Drawing.Point(155, 226); + this.lblCookingUnit.Location = new System.Drawing.Point(155, 206); this.lblCookingUnit.Name = "lblCookingUnit"; this.lblCookingUnit.Size = new System.Drawing.Size(22, 15); this.lblCookingUnit.TabIndex = 17; @@ -234,7 +238,7 @@ private void InitializeComponent() // // txtCookingPattern // - this.txtCookingPattern.Location = new System.Drawing.Point(2, 248); + this.txtCookingPattern.Location = new System.Drawing.Point(1, 230); this.txtCookingPattern.Name = "txtCookingPattern"; this.txtCookingPattern.Size = new System.Drawing.Size(254, 25); this.txtCookingPattern.TabIndex = 19; @@ -242,7 +246,7 @@ private void InitializeComponent() // // txtCookingMessage // - this.txtCookingMessage.Location = new System.Drawing.Point(2, 275); + this.txtCookingMessage.Location = new System.Drawing.Point(2, 261); this.txtCookingMessage.Name = "txtCookingMessage"; this.txtCookingMessage.Size = new System.Drawing.Size(200, 25); this.txtCookingMessage.TabIndex = 20; @@ -250,7 +254,7 @@ private void InitializeComponent() // // btnCookingDefault // - this.btnCookingDefault.Location = new System.Drawing.Point(206, 274); + this.btnCookingDefault.Location = new System.Drawing.Point(205, 264); this.btnCookingDefault.Name = "btnCookingDefault"; this.btnCookingDefault.Size = new System.Drawing.Size(50, 22); this.btnCookingDefault.TabIndex = 21; @@ -258,17 +262,6 @@ private void InitializeComponent() this.btnCookingDefault.UseVisualStyleBackColor = true; this.btnCookingDefault.Click += new System.EventHandler(this.BtnCookingDefault_Click); // - // checkBox1 - // - this.checkBox1.AutoSize = true; - this.checkBox1.Location = new System.Drawing.Point(2, 205); - this.checkBox1.Name = "checkBox1"; - this.checkBox1.Size = new System.Drawing.Size(113, 19); - this.checkBox1.TabIndex = 13; - this.checkBox1.Text = "Custom Notify"; - this.checkBox1.UseVisualStyleBackColor = true; - this.checkBox1.CheckedChanged += new System.EventHandler(this.CheckBox1_CheckedChanged); - // // cgLogListenerTrackBar // this.cgLogListenerTrackBar.AutoSize = false; @@ -294,7 +287,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox6.AutoSize = true; this.cgLogListenerCheckBox6.Location = new System.Drawing.Point(2, 124); this.cgLogListenerCheckBox6.Name = "cgLogListenerCheckBox6"; - this.cgLogListenerCheckBox6.NameInSetting = "ReMaze"; + this.cgLogListenerCheckBox6.NameInSetting = "6"; this.cgLogListenerCheckBox6.RegexPattern = "你感覺到一股不可思議的力量,而『.*』好像快(要?)消失了。"; this.cgLogListenerCheckBox6.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox6.TabIndex = 8; @@ -306,7 +299,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox5.AutoSize = true; this.cgLogListenerCheckBox5.Location = new System.Drawing.Point(2, 99); this.cgLogListenerCheckBox5.Name = "cgLogListenerCheckBox5"; - this.cgLogListenerCheckBox5.NameInSetting = "Sell"; + this.cgLogListenerCheckBox5.NameInSetting = "5"; this.cgLogListenerCheckBox5.RegexPattern = "您順利賣掉了一個.*,(收入|獲得).*魔幣!"; this.cgLogListenerCheckBox5.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox5.TabIndex = 8; @@ -318,7 +311,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox4.AutoSize = true; this.cgLogListenerCheckBox4.Location = new System.Drawing.Point(2, 74); this.cgLogListenerCheckBox4.Name = "cgLogListenerCheckBox4"; - this.cgLogListenerCheckBox4.NameInSetting = "PlayerJoin"; + this.cgLogListenerCheckBox4.NameInSetting = "4"; this.cgLogListenerCheckBox4.RegexPattern = "加入了(你|您)的隊伍。"; this.cgLogListenerCheckBox4.Size = new System.Drawing.Size(134, 19); this.cgLogListenerCheckBox4.TabIndex = 8; @@ -330,7 +323,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox3.AutoSize = true; this.cgLogListenerCheckBox3.Location = new System.Drawing.Point(2, 49); this.cgLogListenerCheckBox3.Name = "cgLogListenerCheckBox3"; - this.cgLogListenerCheckBox3.NameInSetting = "MP0"; + this.cgLogListenerCheckBox3.NameInSetting = "3"; this.cgLogListenerCheckBox3.RegexPattern = "魔力不足。"; this.cgLogListenerCheckBox3.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox3.TabIndex = 8; @@ -360,17 +353,29 @@ private void InitializeComponent() this.cgLogListenerListBox.TabIndex = 9; // // btnDelCus - // + // this.btnDelCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnDelCus.Location = new System.Drawing.Point(326, 194); + this.btnDelCus.Location = new System.Drawing.Point(371, 194); this.btnDelCus.Margin = new System.Windows.Forms.Padding(2); this.btnDelCus.Name = "btnDelCus"; this.btnDelCus.Size = new System.Drawing.Size(47, 22); - this.btnDelCus.TabIndex = 10; + this.btnDelCus.TabIndex = 11; this.btnDelCus.Text = "移除"; this.btnDelCus.UseVisualStyleBackColor = true; this.btnDelCus.Click += new System.EventHandler(this.BtnDelCus_Click); - // + // + // btnEditCus + // + this.btnEditCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnEditCus.Location = new System.Drawing.Point(323, 194); + this.btnEditCus.Margin = new System.Windows.Forms.Padding(2); + this.btnEditCus.Name = "btnEditCus"; + this.btnEditCus.Size = new System.Drawing.Size(47, 22); + this.btnEditCus.TabIndex = 10; + this.btnEditCus.Text = "修改"; + this.btnEditCus.UseVisualStyleBackColor = true; + this.btnEditCus.Click += new System.EventHandler(this.BtnEditCus_Click); + // // btnAddCus // this.btnAddCus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -389,7 +394,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox2.Location = new System.Drawing.Point(2, 25); this.cgLogListenerCheckBox2.Margin = new System.Windows.Forms.Padding(2); this.cgLogListenerCheckBox2.Name = "cgLogListenerCheckBox2"; - this.cgLogListenerCheckBox2.NameInSetting = "ItemFull"; + this.cgLogListenerCheckBox2.NameInSetting = "2"; this.cgLogListenerCheckBox2.RegexPattern = "物品欄沒有空位。"; this.cgLogListenerCheckBox2.Size = new System.Drawing.Size(104, 19); this.cgLogListenerCheckBox2.TabIndex = 1; @@ -402,13 +407,45 @@ private void InitializeComponent() this.cgLogListenerCheckBox1.Location = new System.Drawing.Point(2, 2); this.cgLogListenerCheckBox1.Margin = new System.Windows.Forms.Padding(2); this.cgLogListenerCheckBox1.Name = "cgLogListenerCheckBox1"; - this.cgLogListenerCheckBox1.NameInSetting = "Health"; + this.cgLogListenerCheckBox1.NameInSetting = "1"; this.cgLogListenerCheckBox1.RegexPattern = "在工作時不小心受傷了。"; this.cgLogListenerCheckBox1.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox1.TabIndex = 1; this.cgLogListenerCheckBox1.Text = "採集受傷通知"; this.cgLogListenerCheckBox1.UseVisualStyleBackColor = true; // + // txtCustomNotifier + // + this.txtCustomNotifier.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtCustomNotifier.Location = new System.Drawing.Point(132, 39); + this.txtCustomNotifier.Margin = new System.Windows.Forms.Padding(2); + this.txtCustomNotifier.Name = "txtCustomNotifier"; + this.txtCustomNotifier.Size = new System.Drawing.Size(254, 25); + this.txtCustomNotifier.TabIndex = 13; + this.txtCustomNotifier.Leave += new System.EventHandler(this.TxtCustomNotifier_Leave); + // + // lblCustomNotifier + // + this.lblCustomNotifier.AutoSize = true; + this.lblCustomNotifier.Location = new System.Drawing.Point(11, 42); + this.lblCustomNotifier.Name = "lblCustomNotifier"; + this.lblCustomNotifier.Size = new System.Drawing.Size(116, 15); + this.lblCustomNotifier.TabIndex = 22; + this.lblCustomNotifier.Text = "自定義通知路徑:"; + // + // btnSelectNotifier + // + this.btnSelectNotifier.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSelectNotifier.Location = new System.Drawing.Point(390, 38); + this.btnSelectNotifier.Margin = new System.Windows.Forms.Padding(2); + this.btnSelectNotifier.Name = "btnSelectNotifier"; + this.btnSelectNotifier.Size = new System.Drawing.Size(53, 22); + this.btnSelectNotifier.TabIndex = 23; + this.btnSelectNotifier.Text = "選擇"; + this.btnSelectNotifier.UseVisualStyleBackColor = true; + this.btnSelectNotifier.Click += new System.EventHandler(this.BtnSelectNotifier_Click); + // // timerCooking // this.timerCooking.Interval = 180000; @@ -417,7 +454,7 @@ private void InitializeComponent() // btnSaveAppName // this.btnSaveAppName.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnSaveAppName.Location = new System.Drawing.Point(390, 34); + this.btnSaveAppName.Location = new System.Drawing.Point(390, 68); this.btnSaveAppName.Margin = new System.Windows.Forms.Padding(2); this.btnSaveAppName.Name = "btnSaveAppName"; this.btnSaveAppName.Size = new System.Drawing.Size(53, 22); @@ -462,6 +499,9 @@ private void InitializeComponent() this.Controls.Add(this.txtAppName); this.Controls.Add(this.btnSelectLogPath); this.Controls.Add(this.txtCgLogPath); + this.Controls.Add(this.txtCustomNotifier); + this.Controls.Add(this.lblCustomNotifier); + this.Controls.Add(this.btnSelectNotifier); this.Controls.Add(this.btnExit); this.Font = new System.Drawing.Font("新細明體", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); @@ -491,6 +531,7 @@ private void InitializeComponent() private System.Windows.Forms.TextBox txtAppName; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button btnDelCus; + private System.Windows.Forms.Button btnEditCus; private System.Windows.Forms.Button btnAddCus; private System.Windows.Forms.Button btnExit; private System.Windows.Forms.ContextMenuStrip notifyIconContextMenu; @@ -508,7 +549,9 @@ private void InitializeComponent() private System.Windows.Forms.LinkLabel linkLabel1; private CgLogListenerCheckBox cgLogListenerCheckBox6; private CgLogListenerTrackBar cgLogListenerTrackBar; - private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.TextBox txtCustomNotifier; + private System.Windows.Forms.Label lblCustomNotifier; + private System.Windows.Forms.Button btnSelectNotifier; private System.Windows.Forms.Label lblSoundVol; private System.Windows.Forms.CheckBox chkCookingReminder; private System.Windows.Forms.TextBox txtCookingInterval; diff --git a/src/CgLogListener/FormMain.cs b/src/CgLogListener/FormMain.cs index bab64fe..28058e5 100644 --- a/src/CgLogListener/FormMain.cs +++ b/src/CgLogListener/FormMain.cs @@ -18,6 +18,7 @@ public partial class FormMain : Form private readonly MediaPlayer mp = new MediaPlayer(); private readonly Dictionary soundCheckBoxes = new Dictionary(); private readonly Dictionary mailCheckBoxes = new Dictionary(); + private readonly Dictionary customNotifyCheckBoxes = new Dictionary(); public FormMain() { @@ -63,8 +64,8 @@ private void FrmMain_Load(object sender, EventArgs e) // set playsound vol cgLogListenerTrackBar.Value = settings.SoundVol; - // set line notify - checkBox1.Checked = settings.CustomNotify; + // set custom notifier path + txtCustomNotifier.Text = settings.CustomNotifier ?? ""; // 設定標準關鍵字及其音效/郵件選項 SetupStandardTips(); @@ -73,7 +74,7 @@ private void FrmMain_Load(object sender, EventArgs e) SetupCustomTips(); cgLogListenerTrackBar.ValueChanged += CgLogListenerTrackBar_ValueChanged; - checkBox1.CheckedChanged += CheckBox1_CheckedChanged; + txtCustomNotifier.Leave += TxtCustomNotifier_Leave; } private void SetupStandardTips() @@ -89,9 +90,10 @@ private void SetupStandardTips() cgLogListenerCheckBox7 }; - // 固定位置讓 🔊/✉ checkbox 對齊 + // 固定位置讓 S/M/C checkbox 對齊 const int soundCheckBoxX = 155; const int mailCheckBoxX = 210; + const int customNotifyCheckBoxX = 265; foreach (var chk in standardCheckBoxes) { @@ -104,6 +106,30 @@ private void SetupStandardTips() settings.SetStandardTip(nameInSetting, options); } + // 從 settings 讀取 Text 和 RegexPattern(如果有的話,否則使用 Designer 的預設值) + if (!string.IsNullOrEmpty(options.Text)) + { + chk.Text = options.Text; + } + else + { + // 回寫 Designer 的預設值到 settings,方便使用者編輯 settings.ini + options.Text = chk.Text; + } + + if (!string.IsNullOrEmpty(options.RegexPattern)) + { + chk.RegexPattern = options.RegexPattern; + } + else + { + // 回寫 Designer 的預設值到 settings,方便使用者編輯 settings.ini + options.RegexPattern = chk.RegexPattern; + } + + // 儲存更新後的設定(確保 Text 和 RegexPattern 被寫入 settings.ini) + settings.SetStandardTip(nameInSetting, options); + // 設定主 checkbox chk.Checked = options.Enabled; chk.CheckedChanged += (s, ev) => @@ -149,6 +175,22 @@ private void SetupStandardTips() }; panel1.Controls.Add(mailChk); mailCheckBoxes[nameInSetting] = mailChk; + + // 動態建立 CustomNotify checkbox + var customNotifyChk = new CheckBox + { + Text = "C", + AutoSize = true, + Location = new Point(customNotifyCheckBoxX, chk.Top), + Checked = options.CustomNotify, + Font = new Font("微軟正黑體", 8) + }; + customNotifyChk.CheckedChanged += (s, ev) => + { + settings.SetStandardTipCustomNotify(nameInSetting, ((CheckBox)s).Checked); + }; + panel1.Controls.Add(customNotifyChk); + customNotifyCheckBoxes[nameInSetting] = customNotifyChk; } } @@ -161,6 +203,9 @@ private void SetupCustomTips() cgLogListenerListBox.Items.Add(kv.Key); } } + + // 雙擊修改 + cgLogListenerListBox.DoubleClick += (s, ev) => BtnEditCus_Click(s, ev); } private void CgLogListenerTrackBar_ValueChanged(object sender, EventArgs e) @@ -278,14 +323,17 @@ void Watcher_OnNewLog(string log) catch { } } - // Custom Notifier (全域設定) - if (settings.CustomNotify) + // Custom Notifier (根據個別關鍵字設定) + if (result.CustomNotify && !string.IsNullOrEmpty(settings.CustomNotifier)) { foreach (var notifier in settings.CustomNotifier.Split(',')) { + var trimmedNotifier = notifier.Trim(); + if (string.IsNullOrEmpty(trimmedNotifier)) continue; + try { - ProcessStartInfo p = new ProcessStartInfo(notifier, $"\"[{settings.AppName}] {log}\"") + ProcessStartInfo p = new ProcessStartInfo(trimmedNotifier, $"\"[{settings.AppName}] {log}\"") { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true @@ -313,6 +361,33 @@ private void BtnAddCus_Click(object sender, EventArgs e) cgLogListenerListBox.Items.Add(value); } + private void BtnEditCus_Click(object sender, EventArgs e) + { + if (cgLogListenerListBox.SelectedIndex < 0) + { + MessageBox.Show(this, "請先選擇要修改的關鍵字", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + var oldKeyword = (string)cgLogListenerListBox.SelectedItem; + var oldOptions = settings.CustomizeTips[oldKeyword]; + + if (FormPrompt.ShowDialogForEdit(this, oldKeyword, oldOptions, out string newKeyword, out TipNotifyOptions newOptions) != DialogResult.OK || + string.IsNullOrEmpty(newKeyword)) + { + return; + } + + // 刪除舊的 + settings.RemoveCustomizeTip(oldKeyword); + cgLogListenerListBox.Items.Remove(oldKeyword); + + // 加入新的 + settings.AddCustomizeTip(newKeyword, newOptions); + cgLogListenerListBox.Items.Add(newKeyword); + cgLogListenerListBox.SelectedItem = newKeyword; + } + private void BtnDelCus_Click(object sender, EventArgs e) { if (cgLogListenerListBox.SelectedIndex < 0) @@ -325,18 +400,31 @@ private void BtnDelCus_Click(object sender, EventArgs e) cgLogListenerListBox.Items.Remove(selectItem); } - private void CheckBox1_CheckedChanged(object sender, EventArgs e) + private void TxtCustomNotifier_Leave(object sender, EventArgs e) + { + settings.SetCustomNotifier(txtCustomNotifier.Text); + } + + private void BtnSelectNotifier_Click(object sender, EventArgs e) { - if (checkBox1.Checked) + var dialog = new OpenFileDialog { - FormCustomNotifierPrompt.ShowDialog(this, out string value); - settings.SetCustomNotify(true); - settings.SetCustomNotifier(value); - } - else + Filter = "執行檔 (*.exe)|*.exe|所有檔案 (*.*)|*.*", + Title = "選擇 Notifier 程式" + }; + + if (dialog.ShowDialog(this) == DialogResult.OK) { - settings.SetCustomNotify(false); - settings.SetCustomNotifier(string.Empty); + var currentPaths = txtCustomNotifier.Text; + if (string.IsNullOrEmpty(currentPaths)) + { + txtCustomNotifier.Text = dialog.FileName; + } + else + { + txtCustomNotifier.Text = currentPaths + "," + dialog.FileName; + } + settings.SetCustomNotifier(txtCustomNotifier.Text); } } @@ -493,5 +581,10 @@ private void LinkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs { System.Diagnostics.Process.Start("https://github.com/WindOfNet/CgLogListener"); } + + private void txtCookingInterval_TextChanged(object sender, EventArgs e) + { + + } } } diff --git a/src/CgLogListener/FormPrompt.Designer.cs b/src/CgLogListener/FormPrompt.Designer.cs index 2cb2b90..36ea7ee 100644 --- a/src/CgLogListener/FormPrompt.Designer.cs +++ b/src/CgLogListener/FormPrompt.Designer.cs @@ -38,6 +38,7 @@ private void InitializeComponent() this.chkPlaySound = new System.Windows.Forms.CheckBox(); this.chkSendMail = new System.Windows.Forms.CheckBox(); this.chkIsRegex = new System.Windows.Forms.CheckBox(); + this.chkCustomNotify = new System.Windows.Forms.CheckBox(); this.SuspendLayout(); // // btnOk @@ -137,6 +138,16 @@ private void InitializeComponent() this.chkIsRegex.Text = "Regex"; this.chkIsRegex.UseVisualStyleBackColor = true; // + // chkCustomNotify + // + this.chkCustomNotify.AutoSize = true; + this.chkCustomNotify.Location = new System.Drawing.Point(252, 61); + this.chkCustomNotify.Name = "chkCustomNotify"; + this.chkCustomNotify.Size = new System.Drawing.Size(34, 19); + this.chkCustomNotify.TabIndex = 9; + this.chkCustomNotify.Text = "C"; + this.chkCustomNotify.UseVisualStyleBackColor = true; + // // FormPrompt // this.AcceptButton = this.btnOk; @@ -145,6 +156,7 @@ private void InitializeComponent() this.CancelButton = this.btnCancel; this.ClientSize = new System.Drawing.Size(368, 90); this.ControlBox = false; + this.Controls.Add(this.chkCustomNotify); this.Controls.Add(this.chkIsRegex); this.Controls.Add(this.chkSendMail); this.Controls.Add(this.chkPlaySound); @@ -163,7 +175,7 @@ private void InitializeComponent() this.ShowIcon = false; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "新增自訂關鍵字"; + this.Text = "新增/編輯自訂關鍵字"; this.ResumeLayout(false); this.PerformLayout(); @@ -181,5 +193,6 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox chkPlaySound; private System.Windows.Forms.CheckBox chkSendMail; private System.Windows.Forms.CheckBox chkIsRegex; + private System.Windows.Forms.CheckBox chkCustomNotify; } } \ No newline at end of file diff --git a/src/CgLogListener/FormPrompt.cs b/src/CgLogListener/FormPrompt.cs index c4fcaa6..4be1eb8 100644 --- a/src/CgLogListener/FormPrompt.cs +++ b/src/CgLogListener/FormPrompt.cs @@ -37,7 +37,49 @@ public static DialogResult ShowDialog(IWin32Window owner, out string value, out f.chkEnabled.Checked, f.chkPlaySound.Checked, f.chkSendMail.Checked, - f.chkIsRegex.Checked + f.chkIsRegex.Checked, + f.chkCustomNotify.Checked + ); + + return result; + } + + /// + /// 編輯模式:預填已有的關鍵字和選項 + /// + public static DialogResult ShowDialogForEdit(IWin32Window owner, string existingKeyword, TipNotifyOptions existingOptions, out string value, out TipNotifyOptions options) + { + FormPrompt f = new FormPrompt(); + f.Text = "編輯自訂關鍵字"; + + // 解析 keyword 和排除詞 + var split = existingKeyword.Split('|'); + f.txtValue.Text = split[0]; + if (split.Length > 1) + { + f.txtExp.Text = split[1]; + } + + // 預填選項 + f.chkEnabled.Checked = existingOptions.Enabled; + f.chkPlaySound.Checked = existingOptions.PlaySound; + f.chkSendMail.Checked = existingOptions.SendMail; + f.chkIsRegex.Checked = existingOptions.IsRegex; + f.chkCustomNotify.Checked = existingOptions.CustomNotify; + + DialogResult result = f.ShowDialog(owner); + value = f.txtValue.Text; + if (!string.IsNullOrWhiteSpace(f.txtExp.Text)) + { + value += $"|{f.txtExp.Text}"; + } + + options = new TipNotifyOptions( + f.chkEnabled.Checked, + f.chkPlaySound.Checked, + f.chkSendMail.Checked, + f.chkIsRegex.Checked, + f.chkCustomNotify.Checked ); return result; diff --git a/src/CgLogListener/NotifyResult.cs b/src/CgLogListener/NotifyResult.cs index 63a0706..c401e7a 100644 --- a/src/CgLogListener/NotifyResult.cs +++ b/src/CgLogListener/NotifyResult.cs @@ -5,16 +5,18 @@ public class NotifyResult public bool IsMatch { get; set; } public bool PlaySound { get; set; } public bool SendMail { get; set; } + public bool CustomNotify { get; set; } public static NotifyResult NoMatch => new NotifyResult { IsMatch = false }; - public static NotifyResult Match(bool playSound, bool sendMail) + public static NotifyResult Match(bool playSound, bool sendMail, bool customNotify) { return new NotifyResult { IsMatch = true, PlaySound = playSound, - SendMail = sendMail + SendMail = sendMail, + CustomNotify = customNotify }; } } diff --git a/src/CgLogListener/Settings.cs b/src/CgLogListener/Settings.cs index 508bceb..400d12e 100644 --- a/src/CgLogListener/Settings.cs +++ b/src/CgLogListener/Settings.cs @@ -18,7 +18,6 @@ public sealed class Settings const string custmizeFileName = "custmize.dat"; public string AppName { get; private set; } - public bool CustomNotify { get; private set; } public string CustomNotifier { get; private set; } public int SoundVol { get; private set; } public string CgLogPath { get; private set; } @@ -56,7 +55,6 @@ private void LoadSettings() if (string.IsNullOrEmpty(AppName)) AppName = "CgLogListener"; CgLogPath = baseData[nameof(CgLogPath)]; SoundVol = int.Parse(baseData[nameof(SoundVol)] ?? "5"); - CustomNotify = baseData[nameof(CustomNotify)] == "1"; CustomNotifier = baseData[nameof(CustomNotifier)]; // 載入標準關鍵字設定 @@ -95,7 +93,6 @@ private void GenConfigFile() baseSection[nameof(AppName)] = "CgLogListener"; baseSection[nameof(CgLogPath)] = string.Empty; baseSection[nameof(SoundVol)] = "5"; - baseSection[nameof(CustomNotify)] = "0"; var fileIniDataParser = new FileIniDataParser(); fileIniDataParser.WriteFile(settingsFileName, iniData); @@ -110,7 +107,6 @@ private void UpdateConfig() baseSection[nameof(AppName)] = AppName; baseSection[nameof(CgLogPath)] = CgLogPath; baseSection[nameof(SoundVol)] = SoundVol.ToString(); - baseSection[nameof(CustomNotify)] = CustomNotify ? "1" : "0"; baseSection[nameof(CustomNotifier)] = CustomNotifier; var standardTipData = iniData[settingsStandardTipsSection]; @@ -221,12 +217,25 @@ internal void SetCustomNotifier(string value) UpdateConfig(); } - internal void SetCustomNotify(bool value) + internal void SetStandardTipCustomNotify(string nameInSetting, bool customNotify) { - CustomNotify = value; + if (!StandardTips.ContainsKey(nameInSetting)) + { + StandardTips[nameInSetting] = new TipNotifyOptions(); + } + StandardTips[nameInSetting].CustomNotify = customNotify; UpdateConfig(); } + internal void SetCustomizeTipCustomNotify(string keyword, bool customNotify) + { + if (CustomizeTips.ContainsKey(keyword)) + { + CustomizeTips[keyword].CustomNotify = customNotify; + UpdateConfig(); + } + } + internal void SetAppName(string value) { AppName = string.IsNullOrEmpty(value) ? "CgLogListener" : value; diff --git a/src/CgLogListener/TipNotifyOptions.cs b/src/CgLogListener/TipNotifyOptions.cs index 4b1cfd5..4863b65 100644 --- a/src/CgLogListener/TipNotifyOptions.cs +++ b/src/CgLogListener/TipNotifyOptions.cs @@ -2,10 +2,13 @@ namespace CgLogListener { public class TipNotifyOptions { + public string Text { get; set; } + public string RegexPattern { get; set; } public bool Enabled { get; set; } public bool PlaySound { get; set; } public bool SendMail { get; set; } public bool IsRegex { get; set; } + public bool CustomNotify { get; set; } public TipNotifyOptions() { @@ -13,18 +16,22 @@ public TipNotifyOptions() PlaySound = true; SendMail = false; IsRegex = false; + CustomNotify = false; } - public TipNotifyOptions(bool enabled, bool playSound, bool sendMail, bool isRegex = false) + public TipNotifyOptions(bool enabled, bool playSound, bool sendMail, bool isRegex = false, bool customNotify = false) { Enabled = enabled; PlaySound = playSound; SendMail = sendMail; IsRegex = isRegex; + CustomNotify = customNotify; } /// - /// 從 INI 字串解析 (格式: "enabled,playSound,sendMail,isRegex" 如 "1,1,0,0") + /// 從 INI 字串解析 + /// 新格式: "Text|RegexPattern|enabled,playSound,sendMail,isRegex,customNotify" 如 "通知文字|regex.*pattern|1,1,0,0,1" + /// 舊格式: "enabled,playSound,sendMail,isRegex,customNotify" 如 "1,1,0,0,1" (相容) /// public static TipNotifyOptions Parse(string value) { @@ -33,22 +40,53 @@ public static TipNotifyOptions Parse(string value) return new TipNotifyOptions(); } + // 檢查是否為新格式 (包含 |) + if (value.Contains("|")) + { + var sections = value.Split('|'); + if (sections.Length >= 3) + { + var flags = sections[2].Split(','); + return new TipNotifyOptions + { + Text = sections[0], + RegexPattern = sections[1], + Enabled = flags.Length > 0 && flags[0] == "1", + PlaySound = flags.Length > 1 && flags[1] == "1", + SendMail = flags.Length > 2 && flags[2] == "1", + IsRegex = flags.Length > 3 && flags[3] == "1", + CustomNotify = flags.Length > 4 && flags[4] == "1" + }; + } + } + + // 舊格式相容 var parts = value.Split(','); return new TipNotifyOptions { Enabled = parts.Length > 0 && parts[0] == "1", PlaySound = parts.Length > 1 && parts[1] == "1", SendMail = parts.Length > 2 && parts[2] == "1", - IsRegex = parts.Length > 3 && parts[3] == "1" + IsRegex = parts.Length > 3 && parts[3] == "1", + CustomNotify = parts.Length > 4 && parts[4] == "1" }; } /// - /// 轉成 INI 字串 (格式: "enabled,playSound,sendMail,isRegex") + /// 轉成 INI 字串 + /// 如果有 Text 或 RegexPattern,使用新格式: "Text|RegexPattern|enabled,playSound,sendMail,isRegex,customNotify" + /// 否則使用舊格式: "enabled,playSound,sendMail,isRegex,customNotify" /// public override string ToString() { - return $"{(Enabled ? "1" : "0")},{(PlaySound ? "1" : "0")},{(SendMail ? "1" : "0")},{(IsRegex ? "1" : "0")}"; + var flags = $"{(Enabled ? "1" : "0")},{(PlaySound ? "1" : "0")},{(SendMail ? "1" : "0")},{(IsRegex ? "1" : "0")},{(CustomNotify ? "1" : "0")}"; + + if (!string.IsNullOrEmpty(Text) || !string.IsNullOrEmpty(RegexPattern)) + { + return $"{Text ?? ""}|{RegexPattern ?? ""}|{flags}"; + } + + return flags; } } } From b4e888f0926a1c92bea9ce29edd846cfe719f0e5 Mon Sep 17 00:00:00 2001 From: yorklin Date: Tue, 3 Feb 2026 16:09:54 +0800 Subject: [PATCH 7/8] =?UTF-8?q?update=201.=20=E9=99=90=E5=88=B6=E5=AF=AC?= =?UTF-8?q?=E5=BA=A6=E9=81=BF=E5=85=8D=E8=87=AA=E5=AE=9A=E7=BE=A9=E5=90=8D?= =?UTF-8?q?=E7=A8=B1=E9=81=8E=E9=95=B7=E3=80=82=202.=20=E8=AA=BF=E6=95=B4?= =?UTF-8?q?=E7=89=A9=E5=93=81=E6=90=8D=E5=A3=9E=E9=A0=90=E8=A8=AD=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=B7=E8=A6=8F=E5=89=87=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CgLogListener/FormMain.Designer.cs | 2 +- src/CgLogListener/FormMain.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CgLogListener/FormMain.Designer.cs b/src/CgLogListener/FormMain.Designer.cs index 3fc34cc..6117a38 100644 --- a/src/CgLogListener/FormMain.Designer.cs +++ b/src/CgLogListener/FormMain.Designer.cs @@ -200,7 +200,7 @@ private void InitializeComponent() this.cgLogListenerCheckBox7.Location = new System.Drawing.Point(2, 147); this.cgLogListenerCheckBox7.Name = "cgLogListenerCheckBox7"; this.cgLogListenerCheckBox7.NameInSetting = "7"; - this.cgLogListenerCheckBox7.RegexPattern = "^(?!.*\\d).*壞掉了.*$"; + this.cgLogListenerCheckBox7.RegexPattern = "^(?!.*×\\d+).*壞掉了"; this.cgLogListenerCheckBox7.Size = new System.Drawing.Size(119, 19); this.cgLogListenerCheckBox7.TabIndex = 18; this.cgLogListenerCheckBox7.Text = "物品損壞通知"; diff --git a/src/CgLogListener/FormMain.cs b/src/CgLogListener/FormMain.cs index 28058e5..26016e6 100644 --- a/src/CgLogListener/FormMain.cs +++ b/src/CgLogListener/FormMain.cs @@ -132,6 +132,8 @@ private void SetupStandardTips() // 設定主 checkbox chk.Checked = options.Enabled; + chk.AutoSize = false; + chk.Width = 150; // 限制寬度,避免長文字蓋掉右邊的 🔊 ✉ C (主 checkbox X=2,🔊 X=155,可用空間 153px) chk.CheckedChanged += (s, ev) => { var cb = (CgLogListenerCheckBox)s; From c4145d32a10cf5701f62c789998b236e175d2483 Mon Sep 17 00:00:00 2001 From: yorklin020 <86771074+yorklin020@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:51:27 +0800 Subject: [PATCH 8/8] Enhance README with new features and settings Updated README with new features and settings for notifications and detection. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 9d5302f..63c889a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ CgLogListener ![demo](https://i.imgur.com/xT5ZAwe.png) + 目錄下可替換wav York @@ -17,3 +18,12 @@ York 2. 新增物品損壞通知。 3. 設定應用來源,方便信件過濾。 4. 新增吃料偵測,秒數循環提醒。 + +20260203 +1. 預設選項可由 setting.ini 中自由修改命名、regex 規則。 +2. 吃料偵測改成計時器功能,可自定義秒數、提示訊息、regex 規則,若須恢復吃料判斷,案預設即可。 +3. custom notify 路徑設定改成跟選擇魔力資料夾相同,不用再手動打。 +4. custom notify 是否啟用通知,改成跟 sound, mail 一樣,可自行勾選是否啟用。 +5. 自訂關鍵字功能,可自行增加、修改,並增加 sound mail, notify, regex 彈性設定。 + +![demo](https://i.meee.com.tw/QBmHzKn.png)