From 78e2394bb1290eaca20f3a99133b4ab9efa8f9b7 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 21 Sep 2013 04:35:42 +0900 Subject: [PATCH] =?utf8?q?=E3=82=BF=E3=83=96=E6=8C=AF=E3=82=8A=E5=88=86?= =?utf8?q?=E3=81=91=E3=83=AB=E3=83=BC=E3=83=AB=E3=81=AE=E5=87=A6=E7=90=86?= =?utf8?q?=E3=82=92=E6=9B=B8=E3=81=8D=E7=9B=B4=E3=81=97,=20PostFilterRule?= =?utf8?q?=20=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC?= =?utf8?q?=E3=83=89=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/FiltersClassTest.cs | 98 ---- OpenTween.Tests/OpenTween.Tests.csproj | 2 +- OpenTween.Tests/PostFilterRuleTest.cs | 577 +++++++++++++++++++++ OpenTween/FilterDialog.cs | 124 +++-- OpenTween/OpenTween.csproj | 1 + OpenTween/PostFilterRule.cs | 738 ++++++++++++++++++++++++++ OpenTween/Resources/ChangeLog.txt | 8 + OpenTween/StatusDictionary.cs | 910 +-------------------------------- OpenTween/Tween.cs | 26 +- 9 files changed, 1424 insertions(+), 1060 deletions(-) delete mode 100644 OpenTween.Tests/FiltersClassTest.cs create mode 100644 OpenTween.Tests/PostFilterRuleTest.cs create mode 100644 OpenTween/PostFilterRule.cs diff --git a/OpenTween.Tests/FiltersClassTest.cs b/OpenTween.Tests/FiltersClassTest.cs deleted file mode 100644 index 1b56c1f2..00000000 --- a/OpenTween.Tests/FiltersClassTest.cs +++ /dev/null @@ -1,98 +0,0 @@ -// OpenTween - Client of Twitter -// Copyright (c) 2013 kim_upsilon (@kim_upsilon) -// All rights reserved. -// -// This file is part of OpenTween. -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public License along -// with this program. If not, see , or write to -// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, -// Boston, MA 02110-1301, USA. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NUnit.Framework; - -namespace OpenTween -{ - [TestFixture] - class FiltersClassTest - { - [Test] - public void NameTest() - { - var filter = new FiltersClass(); - PostClass post; - - filter.NameFilter = "hoge"; - post = new PostClass { ScreenName = "hoge", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); - - filter.NameFilter = "hoge"; - post = new PostClass { ScreenName = "foo", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None)); - - // NameFilter は RetweetedBy にもマッチする - filter.NameFilter = "hoge"; - post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hoge" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); - - filter.NameFilter = "hoge"; - post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "bar" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None)); - - // NameFilter は部分一致ではない - filter.NameFilter = "hoge"; - post = new PostClass { ScreenName = "hogehoge", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None)); - - // 大小文字を区別しないオプション - filter.NameFilter = "hoge"; - filter.CaseSensitive = false; - post = new PostClass { ScreenName = "Hoge", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); - } - - [Test] - public void NameRegexTest() - { - var filter = new FiltersClass { UseRegex = true }; - PostClass post; - - filter.NameFilter = "hoge(hoge)+"; - post = new PostClass { ScreenName = "hogehoge", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); - - filter.NameFilter = "hoge(hoge)+"; - post = new PostClass { ScreenName = "hoge", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None)); - - // NameFilter は RetweetedBy にもマッチする - filter.NameFilter = "hoge(hoge)+"; - post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hogehoge" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); - - filter.NameFilter = "hoge(hoge)+"; - post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hoge2" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None)); - - // 大小文字を区別しないオプション - filter.NameFilter = "hoge(hoge)+"; - filter.CaseSensitive = false; - post = new PostClass { ScreenName = "HogeHogeHoge", Text = "test" }; - Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); - } - } -} diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index d17ec2e0..5e45dc62 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -54,7 +54,7 @@ - + diff --git a/OpenTween.Tests/PostFilterRuleTest.cs b/OpenTween.Tests/PostFilterRuleTest.cs new file mode 100644 index 00000000..ea211d4d --- /dev/null +++ b/OpenTween.Tests/PostFilterRuleTest.cs @@ -0,0 +1,577 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2013 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; + +namespace OpenTween +{ + [TestFixture] + class PostFilterRuleTest + { + [TestFixtureSetUp] + public void FixtureSetUp() + { + PostFilterRule.AutoCompile = true; + } + + [Test] + public void EmptyRuleTest() + { + var filter = new PostFilterRule { }; + var post = new PostClass { ScreenName = "hogehoge" }; + + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + } + + [Test] + public void NullTest() + { + var filter = new PostFilterRule + { + FilterName = null, + FilterSource = null, + ExFilterName = null, + ExFilterSource = null, + }; + var post = new PostClass { ScreenName = "hogehoge" }; + + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + Assert.That(() => filter.FilterBody = null, Throws.InstanceOf()); + Assert.That(() => filter.ExFilterBody = null, Throws.InstanceOf()); + } + + [Test] + public void MatchOnlyTest() + { + var filter = new PostFilterRule { FilterName = "hogehoge" }; + var post = new PostClass { ScreenName = "hogehoge" }; + + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); + } + + [Test] + public void ExcludeOnlyTest() + { + var filter = new PostFilterRule { ExFilterName = "hogehoge" }; + var post = new PostClass { ScreenName = "hogehoge" }; + + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Exclude)); + } + + [Test] + public void MatchAndExcludeTest() + { + var filter = new PostFilterRule { FilterName = "hogehoge", ExFilterSource = "tetete" }; + var post = new PostClass { ScreenName = "hogehoge", Source = "tetete" }; + + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + } + + [Test] + public void PostMatchOptionsTest() + { + var filter = new PostFilterRule { FilterName = "hogehoge" }; + var post = new PostClass { ScreenName = "hogehoge" }; + + filter.MoveMatches = false; + filter.MarkMatches = false; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Copy)); + + filter.MoveMatches = false; + filter.MarkMatches = true; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark)); + + filter.MoveMatches = true; + filter.MarkMatches = false; // 無視される + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Move)); + + filter.MoveMatches = true; + filter.MarkMatches = true; // 無視される + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Move)); + } + + [TestCase(false, "hogehoge", false)] + [TestCase(false, "hogehoge", true)] + [TestCase(true, "(hoge){2}", false)] + [TestCase(true, "(hoge){2}", true)] + public void NameTest(bool useRegex, string pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + filter.UseRegex = useRegex; + filter.FilterName = pattern; + } + else + { + filter.ExUseRegex = useRegex; + filter.ExFilterName = pattern; + } + + post = new PostClass { ScreenName = "hogehoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { ScreenName = "foo" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterName は RetweetedBy にもマッチする + post = new PostClass { ScreenName = "foo", RetweetedBy = "hogehoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { ScreenName = "foo", RetweetedBy = "bar" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + if (!useRegex) + { + // FilterName は完全一致 (UseRegex = false の場合) + post = new PostClass { ScreenName = "_hogehoge_" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + } + else + { + // FilterName は部分一致 (UseRegex = true の場合) + post = new PostClass { ScreenName = "_hogehoge_" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { ScreenName = "HogeHoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { ScreenName = "HogeHoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [TestCase(false, new[] { "aaa", "bbb" }, false)] + [TestCase(false, new[] { "aaa", "bbb" }, true)] + [TestCase(true, new[] { "a{3}", "b{3}" }, false)] + [TestCase(true, new[] { "a{3}", "b{3}" }, true)] + public void BodyTest(bool useRegex, string[] pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + filter.UseRegex = useRegex; + filter.FilterBody = pattern; + } + else + { + filter.ExUseRegex = useRegex; + filter.ExFilterBody = pattern; + } + + post = new PostClass { TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 片方だけではマッチしない + post = new PostClass { TextFromApi = "aaa" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterBody の文字列が全て含まれている + post = new PostClass { TextFromApi = "123aaa456bbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // ScreenName にはマッチしない (UseNameField = true の場合) + post = new PostClass { ScreenName = "aaabbb", TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { TextFromApi = "AaaBbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { TextFromApi = "AaaBbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [TestCase(false, new[] { "aaa", "bbb" }, false)] + [TestCase(false, new[] { "aaa", "bbb" }, true)] + [TestCase(true, new[] { "a{3}", "b{3}" }, false)] + [TestCase(true, new[] { "a{3}", "b{3}" }, true)] + public void BodyUrlTest(bool useRegex, string[] pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + // FilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる + filter.FilterByUrl = true; + + filter.UseRegex = useRegex; + filter.FilterBody = pattern; + } + else + { + // ExFilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる + filter.ExFilterByUrl = true; + + filter.ExUseRegex = useRegex; + filter.ExFilterBody = pattern; + } + + post = new PostClass { Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 片方だけではマッチしない + post = new PostClass { Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterBody の文字列が全て含まれている + post = new PostClass { Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // ScreenName にはマッチしない (UseNameField = true の場合) + post = new PostClass { ScreenName = "aaabbb", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [TestCase(false, new[] { "aaa", "bbb" }, false)] + [TestCase(false, new[] { "aaa", "bbb" }, true)] + [TestCase(true, new[] { "a{3}", "b{3}" }, false)] + [TestCase(true, new[] { "a{3}", "b{3}" }, true)] + public void BodyAndNameTest(bool useRegex, string[] pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + // UseNameField = false の場合は FilterBody のマッチ対象が Text と ScreenName の両方になる + filter.UseNameField = false; + + filter.UseRegex = useRegex; + filter.FilterBody = pattern; + } + else + { + // ExUseNameField = false の場合は ExFilterBody のマッチ対象が Text と ScreenName の両方になる + filter.ExUseNameField = false; + + filter.ExUseRegex = useRegex; + filter.ExFilterBody = pattern; + } + + post = new PostClass { ScreenName = "hoge", TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterBody の片方だけではマッチしない + post = new PostClass { ScreenName = "hoge", TextFromApi = "aaa" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterBody の片方だけではマッチしない + post = new PostClass { ScreenName = "aaa", TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // TextFromApi に FilterBody の文字列が全て含まれている + post = new PostClass { ScreenName = "hoge", TextFromApi = "123aaa456bbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // ScreenName に FilterBody の文字列が全て含まれている + post = new PostClass { ScreenName = "aaabbb", TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // FilterBody と ScreenName に FilterBody の文字列がそれぞれ含まれている + post = new PostClass { ScreenName = "aaa", TextFromApi = "bbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { ScreenName = "hoge", TextFromApi = "AaaBbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + post = new PostClass { ScreenName = "AaaBbb", TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { ScreenName = "hoge", TextFromApi = "AaaBbb" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { ScreenName = "AaaBbb", TextFromApi = "test" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [TestCase(false, new[] { "aaa", "bbb" }, false)] + [TestCase(false, new[] { "aaa", "bbb" }, true)] + [TestCase(true, new[] { "a{3}", "b{3}" }, false)] + [TestCase(true, new[] { "a{3}", "b{3}" }, true)] + public void BodyUrlAndNameTest(bool useRegex, string[] pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + // FilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる + filter.FilterByUrl = true; + + // UseNameField = false の場合は FilterBody のマッチ対象が Text と ScreenName の両方になる + filter.UseNameField = false; + + filter.UseRegex = useRegex; + filter.FilterBody = pattern; + } + else + { + // ExFilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる + filter.ExFilterByUrl = true; + + // ExUseNameField = false の場合は ExFilterBody のマッチ対象が Text と ScreenName の両方になる + filter.ExUseNameField = false; + + filter.ExUseRegex = useRegex; + filter.ExFilterBody = pattern; + } + + post = new PostClass { ScreenName = "hoge", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterBody の片方だけではマッチしない + post = new PostClass { ScreenName = "hoge", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // FilterBody の片方だけではマッチしない + post = new PostClass { ScreenName = "aaa", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // TextFromApi に FilterBody の文字列が全て含まれている + post = new PostClass { ScreenName = "hoge", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // ScreenName に FilterBody の文字列が全て含まれている + post = new PostClass { ScreenName = "aaabbb", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // FilterBody と ScreenName に FilterBody の文字列がそれぞれ含まれている + post = new PostClass { ScreenName = "aaa", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { ScreenName = "hoge", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + post = new PostClass { ScreenName = "AaaBbb", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { ScreenName = "hoge", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { ScreenName = "AaaBbb", Text = "t.co/hoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [TestCase(false, "hogehoge", false)] + [TestCase(false, "hogehoge", true)] + [TestCase(true, "(hoge){2}", false)] + [TestCase(true, "(hoge){2}", true)] + public void SourceTest(bool useRegex, string pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + filter.UseRegex = useRegex; + filter.FilterSource = pattern; + } + else + { + filter.ExUseRegex = useRegex; + filter.ExFilterSource = pattern; + } + + post = new PostClass { Source = "hogehoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { Source = "foo" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + if (!useRegex) + { + // FilterSource は完全一致 (UseRegex = false の場合) + post = new PostClass { Source = "_hogehoge_" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + } + else + { + // FilterSource は部分一致 (UseRegex = true の場合) + post = new PostClass { Source = "_hogehoge_" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { Source = "HogeHoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { Source = "HogeHoge" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [TestCase(false, "hogehoge", false)] + [TestCase(false, "hogehoge", true)] + [TestCase(true, "(hoge){2}", false)] + [TestCase(true, "(hoge){2}", true)] + public void SourceHtmlTest(bool useRegex, string pattern, bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + { + // FilterByUrl = true の場合は Source ではなく SourceHtml がマッチ対象になる + filter.FilterByUrl = true; + + filter.UseRegex = useRegex; + filter.FilterSource = pattern; + } + else + { + // ExFilterByUrl = true の場合は Source ではなく SourceHtml がマッチ対象になる + filter.ExFilterByUrl = true; + + filter.ExUseRegex = useRegex; + filter.ExFilterSource = pattern; + } + + // FilterSource は UseRegex の値に関わらず部分一致 + post = new PostClass { SourceHtml = "****" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { SourceHtml = "****" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別する + if (!exclude) + filter.CaseSensitive = true; + else + filter.ExCaseSensitive = true; + + post = new PostClass { SourceHtml = "****" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + + // 大小文字を区別しない + if (!exclude) + filter.CaseSensitive = false; + else + filter.ExCaseSensitive = false; + + post = new PostClass { SourceHtml = "****" }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + } + + [Test] + public void IsRtTest([Values(true, false)] bool exclude) + { + var filter = new PostFilterRule(); + PostClass post; + + if (!exclude) + filter.FilterRt = true; + else + filter.ExFilterRt = true; + + post = new PostClass { RetweetedBy = "hogehoge", RetweetedId = 123L }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark)); + + post = new PostClass { }; + Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None)); + } + } +} diff --git a/OpenTween/FilterDialog.cs b/OpenTween/FilterDialog.cs index a10e3281..ebc07982 100644 --- a/OpenTween/FilterDialog.cs +++ b/OpenTween/FilterDialog.cs @@ -345,7 +345,7 @@ namespace OpenTween { if (ListFilters.GetSelected(idx)) { - _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((FiltersClass)ListFilters.Items[idx]); + _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((PostFilterRule)ListFilters.Items[idx]); ListFilters.Items.RemoveAt(idx); } } @@ -394,19 +394,19 @@ namespace OpenTween if (ListFilters.SelectedIndex > -1) { - FiltersClass fc = (FiltersClass)ListFilters.SelectedItem; - if (fc.SearchBoth) + PostFilterRule fc = (PostFilterRule)ListFilters.SelectedItem; + if (fc.UseNameField) { RadioAND.Checked = true; RadioPLUS.Checked = false; UID.Enabled = true; MSG1.Enabled = true; MSG2.Enabled = false; - UID.Text = fc.NameFilter; + UID.Text = fc.FilterName; UID.SelectAll(); MSG1.Text = ""; MSG2.Text = ""; - foreach (string bf in fc.BodyFilter) + foreach (string bf in fc.FilterBody) { MSG1.Text += bf + " "; } @@ -423,32 +423,32 @@ namespace OpenTween UID.Text = ""; MSG1.Text = ""; MSG2.Text = ""; - foreach (string bf in fc.BodyFilter) + foreach (string bf in fc.FilterBody) { MSG2.Text += bf + " "; } MSG2.Text = MSG2.Text.Trim(); MSG2.SelectAll(); } - TextSource.Text = fc.Source; + TextSource.Text = fc.FilterSource; CheckRegex.Checked = fc.UseRegex; - CheckURL.Checked = fc.SearchUrl; + CheckURL.Checked = fc.FilterByUrl; CheckCaseSensitive.Checked = fc.CaseSensitive; - CheckRetweet.Checked = fc.IsRt; + CheckRetweet.Checked = fc.FilterRt; CheckLambda.Checked = fc.UseLambda; - if (fc.ExSearchBoth) + if (fc.ExUseNameField) { RadioExAnd.Checked = true; RadioExPLUS.Checked = false; ExUID.Enabled = true; ExMSG1.Enabled = true; ExMSG2.Enabled = false; - ExUID.Text = fc.ExNameFilter; + ExUID.Text = fc.ExFilterName; ExUID.SelectAll(); ExMSG1.Text = ""; ExMSG2.Text = ""; - foreach (string bf in fc.ExBodyFilter) + foreach (string bf in fc.ExFilterBody) { ExMSG1.Text += bf + " "; } @@ -465,21 +465,21 @@ namespace OpenTween ExUID.Text = ""; ExMSG1.Text = ""; ExMSG2.Text = ""; - foreach (string bf in fc.ExBodyFilter) + foreach (string bf in fc.ExFilterBody) { ExMSG2.Text += bf + " "; } ExMSG2.Text = ExMSG2.Text.Trim(); ExMSG2.SelectAll(); } - TextExSource.Text = fc.ExSource; + TextExSource.Text = fc.ExFilterSource; CheckExRegex.Checked = fc.ExUseRegex; - CheckExURL.Checked = fc.ExSearchUrl; + CheckExURL.Checked = fc.ExFilterByUrl; CheckExCaseSensitive.Checked = fc.ExCaseSensitive; - CheckExRetweet.Checked = fc.IsExRt; + CheckExRetweet.Checked = fc.ExFilterRt; CheckExLambDa.Checked = fc.ExUseLambda; - if (fc.MoveFrom) + if (fc.MoveMatches) { OptMove.Checked = true; } @@ -487,7 +487,7 @@ namespace OpenTween { OptCopy.Checked = true; } - CheckMark.Checked = fc.SetMark; + CheckMark.Checked = fc.MarkMatches; ButtonEdit.Enabled = true; ButtonDelete.Enabled = true; @@ -565,86 +565,84 @@ namespace OpenTween } int i = ListFilters.SelectedIndex; - FiltersClass ft; + PostFilterRule ft; - ft = new FiltersClass(); + ft = new PostFilterRule(); - ft.MoveFrom = OptMove.Checked; - ft.SetMark = CheckMark.Checked; + ft.MoveMatches = OptMove.Checked; + ft.MarkMatches = CheckMark.Checked; string bdy = ""; if (RadioAND.Checked) { - ft.NameFilter = UID.Text; + ft.FilterName = UID.Text; TweenMain owner = (TweenMain)this.Owner; int cnt = owner.AtIdSupl.ItemCount; - owner.AtIdSupl.AddItem("@" + ft.NameFilter); + owner.AtIdSupl.AddItem("@" + ft.FilterName); if (cnt != owner.AtIdSupl.ItemCount) { owner.ModifySettingAtId = true; } - ft.SearchBoth = true; + ft.UseNameField = true; bdy = MSG1.Text; } else { - ft.NameFilter = ""; - ft.SearchBoth = false; + ft.FilterName = ""; + ft.UseNameField = false; bdy = MSG2.Text; } - ft.Source = TextSource.Text.Trim(); + ft.FilterSource = TextSource.Text.Trim(); if (CheckRegex.Checked || CheckLambda.Checked) { - ft.BodyFilter.Add(bdy); + ft.FilterBody = new[] { bdy }; } else { - string[] bf = bdy.Trim().Split((char)32); - foreach (string bfs in bf) - { - if (!string.IsNullOrEmpty(bfs)) ft.BodyFilter.Add(bfs.Trim()); - } + ft.FilterBody = bdy.Split((char)32) + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .ToArray(); } ft.UseRegex = CheckRegex.Checked; - ft.SearchUrl = CheckURL.Checked; + ft.FilterByUrl = CheckURL.Checked; ft.CaseSensitive = CheckCaseSensitive.Checked; - ft.IsRt = CheckRetweet.Checked; + ft.FilterRt = CheckRetweet.Checked; ft.UseLambda = CheckLambda.Checked; bdy = ""; if (RadioExAnd.Checked) { - ft.ExNameFilter = ExUID.Text; - ft.ExSearchBoth = true; + ft.ExFilterName = ExUID.Text; + ft.ExUseNameField = true; bdy = ExMSG1.Text; } else { - ft.ExNameFilter = ""; - ft.ExSearchBoth = false; + ft.ExFilterName = ""; + ft.ExUseNameField = false; bdy = ExMSG2.Text; } - ft.ExSource = TextExSource.Text.Trim(); + ft.ExFilterSource = TextExSource.Text.Trim(); if (CheckExRegex.Checked || CheckExLambDa.Checked) { - ft.ExBodyFilter.Add(bdy); + ft.ExFilterBody = new[] { bdy }; } else { - string[] bf = bdy.Trim().Split((char)32); - foreach (string bfs in bf) - { - if (!string.IsNullOrEmpty(bfs)) ft.ExBodyFilter.Add(bfs.Trim()); - } + ft.ExFilterBody = bdy.Split((char)32) + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .ToArray(); } ft.ExUseRegex = CheckExRegex.Checked; - ft.ExSearchUrl = CheckExURL.Checked; + ft.ExFilterByUrl = CheckExURL.Checked; ft.ExCaseSensitive = CheckExCaseSensitive.Checked; - ft.IsExRt = CheckExRetweet.Checked; + ft.ExFilterRt = CheckExRetweet.Checked; ft.ExUseLambda = CheckExLambDa.Checked; if (_mode == EDITMODE.AddNew) @@ -654,7 +652,7 @@ namespace OpenTween } else { - _sts.Tabs[ListTabs.SelectedItem.ToString()].EditFilter((FiltersClass)ListFilters.SelectedItem, ft); + _sts.Tabs[ListTabs.SelectedItem.ToString()].EditFilter((PostFilterRule)ListFilters.SelectedItem, ft); } SetFilters(ListTabs.SelectedItem.ToString()); @@ -1089,8 +1087,8 @@ namespace OpenTween if (ListTabs.SelectedIndex > -1 && ListFilters.SelectedItem != null && ListFilters.SelectedIndex > 0) { string tabname = ListTabs.SelectedItem.ToString(); - FiltersClass selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex]; - FiltersClass target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex - 1]; + PostFilterRule selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex]; + PostFilterRule target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex - 1]; int idx = ListFilters.SelectedIndex; ListFilters.Items.RemoveAt(idx - 1); ListFilters.Items.Insert(idx, target); @@ -1104,8 +1102,8 @@ namespace OpenTween if (ListTabs.SelectedIndex > -1 && ListFilters.SelectedItem != null && ListFilters.SelectedIndex < ListFilters.Items.Count - 1) { string tabname = ListTabs.SelectedItem.ToString(); - FiltersClass selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex]; - FiltersClass target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex + 1]; + PostFilterRule selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex]; + PostFilterRule target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex + 1]; int idx = ListFilters.SelectedIndex; ListFilters.Items.RemoveAt(idx + 1); ListFilters.Items.Insert(idx, target); @@ -1130,20 +1128,20 @@ namespace OpenTween } string tabname = ListTabs.SelectedItem.ToString(); - List filters = new List(); + List filters = new List(); foreach (int idx in ListFilters.SelectedIndices) { - filters.Add(_sts.Tabs[tabname].Filters[idx].CopyTo(new FiltersClass())); + filters.Add(_sts.Tabs[tabname].Filters[idx].Clone()); } foreach (var tb in selectedTabs) { if (tb.TabName == tabname) continue; - foreach (FiltersClass flt in filters) + foreach (PostFilterRule flt in filters) { if (!tb.Filters.Contains(flt)) - tb.AddFilter(flt.CopyTo(new FiltersClass())); + tb.AddFilter(flt.Clone()); } } SetFilters(tabname); @@ -1165,28 +1163,28 @@ namespace OpenTween selectedTabs = dialog.SelectedTabs; } string tabname = ListTabs.SelectedItem.ToString(); - List filters = new List(); + List filters = new List(); foreach (int idx in ListFilters.SelectedIndices) { - filters.Add(_sts.Tabs[tabname].Filters[idx].CopyTo(new FiltersClass())); + filters.Add(_sts.Tabs[tabname].Filters[idx].Clone()); } if (selectedTabs.Length == 1 && selectedTabs[0].TabName == tabname) return; foreach (var tb in selectedTabs) { if (tb.TabName == tabname) continue; - foreach (FiltersClass flt in filters) + foreach (PostFilterRule flt in filters) { if (!tb.Filters.Contains(flt)) - tb.AddFilter(flt.CopyTo(new FiltersClass())); + tb.AddFilter(flt.Clone()); } } for (int idx = ListFilters.Items.Count - 1; idx >= 0; idx--) { if (ListFilters.GetSelected(idx)) { - _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((FiltersClass)ListFilters.Items[idx]); + _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((PostFilterRule)ListFilters.Items[idx]); ListFilters.Items.RemoveAt(idx); } } diff --git a/OpenTween/OpenTween.csproj b/OpenTween/OpenTween.csproj index ac83c5f1..d6758897 100644 --- a/OpenTween/OpenTween.csproj +++ b/OpenTween/OpenTween.csproj @@ -102,6 +102,7 @@ FilterDialog.cs + Form diff --git a/OpenTween/PostFilterRule.cs b/OpenTween/PostFilterRule.cs new file mode 100644 index 00000000..68e5a6a4 --- /dev/null +++ b/OpenTween/PostFilterRule.cs @@ -0,0 +1,738 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2013 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml.Serialization; + +namespace OpenTween +{ + /// + /// タブで使用する振り分けルールを表すクラス + /// + [XmlType("FiltersClass")] + public class PostFilterRule + { + /// + /// Compile() メソッドの呼び出しが必要な状態か否か + /// + [XmlIgnore] + public bool IsDirty { get; protected set; } + + /// + /// コンパイルされた振り分けルール + /// + [XmlIgnore] + protected Func FilterDelegate; + + /// + /// 振り分けルールの概要 + /// + [XmlIgnore] + public string SummaryText + { + get { return this.MakeSummary(); } + } + + /// + /// ExecFilter() メソッドの実行時に自動でコンパイルを実行する + /// + /// + /// テスト用途以外では AutoCompile に頼らず事前に Compile() メソッドを呼び出すこと + /// + internal static bool AutoCompile { get; set; } + + [XmlElement("NameFilter")] + public string FilterName + { + get { return this._FilterName; } + set + { + this.IsDirty = true; + this._FilterName = value; + } + } + private string _FilterName; + + [XmlElement("ExNameFilter")] + public string ExFilterName + { + get { return this._ExFilterName; } + set + { + this.IsDirty = true; + this._ExFilterName = value; + } + } + private string _ExFilterName; + + [XmlArray("BodyFilterArray")] + public string[] FilterBody + { + get { return this._FilterBody; } + set + { + if (value == null) + throw new ArgumentNullException(); + + this.IsDirty = true; + this._FilterBody = value; + } + } + private string[] _FilterBody = new string[0]; + + [XmlArray("ExBodyFilterArray")] + public string[] ExFilterBody + { + get { return this._ExFilterBody; } + set + { + if (value == null) + throw new ArgumentNullException(); + + this.IsDirty = true; + this._ExFilterBody = value; + } + } + private string[] _ExFilterBody = new string[0]; + + [XmlElement("SearchBoth")] + public bool UseNameField + { + get { return this._UseNameField; } + set + { + this.IsDirty = true; + this._UseNameField = value; + } + } + private bool _UseNameField; + + [XmlElement("ExSearchBoth")] + public bool ExUseNameField + { + get { return this._ExUseNameField; } + set + { + this.IsDirty = true; + this._ExUseNameField = value; + } + } + private bool _ExUseNameField; + + [XmlElement("MoveForm")] + public bool MoveMatches + { + get { return this._MoveMatches; } + set + { + this.IsDirty = true; + this._MoveMatches = value; + } + } + private bool _MoveMatches; + + [XmlElement("SetMark")] + public bool MarkMatches + { + get { return this._MarkMatches; } + set + { + this.IsDirty = true; + this._MarkMatches = value; + } + } + private bool _MarkMatches; + + [XmlElement("SearchUrl")] + public bool FilterByUrl + { + get { return this._FilterByUrl; } + set + { + this.IsDirty = true; + this._FilterByUrl = value; + } + } + private bool _FilterByUrl; + + [XmlElement("ExSearchUrl")] + public bool ExFilterByUrl + { + get { return this._ExFilterByUrl; } + set + { + this.IsDirty = true; + this._ExFilterByUrl = value; + } + } + private bool _ExFilterByUrl; + + public bool CaseSensitive + { + get { return this._CaseSensitive; } + set + { + this.IsDirty = true; + this._CaseSensitive = value; + } + } + private bool _CaseSensitive; + + public bool ExCaseSensitive + { + get { return this._ExCaseSensitive; } + set + { + this.IsDirty = true; + this._ExCaseSensitive = value; + } + } + private bool _ExCaseSensitive; + + public bool UseLambda + { + get { return this._UseLambda; } + set + { + this.IsDirty = true; + this._UseLambda = value; + } + } + private bool _UseLambda; + + public bool ExUseLambda + { + get { return this._ExUseLambda; } + set + { + this.IsDirty = true; + this._ExUseLambda = value; + } + } + private bool _ExUseLambda; + + public bool UseRegex + { + get { return this._UseRegex; } + set + { + this.IsDirty = true; + this._UseRegex = value; + } + } + private bool _UseRegex; + + public bool ExUseRegex + { + get { return this._ExUseRegex; } + set + { + this.IsDirty = true; + this._ExUseRegex = value; + } + } + private bool _ExUseRegex; + + [XmlElement("IsRt")] + public bool FilterRt + { + get { return this._FilterRt; } + set + { + this.IsDirty = true; + this._FilterRt = value; + } + } + private bool _FilterRt; + + [XmlElement("IsExRt")] + public bool ExFilterRt + { + get { return this._ExFilterRt; } + set + { + this.IsDirty = true; + this._ExFilterRt = value; + } + } + private bool _ExFilterRt; + + [XmlElement("Source")] + public string FilterSource + { + get { return this._FilterSource; } + set + { + this.IsDirty = true; + this._FilterSource = value; + } + } + private string _FilterSource; + + [XmlElement("ExSource")] + public string ExFilterSource + { + get { return this._ExFilterSource; } + set + { + this.IsDirty = true; + this._ExFilterSource = value; + } + } + private string _ExFilterSource; + + public PostFilterRule() + { + this.IsDirty = true; + + this.MarkMatches = true; + this.UseNameField = true; + this.ExUseNameField = true; + } + + static PostFilterRule() + { + // TODO: TabsClass とかの改修が終わるまでデフォルト有効 + PostFilterRule.AutoCompile = true; + } + + /// + /// 振り分けルールをコンパイルします + /// + public void Compile() + { + var postParam = Expression.Parameter(typeof(PostClass), "x"); + + var matchExpr = this.MakeFiltersExpr( + postParam, + this.FilterName, this.FilterBody, this.FilterSource, this.FilterRt, + this.UseRegex, this.CaseSensitive, this.UseNameField, this.UseLambda, this.FilterByUrl); + + var excludeExpr = this.MakeFiltersExpr( + postParam, + this.ExFilterName, this.ExFilterBody, this.ExFilterSource, this.ExFilterRt, + this.ExUseRegex, this.ExCaseSensitive, this.ExUseNameField, this.ExUseLambda, this.ExFilterByUrl); + + Expression> filterExpr; + + if (matchExpr != null) + { + MyCommon.HITRESULT hitResult; + + if (this.MoveMatches) + hitResult = MyCommon.HITRESULT.Move; + else if (this.MarkMatches) + hitResult = MyCommon.HITRESULT.CopyAndMark; + else + hitResult = MyCommon.HITRESULT.Copy; + + if (excludeExpr != null) + { + // x => matchExpr ? (!excludeExpr ? hitResult : None) : None + filterExpr = + Expression.Lambda>( + Expression.Condition( + matchExpr, + Expression.Condition( + Expression.Not(excludeExpr), + Expression.Constant(hitResult), + Expression.Constant(MyCommon.HITRESULT.None)), + Expression.Constant(MyCommon.HITRESULT.None)), + postParam); + } + else + { + // x => matchExpr ? hitResult : None + filterExpr = + Expression.Lambda>( + Expression.Condition( + matchExpr, + Expression.Constant(hitResult), + Expression.Constant(MyCommon.HITRESULT.None)), + postParam); + } + } + else if (excludeExpr != null) + { + // x => excludeExpr ? Exclude : None + filterExpr = + Expression.Lambda>( + Expression.Condition( + excludeExpr, + Expression.Constant(MyCommon.HITRESULT.Exclude), + Expression.Constant(MyCommon.HITRESULT.None)), + postParam); + } + else // matchExpr == null && excludeExpr == null + { + filterExpr = x => MyCommon.HITRESULT.None; + } + + this.FilterDelegate = filterExpr.Compile(); + this.IsDirty = false; + } + + protected virtual Expression MakeFiltersExpr( + ParameterExpression postParam, + string filterName, string[] filterBody, string filterSource, bool filterRt, + bool useRegex, bool caseSensitive, bool useNameField, bool useLambda, bool filterByUrl) + { + var filterExprs = new List(); + + if (useNameField && !string.IsNullOrEmpty(filterName)) + { + filterExprs.Add(Expression.OrElse( + this.MakeGenericFilter(postParam, "ScreenName", filterName, useRegex, caseSensitive, exactMatch: true), + this.MakeGenericFilter(postParam, "RetweetedBy", filterName, useRegex, caseSensitive, exactMatch: true))); + } + foreach (var body in filterBody) + { + if (string.IsNullOrEmpty(body)) + continue; + + Expression bodyExpr; + if (useLambda) + { + // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する + Expression> lambdaExpr = x => false; + bodyExpr = lambdaExpr.Body; + } + else + { + if (filterByUrl) + bodyExpr = this.MakeGenericFilter(postParam, "Text", body, useRegex, caseSensitive); + else + bodyExpr = this.MakeGenericFilter(postParam, "TextFromApi", body, useRegex, caseSensitive); + + if (!useNameField) + { + // useNameField = false の場合は ScreenName も filterBody のマッチ対象となる + bodyExpr = Expression.OrElse( + bodyExpr, + this.MakeGenericFilter(postParam, "ScreenName", body, useRegex, caseSensitive)); + } + } + + filterExprs.Add(bodyExpr); + } + if (!string.IsNullOrEmpty(filterSource)) + { + if (filterByUrl) + filterExprs.Add(this.MakeGenericFilter(postParam, "SourceHtml", filterSource, useRegex, caseSensitive)); + else + filterExprs.Add(this.MakeGenericFilter(postParam, "Source", filterSource, useRegex, caseSensitive, exactMatch: true)); + } + if (filterRt) + { + // x.RetweetedId != null + filterExprs.Add(Expression.NotEqual( + Expression.Property( + postParam, + typeof(PostClass).GetProperty("RetweetedId")), + Expression.Constant(null))); + } + + if (filterExprs.Count == 0) + { + return null; + } + else + { + // filterExpr[0] && filterExpr[1] && ... + var filterExpr = filterExprs[0]; + foreach (var expr in filterExprs.Skip(1)) + { + filterExpr = Expression.AndAlso(filterExpr, expr); + } + + return filterExpr; + } + } + + protected Expression MakeGenericFilter( + ParameterExpression postParam, string targetFieldName, string pattern, + bool useRegex, bool caseSensitive, bool exactMatch = false) + { + if (useRegex) + { + var regex = new Regex(pattern, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase); + + // regex.IsMatch(x.) + return Expression.Call( + Expression.Constant(regex), + typeof(Regex).GetMethod("IsMatch", new[] { typeof(string) }), + Expression.Property( + postParam, + typeof(PostClass).GetProperty(targetFieldName))); + } + else + { + var compOpt = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + if (exactMatch) + { + // 完全一致 + // pattern.Equals(x., compOpt) + return Expression.Call( + Expression.Constant(pattern), + typeof(string).GetMethod("Equals", new[] { typeof(string), typeof(StringComparison) }), + Expression.Property( + postParam, + typeof(PostClass).GetProperty(targetFieldName)), + Expression.Constant(compOpt)); + + } + else + { + // 部分一致 + // x..IndexOf(pattern, compOpt) != -1 + return Expression.NotEqual( + Expression.Call( + Expression.Property( + postParam, + typeof(PostClass).GetProperty(targetFieldName)), + typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) }), + Expression.Constant(pattern), + Expression.Constant(compOpt)), + Expression.Constant(-1)); + } + } + } + + /// + /// ツイートの振り分けを実行する + /// + /// 振り分けるツイート + /// 振り分け結果 + public MyCommon.HITRESULT ExecFilter(PostClass post) + { + if (this.IsDirty) + { + if (!PostFilterRule.AutoCompile) + throw new InvalidOperationException("振り分け実行前に Compile() を呼び出す必要があります"); + + this.Compile(); + } + + return this.FilterDelegate(post); + } + + public PostFilterRule Clone() + { + return new PostFilterRule + { + FilterBody = this.FilterBody, + FilterName = this.FilterName, + FilterSource = this.FilterSource, + FilterRt = this.FilterRt, + FilterByUrl = this.FilterByUrl, + UseLambda = this.UseLambda, + UseNameField = this.UseNameField, + UseRegex = this.UseRegex, + CaseSensitive = this.CaseSensitive, + ExFilterBody = this.ExFilterBody, + ExFilterName = this.ExFilterName, + ExFilterSource = this.ExFilterSource, + ExFilterRt = this.ExFilterRt, + ExFilterByUrl = this.ExFilterByUrl, + ExUseLambda = this.ExUseLambda, + ExUseNameField = this.ExUseNameField, + ExUseRegex = this.ExUseRegex, + ExCaseSensitive = this.ExCaseSensitive, + MoveMatches = this.MoveMatches, + MarkMatches = this.MarkMatches, + }; + } + + /// + /// 振り分けルールの文字列表現を返します + /// + /// 振り分けルールの概要 + public override string ToString() + { + return this.SummaryText; + } + + #region from Tween v1.1.0.0 + + // The code in this region block is based on code written by the following authors: + // (C) 2007 kiri_feather (@kiri_feather) + // (C) 2008 Moz (@syo68k) + + /// + /// フィルタ一覧に表示する文言生成 + /// + protected virtual string MakeSummary() + { + var fs = new StringBuilder(); + if (!string.IsNullOrEmpty(this.FilterName) || this.FilterBody.Length > 0 || this.FilterRt || !string.IsNullOrEmpty(this.FilterSource)) + { + if (this.UseNameField) + { + if (!string.IsNullOrEmpty(this.FilterName)) + { + fs.AppendFormat(Properties.Resources.SetFiltersText1, this.FilterName); + } + else + { + fs.Append(Properties.Resources.SetFiltersText2); + } + } + if (this.FilterBody.Length > 0) + { + fs.Append(Properties.Resources.SetFiltersText3); + foreach (var bf in this.FilterBody) + { + fs.Append(bf); + fs.Append(" "); + } + fs.Length--; + fs.Append(Properties.Resources.SetFiltersText4); + } + fs.Append("("); + if (this.UseNameField) + { + fs.Append(Properties.Resources.SetFiltersText5); + } + else + { + fs.Append(Properties.Resources.SetFiltersText6); + } + if (this.UseRegex) + { + fs.Append(Properties.Resources.SetFiltersText7); + } + if (this.FilterByUrl) + { + fs.Append(Properties.Resources.SetFiltersText8); + } + if (this.CaseSensitive) + { + fs.Append(Properties.Resources.SetFiltersText13); + } + if (this.FilterRt) + { + fs.Append("RT/"); + } + if (this.UseLambda) + { + fs.Append("LambdaExp/"); + } + if (!string.IsNullOrEmpty(this.FilterSource)) + { + fs.AppendFormat("Src…{0}/", this.FilterSource); + } + fs.Length--; + fs.Append(")"); + } + if (!string.IsNullOrEmpty(this.ExFilterName) || this.ExFilterBody.Length > 0 || this.ExFilterRt || !string.IsNullOrEmpty(this.ExFilterSource)) + { + //除外 + fs.Append(Properties.Resources.SetFiltersText12); + if (this.ExUseNameField) + { + if (!string.IsNullOrEmpty(this.ExFilterName)) + { + fs.AppendFormat(Properties.Resources.SetFiltersText1, this.ExFilterName); + } + else + { + fs.Append(Properties.Resources.SetFiltersText2); + } + } + if (this.ExFilterBody.Length > 0) + { + fs.Append(Properties.Resources.SetFiltersText3); + foreach (var bf in this.ExFilterBody) + { + fs.Append(bf); + fs.Append(" "); + } + fs.Length--; + fs.Append(Properties.Resources.SetFiltersText4); + } + fs.Append("("); + if (this.ExUseNameField) + { + fs.Append(Properties.Resources.SetFiltersText5); + } + else + { + fs.Append(Properties.Resources.SetFiltersText6); + } + if (this.ExUseRegex) + { + fs.Append(Properties.Resources.SetFiltersText7); + } + if (this.ExFilterByUrl) + { + fs.Append(Properties.Resources.SetFiltersText8); + } + if (this.ExCaseSensitive) + { + fs.Append(Properties.Resources.SetFiltersText13); + } + if (this.ExFilterRt) + { + fs.Append("RT/"); + } + if (this.ExUseLambda) + { + fs.Append("LambdaExp/"); + } + if (!string.IsNullOrEmpty(this.ExFilterSource)) + { + fs.AppendFormat("Src…{0}/", this.ExFilterSource); + } + fs.Length--; + fs.Append(")"); + } + + fs.Append("("); + if (this.MoveMatches) + { + fs.Append(Properties.Resources.SetFiltersText9); + } + else + { + fs.Append(Properties.Resources.SetFiltersText11); + } + if (!this.MoveMatches && this.MarkMatches) + { + fs.Append(Properties.Resources.SetFiltersText10); + } + else if (!this.MoveMatches) + { + fs.Length--; + } + + fs.Append(")"); + + return fs.ToString(); + } + #endregion + } +} diff --git a/OpenTween/Resources/ChangeLog.txt b/OpenTween/Resources/ChangeLog.txt index 95c74591..b0332db2 100644 --- a/OpenTween/Resources/ChangeLog.txt +++ b/OpenTween/Resources/ChangeLog.txt @@ -1,8 +1,16 @@ 更新履歴 ==== Ver 1.1.3-beta1(2013/xx/xx) + * タブ振り分けルールの挙動が若干変わりました + * 基本的には Tween の Wiki に記載されている挙動に忠実にしたものですが、一部利用者には影響があるかもしれません + * http://sourceforge.jp/projects/tween/wiki/%E3%82%BF%E3%83%96%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%83%AB%E3%83%BC%E3%83%AB + * CHG: Twitter API のエラー処理を改善 * CHG: 旧 API v1 関連の機能を削除 + * CHG: タブ振り分けルールの処理を改善 + - 除外ルールが単独で指定された場合 (マッチルールが空欄) でなくても除外ルールがタブ全体への例外指定として作用してしまう挙動を修正 + - (正規表現無効時) 除外ルールの「発言内容」を半角スペース区切りで複数指定したときに AND 条件ではなく OR 条件として判定してしまう挙動を修正 + - URL 検索が有効の時は Source を完全一致ではなく部分一致でマッチするように修正 * FIX: 「片思いユーザーリストを取得する」を無効にするとRT非表示設定が反映されない問題の修正 (thx @ui_nyan!) * FIX: ブロックされているユーザーのツイートをふぁぼった時にUserStreamsが切断されてしまう問題の修正 * FIX: 片思いユーザーリストの更新に失敗すると希に片思い表示がされなくなる現象を修正 diff --git a/OpenTween/StatusDictionary.cs b/OpenTween/StatusDictionary.cs index 9d4526f7..d872e711 100644 --- a/OpenTween/StatusDictionary.cs +++ b/OpenTween/StatusDictionary.cs @@ -2018,7 +2018,7 @@ namespace OpenTween public sealed class TabClass { private bool _unreadManage = false; - private List _filters; + private List _filters; private int _unreadCount = 0; private List _ids; private List _tmpIds = new List(); @@ -2175,7 +2175,7 @@ namespace OpenTween SoundFile = ""; OldestUnreadId = -1; TabName = ""; - _filters = new List(); + _filters = new List(); Protected = false; Notify = true; SoundFile = ""; @@ -2317,7 +2317,7 @@ namespace OpenTween { try { - switch (ft.IsHit(post)) //フィルタクラスでヒット判定 + switch (ft.ExecFilter(post)) //フィルタクラスでヒット判定 { case MyCommon.HITRESULT.None: break; @@ -2337,8 +2337,8 @@ namespace OpenTween } catch (NullReferenceException) { - //IsHitでNullRef出る場合あり。暫定対応 - MyCommon.TraceOut("IsHitでNullRef: " + ft.ToString()); + // ExecFilterでNullRef出る場合あり。暫定対応 + MyCommon.TraceOut("ExecFilterでNullRef: " + ft.ToString()); rslt = MyCommon.HITRESULT.None; } } @@ -2451,7 +2451,7 @@ namespace OpenTween } } - public FiltersClass[] GetFilters() + public PostFilterRule[] GetFilters() { lock (this._lockObj) { @@ -2459,7 +2459,7 @@ namespace OpenTween } } - public void RemoveFilter(FiltersClass filter) + public void RemoveFilter(PostFilterRule filter) { lock (this._lockObj) { @@ -2468,7 +2468,7 @@ namespace OpenTween } } - public bool AddFilter(FiltersClass filter) + public bool AddFilter(PostFilterRule filter) { lock (this._lockObj) { @@ -2479,33 +2479,33 @@ namespace OpenTween } } - public void EditFilter(FiltersClass original, FiltersClass modified) + public void EditFilter(PostFilterRule original, PostFilterRule modified) { - original.BodyFilter = modified.BodyFilter; - original.NameFilter = modified.NameFilter; - original.SearchBoth = modified.SearchBoth; - original.SearchUrl = modified.SearchUrl; + original.FilterBody = modified.FilterBody; + original.FilterName = modified.FilterName; + original.UseNameField = modified.UseNameField; + original.FilterByUrl = modified.FilterByUrl; original.UseRegex = modified.UseRegex; original.CaseSensitive = modified.CaseSensitive; - original.IsRt = modified.IsRt; + original.FilterRt = modified.FilterRt; original.UseLambda = modified.UseLambda; - original.Source = modified.Source; - original.ExBodyFilter = modified.ExBodyFilter; - original.ExNameFilter = modified.ExNameFilter; - original.ExSearchBoth = modified.ExSearchBoth; - original.ExSearchUrl = modified.ExSearchUrl; + original.FilterSource = modified.FilterSource; + original.ExFilterBody = modified.ExFilterBody; + original.ExFilterName = modified.ExFilterName; + original.ExUseNameField = modified.ExUseNameField; + original.ExFilterByUrl = modified.ExFilterByUrl; original.ExUseRegex = modified.ExUseRegex; original.ExCaseSensitive = modified.ExCaseSensitive; - original.IsExRt = modified.IsExRt; + original.ExFilterRt = modified.ExFilterRt; original.ExUseLambda = modified.ExUseLambda; - original.ExSource = modified.ExSource; - original.MoveFrom = modified.MoveFrom; - original.SetMark = modified.SetMark; + original.ExFilterSource = modified.ExFilterSource; + original.MoveMatches = modified.MoveMatches; + original.MarkMatches = modified.MarkMatches; this.FilterModified = true; } [XmlIgnore] - public List Filters + public List Filters { get { @@ -2523,7 +2523,7 @@ namespace OpenTween } } - public FiltersClass[] FilterArray + public PostFilterRule[] FilterArray { get { @@ -2623,866 +2623,6 @@ namespace OpenTween } } - [Serializable] - public sealed class FiltersClass : System.IEquatable - { - private string _name = ""; - private List _body = new List(); - private bool _searchBoth = true; - private bool _searchUrl = false; - private bool _caseSensitive = false; - private bool _useRegex = false; - private bool _isRt = false; - private string _source = ""; - private string _exname = ""; - private List _exbody = new List(); - private bool _exsearchBoth = true; - private bool _exsearchUrl = false; - private bool _exuseRegex = false; - private bool _excaseSensitive = false; - private bool _isExRt = false; - private string _exSource = ""; - private bool _moveFrom = false; - private bool _setMark = true; - private bool _useLambda = false; - private bool _exuseLambda = false; - - public FiltersClass() {} - - //フィルタ一覧に表示する文言生成 - private string MakeSummary() - { - var fs = new StringBuilder(); - if (!string.IsNullOrEmpty(_name) || _body.Count > 0 || _isRt || !string.IsNullOrEmpty(_source)) - { - if (_searchBoth) - { - if (!string.IsNullOrEmpty(_name)) - { - fs.AppendFormat(Properties.Resources.SetFiltersText1, _name); - } - else - { - fs.Append(Properties.Resources.SetFiltersText2); - } - } - if (_body.Count > 0) - { - fs.Append(Properties.Resources.SetFiltersText3); - foreach (var bf in _body) - { - fs.Append(bf); - fs.Append(" "); - } - fs.Length--; - fs.Append(Properties.Resources.SetFiltersText4); - } - fs.Append("("); - if (_searchBoth) - { - fs.Append(Properties.Resources.SetFiltersText5); - } - else - { - fs.Append(Properties.Resources.SetFiltersText6); - } - if (_useRegex) - { - fs.Append(Properties.Resources.SetFiltersText7); - } - if (_searchUrl) - { - fs.Append(Properties.Resources.SetFiltersText8); - } - if (_caseSensitive) - { - fs.Append(Properties.Resources.SetFiltersText13); - } - if (_isRt) - { - fs.Append("RT/"); - } - if (_useLambda) - { - fs.Append("LambdaExp/"); - } - if (!string.IsNullOrEmpty(_source)) - { - fs.AppendFormat("Src…{0}/", _source); - } - fs.Length--; - fs.Append(")"); - } - if (!string.IsNullOrEmpty(_exname) || _exbody.Count > 0 || _isExRt || !string.IsNullOrEmpty(_exSource)) - { - //除外 - fs.Append(Properties.Resources.SetFiltersText12); - if (_exsearchBoth) - { - if (!string.IsNullOrEmpty(_exname)) - { - fs.AppendFormat(Properties.Resources.SetFiltersText1, _exname); - } - else - { - fs.Append(Properties.Resources.SetFiltersText2); - } - } - if (_exbody.Count > 0) - { - fs.Append(Properties.Resources.SetFiltersText3); - foreach (var bf in _exbody) - { - fs.Append(bf); - fs.Append(" "); - } - fs.Length--; - fs.Append(Properties.Resources.SetFiltersText4); - } - fs.Append("("); - if (_exsearchBoth) - { - fs.Append(Properties.Resources.SetFiltersText5); - } - else - { - fs.Append(Properties.Resources.SetFiltersText6); - } - if (_exuseRegex) - { - fs.Append(Properties.Resources.SetFiltersText7); - } - if (_exsearchUrl) - { - fs.Append(Properties.Resources.SetFiltersText8); - } - if (_excaseSensitive) - { - fs.Append(Properties.Resources.SetFiltersText13); - } - if (_isExRt) - { - fs.Append("RT/"); - } - if (_exuseLambda) - { - fs.Append("LambdaExp/"); - } - if (!string.IsNullOrEmpty(_exSource)) - { - fs.AppendFormat("Src…{0}/", _exSource); - } - fs.Length--; - fs.Append(")"); - } - - fs.Append("("); - if (_moveFrom) - { - fs.Append(Properties.Resources.SetFiltersText9); - } - else - { - fs.Append(Properties.Resources.SetFiltersText11); - } - if (!_moveFrom && _setMark) - { - fs.Append(Properties.Resources.SetFiltersText10); - } - else if (!_moveFrom) - { - fs.Length--; - } - - fs.Append(")"); - - return fs.ToString(); - } - - public string NameFilter - { - get - { - return _name; - } - set - { - _name = value; - } - } - - public string ExNameFilter - { - get - { - return _exname; - } - set - { - _exname = value; - } - } - - [XmlIgnore] - public List BodyFilter - { - get - { - return _body; - } - set - { - _body = value; - } - } - - public string[] BodyFilterArray - { - get - { - return _body.ToArray(); - } - set - { - _body = new List(); - foreach (var filter in value) - { - _body.Add(filter); - } - } - } - - [XmlIgnore] - public List ExBodyFilter - { - get - { - return _exbody; - } - set - { - _exbody = value; - } - } - - public string[] ExBodyFilterArray - { - get - { - return _exbody.ToArray(); - } - set - { - _exbody = new List(); - foreach (var filter in value) - { - _exbody.Add(filter); - } - } - } - - public bool SearchBoth - { - get - { - return _searchBoth; - } - set - { - _searchBoth = value; - } - } - - public bool ExSearchBoth - { - get - { - return _exsearchBoth; - } - set - { - _exsearchBoth = value; - } - } - - public bool MoveFrom - { - get - { - return _moveFrom; - } - set - { - _moveFrom = value; - } - } - - public bool SetMark - { - get - { - return _setMark; - } - set - { - _setMark = value; - } - } - - public bool SearchUrl - { - get - { - return _searchUrl; - } - set - { - _searchUrl = value; - } - } - - public bool ExSearchUrl - { - get - { - return _exsearchUrl; - } - set - { - _exsearchUrl = value; - } - } - - public bool CaseSensitive - { - get - { - return _caseSensitive; - } - set - { - _caseSensitive = value; - } - } - - public bool ExCaseSensitive - { - get - { - return _excaseSensitive; - } - set - { - _excaseSensitive = value; - } - } - - public bool UseLambda - { - get - { - return _useLambda; - } - set - { - _useLambda = value; - } - } - - public bool ExUseLambda - { - get - { - return _exuseLambda; - } - set - { - _exuseLambda = value; - } - } - - public bool UseRegex - { - get - { - return _useRegex; - } - set - { - _useRegex = value; - } - } - - public bool ExUseRegex - { - get - { - return _exuseRegex; - } - set - { - _exuseRegex = value; - } - } - - public bool IsRt - { - get - { - return _isRt; - } - set - { - _isRt = value; - } - } - - public bool IsExRt - { - get - { - return _isExRt; - } - set - { - _isExRt = value; - } - } - - public string Source - { - get - { - return _source; - } - set - { - _source = value; - } - } - - public string ExSource - { - get - { - return _exSource; - } - set - { - _exSource = value; - } - } - - public override string ToString() - { - return MakeSummary(); - } - - public bool ExecuteLambdaExpression(string expr, PostClass post) - { - return false; - // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する - } - - public bool ExecuteExLambdaExpression(string expr, PostClass post) - { - return false; - // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する - } - - public MyCommon.HITRESULT IsHit(PostClass post) - { - var bHit = true; - string tBody; - string tSource; - if (_searchUrl) - { - tBody = post.Text; - tSource = post.SourceHtml; - } - else - { - tBody = post.TextFromApi; - tSource = post.Source; - } - //検索オプション - System.StringComparison compOpt; - System.Text.RegularExpressions.RegexOptions rgOpt; - if (_caseSensitive) - { - compOpt = StringComparison.Ordinal; - rgOpt = RegexOptions.None; - } - else - { - compOpt = StringComparison.OrdinalIgnoreCase; - rgOpt = RegexOptions.IgnoreCase; - } - if (_searchBoth) - { - if (string.IsNullOrEmpty(_name) || - (!_useRegex && - (post.ScreenName.Equals(_name, compOpt) || - post.RetweetedBy.Equals(_name, compOpt) - ) - ) || - (_useRegex && - (Regex.IsMatch(post.ScreenName, _name, rgOpt) || - (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, _name, rgOpt)) - ) - )) - { - if (_useLambda) - { - if (!ExecuteLambdaExpression(_body[0], post)) bHit = false; - } - else - { - foreach (var fs in _body) - { - if (_useRegex) - { - if (!Regex.IsMatch(tBody, fs, rgOpt)) bHit = false; - } - else - { - if (_caseSensitive) - { - if (!tBody.Contains(fs)) bHit = false; - } - else - { - if (!tBody.ToLower().Contains(fs.ToLower())) bHit = false; - } - } - if (!bHit) break; - } - } - } - else - { - bHit = false; - } - } - else - { - if (_useLambda) - { - if (!ExecuteLambdaExpression(_body[0], post)) bHit = false; - } - else - { - foreach (var fs in _body) - { - if (_useRegex) - { - if (!(Regex.IsMatch(post.ScreenName, fs, rgOpt) || - (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, fs, rgOpt)) || - Regex.IsMatch(tBody, fs, rgOpt))) bHit = false; - } - else - { - if (_caseSensitive) - { - if (!(post.ScreenName.Contains(fs) || - post.RetweetedBy.Contains(fs) || - tBody.Contains(fs))) bHit = false; - } - else - { - if (!(post.ScreenName.ToLower().Contains(fs.ToLower()) || - post.RetweetedBy.ToLower().Contains(fs.ToLower()) || - tBody.ToLower().Contains(fs.ToLower()))) bHit = false; - } - } - if (!bHit) break; - } - } - } - if (_isRt) - { - if (post.RetweetedId == null) bHit = false; - } - if (!string.IsNullOrEmpty(_source)) - { - if (_useRegex) - { - if (!Regex.IsMatch(tSource, _source, rgOpt)) bHit = false; - } - else - { - if (!tSource.Equals(_source, compOpt)) bHit = false; - } - } - if (bHit) - { - //除外判定 - if (_exsearchUrl) - { - tBody = post.Text; - tSource = post.SourceHtml; - } - else - { - tBody = post.TextFromApi; - tSource = post.Source; - } - - var exFlag = false; - if (!string.IsNullOrEmpty(_exname) || _exbody.Count > 0) - { - if (_excaseSensitive) - { - compOpt = StringComparison.Ordinal; - rgOpt = RegexOptions.None; - } - else - { - compOpt = StringComparison.OrdinalIgnoreCase; - rgOpt = RegexOptions.IgnoreCase; - } - if (_exsearchBoth) - { - if (string.IsNullOrEmpty(_exname) || - (!_exuseRegex && - (post.ScreenName.Equals(_exname, compOpt) || - post.RetweetedBy.Equals(_exname, compOpt) - ) - ) || - (_exuseRegex && - (Regex.IsMatch(post.ScreenName, _exname, rgOpt) || - (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, _exname, rgOpt)) - ) - )) - { - if (_exbody.Count > 0) - { - if (_exuseLambda) - { - if (ExecuteExLambdaExpression(_exbody[0], post)) exFlag = true; - } - else - { - foreach (var fs in _exbody) - { - if (_exuseRegex) - { - if (Regex.IsMatch(tBody, fs, rgOpt)) exFlag = true; - } - else - { - if (_excaseSensitive) - { - if (tBody.Contains(fs)) exFlag = true; - } - else - { - if (tBody.ToLower().Contains(fs.ToLower())) exFlag = true; - } - } - if (exFlag) break; - } - } - } - else - { - exFlag = true; - } - } - } - else - { - if (_exuseLambda) - { - if (ExecuteExLambdaExpression(_exbody[0], post)) exFlag = true; - } - else - { - foreach (var fs in _exbody) - { - if (_exuseRegex) - { - if (Regex.IsMatch(post.ScreenName, fs, rgOpt) || - (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, fs, rgOpt)) || - Regex.IsMatch(tBody, fs, rgOpt)) exFlag = true; - } - else - { - if (_excaseSensitive) - { - if (post.ScreenName.Contains(fs) || - post.RetweetedBy.Contains(fs) || - tBody.Contains(fs)) exFlag = true; - } - else - { - if (post.ScreenName.ToLower().Contains(fs.ToLower()) || - post.RetweetedBy.ToLower().Contains(fs.ToLower()) || - tBody.ToLower().Contains(fs.ToLower())) exFlag = true; - } - } - if (exFlag) break; - } - } - } - } - if (_isExRt) - { - if (post.RetweetedId != null) exFlag = true; - } - if (!string.IsNullOrEmpty(_exSource)) - { - if (_exuseRegex) - { - if (Regex.IsMatch(tSource, _exSource, rgOpt)) exFlag = true; - } - else - { - if (tSource.Equals(_exSource, compOpt)) exFlag = true; - } - } - - if (string.IsNullOrEmpty(_name) && _body.Count == 0 && !_isRt && string.IsNullOrEmpty(_source)) - { - bHit = false; - } - if (bHit) - { - if (!exFlag) - { - if (_moveFrom) - { - return MyCommon.HITRESULT.Move; - } - else - { - if (_setMark) - { - return MyCommon.HITRESULT.CopyAndMark; - } - return MyCommon.HITRESULT.Copy; - } - } - else - { - return MyCommon.HITRESULT.Exclude; - } - } - else - { - if (exFlag) - { - return MyCommon.HITRESULT.Exclude; - } - else - { - return MyCommon.HITRESULT.None; - } - } - } - else - { - return MyCommon.HITRESULT.None; - } - } - - public bool Equals(FiltersClass other) - { - if (this.BodyFilter.Count != other.BodyFilter.Count) return false; - if (this.ExBodyFilter.Count != other.ExBodyFilter.Count) return false; - for (int i = 0; i < this.BodyFilter.Count; i++) - { - if (this.BodyFilter[i] != other.BodyFilter[i]) return false; - } - for (int i = 0; i < this.ExBodyFilter.Count; i++) - { - if (this.ExBodyFilter[i] != other.ExBodyFilter[i]) return false; - } - - return (this.MoveFrom == other.MoveFrom) & - (this.SetMark == other.SetMark) & - (this.NameFilter == other.NameFilter) & - (this.SearchBoth == other.SearchBoth) & - (this.SearchUrl == other.SearchUrl) & - (this.UseRegex == other.UseRegex) & - (this.ExNameFilter == other.ExNameFilter) & - (this.ExSearchBoth == other.ExSearchBoth) & - (this.ExSearchUrl == other.ExSearchUrl) & - (this.ExUseRegex == other.ExUseRegex) & - (this.IsRt == other.IsRt) & - (this.Source == other.Source) & - (this.IsExRt == other.IsExRt) & - (this.ExSource == other.ExSource) & - (this.UseLambda == other.UseLambda) & - (this.ExUseLambda == other.ExUseLambda); - } - - public FiltersClass CopyTo(FiltersClass destination) - { - if (this.BodyFilter.Count > 0) - { - foreach (var flt in this.BodyFilter) - { - destination.BodyFilter.Add(flt); - } - } - - if (this.ExBodyFilter.Count > 0) - { - foreach (var flt in this.ExBodyFilter) - { - destination.ExBodyFilter.Add(flt); - } - } - - destination.MoveFrom = this.MoveFrom; - destination.SetMark = this.SetMark; - destination.NameFilter = this.NameFilter; - destination.SearchBoth = this.SearchBoth; - destination.SearchUrl = this.SearchUrl; - destination.UseRegex = this.UseRegex; - destination.ExNameFilter = this.ExNameFilter; - destination.ExSearchBoth = this.ExSearchBoth; - destination.ExSearchUrl = this.ExSearchUrl; - destination.ExUseRegex = this.ExUseRegex; - destination.IsRt = this.IsRt; - destination.Source = this.Source; - destination.IsExRt = this.IsExRt; - destination.ExSource = this.ExSource; - destination.UseLambda = this.UseLambda; - destination.ExUseLambda = this.ExUseLambda; - return destination; - } - - public override bool Equals(object obj) - { - if (obj == null || this.GetType() != obj.GetType()) return false; - return this.Equals((FiltersClass)obj); - } - - public override int GetHashCode() - { - return this.MoveFrom.GetHashCode() ^ - this.SetMark.GetHashCode() ^ - this.BodyFilter.GetHashCode() ^ - this.NameFilter.GetHashCode() ^ - this.SearchBoth.GetHashCode() ^ - this.SearchUrl.GetHashCode() ^ - this.UseRegex.GetHashCode() ^ - this.ExBodyFilter.GetHashCode() ^ - this.ExNameFilter.GetHashCode() ^ - this.ExSearchBoth.GetHashCode() ^ - this.ExSearchUrl.GetHashCode() ^ - this.ExUseRegex.GetHashCode() ^ - this.IsRt.GetHashCode() ^ - this.Source.GetHashCode() ^ - this.IsExRt.GetHashCode() ^ - this.ExSource.GetHashCode() ^ - this.UseLambda.GetHashCode() ^ - this.ExUseLambda.GetHashCode(); - } - } - //ソート比較クラス:ID比較のみ public sealed class IdComparerClass : IComparer { diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 1980686e..e561b844 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -9058,21 +9058,21 @@ namespace OpenTween PostClass post = _statuses[_curTab.Text, idx]; if (!ids.Contains(post.ScreenName)) { - FiltersClass fc = new FiltersClass(); + PostFilterRule fc = new PostFilterRule(); ids.Add(post.ScreenName); if (post.RetweetedId == null) { - fc.NameFilter = post.ScreenName; + fc.FilterName = post.ScreenName; } else { - fc.NameFilter = post.RetweetedBy; + fc.FilterName = post.RetweetedBy; } - fc.SearchBoth = true; - fc.MoveFrom = mv; - fc.SetMark = mk; + fc.UseNameField = true; + fc.MoveMatches = mv; + fc.MarkMatches = mk; fc.UseRegex = false; - fc.SearchUrl = false; + fc.FilterByUrl = false; _statuses.Tabs[tabName].AddFilter(fc); } } @@ -11620,13 +11620,13 @@ namespace OpenTween bool mk = false; MoveOrCopy(ref mv, ref mk); - FiltersClass fc = new FiltersClass(); - fc.NameFilter = name; - fc.SearchBoth = true; - fc.MoveFrom = mv; - fc.SetMark = mk; + PostFilterRule fc = new PostFilterRule(); + fc.FilterName = name; + fc.UseNameField = true; + fc.MoveMatches = mv; + fc.MarkMatches = mk; fc.UseRegex = false; - fc.SearchUrl = false; + fc.FilterByUrl = false; _statuses.Tabs[tabName].AddFilter(fc); try -- 2.11.0