├── image ├── after1.png ├── after2.png └── before.png ├── README.md ├── README_en.md ├── LICENSE └── script.user.js /image/after1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hi2ma-bu4/X_impression_hide/HEAD/image/after1.png -------------------------------------------------------------------------------- /image/after2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hi2ma-bu4/X_impression_hide/HEAD/image/after2.png -------------------------------------------------------------------------------- /image/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hi2ma-bu4/X_impression_hide/HEAD/image/before.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Twitter(旧:𝕏)のインプレッション小遣い稼ぎ野郎どもをdisplay:none;するやつ 2 | 3 | [![GitHub License](https://img.shields.io/github/license/hi2ma-bu4/X_impression_hide)](https://github.com/hi2ma-bu4/X_impression_hide/blob/main/LICENSE) 4 | [![Greasy Fork Downloads](https://img.shields.io/greasyfork/dt/484303)](https://greasyfork.org/ja/scripts/484303/stats) 5 | 6 | [![GitHub Release](https://img.shields.io/github/v/release/hi2ma-bu4/X_impression_hide?label=github)](https://github.com/hi2ma-bu4/X_impression_hide/releases) 7 | [![Greasy Fork Version](https://img.shields.io/greasyfork/v/484303)](https://greasyfork.org/ja/scripts/484303) 8 | 9 | 10 | 略して「#インプレゾンビをnoneするやつ」 11 | 12 | [English README is here](README_en.md) 13 | 14 | 現在更新停止。 15 | 動かなくなった場合はIssuesに報告して下さると修正します。 16 | 17 | ### 前提条件 18 | > [!WARNING] 19 | > PC専用です 20 | >
21 | > v1.11.11からkiwiブラウザ対応! 22 | >
23 | > v2.1.3からOldTweetDeck部分対応! 24 | >
25 | >
26 | > [Tampermonkey](https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo)が必要です。 27 | 28 | README更新Ver: 2.1.3 29 | 30 | ### 参考画像 31 | |![before](image/before.png)|| 32 | |:---:|:---:| 33 | |![after1](image/after1.png)|![after2](image/after2.png)| 34 | 35 | 画像は開発段階のものです。 36 | 37 | ### 使い方 38 | Tampermonkeyに追加するだけで使用できます。 39 | 40 | 追加は[こちら](https://github.com/hi2ma-bu4/X_impression_hide/raw/main/script.user.js)から出来ます。 41 | 42 | また、Gresy Forkのページは[こちら](https://greasyfork.org/ja/scripts/484303-twitter-%E6%97%A7-%F0%9D%95%8F-%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AC%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E5%B0%8F%E9%81%A3%E3%81%84%E7%A8%BC%E3%81%8E%E9%87%8E%E9%83%8E%E3%81%A9%E3%82%82%E3%82%92display-none-%E3%81%99%E3%82%8B%E3%82%84%E3%81%A4)からどうぞ。 43 | 44 | 設定は 45 | > 右クリック > Tampermonkey > Twitter(旧:𝕏)の(略) > Settings 46 | 47 | か、 48 | > 右上拡張機能一覧 > Tampermonkey > Twitter(旧:𝕏)の(略) > Settings(s) 49 | 50 | で、開く事ができます。 51 | 52 | ### 設定の内容 53 | |名前|説明|初期値|型・範囲| 54 | |:---|:---|:---:|:---:| 55 | |非表示ログを表示|非表示にしたログを画面から消します。
画面が平和になりますが、投稿を非表示にされた理由・元投稿が確認出来なくなります。|true|boolean| 56 | |非表示ログに認証マーク表示|非表示にしたログの名前の後ろに認証マークを追加します。
企業バッジでも青バッジで表示されます。|true|boolean| 57 | |禁止する表現|非表示にするテキストを指定します[^1][^2]。
記述方法は正規表現[^3]で記述します。|長い為省略|string| 58 | |許可する表現|許可するテキストを指定します。
一致する投稿は非表示の対象になりません。
指定方法などは[禁止する表現]と同じです。|長い為省略|string| 59 | |禁止する名前|非表示にするユーザー名を指定します。
指定方法などは[禁止する表現]と同じです。|長い為省略|string| 60 | |除外ユーザー|指定されたユーザーidは検知の対象になりません。
指定方法はユーザーidを改行で区切って記述するだけです。
idは完全一致のみ有効です。|長い為省略|string| 61 | |許可する言語|許可する言語を指定します。
記述方法は正規表現[^3]で記述します。|長い為省略|string| 62 | |自身の引用禁止|自身を引用ツイートする投稿を非表示にします。|true|boolean| 63 | |絵文字投稿禁止|絵文字のみで構成された投稿を非表示にします。|true|boolean| 64 | |絵文字ユーザー名禁止|絵文字のみで構成されたユーザー名を非表示にします。|true|boolean| 65 | |認証アカウント禁止|認証済アカウントを無差別に非表示にします。|false|boolean| 66 | |認証RT禁止|認証済アカウント投稿に対する引用RTを非表示にします。|false|boolean| 67 | |認証アカウントのみ判定|認証済アカウントのみを検知の対象にします。
通常アカウントや認証マークの無いアカウントはブロックされなくなります。|false|boolean| 68 | |認証公式アカウントを保護|公式アカウント[^7]を検知の対象から除外します。|true|boolean| 69 | |クイックブロック表示|1クリックでブロックできるボタンを表示します。
検出された投稿にしか表示されません。|true|boolean| 70 | |クイック通報表示|1クリックで通報できるボタンを表示します。
検出された投稿にしか表示されません。
(初期値はスパム報告です)|true|boolean| 71 | |ハッシュタグの上限数|1つの投稿内でのハッシュタグの使用上限数を指定します。|6|int (1~)| 72 | |シンボルタグの上限数|1つの投稿内でのシンボルタグの使用上限数を指定します[^9]。|1|int(1~)| 73 | |ツリー返信上限数|1つの投稿ツリーでの返信上限数を指定します。
値は許可のラインです。(例: 1で2投稿以上は非表示)
0を指定するとこの設定は無効化されます。|1|int (0~)| 74 | |1人によるRT上限数|1つの投稿ツリーでの1ユーザーの引用RT返信上限数を指定します。
値は[ツリー返信上限数]と同じ指定方法です。|1|int(0~)| 75 | |同一RT上限数|1つの投稿ツリーでの複数人からの同じユーザーに対する引用RT返信上限数を指定します。
値は[ツリー返信上限数]と同じ指定方法です。|1|int(0~)| 76 | |文章類似度許可ライン|コピペ文章かを判別する為の基準値を指定します。|0.85|float (0~1)| 77 | |比較される最大テキストサイズ|コピペ投稿の文章比較の最大文字数を指定します[^4]。
値を大きくするほど誤検知率は減り、検知率も減ります。|80|int (0~)| 78 | |一時保存・比較される最小テキストサイズ|比較用文章の最小文字数を指定します[^5]。
値が大きくするほど誤検知率は減り、検知率も減ります。|8|int (0~)| 79 | |一時保存される投稿の最大数|比較用文章の保持数を指定します。
値が小さいほど処理は軽くなりますが、検知率が減ります。|100|int (1~)| 80 | |言語|表示言語を設定します。|ja|str (ja\|en)| 81 | |ページ適用css設定|ページへ適用するcssを指定します。|長い為省略|string| 82 | |ページ更新検知用処理待機時間(ms)|ページ更新を検知する際の検知の更新間隔を指定します。
値が大きいほど処理が軽くなりますが、非表示にする初速が落ちる可能性あります。|3000|int (100~)| 83 | |検知対象の記憶|検出された対象を記憶します。
ページを更新などしても過去に検知した対象を素早く非表示に出来ます[^8]。|false|boolean| 84 | |【非推奨】自動ブロック|検出された対象を自動でブロックします[^6]。|false|boolean| 85 | |設定のリセット|設定項目をリセットします||| 86 | |検知済idのリセット|検知済idをリセットします。||| 87 | |OldTweetDeck対応|負荷軽減の為に分離[^6]|false|boolean| 88 | |jQuery自動読み込み|OldTweetDeckではなぜかjQueryが使用されているのにjQueryが読み込まれていない為、
jQueryが読み込まれていない場合にjQueryを読み込む機能です。|true|boolean| 89 | |起動時設定自動表示|設定画面を自動で開く|false|boolean| 90 | 91 | [^1]: 半角カタカナ、カタカナはひらがなに自動変換され、
全角英数字は半角英数字に、改行文字は半角スペースに自動変換されます。 92 | [^2]: "!#"を行頭に記述するとコメントアウト扱いになります。 93 | [^3]: ここでの正規表現とは"/"の間部分の事を指します。 94 | [^4]: 投稿の文字数が最大値以下の場合、この値は使用されません。 95 | [^5]: [比較される最大テキストサイズ]より大きい場合、比較処理は実行されません。 96 | [^6]: この機能はbeta版です!!
誤検知でも戸惑いなくブロックされます。 97 | [^7]: 公式とは青いバッジ以外を指します。 98 | [^8]: この機能はbeta版です!!
誤検知されたアカウントが非表示のままになります。
[除外ユーザー]と併用して使用して下さい。 99 | [^9]: シンボルタグとは「$TWTR」のような#を$に置き換えた株を表す表現 100 | 101 | ### ログの種類 102 | |名前|検出値|項目| 103 | |:---|:---:|:---:| 104 | |フィルター検出|comment_フィルター「/<正規表現>/uim」|禁止する表現| 105 | |フィルター検出|name_フィルター「/<正規表現>/uim」|禁止する名前| 106 | |非許可言語|<言語コード>|許可する言語| 107 | |自身の引用||自身の引用禁止| 108 | |絵文字のみ|comment|絵文字投稿禁止| 109 | |絵文字のみ|name|絵文字ユーザー名禁止| 110 | |認証垢||認証アカウント禁止| 111 | |認証RT垢||認証RT禁止| 112 | |#多量使用|使用回数: <使用数>|ハッシュタグの上限数| 113 | |$多量使用|使用回数: <使用数>|シンボルタグの上限数| 114 | |連投||ツリー返信上限数| 115 | |RT連投||1人によるRT上限数| 116 | |RT共有連投||同一RT上限数| 117 | |文章の複製|類似度:<数値>%|文章類似度許可ライン| 118 | |再帰的検出||| 119 | |他で検出済||| 120 | 121 | ### 設定ミス、バージョンアップによる設定更新ずれが発生した場合 122 | まず、以下のページまで移動します。 123 | > 右上拡張機能一覧 > ダッシュボード > インストール済UserScript > Twitter(旧:𝕏)の(略) > ストレージ 124 | 125 | > [!TIP] 126 | > ストレージの欄の例 127 | > ```json 128 | > { 129 | > "X_impression_hide_json": "{\"visibleLog\":true, ..." 130 | > } 131 | > ``` 132 | 133 | ストレージの欄に含まれている以下の記述を削除してください 134 | > [!CAUTION] 135 | > 設定されているCSSデータが全てリセットされます! 136 | ``` 137 | ,\"customCss\":\" ---ユーザーによって異なる為省略--- \" 138 | ``` 139 | 140 | わからない・問題が解決しなかった場合は以下の記述に変更してください。 141 | > [!CAUTION] 142 | > 設定されているデータが全てリセットされます! 143 | 144 | ```json 145 | { 146 | } 147 | ``` 148 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | ## Hide the Twitter (formerly: 𝕏) impression-earning scammers with "display:none;" 2 | 3 | [![GitHub License](https://img.shields.io/github/license/hi2ma-bu4/X_impression_hide)](https://github.com/hi2ma-bu4/X_impression_hide/blob/main/LICENSE) 4 | [![Greasy Fork Downloads](https://img.shields.io/greasyfork/dt/484303)](https://greasyfork.org/ja/scripts/484303/stats) 5 | 6 | [![GitHub Release](https://img.shields.io/github/v/release/hi2ma-bu4/X_impression_hide?label=github)](https://github.com/hi2ma-bu4/X_impression_hide/releases) 7 | [![Greasy Fork Version](https://img.shields.io/greasyfork/v/484303)](https://greasyfork.org/ja/scripts/484303) 8 | 9 | The Twitter hashtag is "#インプレゾンビをnoneするやつ" 10 | 11 | [日本語版のREADMEはこちら](README.md) 12 | 13 | Currently not updating. 14 | If it stops working, please report it to Issues and we will fix it. 15 | 16 | The latest and most correct content is in the [Japanese version](README.md). 17 | 18 | ### Prerequisite 19 | > [!WARNING] 20 | > This is for PC use only. 21 | >
22 | > Kiwi browser compatible from v1.11.11! 23 | >
24 | > OldTweetDeck is now supported from v2.1.3! 25 | >
26 | >
27 | > [Tampermonkey](https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) is required. 28 | 29 | README Update Version: 1.13.9 30 | 31 | ### Reference image 32 | |![before](image/before.png)|| 33 | |:---:|:---:| 34 | |![after1](image/after1.png)|![after2](image/after2.png)| 35 | 36 | Images are from the development stage. 37 | 38 | ### Usage 39 | Simply add it to Tampermonkey to use. 40 | 41 | You can add it [here](https://github.com/hi2ma-bu4/X_impression_hide/raw/main/script.user.js). 42 | 43 | Also, you can find the Greasy Fork page [here](https://greasyfork.org/ja/scripts/484303-twitter-%E6%97%A7-%F0%9D%95%8F-%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AC%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E5%B0%8F%E9%81%A3%E3%81%84%E7%A8%BC%E3%81%8E%E9%87%8E%E9%83%8E%E3%81%A9%E3%82%82%E3%82%92display-none-%E3%81%99%E3%82%8B%E3%82%84%E3%81%A4). 44 | 45 | The setting is 46 | > right-click > Tampermonkey > Hide the Twitter... > Settings 47 | 48 | or 49 | > Extensions Menu in the Upper Right Corner > Tampermonkey > Hide the Twitter... > Settings(s) 50 | 51 | and you can open it. 52 | 53 | ### Setting details 54 | |name|explanation|initial value|type,extent| 55 | |:---|:---|:---:|:---:| 56 | |Show hidden logs|It will remove the hidden logs from the screen.
The screen will be peaceful, but the reasons for hiding the posts and the original posts will no longer be visible.|true|boolean| 57 | |Prohibited expressions|Specify the text to hide[^1][^2].
The description should be written using regular expressions[^3].|Omitted due to length|string| 58 | |Expressions allowed|Specify the text to allow.
Matching posts will not be hidden.
The specification method is the same as [Prohibited expressions].|Omitted due to length|string| 59 | |Prohibited name|Specify the username to hide.
The specification method is the same as [Prohibited expressions].|true|boolean| 60 | |Allowed languages|Specify the allowed languages.
The description should be written using regular expressions[^3].|Omitted due to length|string| 61 | |Prohibition of self-quotation|It hides posts that quote oneself.|true|boolean| 62 | |No emoji posting|Hide posts composed only of emojis.|true|boolean| 63 | |Prohibit emoji usernames|Hide usernames composed only of emojis.|true|boolean| 64 | |Prohibition of authenticated accounts|It indiscriminately hides authenticated accounts.|false|boolean| 65 | |Authenticate accounts only|It detects only authenticated accounts.
Regular accounts and accounts without verification badges will no longer be blocked.|false|boolean| 66 | |Protect your authenticated official account|Exclude official accounts[^7] from detection.|true|boolean| 67 | |Quick block button display|Displays a button that allows you to block with one click.
It will only appear on detected posts.|true|boolean| 68 | |Quick report button display|Displays a button that allows you to block with one click.
It will only appear on detected posts.
(Initial value is spam report)|true|boolean| 69 | |Maximum number of hashtags|It specifies the maximum number of hashtags allowed in a single post.|6|int (1~)| 70 | |Maximum number of tree replies|Specify the maximum number of replies in one post tree.
The value is the line of permission. (Example: 1 hides 2 or more posts)
Specifying 0 disables this setting.|1|int (0~)| 71 | |Text similarity threshold|It specifies the threshold value for determining whether a text is a copied and pasted text.|0.85|float (0~1)| 72 | |Maximum text size for comparison|It specifies the maximum number of characters for text comparison in copied and pasted posts.[^4].
Increasing the value reduces the false positive rate but also reduces the detection rate.|80|int (0~)| 73 | |The minimum text size that is temporarily saved and compared|This specifies the minimum number of characters for the comparison text[^5].
Increasing the value reduces the false detection rate as well as the detection rate.|8|int (0~)| 74 | |The maximum number of posts that are temporarily saved|This specifies the number of comparison texts to be retained.
A smaller value reduces the processing load but also decreases the detection rate.|100|int (1~)| 75 | |Language|Set the display language.|ja|string (ja\|en)| 76 | |Processing wait time (in milliseconds) for page update detection|This specifies the interval for detecting page updates.
A larger value reduces the processing load but may potentially delay the initial speed of hiding.|3000|int (100~)| 77 | |Page-specific CSS settings|Specify the CSS to apply to the page.|Omitted due to length|string| 78 | |[Not recommended] Automatic block|Automatically block detected targets[^6].|false|boolean| 79 | |Reset settings|Reset the settings.|No value because it is a button|| 80 | 81 | [^1]: Half-width katakana and katakana are automatically converted to hiragana,
full-width alphanumeric characters are automatically converted to half-width alphanumeric characters,
and line feed characters are automatically converted to half-width spaces. 82 | [^2]: If you write "!#" at the beginning of a line, it will be treated as a comment. 83 | [^3]: The regular expression here refers to the part between "/". 84 | [^4]: If your post's character count is less than or equal to the maximum value, this value will not be used. 85 | [^5]: If it is larger than [Maximum text size for comparison], the comparison process will not be performed. 86 | [^6]: This feature is in beta version! !
Even false positives are blocked without hesitation. 87 | [^7]: Official means anything other than the blue badge. 88 | 89 | 90 | ### If there is a configuration mistake or a configuration update misalignment due to a version upgrade. 91 | First, go to the page below. 92 | > Extensions Menu in the Upper Right Corner > Dashboard > Installed UserScript > Hide the Twitter... > Storage 93 | 94 | > [!TIP] 95 | > Example of storage column 96 | > ```json 97 | > { 98 | > "X_impression_hide_json": "{\"visibleLog\":true, ..." 99 | > } 100 | > ``` 101 | 102 | Please delete the following description in the storage field. 103 | > [!CAUTION] 104 | > All set CSS data will be reset! 105 | ``` 106 | ,\"customCss\":\" ---Omitted as it varies depending on the user--- \" 107 | ``` 108 | 109 | If you do not understand or the problem is not resolved, please change the description below. 110 | > [!CAUTION] 111 | > All set data will be reset! 112 | 113 | ```json 114 | { 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /script.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Twitter(旧:𝕏)のインプレッション小遣い稼ぎ野郎どもをdisplay:none;するやつ 3 | // @name:ja Twitter(旧:𝕏)のインプレッション小遣い稼ぎ野郎どもをdisplay:none;するやつ 4 | // @name:en Hide the Twitter (formerly: 𝕏) impression-earning scammers with "display:none;" 5 | // @name:zh-CN 使用 "display:none;" 隐藏 Twitter(曾用名: 𝕏)的印象收益骗子。 6 | // @name:zh-TW 使用 "display:none;" 隱藏 Twitter(曾用名: 𝕏)的印象詐騙者。 7 | // @namespace https://github.com/hi2ma-bu4 8 | // @version 2.1.5 9 | // @description Twitterのインプレゾンビを非表示にしたりブロック・通報するツールです。 10 | // @description:ja Twitterのインプレゾンビを非表示にしたりブロック・通報するツールです。 11 | // @description:en A tool to hide, block, and report spam on Twitter. 12 | // @description:zh-CN 用于隐藏、阻止和报告 Twitter 上的垃圾邮件的工具。 13 | // @description:zh-TW 用於隱藏、封鎖和報告 Twitter 上的垃圾郵件的工具。 14 | // @author tromtub(snows) 15 | // @license LGPL-2.1 16 | // @match *://twitter.com/* 17 | // @match *://x.com/* 18 | // @match *://tweetdeck.twitter.com/* 19 | // @icon data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB7ElEQVR4Ae1XMZLCMAwUdw0ldJQ8ATpKnkBJByUd8ALyA/gBdJTQUtHS8QT4AaRM5ctmThmfogQ75CYNmhGTbGJr45Vk0yAiQzXaF9VsHwIZAofDgYwxqo9GI/K16/X6cqyxvdVqmdvtZh6PhwmCIHXcw7vdrpFj8ny9XhsYxhe8lwWHw2EycLFYpNh0Ok2w8/nsFHy1WrkE1wnAN5tNMkGv10ux3W6XIab5fD5P3ovldCGrP2Ap4LiW8uRJAcIwe1wpArYU0FJimhQgxaQ9cqX4BZYCgSVmS8HBfRP1JQEsY1xKGSmAcTC+l0QrIWDraicVMBBA4O1265ScpQnAMbkMwphjub1HAI7EkxoDK7n0/gQQGATsCmDMo+z++Hf8E5CjPZ9PiqKIZrMZhWFIl8slxcbjMTWbTTqdTuRrXoz5i2WXRIL+WxWw2+Uml13rnJUT4K9E9nMFaF3SxiojoO1u2rJzl4z3/+oIcHBMLiUp2rDe3ozg+BIYtNee87KjGzLGndPx7JD/0K7xog2Gl30ymaSY1jm9CPhsrXnnBK1zOhHgCWWtF7l2TtA6p3S1E+73exoMBrRcLul4PJKL3e93arfbSUeMA1O/36eYPHU6nWQu7pyaqRlfZnezV05anhSN34va7PPXrHYCP+VaTG3LBV1KAAAAAElFTkSuQmCC 20 | // @updateURL https://github.com/hi2ma-bu4/X_impression_hide/raw/main/script.user.js 21 | // @downloadURL https://github.com/hi2ma-bu4/X_impression_hide/raw/main/script.user.js 22 | // @supportURL https://github.com/hi2ma-bu4/X_impression_hide 23 | // @supportURL https://greasyfork.org/ja/scripts/484303 24 | // @compatible chrome 25 | // @compatible edge 26 | // @compatible opera chromium製なので動くと仮定(It's made with chromium so I assume it works) 27 | // @compatible firefox 28 | // @compatible kiwi 29 | // @compatible safari 確実に動く事は保証しません(I can't guarantee that it will work) 30 | // @grant GM.addStyle 31 | // @grant GM.setValue 32 | // @grant GM.getValue 33 | // @grant GM.deleteValue 34 | // @grant GM.registerMenuCommand 35 | // @run-at document-idle 36 | // @noframes 37 | // ==/UserScript== 38 | 39 | /* 40 | Twitter(旧:𝕏)のインプレッション小遣い稼ぎ野郎どもをdisplay:none;するやつ 41 | 42 | 略して、 43 | 44 | インプレゾンビをnoneするやつ 45 | 46 | */ 47 | /* 48 | コピー・改変してもいいけど、 49 | 「tromtub(snows)」は変えないでね。 50 | 51 | */ 52 | /* todo 53 | ・URLフィルター作成 54 | ・プロフィールメッセージフィルター作成 55 | ・画像リンク取得などを高速に 56 | ・gifをブロック 57 | 58 | ・検知率を上げる 59 | ・あやしい日本語の検知(多分自分の実力じゃ無理) 60 | ・フィルターをもっと有能に 61 | ・誤検知を減らす(今はまだいい?) 62 | ・クイックミュートボタンを作成 63 | ・whitelist_filterの実装 64 | ・名前 65 | ・他人の引用ツイートテキストフィルターを作成 66 | ・menuのresize:both;を左下に 67 | ・menuをもっと見やすく(たすけて) 68 | ・正規表現などの最適化 69 | ・軽量化 70 | */ 71 | 72 | (function () { 73 | ("use strict"); 74 | 75 | const PRO_NAME = "X_impression_hide"; 76 | const VERSION = "v2.1.5"; 77 | 78 | // スマホ判定 79 | const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 80 | 81 | // ここから設定 82 | const DEBUG = true; 83 | 84 | // ========================================================================================== 85 | // 設定初期値(定数) 86 | // ========================================================================================== 87 | const BLACK_TEXT_REG = `!# 行頭が"!#"だとコメント 88 | 89 | !# プロフィールメッセージを異常に推してる人 90 | ((初|はじ)めまして|こんにち[はわ]|こんばん[はわ]|やっほ|[き気]になった|良かったら).*?ぷろふ 91 | ぷろふ.*?の(確認|チェック|check) 92 | (follow|フォロー).*?の(確認|チェック|check) 93 | (^(連絡|絡み)|[→⇒➡]).*(よろ|おねがいします|返事) 94 | 95 | !# chatGPTのエラーメッセージを取り敢えず対処 96 | ^申し訳ありません.*?(過激な表現や性的な内容|不適切なコンテンツや言葉).*?他の(質問や話題|トピックで質問)があれば.*?。$ 97 | 98 | !# 謎投資話 99 | 観察.*?毎日.*?銘柄.*?[万萬]円 100 | (偶然|指摘|ブロガー|トレーダー?|毎日|金融).*?(株|投資|銘柄|アドバイス).*?[万萬][円元] 101 | 毎日.*?相場.*?予測.*?株式 102 | 相場.*?85% 103 | 104 | !# chatGPT構文 105 | ですね!.+(です|ね)[!。]$ 106 | されましたね!.+(です|ね)[!。]$ 107 | でしょう.+かもしれません.+(です|ね)[!。]$ 108 | 109 | !# 翻訳ってこと?! 110 | ^ハハハ、.+ます。? 111 | ^ああ、.+です。? 112 | それは.+ますね。.+ですか\\? 113 | 114 | !# 文章名指し 115 | この情報を共有していただきありがとうございます 116 | これはどういう意味ですか 117 | 118 | !# 陰謀的単語 119 | 人口地震 120 | 121 | !# 炎上商法 122 | 炎上覚悟で 123 | !# 大切なことを?言います|断言します|何度も言いますが|勘違いしてい?る人が多いですが 124 | 125 | !# タイ語のハッシュタグを含む場合 126 | #[\\u0E00-\\u0F7F]+ 127 | 128 | !# アラビア語の単語を含む場合 129 | [\\u0600-\\u07FF]{4,} 130 | 131 | !# 中国語のなんかよく見るやつ 132 | ^想上课的私信主人 133 | ^太阳射不进来的地方 134 | ^挂空就是舒服,接点地气 135 | ^总说我下面水太多 136 | ^在这个炮火连天的夜晚 137 | ^只进入身体不进入生活 138 | ^生活太多伪装,只能在推上面卸下伪装 139 | ^生活枯燥无味,一个人的夜晚总想找个 140 | ^我每天都有好好的穿衣服.*俘获 141 | ^人不可能每一步都正确,我不想回头看,也不想批判当时的自己 142 | ^如果你连试着的胆量也没有,你也就配不上拥有性福 143 | ^我希望以后可以不用再送我回家,而是我们一起回我们的家 144 | ^勇敢一点我们在.*就有故事 145 | ^只要你主动一点点我们就会有机会.*线下 146 | `; 147 | // -------------------------------------------------- 148 | const BLACK_FULL_TEXT_REG = `!# 行頭が"!#"だとコメント 149 | 150 | !# 謎投資話 151 | @[a-z0-9_]{3,30}.*?(先生|観察|発見).*?(助け|共有) 152 | (金融|借金|最近興味).*?@[a-z0-9_]{3,30}.*?(金融|投資|アドバイス|株|[万萬][円元]) 153 | `; 154 | // -------------------------------------------------- 155 | const WHITE_TEXT_REG = `!# 同上 156 | 157 | !# 例としてMisskey構文に対応してみる 158 | ^:[a-z0-9\-_]:$ 159 | 160 | !# 緊急性の高い単語を除外 161 | !# ゾンビも使ってくるので除外ユーザー(Excluded users)を併用推奨 162 | !# (災害・防災アカウントidをフィルターに追記した為コメントアウト) 163 | !# 164 | !# 地震|余震|マグニチュード|火災|災害|津波|波浪|台風|震度 165 | !# jQuake 166 | 167 | `; 168 | // -------------------------------------------------- 169 | /* 170 | const BLACK_RT_TEXT_REG = `!# 同上 171 | 172 | !# 英語の動画宣伝RTの構文 173 | (vid|video).*free 174 | free.*(vid|video) 175 | `; 176 | */ 177 | // -------------------------------------------------- 178 | const BLACK_NAME_REG = `!# 同上 179 | 180 | !# アラビア語のみで構成 181 | ^([\\u0600-\\u07FF ]|\\p{P}|\\p{S})+$ 182 | 183 | !# ヒンディー語のみで構成 184 | ^([\\u0900-\\u097F ]|\\p{P}|\\p{S})+$ 185 | 186 | !# エロ垢抹消 187 | ぷろふ.*(確認|ちぇっく|check) 188 | おふぱこ 189 | 190 | !# 謎投資話 191 | NFT|投資 192 | 193 | !# 中国語のなんかよく見るやつ 194 | 反差 195 | 私信领福利 196 | 同城 197 | 可约 198 | `; 199 | // -------------------------------------------------- 200 | const EXCLUDED_USERS = `!# 同上 201 | 202 | !# 例として製作者のidを指定 203 | @hi2ma_bu4 204 | 205 | !# 災害(緊急)情報発信者を除外 206 | !# 表記抜けや、誤字はGithubのIssuesにご報告下さい。 207 | @UN_NERV 208 | @EN_NERV 209 | @EqAlarm 210 | @saigai_sokuho 211 | @MLIT_JAPAN 212 | @CAO_BOUSAI 213 | @JMA_bousai 214 | @JMA_kishou 215 | @JCG_koho 216 | @meti_NIPPON 217 | @ModJapan_saigai 218 | @Kanboukansen 219 | @NPA_saigaiKOHO 220 | @MPD_bousai 221 | @JapanSafeTravel 222 | @JSCE_Saigai 223 | @nhk_seikatsu 224 | @TBC_saigai 225 | @ats_saigai 226 | @tokyo_bousai 227 | @yokohama_saigai 228 | @yamaguchiSaigai 229 | @y_minami_saigai 230 | @w_city_saigai 231 | @sakai_saigai 232 | @Saigai_ishikawa 233 | @saigai01 234 | @HiroshimaBousai 235 | @etajima_bousai 236 | @chibaken_saigai 237 | @aichi_bousai 238 | @kawasaki_bousai 239 | @EhimeBousai 240 | @Gunma_bousai 241 | @nodasi_saigai 242 | @IshiSaigai 243 | @kfb_saigai 244 | @KagoshimaSaigai 245 | @kouchi_bousai 246 | @NTTWestOfficial 247 | @rikudennw 248 | @denjiren 249 | @denjiren_saigai 250 | @mlit_chokoku 251 | @JREast_official 252 | 253 | !# サイバーセキュリティ 254 | @cas_nisc 255 | @nisc_forecast 256 | 257 | !# TV 258 | @news24ntv 259 | 260 | !# 交通情報 261 | @shutoko_traffic 262 | @nexco_kanto 263 | @e_nexco_touhoku 264 | @JAL_flight_info 265 | @JRE_Super_Exp 266 | @odakyuline_info 267 | `; 268 | // TODO: プロフィールメッセージフィルター機能を作る 269 | // Bimbo 270 | // -------------------------------------------------- 271 | const ALLOW_LANG = "ja|en|es|zh|ko|pt|qme|qam|und"; 272 | // -------------------------------------------------- 273 | const SUB_DEFINITION_SUB = `!# 同上 274 | 275 | !# それっぽいのをまとめとく 276 | ((season|シーズン).{0,2}(\\d{1,2}|[IVX]{1,5})|サブ|ファースト|セカンド|サード|新・?|ファイナル|(\\d{1,4}|[一二三四五六七八九十百千万壱弐参肆伍陸漆捌玖拾陌阡萬廿丗卅世]+)代目|sub|first|1st|second|2nd|third|3rd|fourth|4th|new|final) 277 | `; 278 | // -------------------------------------------------- 279 | const PLAT_FORM_BLACK_REG = `!# 同上 280 | 281 | !# 例: 282 | !# Twitter for Android 283 | !# Twitter for iPhone 284 | !# Twitter Web App 285 | 286 | !# 広告などの投稿元 287 | Twitter for Advertisers 288 | `; 289 | 290 | // ========================================================================================== 291 | // 要素命名用 定数 292 | // ========================================================================================== 293 | const EX_MENU_ID = PRO_NAME + "_menu"; 294 | /** 295 | * 独自利用id,class名定義 296 | * @readonly 297 | * @enum {string} 298 | */ 299 | const ELEM_NAME_DICT = { 300 | PARENT_CLASS: PRO_NAME + "_parent", 301 | CHECK_CLASS: PRO_NAME + "_check", 302 | HIDE_CLASS: PRO_NAME + "_none", 303 | LOG_CLASS: PRO_NAME + "_log", 304 | HIDE_TITLE_CLASS: PRO_NAME + "_title", 305 | HIDE_TITLE_SHOW_CLASS: PRO_NAME + "_title_show", 306 | HIDE_TITLE_BUBBLE_CLASS: PRO_NAME + "_title_bubble", 307 | MORE_TWEET_CLASS: PRO_NAME + "_moreTweet", 308 | VERIFY_CLASS: PRO_NAME + "_verify", 309 | PC_FLAG_CLASS: PRO_NAME + "_pc", 310 | MOBILE_FLAG_CLASS: PRO_NAME + "_mobile", 311 | EX_MENU_ID: EX_MENU_ID, 312 | EX_MENU_OPEN_CLASS: EX_MENU_ID + "_open", 313 | EX_MENU_ITEM_BASE_ID: EX_MENU_ID + "_item_", 314 | EX_MENU_ITEM_ERROR_CLASS: EX_MENU_ID + "_err", 315 | // Userscripts対応(ゴリ押し) 316 | EX_MENU_OPEN_BUTTON: EX_MENU_ID + "_openBtn", 317 | // OldTweetDeck対応(ゴリ押し) 318 | USE_TWEET_DECK_CLASS: PRO_NAME + "_tweetDeck", 319 | }; 320 | 321 | // ========================================================================================== 322 | // css初期値(定数) 323 | // ========================================================================================== 324 | const BASE_CSS = /* css */ ` 325 | #${EX_MENU_ID} { 326 | display: none; 327 | position: fixed; 328 | color: #111; 329 | top: 0; 330 | right: 0; 331 | z-index: 2000; 332 | } 333 | /* 積み防止 */ 334 | #${EX_MENU_ID}.${ELEM_NAME_DICT.EX_MENU_OPEN_CLASS} { 335 | display: block !important; 336 | visibility: visible !important; 337 | } 338 | 339 | #${EX_MENU_ID} > div { 340 | position: relative; 341 | overflow-y: scroll; 342 | overscroll-behavior: contain; 343 | width: 50vh; 344 | min-width: 200px; 345 | max-width: 90vw; 346 | height: 50vh; 347 | min-height: 200px; 348 | max-height: 90vh; 349 | resize: both; 350 | border: solid #000 2px; 351 | background: #fafafaee; 352 | } 353 | 354 | #${ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID}__btns { 355 | position: sticky; 356 | right: 0; 357 | bottom: 0; 358 | text-align: right; 359 | } 360 | 361 | /* ツイート非表示 */ 362 | .${ELEM_NAME_DICT.HIDE_CLASS}:has(.${ELEM_NAME_DICT.LOG_CLASS} input[type=checkbox]:not(:checked)) > div:not(.${ELEM_NAME_DICT.LOG_CLASS}), 363 | .${ELEM_NAME_DICT.HIDE_CLASS}:not(:has(.${ELEM_NAME_DICT.LOG_CLASS})) > div:not(.${ELEM_NAME_DICT.LOG_CLASS}) { 364 | display: none; 365 | } 366 | 367 | body:not(.${ELEM_NAME_DICT.USE_TWEET_DECK_CLASS}) .${ELEM_NAME_DICT.HIDE_CLASS}:has(.${ELEM_NAME_DICT.LOG_CLASS}):not(:has(article)) { 368 | display: none; 369 | } 370 | 371 | /* 検出内容の表示設定 */ 372 | .${ELEM_NAME_DICT.PARENT_CLASS} .${ELEM_NAME_DICT.HIDE_CLASS} { 373 | background: #aaaa; 374 | } 375 | 376 | /* 以下非表示後の表示内容設定 */ 377 | .${ELEM_NAME_DICT.LOG_CLASS} { 378 | display: flex; 379 | justify-content: space-between; 380 | } 381 | 382 | .${ELEM_NAME_DICT.HIDE_TITLE_CLASS} { 383 | position: relative; 384 | display: inline-block; 385 | cursor: help; 386 | } 387 | 388 | .${ELEM_NAME_DICT.HIDE_TITLE_BUBBLE_CLASS} { 389 | position: absolute; 390 | background-color: rgba(0, 0, 0, 0.85); 391 | color: #fff; 392 | padding: 0.3em 0.5em; 393 | border-radius: 6px; 394 | font-size: 0.8em; 395 | white-space: nowrap; 396 | word-break: keep-all; 397 | overflow-wrap: break-word; 398 | z-index: 100; 399 | bottom: 125%; 400 | left: 50%; 401 | transform: translateX(-50%); 402 | opacity: 0; 403 | pointer-events: none; 404 | transition: opacity 0.2s; 405 | max-width: 90vw; 406 | } 407 | 408 | .${ELEM_NAME_DICT.HIDE_TITLE_CLASS}.${ELEM_NAME_DICT.HIDE_TITLE_SHOW_CLASS} .${ELEM_NAME_DICT.HIDE_TITLE_BUBBLE_CLASS} { 409 | opacity: 1; 410 | pointer-events: auto; 411 | } 412 | 413 | .${ELEM_NAME_DICT.LOG_CLASS} > span > a { 414 | color: rgb(29, 155, 240); 415 | margin-left: 0.25em; 416 | text-decoration: underline; 417 | } 418 | 419 | .${ELEM_NAME_DICT.LOG_CLASS} input[type=checkbox] { 420 | display: none; 421 | } 422 | .${ELEM_NAME_DICT.LOG_CLASS} label { 423 | cursor: pointer; 424 | } 425 | .${ELEM_NAME_DICT.LOG_CLASS} label:hover { 426 | text-decoration: underline; 427 | } 428 | 429 | .${ELEM_NAME_DICT.LOG_CLASS} input[type=button] { 430 | cursor: pointer; 431 | background-color: rgba(0,0,0,0); 432 | border: white 2px outset; 433 | } 434 | .${ELEM_NAME_DICT.LOG_CLASS} input[type=button]:hover { 435 | background-color: rgba(29, 155, 240, .5); 436 | } 437 | 438 | .${ELEM_NAME_DICT.VERIFY_CLASS} { 439 | max-width: 20px; 440 | max-height: 20px; 441 | color: rgb(29, 155, 240); 442 | fill: currentcolor; 443 | user-select: none; 444 | height: 1.25em; 445 | display: inline-block; 446 | vertical-align: middle; 447 | } 448 | 449 | /* メニュー表示設定 */ 450 | #${EX_MENU_ID}.${ELEM_NAME_DICT.MOBILE_FLAG_CLASS} { 451 | font-size: 0.8em; 452 | } 453 | #${EX_MENU_ID} textarea { 454 | width: 95%; 455 | resize: vertical; 456 | height: 8em; 457 | max-height: 25em; 458 | tab-size: 4; 459 | white-space: pre; 460 | font-size: 0.89em; 461 | } 462 | #${EX_MENU_ID} input[type=text] { 463 | width: 95%; 464 | } 465 | 466 | #${EX_MENU_ID} input[type=text], 467 | #${EX_MENU_ID} input[type=number], 468 | #${EX_MENU_ID} textarea, 469 | #${EX_MENU_ID} select { 470 | border: 1px solid #ccc; 471 | } 472 | 473 | #${EX_MENU_ID} input[type=button] { 474 | background-color: #ffffffaa; 475 | border: 1px solid #ccc; 476 | border-radius: 4px; 477 | color: #111133; 478 | cursor: pointer; 479 | margin: 0; 480 | padding: 0.08em 0.2em; 481 | transition: background 0.2s; 482 | } 483 | #${EX_MENU_ID} input[type=button]:hover { 484 | background-color: rgba(29, 155, 240, .5); 485 | } 486 | 487 | #${EX_MENU_ID} input[type=checkbox] + label::after { 488 | content: "Invalid"; 489 | } 490 | #${EX_MENU_ID} input[type=checkbox]:checked + label::after { 491 | content: "Validity"; 492 | } 493 | #${EX_MENU_ID}[lang=ja] input[type=checkbox] + label::after { 494 | content: "無効"; 495 | } 496 | #${EX_MENU_ID}[lang=ja] input[type=checkbox]:checked + label::after { 497 | content: "有効"; 498 | } 499 | 500 | #${EX_MENU_ID} summary { 501 | cursor: pointer; 502 | } 503 | 504 | #${EX_MENU_ID} details { 505 | margin-top: 1em; 506 | } 507 | 508 | .${ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID}_name { 509 | font-size: 1.3em; 510 | margin-bottom: 3px; 511 | margin-left: 2px; 512 | } 513 | .${ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID}_name + p { 514 | font-size: .8em; 515 | margin: 0 4px; 516 | } 517 | 518 | .${ELEM_NAME_DICT.EX_MENU_ITEM_ERROR_CLASS} { 519 | color: red; 520 | margin: 0; 521 | } 522 | 523 | #${ELEM_NAME_DICT.EX_MENU_OPEN_BUTTON} { 524 | background: transparent; 525 | font-weight: bold; 526 | position: fixed; 527 | width: 10em; 528 | height: 2em; 529 | top: 0; 530 | right: 0; 531 | } 532 | 533 | /* iPad 第1~3世代(画面横)*/ 534 | @media (max-device-width: 1024px) and (orientation: landscape) { 535 | #${ELEM_NAME_DICT.EX_MENU_OPEN_BUTTON} { 536 | width: 20em; 537 | height: 4em; 538 | } 539 | } 540 | /* iPad 第4世代*/ 541 | @media screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) { 542 | #${ELEM_NAME_DICT.EX_MENU_OPEN_BUTTON} { 543 | width: 20em; 544 | height: 4em; 545 | } 546 | } 547 | 548 | `; 549 | // -------------------------------------------------- 550 | const CUSTOM_CSS = /* css */ ``; 551 | 552 | // ========================================================================================== 553 | // 内部使用他(定数) 554 | // ========================================================================================== 555 | /** 556 | * メニューform分類 557 | * @readonly 558 | * @enum {string} 559 | */ 560 | const MENU_INPUT_TYPE = { 561 | text: "text", 562 | num: "number", 563 | check: "checkbox", 564 | textarea: "textarea", 565 | select: "select", 566 | btn: "button", 567 | }; 568 | /** 569 | * メニュー分類グループ分類 570 | * @readonly 571 | * @enum {string} 572 | */ 573 | const MENU_GROUP_TYPE = { 574 | basic: "basic", 575 | internalRef : "internalRef", 576 | advanced: "advanced", 577 | tweetDeck: "tweetDeck", 578 | debug: "debug", 579 | }; 580 | // -------------------------------------------------- 581 | // 非表示id 582 | 583 | /** 584 | * メッセージフィルターの非表示id 585 | * @readonly 586 | * @enum {number} 587 | */ 588 | const FILTED_HIDDEN_ID = { 589 | processed: -2, 590 | evaluated: -1, 591 | newEntry: 0, 592 | commentFilterDetection: 1, 593 | commentEmojiOnly: 2, 594 | textDuplication: 3, 595 | highUsage: 4, 596 | selfCitation: 5, 597 | nameFilterDetection: 6, 598 | nameEmojiOnly: 7, 599 | verifyRtBlock: 8, 600 | symbolUsage: 9, 601 | detectedElsewhere: 10, 602 | authenticatedAccount: 11, 603 | unauthorizedLanguage: 12, 604 | selfCitationSub: 13, 605 | contributtonCount: 14, 606 | rtContributtonCount: 15, 607 | rtSharingSeries: 16, 608 | fullCommentFilterDetection: 17, 609 | platformFilterDetection: 18, 610 | }; 611 | 612 | // -------------------------------------------------- 613 | // データ保存用 定数 614 | const SETTING_SAVE_KEY = PRO_NAME + "_json"; 615 | const BLACK_MEMORY_KEY = PRO_NAME + "_blackMemory"; 616 | 617 | // -------------------------------------------------- 618 | // 許可URL (ページ) 619 | const ALLOW_PAGE_SET = new Set(["home", "search"]); 620 | // 許可URL (ステータス) 621 | const ALLOW_STATUS_SET = new Set(["status", "tweetdeck"]); 622 | 623 | // -------------------------------------------------- 624 | // 翻訳key 625 | const MENU_LANG_KEY = "menu_"; 626 | const MENU_LANG_KEY_NAME = "_name"; 627 | const MENU_LANG_KEY_EXPLANATION = "_explanation"; 628 | 629 | // -------------------------------------------------- 630 | /** 631 | * 翻訳データ 632 | * @readonly 633 | * @constant {Object.>} LANGUAGE_DICT 634 | */ 635 | const LANGUAGE_DICT = { 636 | ja: { 637 | // 日本語 638 | menu_warn: /* html */ ` 639 | 現在のバージョン: ${VERSION}
640 | 変更の保存をした場合、ページを更新してください。
641 | 使い方の説明はこちらから`, 642 | menu_internalRef: "追加参照機能", 643 | menu_advanced: "高度な設定", 644 | menu_tweetDeck: "OldTweetDeck", 645 | menu_debug: "デバッグ", 646 | menu_error: "上記の設定内容の実行に失敗しました", 647 | save: "保存", 648 | close: "閉じる", 649 | filter: "フィルター", 650 | similarity: "類似度", 651 | usageCount: "使用回数", 652 | viewOriginalTweet: "元Tweetを見る", 653 | sureReset: "本当にリセットを実行しますか?", 654 | 655 | // setting menu 656 | menu_visibleLog_name: "非表示ログを表示", 657 | menu_visibleLog_explanation: `非表示にしたログを画面から消します。 658 | 画面が平和になりますが、投稿を非表示にされた理由・元投稿が確認出来なくなります。`, 659 | menu_visibleVerifyLog_name: "非表示ログに認証マーク表示", 660 | menu_visibleVerifyLog_explanation: `非表示にしたログの名前の後ろに認証マークを追加します。 661 | 企業バッジでも青バッジで表示されます。`, 662 | menu_blackTextReg_name: "禁止する表現", 663 | menu_blackTextReg_explanation: `非表示にするテキストを指定します。 664 | メンション・ハッシュタグ・シンボルタグ・URLなどのリンク、絵文字は判定に含まれません。 665 | 記述方法は正規表現(/の間部分)で記述します。 666 | (半角カタカナ、カタカナはひらがなに自動変換されます) 667 | (全角英数字は半角英数字に、改行文字は半角スペースに自動変換されます)`, 668 | menu_blackFullTextReg_name: "禁止する表現[拡張]", 669 | menu_blackFullTextReg_explanation: `非表示にするテキストを指定します。 670 | メンション・ハッシュタグ・シンボルタグ・URLなどのリンク、絵文字を判定に含みます。 671 | 指定方法などは[禁止する表現]と同じです。`, 672 | menu_whiteTextReg_name: "許可する表現", 673 | menu_whiteTextReg_explanation: `許可するテキストを指定します。 674 | 一致する投稿は非表示の対象になりません。 675 | 指定方法などは[禁止する表現]と同じです。`, 676 | menu_blackRtTextReg_name: "禁止するRT表現", 677 | menu_blackRtTextReg_explanation: `非表示にするRT元テキストを指定します。 678 | 指定方法などは[禁止する表現]と同じです。`, 679 | menu_blackNameReg_name: "禁止する名前", 680 | menu_blackNameReg_explanation: `非表示にするユーザー名を指定します。 681 | 指定方法などは[禁止する表現]と同じです。`, 682 | menu_excludedUsers_name: "除外ユーザー", 683 | menu_excludedUsers_explanation: `指定されたユーザーidは検知の対象になりません。 684 | 指定方法はユーザーidを改行で区切って記述するだけです。 685 | idは完全一致のみ有効です。`, 686 | menu_allowLang_name: "許可する言語", 687 | menu_allowLang_explanation: `許可する言語を指定します。 688 | 記述方法は正規表現(/の間部分)で記述します。`, 689 | menu_oneselfRetweetBlock_name: "自身の引用禁止", 690 | menu_oneselfRetweetBlock_explanation: `自身を引用ツイートする投稿を非表示にします。`, 691 | menu_oneselfSubRetweetBlock_name: "サブ垢での自身の引用禁止", 692 | menu_oneselfSubRetweetBlock_explanation: `サブ垢での自身を引用ツイートする投稿を非表示にします。 693 | ユーザー名から[サブ,2nd]などを除外しての一致検索です。`, 694 | menu_subDefinitionReg_name: "サブ垢定義用表現", 695 | menu_subDefinitionReg_explanation: `[サブ垢での自身の引用禁止]での除外文字を指定します。 696 | 1行ずつ評価していく為同時評価が必要な場合は「(aaa|bbb)」を使用して下さい。 697 | 指定方法などは[禁止する表現]と同じです。`, 698 | menu_emojiOnryBlock_name: "絵文字投稿禁止", 699 | menu_emojiOnryBlock_explanation: `絵文字のみで構成された投稿を非表示にします。`, 700 | menu_emojiOnryNameBlock_name: "絵文字ユーザー名禁止", 701 | menu_emojiOnryNameBlock_explanation: `絵文字のみで構成されたユーザー名を非表示にします。`, 702 | menu_verifyBlock_name: "認証アカウント禁止", 703 | menu_verifyBlock_explanation: `認証済アカウントを無差別に非表示にします。`, 704 | menu_verifyRtBlock_name: "認証RT禁止", 705 | menu_verifyRtBlock_explanation: `認証済アカウント投稿に対する引用RTを非表示にします。`, 706 | menu_verifyOnryFilter_name: "認証アカウントのみ判定", 707 | menu_verifyOnryFilter_explanation: `認証済アカウントのみを検知の対象にします。 708 | 通常アカウントや認証マークの無いアカウントはブロックされなくなります。`, 709 | menu_formalityCare_name: "認証公式アカウントを保護", 710 | menu_formalityCare_explanation: `公式アカウントを検知の対象から除外します。 711 | (公式とは青いバッジ以外を指します)`, 712 | menu_visibleBlockButton_name: "クイックブロック表示", 713 | menu_visibleBlockButton_explanation: `1クリックでブロックできるボタンを表示します。 714 | 検出された投稿にしか表示されません。`, 715 | menu_visibleReportButton_name: "クイック通報表示", 716 | menu_visibleReportButton_explanation: `1クリックで通報できるボタンを表示します。 717 | 検出された投稿にしか表示されません。 718 | (初期値はスパム報告です)`, 719 | menu_maxHashtagCount_name: "ハッシュタグの上限数", 720 | menu_maxHashtagCount_explanation: `1つの投稿内でのハッシュタグの使用上限数を指定します。`, 721 | menu_maxSymboltagCount_name: "シンボルタグの上限数", 722 | menu_maxSymboltagCount_explanation: `1つの投稿内でのシンボルタグの使用上限数を指定します。 723 | ※シンボルタグとは「$TWTR」のような#を$に置き換えた株を表す表現`, 724 | menu_maxContributtonCount_name: "ツリー返信上限数", 725 | menu_maxContributtonCount_explanation: `1つの投稿ツリーでの返信上限数を指定します。 726 | 値は許可のラインです。(例: 1で2投稿以上は非表示) 727 | 0を指定するとこの設定は無効化されます。`, 728 | menu_maxRtCount_name: "1人によるRT上限数", 729 | menu_maxRtCount_explanation: `1つの投稿ツリーでの1ユーザーの引用RT返信上限数を指定します。 730 | 値は[ツリー返信上限数]と同じ指定方法です。`, 731 | menu_maxSameRtCount_name: "同一RT上限数", 732 | menu_maxSameRtCount_explanation: `1つの投稿ツリーでの複数人からの同じユーザーに対する引用RT返信上限数を指定します。 733 | 値は[ツリー返信上限数]と同じ指定方法です。`, 734 | menu_msgResemblance_name: "文章類似度許可ライン", 735 | menu_msgResemblance_explanation: `コピペ文章かを判別する為の基準値を指定します。`, 736 | menu_maxSaveTextSize_name: "比較される最大テキストサイズ", 737 | menu_maxSaveTextSize_explanation: `コピペ投稿の文章比較の最大文字数を指定します。 738 | 値を大きくするほど誤検知率は減り、検知率も減ります。 739 | (投稿の文字数が最大値以下の場合、この値は使用されません)`, 740 | menu_minSaveTextSize_name: "一時保存・比較される最小テキストサイズ", 741 | menu_minSaveTextSize_explanation: `比較用文章の最小文字数を指定します。 742 | 値が大きくするほど誤検知率は減り、検知率も減ります。 743 | ([比較される最大テキストサイズ]より大きい場合、比較処理は実行されません)`, 744 | menu_maxSaveLogSize_name: "一時保存される投稿の最大数", 745 | menu_maxSaveLogSize_explanation: `比較用文章の保持数を指定します。 746 | 値が小さいほど処理は軽くなりますが、検知率が減ります。`, 747 | menu_language_name: "言語", 748 | menu_language_explanation: `表示言語を設定します。`, 749 | menu_useTwitterInternalData_name: "Twitter内部データ使用", 750 | menu_useTwitterInternalData_explanation: `ユーザーと同じ画面(DOM)からのデータ取得ではなく、 751 | 内部で使用されているオブジェクトを処理に流用します。 752 | Twitterのアップデートで動作しなくなる可能性があります。 753 | 取得に失敗した場合既存の取得方法が代替で使用されます。 754 | OldTweetDeckでは無効です。`, 755 | menu_platformBlackReg_name: "禁止するプラットフォーム", 756 | menu_platformBlackReg_explanation: `「Twitter Web App」などの投稿環境で判定します。 757 | 指定方法などは[禁止する表現]と同じです。 758 | [Twitter内部データ使用]が無効の場合、動作しません。`, 759 | menu_customCss_name: "ページ適用css設定", 760 | menu_customCss_explanation: `ページへ適用するcssを指定します。`, 761 | menu_bodyObsTimeout_name: "ページ更新検知用処理待機時間(ms)", 762 | menu_bodyObsTimeout_explanation: `ページ更新を検知する際の検知の更新間隔を指定します。 763 | 値が大きいほど処理が軽くなりますが、非表示にする初速が落ちる可能性あります。`, 764 | menu_blackMemory_name: "検知対象の記憶", 765 | menu_blackMemory_explanation: `検出された対象を記憶します。 766 | ページを更新などしても過去に検知した対象を素早く非表示に出来ます。 767 | ※この機能はbeta版です!! 768 | 誤検知されたアカウントが非表示のままになります。 769 | [除外ユーザー]と併用して使用して下さい。`, 770 | menu_autoBlock_name: "【非推奨】自動ブロック", 771 | menu_autoBlock_explanation: `検出された対象を自動でブロックします。 772 | ※この機能はbeta版です!! 773 | 誤検知でも戸惑いなくブロックされます。`, 774 | menu_useRegModeDotAll_name: "dotAllモードの利用", 775 | menu_useRegModeDotAll_explanation: `内部で使用する正規表現でdotAllモードを使用可能にします。 776 | この機能を無効にした場合、正規表現を記述するフィルターで改行を明示的に指定する必要があります。 777 | 変更を行う前に、その影響について理解して変更して下さい。(ES9)`, 778 | menu_resetSetting_name: "設定のリセット", 779 | menu_resetSetting_explanation: `設定項目をリセットします。 780 | (ページがリロードされます) 781 | 実行すると設定は復元出来ません!!!`, 782 | menu_resetBlackMemory_name: "検知済idのリセット", 783 | menu_resetBlackMemory_explanation: `検知済idをリセットします。 784 | (ページがリロードされます) 785 | 実行するとこれまで検知・非表示にされたユーザーが再度表示される可能性が高くなります! 786 | [検知対象の記憶]を使用している状況で以前より処理が重いと感じた場合、リセットすると処理が軽くなる可能性があります。`, 787 | menu_enableOldTweetDeckMode_name: "OldTweetDeck対応", 788 | menu_enableOldTweetDeckMode_explanation: `負荷軽減の為に分離 789 | ※この機能はbeta版です!! 790 | 動作の安定性を保証出来ません。`, 791 | menu_autoLoadJQuery_name: "jQuery自動読み込み", 792 | menu_autoLoadJQuery_explanation: `OldTweetDeckではなぜかjQueryが使用されているのにjQueryが読み込まれていない為、 793 | jQueryが読み込まれていない場合にjQueryを読み込む機能です。`, 794 | menu_debug_viewSettingMenu_name: "起動時設定自動表示", 795 | menu_debug_viewSettingMenu_explanation: `設定画面を自動で開く`, 796 | menu_debug_visibleCardDebugButton_name: "クイックデバッグ表示", 797 | menu_debug_visibleCardDebugButton_explanation: `1クリックでコンソールに該当MsgDataを出力できるボタンを表示します。 798 | 検出された投稿にしか表示されません。 799 | 検出された投稿は軽量化のためMsgDataを破棄するのでこの参照が最後の記録`, 800 | menu_debug_viewBlacklist_name: "blacklist表示", 801 | menu_debug_viewBlacklist_explanation: `現在のblacklist_idをconsoleに出力する。`, 802 | menu_debug_viewMsgDB_name: "MsgDB表示", 803 | menu_debug_viewMsgDB_explanation: `現在のMsgDBをconsoleに出力する。`, 804 | menu_debug_reInit_name: "init再実行", 805 | menu_debug_reInit_explanation: `強制的にDOM設定を再設定する。 806 | [ページ更新検知用処理待機時間(ms)]が仕事を放棄した際に使用。`, 807 | 808 | //hideComment 809 | detectedElsewhere: "他で検出済", 810 | authenticatedAccount: "認証垢", 811 | verifyRtBlock: "認証RT垢", 812 | unauthorizedLanguage: "非許可言語", 813 | contributtonCount: "連投", 814 | rtContributtonCount: "RT連投", 815 | rtSharingSeries: "RT共有連投", 816 | filterDetection: "フィルター検出", 817 | emojiOnly: "絵文字のみ", 818 | textDuplication: "文章の複製", 819 | highUsage: "#多量使用", 820 | symbolUsage: "$多量使用", 821 | selfCitation: "自身の引用", 822 | selfCitationSub: "自身を引用?", 823 | platformFilterDetection: "投稿元規制", 824 | recursiveDetection: "再帰的検出", 825 | }, 826 | en: { 827 | // 英語 828 | menu_warn: /* html */ ` 829 | Current version: ${VERSION}
830 | If you have saved the changes, please refresh the page.
831 | You can find the usage instructions here`, 832 | menu_internalRef: "Additional Reference", 833 | menu_advanced: "Advanced settings", 834 | menu_tweetDeck: "OldTweetDeck", 835 | menu_debug: "Debug", 836 | menu_error: "Failed to execute the above settings", 837 | save: "Save", 838 | close: "Close", 839 | filter: "Filter", 840 | similarity: "Similarity", 841 | usageCount: "UsageCount", 842 | viewOriginalTweet: "View original Tweet", 843 | sureReset: "Are you sure you want to execute the reset?", 844 | 845 | // setting menu 846 | menu_visibleLog_name: "Show hidden logs", 847 | menu_visibleLog_explanation: `It will remove the hidden logs from the screen. 848 | The screen will be peaceful, but the reasons for hiding the posts and the original posts will no longer be visible.`, 849 | menu_visibleVerifyLog_name: "Certification mark displayed on hidden log", 850 | menu_visibleVerifyLog_explanation: `Adds a certification mark after the name of the hidden log. 851 | Corporate badges are also displayed as blue badges.`, 852 | menu_blackTextReg_name: "Prohibited expressions", 853 | menu_blackTextReg_explanation: `Specify the text to hide. 854 | Mentions, hashtags, symbol tags, URLs, and emojis are excluded from the evaluation. 855 | The description should be written using regular expressions (between the / characters). 856 | Half-width katakana and katakana will be automatically converted to hiragana. 857 | Full-width alphanumeric characters will be converted to half-width, 858 | and line breaks will be converted to spaces automatically.`, 859 | menu_blackFullTextReg_name: "Prohibited expressions[Plus]", 860 | menu_blackFullTextReg_explanation: `Specify the text to hide. 861 | Mentions, hashtags, symbol tags, URLs, and emojis are included in the evaluation. 862 | The specification method is the same as [Prohibited expressions].`, 863 | menu_whiteTextReg_name: "Expressions allowed", 864 | menu_whiteTextReg_explanation: `Specify the text to allow. 865 | Matching posts will not be hidden. 866 | The specification method is the same as [Prohibited expressions].`, 867 | menu_blackRtTextReg_name: "Prohibited RT expressions", 868 | menu_blackRtTextReg_explanation: `Specify the RT source text to hide. 869 | The specification method is the same as [Prohibited expressions].`, 870 | menu_blackNameReg_name: "Prohibited name", 871 | menu_blackNameReg_explanation: `Specify the username to hide. 872 | The specification method is the same as [Prohibited expressions].`, 873 | menu_excludedUsers_name: "Excluded users", 874 | menu_excludedUsers_explanation: `The specified user ID will not be detected. 875 | To specify, simply write the user IDs separated by line breaks. 876 | Only exact matches are valid for id.`, 877 | menu_allowLang_name: "Allowed languages", 878 | menu_allowLang_explanation: `Specify the allowed languages. 879 | The description should be written using regular expressions (between the / characters).`, 880 | menu_oneselfRetweetBlock_name: "Prohibition of self-quotation", 881 | menu_oneselfRetweetBlock_explanation: `It hides posts that quote oneself.`, 882 | menu_oneselfSubRetweetBlock_name: "Prohibition of quoting yourself in sub-text", 883 | menu_oneselfSubRetweetBlock_explanation: `It hides posts that quote oneself.`, 884 | menu_subDefinitionReg_name: "Expression for sub-scale definition", 885 | menu_subDefinitionReg_explanation: `Specify the excluded characters for [Prohibit quoting yourself in sub-text]. 886 | If you need simultaneous evaluation, use "(aaa|bbb)" as each line is evaluated one by one. 887 | The specification method is the same as [Prohibited expressions].`, 888 | menu_emojiOnryBlock_name: "No emoji posting", 889 | menu_emojiOnryBlock_explanation: `Hide posts composed only of emojis.`, 890 | menu_emojiOnryNameBlock_name: "Prohibit emoji usernames", 891 | menu_emojiOnryNameBlock_explanation: `Hide usernames composed only of emojis.`, 892 | menu_verifyBlock_name: "Prohibition of authenticated accounts", 893 | menu_verifyBlock_explanation: `It indiscriminately hides authenticated accounts.`, 894 | menu_verifyRtBlock_name: "Authentication RT prohibited", 895 | menu_verifyRtBlock_explanation: `Hide quoted RTs for authenticated account posts.`, 896 | menu_verifyOnryFilter_name: "Authenticate accounts only", 897 | menu_verifyOnryFilter_explanation: `It detects only authenticated accounts. 898 | Regular accounts and accounts without verification badges will no longer be blocked.`, 899 | menu_formalityCare_name: "Protect your authenticated official account", 900 | menu_formalityCare_explanation: `Exclude official accounts from detection. 901 | (Official means anything other than the blue badge)`, 902 | menu_visibleBlockButton_name: "Quick block button display", 903 | menu_visibleBlockButton_explanation: `Displays a button that allows you to block with one click. 904 | It will only appear on detected posts.`, 905 | menu_visibleReportButton_name: "Quick report button display", 906 | menu_visibleReportButton_explanation: `Displays a button that allows you to report with one click. 907 | It will only appear on detected posts. 908 | (Initial value is spam report)`, 909 | menu_maxHashtagCount_name: "Maximum number of hashtags", 910 | menu_maxHashtagCount_explanation: `It specifies the maximum number of hashtags allowed in a single post.`, 911 | menu_maxSymboltagCount_name: "Maximum number of symboltags", 912 | menu_maxSymboltagCount_explanation: `It specifies the maximum number of symboltags allowed in a single post. 913 | *Symbol tag is an expression that represents a stock by replacing # with $, such as "$TWTR"`, 914 | menu_maxContributtonCount_name: "Maximum number of tree replies", 915 | menu_maxContributtonCount_explanation: `Specify the maximum number of replies in one post tree. 916 | The value is the line of permission. (Example: 1 hides 2 or more posts) 917 | Specifying 0 disables this setting.`, 918 | menu_maxRtCount_name: "Maximum number of RTs by one person", 919 | menu_maxRtCount_explanation: `Specify the maximum number of quote RT replies for one user in one post tree. 920 | The value is specified in the same way as [Maximum number of tree replies].`, 921 | menu_maxSameRtCount_name: "Maximum number of same RTs", 922 | menu_maxSameRtCount_explanation: `Specify the maximum number of quote RT replies to the same user from multiple people in one post tree. 923 | The value is specified in the same way as [Maximum number of tree replies].`, 924 | menu_msgResemblance_name: "Text similarity threshold", 925 | menu_msgResemblance_explanation: `It specifies the threshold value for determining whether a text is a copied and pasted text.`, 926 | menu_maxSaveTextSize_name: "Maximum text size for comparison", 927 | menu_maxSaveTextSize_explanation: `It specifies the maximum number of characters for text comparison in copied and pasted posts. 928 | Increasing the value reduces the false positive rate but also reduces the detection rate. 929 | (This value is not used if the post's character count is below the maximum value.)`, 930 | menu_minSaveTextSize_name: "The minimum text size that is temporarily saved and compared", 931 | menu_minSaveTextSize_explanation: `This specifies the minimum number of characters for the comparison text. 932 | Increasing the value reduces the false detection rate as well as the detection rate. 933 | If it is larger than the [Maximum text size for comparison], the comparison process will not be executed.`, 934 | menu_maxSaveLogSize_name: "The maximum number of posts that are temporarily saved", 935 | menu_maxSaveLogSize_explanation: `This specifies the number of comparison texts to be retained. 936 | A smaller value reduces the processing load but also decreases the detection rate.`, 937 | menu_language_name: "Language", 938 | menu_language_explanation: `Set the display language.`, 939 | menu_useTwitterInternalData_name: "Use of Twitter internal data", 940 | menu_useTwitterInternalData_explanation: `Instead of retrieving data from the same screen (DOM) as the user, this feature reuses internal objects used by Twitter. 941 | This may stop working if Twitter updates their platform. 942 | If data retrieval fails, the existing method will be used as a fallback. 943 | This is not supported on OldTweetDeck.`, 944 | menu_platformBlackReg_name: "Disallowed platforms", 945 | menu_platformBlackReg_explanation: `Posts are identified based on the source client, like "Twitter Web App". 946 | The specification method is the same as [Prohibited expressions]. 947 | Does not work if [Use of Twitter internal data] is disabled.`, 948 | menu_customCss_name: "Page-specific CSS settings", 949 | menu_customCss_explanation: `Specify the CSS to apply to the page.`, 950 | menu_bodyObsTimeout_name: "Processing wait time (in milliseconds) for page update detection", 951 | menu_bodyObsTimeout_explanation: `This specifies the interval for detecting page updates. 952 | A larger value reduces the processing load but may potentially delay the initial speed of hiding.`, 953 | menu_blackMemory_name: "Memory of detection target", 954 | menu_blackMemory_explanation: `Remembers detected objects. 955 | Even if you refresh the page, you can quickly hide objects detected in the past. 956 | *This feature is in beta version! ! 957 | Falsely detected accounts remain hidden. 958 | Please use it in conjunction with [Excluded User]. `, 959 | menu_autoBlock_name: "[Not recommended] Automatic block", 960 | menu_autoBlock_explanation: `Automatically block detected targets. 961 | *This feature is in beta version! ! 962 | Even false positives are blocked without hesitation.`, 963 | menu_useRegModeDotAll_name: "Using dotAll mode", 964 | menu_useRegModeDotAll_explanation: `Enables the use of dotAll mode in regular expressions used internally. 965 | If this feature is disabled, you will need to explicitly specify line breaks in filters that use regular expressions. 966 | Please make sure you understand the implications before making changes. (ES9)`, 967 | menu_resetSetting_name: "Reset settings", 968 | menu_resetSetting_explanation: `Reset the settings. 969 | (The page will be reloaded.) 970 | Once executed, the settings cannot be restored!!!`, 971 | menu_resetBlackMemory_name: "Reset detected ID", 972 | menu_resetBlackMemory_explanation: `Reset detected ID. 973 | (The page will be reloaded.) 974 | If you run it, there is a high possibility that users who have been detected/hidden will be displayed again! 975 | If you feel that the processing is slower than before when using [Remember detection targets], resetting it may make the processing faster. `, 976 | menu_enableOldTweetDeckMode_name: "OldTweetDeck compatible", 977 | menu_enableOldTweetDeckMode_explanation: `Separated to reduce load 978 | *This feature is in beta version! ! 979 | We cannot guarantee the stability of operation.`, 980 | menu_autoLoadJQuery_name: "jQuery Autoload", 981 | menu_autoLoadJQuery_explanation: `For some reason, jQuery is used in OldTweetDeck but is not loaded, so this function loads jQuery when jQuery is not loaded.`, 982 | menu_debug_viewSettingMenu_name: "Automatic display of settings at startup", 983 | menu_debug_viewSettingMenu_explanation: `Automatically open the settings screen`, 984 | menu_debug_viewBlacklist_name: "Blacklist display", 985 | menu_debug_viewBlacklist_explanation: `Output current blacklist_id to console.`, 986 | menu_debug_viewMsgDB_name: "MsgDB display", 987 | menu_debug_viewMsgDB_explanation: `Output current MsgDB to console.`, 988 | menu_debug_reInit_name: "init rerun", 989 | menu_debug_reInit_explanation: `Force DOM settings to be reset. 990 | Used when [Processing wait time (in milliseconds) for page update detection] is abandoned.`, 991 | 992 | //hideComment 993 | detectedElsewhere: "DetectedElsewhere", 994 | authenticatedAccount: "AuthenticatedAccount", 995 | verifyRtBlock: "AuthenticationRtPlaque", 996 | unauthorizedLanguage: "UnauthorizedLanguage: ", 997 | contributtonCount: "doubleTexting", 998 | rtContributtonCount: "rtDoubleTexting", 999 | rtSharingSeries: "rtSharingSeries", 1000 | filterDetection: "FilterDetection", 1001 | emojiOnly: "EmojiOnly", 1002 | textDuplication: "TextDuplication", 1003 | highUsage: "#HighUsage", 1004 | symbolUsage: "$HighUsage", 1005 | selfCitation: "SelfCitation", 1006 | selfCitationSub: "selfCitationSub", 1007 | platformFilterDetection: "PlatformRestriction", 1008 | recursiveDetection: "RecursiveDetection", 1009 | }, 1010 | }; 1011 | 1012 | /** 1013 | * メニューのセレクトボックス内容 1014 | * @readonly 1015 | * @constant {Object.} SETTING_LANG_SELECT 1016 | */ 1017 | const SETTING_LANG_SELECT = { 1018 | ja: "日本語(ja)", 1019 | en: "English(en)", 1020 | }; 1021 | 1022 | // -------------------------------------------------- 1023 | 1024 | /** 1025 | * 標準参照Query一覧 1026 | * @enum {string} 1027 | */ 1028 | const _BASIC_QUERY_DICT = { 1029 | OBS_QUERY: "section > div > div:has(article)", 1030 | RE_QUERY: `div:has(div > div > article):not(.${ELEM_NAME_DICT.CHECK_CLASS})`, 1031 | NAME_SPACE_QUERY: `[data-testid="User-Name"]`, 1032 | NAME_QUERY: `:not(span) > span > span`, 1033 | ID_QUERY: "div > span:not(:has(span))", 1034 | VERIFY_QUERY: `svg:not(:has([fill^="#"]))`, 1035 | VERIFY_FORMALITY_QUERY: `svg:has([fill^="#"])`, 1036 | TEXT_DIV_QUERY: "div[lang]", 1037 | IMAGE_QUERY: `a img, [data-testid="videoComponent"] video`, 1038 | MENU_BUTTON_QUERY: "[aria-haspopup=menu][role=button]:has(svg)", 1039 | MENU_DISP_QUERY: "[role=group] [role=menu]", 1040 | }; 1041 | if (isMobile) { 1042 | _BASIC_QUERY_DICT.MENU_DISP_QUERY = "#layers [role=menu] [role=group]"; 1043 | } 1044 | 1045 | /** 1046 | * OldTweetDeck参照Query一覧 1047 | * @enum {string} 1048 | */ 1049 | const _OLD_TWEET_DECK_QUERY_DICT = { 1050 | OBS_QUERY: "body .js-app-columns:has(section)", 1051 | RE_QUERY: `article:has(div > div > header):not(.${ELEM_NAME_DICT.CHECK_CLASS})`, 1052 | NAME_SPACE_QUERY: "header", 1053 | NAME_QUERY: ".fullname", 1054 | ID_QUERY: ".username", 1055 | TEXT_DIV_QUERY: ".tweet-text", 1056 | }; 1057 | 1058 | /** 1059 | * 参照Query一覧 1060 | * @enum {string} 1061 | */ 1062 | const EX_QUERY_DICT = new Proxy(_BASIC_QUERY_DICT, { 1063 | get(target, prop, receiver) { 1064 | if (useOldTweetDeck) { 1065 | let ret = _OLD_TWEET_DECK_QUERY_DICT[prop]; 1066 | if (ret) return ret; 1067 | } 1068 | return target[prop]; 1069 | }, 1070 | set(target, prop, value, receiver) { 1071 | console.warn(`Cannot assign to read-only property: ${prop}`); 1072 | return false; 1073 | }, 1074 | }); 1075 | 1076 | /** 1077 | * 標準参照Queryリスト一覧 1078 | * @enum {Array} 1079 | */ 1080 | const _BASIC_QUERY_LIST_DICT = { 1081 | BLOCK_QUERY_LIST: [`${_BASIC_QUERY_DICT.MENU_DISP_QUERY} div[role=menuitem]:has(path[d^="M12 3.75c"])`, "[role=alertdialog] [role=group] [role=button] div"], 1082 | // 3行目は場合によっては消す 1083 | REPORT_QUERY_LIST: [`${_BASIC_QUERY_DICT.MENU_DISP_QUERY} div[role=menuitem]:has(path[d^="M3 2h18"])`, ["[role=radiogroup] label", 5], "[role=group]:has([role=radiogroup]) [role=button]:not(:has(svg))", ["[role=group] [role=button]:not(:has(svg))", 1], ["__wait__", 1000], ["[role=group] [role=button]:not(:has(svg))", 1]], 1084 | }; 1085 | 1086 | /** 1087 | * OldTweetDeck参照Queryリスト一覧 1088 | * @enum {Array} 1089 | */ 1090 | const _OLD_TWEET_DECK_QUERY_LIST_DICT = {}; 1091 | 1092 | /** 1093 | * 参照Queryリスト一覧 1094 | * @enum {Array} 1095 | */ 1096 | const EX_QUERY_LIST_DICT = new Proxy(_BASIC_QUERY_LIST_DICT, { 1097 | get(target, prop, receiver) { 1098 | if (useOldTweetDeck) { 1099 | let ret = _OLD_TWEET_DECK_QUERY_LIST_DICT[prop]; 1100 | if (ret) return ret; 1101 | } 1102 | return target[prop]; 1103 | }, 1104 | set(target, prop, value, receiver) { 1105 | console.warn(`Cannot assign to read-only property: ${prop}`); 1106 | return false; 1107 | }, 1108 | }); 1109 | 1110 | const VERIFY_SVG = ` 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | `; 1118 | 1119 | // -------------------------------------------------- 1120 | /** 1121 | * 設定リスト内容定義 1122 | * @typedef {Object} SettingItem 1123 | * @property {boolean|string|number} [initData] - 設定項目の初期データ 1124 | * @property {boolean|string|number} [data] - 設定項目データ 1125 | * @property {MENU_INPUT_TYPE} input - 設定項目の入力タイプ 1126 | * @property {MENU_GROUP_TYPE} group - 所属グループ 1127 | * @property {string} [value] - `MENU_INPUT_TYPE`が`btn`の場合のvalue 1128 | * @property {number} [min] - `MENU_INPUT_TYPE`が`num`の場合の最小値 1129 | * @property {number} [max] - `MENU_INPUT_TYPE`が`num`の場合の最小値 1130 | * @property {number} [step] - `MENU_INPUT_TYPE`が`num`の場合の増分値 1131 | * @property {string} [select] - `MENU_INPUT_TYPE`が`select`の場合のoptions 1132 | * @property {boolean} [isError] - 【自動設定】エラーが設定項目に含まれる場合true 1133 | * @property {RegExp[]} [regexp_list] - 【自動設定】regRestorationで使用 1134 | * @property {RegExp} [regexp] - 【自動設定】1項目regで使用 1135 | */ 1136 | 1137 | /** 1138 | * 設定リスト 1139 | * @type {Object.} 1140 | */ 1141 | const SETTING_LIST = { 1142 | visibleLog: { 1143 | initData: true, 1144 | input: MENU_INPUT_TYPE.check, 1145 | group: MENU_GROUP_TYPE.basic, 1146 | }, 1147 | visibleVerifyLog: { 1148 | initData: true, 1149 | input: MENU_INPUT_TYPE.check, 1150 | group: MENU_GROUP_TYPE.basic, 1151 | }, 1152 | blackTextReg: { 1153 | initData: BLACK_TEXT_REG, 1154 | input: MENU_INPUT_TYPE.textarea, 1155 | group: MENU_GROUP_TYPE.basic, 1156 | }, 1157 | blackFullTextReg: { 1158 | initData: BLACK_FULL_TEXT_REG, 1159 | input: MENU_INPUT_TYPE.textarea, 1160 | group: MENU_GROUP_TYPE.basic, 1161 | }, 1162 | whiteTextReg: { 1163 | initData: WHITE_TEXT_REG, 1164 | input: MENU_INPUT_TYPE.textarea, 1165 | group: MENU_GROUP_TYPE.basic, 1166 | }, 1167 | // blackRtTextReg: { 1168 | // initData: BLACK_RT_TEXT_REG, 1169 | // input: MENU_INPUT_TYPE.textarea, 1170 | // group: MENU_GROUP_TYPE.basic, 1171 | // }, 1172 | blackNameReg: { 1173 | initData: BLACK_NAME_REG, 1174 | input: MENU_INPUT_TYPE.textarea, 1175 | group: MENU_GROUP_TYPE.basic, 1176 | }, 1177 | excludedUsers: { 1178 | initData: EXCLUDED_USERS, 1179 | input: MENU_INPUT_TYPE.textarea, 1180 | group: MENU_GROUP_TYPE.basic, 1181 | }, 1182 | allowLang: { 1183 | initData: ALLOW_LANG, 1184 | input: MENU_INPUT_TYPE.text, 1185 | group: MENU_GROUP_TYPE.basic, 1186 | regexp: /.*/, 1187 | }, 1188 | oneselfRetweetBlock: { 1189 | initData: true, 1190 | input: MENU_INPUT_TYPE.check, 1191 | group: MENU_GROUP_TYPE.basic, 1192 | }, 1193 | oneselfSubRetweetBlock: { 1194 | initData: true, 1195 | input: MENU_INPUT_TYPE.check, 1196 | group: MENU_GROUP_TYPE.basic, 1197 | }, 1198 | subDefinitionReg: { 1199 | initData: SUB_DEFINITION_SUB, 1200 | input: MENU_INPUT_TYPE.textarea, 1201 | group: MENU_GROUP_TYPE.basic, 1202 | }, 1203 | emojiOnryBlock: { 1204 | initData: true, 1205 | input: MENU_INPUT_TYPE.check, 1206 | group: MENU_GROUP_TYPE.basic, 1207 | }, 1208 | emojiOnryNameBlock: { 1209 | initData: true, 1210 | input: MENU_INPUT_TYPE.check, 1211 | group: MENU_GROUP_TYPE.basic, 1212 | }, 1213 | verifyBlock: { 1214 | initData: false, 1215 | input: MENU_INPUT_TYPE.check, 1216 | group: MENU_GROUP_TYPE.basic, 1217 | }, 1218 | verifyRtBlock: { 1219 | initData: false, 1220 | input: MENU_INPUT_TYPE.check, 1221 | group: MENU_GROUP_TYPE.basic, 1222 | }, 1223 | verifyOnryFilter: { 1224 | initData: false, 1225 | input: MENU_INPUT_TYPE.check, 1226 | group: MENU_GROUP_TYPE.basic, 1227 | }, 1228 | formalityCare: { 1229 | initData: true, 1230 | input: MENU_INPUT_TYPE.check, 1231 | group: MENU_GROUP_TYPE.basic, 1232 | }, 1233 | visibleBlockButton: { 1234 | initData: true, 1235 | input: MENU_INPUT_TYPE.check, 1236 | group: MENU_GROUP_TYPE.basic, 1237 | }, 1238 | visibleReportButton: { 1239 | initData: true, 1240 | input: MENU_INPUT_TYPE.check, 1241 | group: MENU_GROUP_TYPE.basic, 1242 | }, 1243 | maxHashtagCount: { 1244 | initData: 6, 1245 | input: MENU_INPUT_TYPE.num, 1246 | group: MENU_GROUP_TYPE.basic, 1247 | min: 1, 1248 | step: 1, 1249 | }, 1250 | maxSymboltagCount: { 1251 | initData: 1, 1252 | input: MENU_INPUT_TYPE.num, 1253 | group: MENU_GROUP_TYPE.basic, 1254 | min: 1, 1255 | step: 1, 1256 | }, 1257 | maxContributtonCount: { 1258 | initData: 2, 1259 | input: MENU_INPUT_TYPE.num, 1260 | group: MENU_GROUP_TYPE.basic, 1261 | min: 0, 1262 | step: 1, 1263 | }, 1264 | maxRtCount: { 1265 | initData: 1, 1266 | input: MENU_INPUT_TYPE.num, 1267 | group: MENU_GROUP_TYPE.basic, 1268 | min: 0, 1269 | step: 1, 1270 | }, 1271 | maxSameRtCount: { 1272 | initData: 1, 1273 | input: MENU_INPUT_TYPE.num, 1274 | group: MENU_GROUP_TYPE.basic, 1275 | min: 0, 1276 | step: 1, 1277 | }, 1278 | msgResemblance: { 1279 | initData: 0.85, 1280 | input: MENU_INPUT_TYPE.num, 1281 | group: MENU_GROUP_TYPE.basic, 1282 | min: 0, 1283 | max: 1, 1284 | step: 0.01, 1285 | }, 1286 | maxSaveTextSize: { 1287 | initData: 100, 1288 | input: MENU_INPUT_TYPE.num, 1289 | group: MENU_GROUP_TYPE.basic, 1290 | min: 0, 1291 | step: 1, 1292 | }, 1293 | minSaveTextSize: { 1294 | initData: 8, 1295 | input: MENU_INPUT_TYPE.num, 1296 | group: MENU_GROUP_TYPE.basic, 1297 | min: 0, 1298 | step: 1, 1299 | }, 1300 | maxSaveLogSize: { 1301 | initData: 200, 1302 | input: MENU_INPUT_TYPE.num, 1303 | group: MENU_GROUP_TYPE.basic, 1304 | min: 1, 1305 | step: 1, 1306 | }, 1307 | language: { 1308 | initData: "ja", 1309 | input: MENU_INPUT_TYPE.select, 1310 | group: MENU_GROUP_TYPE.basic, 1311 | select: SETTING_LANG_SELECT, 1312 | }, 1313 | // ------------------------- 1314 | useTwitterInternalData: { 1315 | initData: true, 1316 | input: MENU_INPUT_TYPE.check, 1317 | group: MENU_GROUP_TYPE.internalRef, 1318 | }, 1319 | platformBlackReg: { 1320 | initData: PLAT_FORM_BLACK_REG, 1321 | input: MENU_INPUT_TYPE.textarea, 1322 | group: MENU_GROUP_TYPE.internalRef, 1323 | }, 1324 | // ------------------------- 1325 | customCss: { 1326 | initData: CUSTOM_CSS, 1327 | input: MENU_INPUT_TYPE.textarea, 1328 | group: MENU_GROUP_TYPE.advanced, 1329 | }, 1330 | bodyObsTimeout: { 1331 | initData: 3000, 1332 | input: MENU_INPUT_TYPE.num, 1333 | group: MENU_GROUP_TYPE.advanced, 1334 | min: 100, 1335 | step: 1, 1336 | }, 1337 | blackMemory: { 1338 | initData: false, 1339 | input: MENU_INPUT_TYPE.check, 1340 | group: MENU_GROUP_TYPE.advanced, 1341 | }, 1342 | autoBlock: { 1343 | initData: false, // trueにしてはいけない(戒め) 1344 | input: MENU_INPUT_TYPE.check, 1345 | group: MENU_GROUP_TYPE.advanced, 1346 | }, 1347 | useRegModeDotAll: { 1348 | initData: true, 1349 | input: MENU_INPUT_TYPE.check, 1350 | group: MENU_GROUP_TYPE.advanced, 1351 | }, 1352 | resetSetting: { 1353 | input: MENU_INPUT_TYPE.btn, 1354 | group: MENU_GROUP_TYPE.advanced, 1355 | value: "Reset", 1356 | }, 1357 | resetBlackMemory: { 1358 | input: MENU_INPUT_TYPE.btn, 1359 | group: MENU_GROUP_TYPE.advanced, 1360 | value: "Reset", 1361 | }, 1362 | // ------------------------- 1363 | enableOldTweetDeckMode: { 1364 | initData: false, 1365 | input: MENU_INPUT_TYPE.check, 1366 | group: MENU_GROUP_TYPE.tweetDeck, 1367 | }, 1368 | autoLoadJQuery: { 1369 | initData: true, 1370 | input: MENU_INPUT_TYPE.check, 1371 | group: MENU_GROUP_TYPE.tweetDeck, 1372 | }, 1373 | // ------------------------- 1374 | debug_viewSettingMenu: { 1375 | initData: false, 1376 | input: MENU_INPUT_TYPE.check, 1377 | group: MENU_GROUP_TYPE.debug, 1378 | }, 1379 | debug_visibleCardDebugButton: { 1380 | initData: false, 1381 | input: MENU_INPUT_TYPE.check, 1382 | group: MENU_GROUP_TYPE.debug, 1383 | }, 1384 | debug_viewBlacklist: { 1385 | input: MENU_INPUT_TYPE.btn, 1386 | group: MENU_GROUP_TYPE.debug, 1387 | value: "Output", 1388 | }, 1389 | debug_viewMsgDB: { 1390 | input: MENU_INPUT_TYPE.btn, 1391 | group: MENU_GROUP_TYPE.debug, 1392 | value: "Output", 1393 | }, 1394 | debug_reInit: { 1395 | input: MENU_INPUT_TYPE.btn, 1396 | group: MENU_GROUP_TYPE.debug, 1397 | value: "Retry", 1398 | }, 1399 | }; 1400 | 1401 | // 元データ保存 1402 | for (let key in SETTING_LIST) { 1403 | if (SETTING_LIST[key].initData !== undefined) { 1404 | SETTING_LIST[key].data = SETTING_LIST[key].initData; 1405 | } 1406 | } 1407 | 1408 | // -------------------------------------------------- 1409 | 1410 | /** @type {Object. | null} */ 1411 | let lang_dict = null; 1412 | 1413 | /** @type {HTMLElement} */ 1414 | let parentDOM = null; 1415 | /** @type {MutationObserver} */ 1416 | let parent_observer = null; 1417 | /** @type {string} */ 1418 | let oldUrl = location.href; 1419 | /** @type {string} */ 1420 | let parent_id = null; 1421 | /** @type {HTMLElement} */ 1422 | let exMenuDOM = null; 1423 | 1424 | /** @type {MessageData[]} */ 1425 | const msgDB = []; 1426 | /** @type {Set} */ 1427 | const msgDB_id = new Set(); 1428 | /** @type {Set} */ 1429 | const blacklist_id = new Set(); 1430 | /** @type {Set} */ 1431 | const excludedUsersSet = new Set(); 1432 | 1433 | // 類似文字列検索使用フラグ 1434 | let levenshteinDistanceUseFlag = true; 1435 | let internalDataRefFlag = true; 1436 | // ページ読み込み停止フラグ 1437 | let stopFlag = false; 1438 | 1439 | /** 1440 | * 正規表現で以下を使用するか 1441 | * @enum {boolean} 1442 | */ 1443 | const useRegModeList = { 1444 | i: true, // ES1 : 大文字・小文字を区別しない 1445 | m: true, // ES3 : 複数行モード (^ や $ が各行の先頭/末尾にマッチ) 1446 | u: true, // ES6 : Unicodeモード (サロゲートペアを正しく処理) 1447 | s: true, // ES9 : dotAllモード (. が改行 \n にもマッチするようになる) 1448 | }; 1449 | let useRegMode = ""; 1450 | 1451 | // OldTweetDeck関連 1452 | let isPageOldTweetDeck = false; 1453 | let useOldTweetDeck = false; 1454 | 1455 | // ページ変更確認に使用 1456 | let body_isReservation = false; 1457 | let body_isWait = false; 1458 | // もっと見るを軽量で観測する為に使用 1459 | let existMoreTweet = false; 1460 | 1461 | // -------------------------------------------------- 1462 | const spaceRegList = [ 1463 | /[  \t]/gu, 1464 | /[\u00A0\u00AD\u034F\u061C]/gu, 1465 | /[\u115F\u1160\u17B4\u17B5\u180E]/gu, 1466 | // \u200Dが合成時に消失したため部分対処 1467 | /[\u2000-\u200C\u200E-\u200F\u202F\u205F\u2060-\u2064\u206A-\u206F\u2800]/gu, 1468 | /[\u3000\u3164]/gu, 1469 | /[\uFEFF\uFFA0]/gu, 1470 | /[\u{1D159}\u{1D173}-\u{1D17A}]/gu, 1471 | ]; 1472 | const normalizeRegList = [ 1473 | [/[ア-ヺ]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 0x60)], 1474 | [/[”“″‶〝‟]/gu, '"'], 1475 | [/[’‘′´‛‵']/gu, "'"], 1476 | ]; 1477 | const reRegExpReg = /\\x([0-9a-fA-F]{2})|\\u([0-9a-fA-F]{4})|\\u\{([0-9a-fA-F]{1,6})\}/g; 1478 | const CrLfReg = /[\r\n]/gu; 1479 | const spaceReg = / /g; 1480 | 1481 | const tweetUrlImgReg = /^https?:\/\/pbs\.twimg\.com\/media\//; 1482 | const tweetUrlVideoReg = /^https?:\/\/video\.twimg\.com\/tweet_video\//; 1483 | 1484 | // ========================================================================================== 1485 | // メッセージデータ保存 クラス 1486 | // ========================================================================================== 1487 | 1488 | /** 1489 | * リンクデータ 1490 | */ 1491 | class LinkData { 1492 | /** 1493 | * リンクデータ保存 1494 | * @param {string} href 1495 | * @param {string} value 1496 | */ 1497 | constructor(href, value) { 1498 | this.href = href; 1499 | this.value = value; 1500 | } 1501 | } 1502 | 1503 | /** 1504 | * メッセージデータ 1505 | */ 1506 | class MessageData { 1507 | /** 1508 | * メッセージデータ保存 1509 | * @param {string} url 1510 | * @param {HTMLElement} card 1511 | */ 1512 | constructor(url, card) { 1513 | this.base_url = url; 1514 | /** @type {HTMLElement} */ 1515 | this.card = card; 1516 | this.verify = false; 1517 | this.formality = false; 1518 | /** 1519 | * LinkDataを入れるオブジェクト 1520 | * @type {{mention: LinkData[], hashtag: LinkData[], symboltag: LinkData[], url: LinkData[]}} 1521 | */ 1522 | this.has_link_dict = { 1523 | mention:[], 1524 | hashtag:[], 1525 | symboltag: [], 1526 | url:[], 1527 | } 1528 | this.attach_img = false; 1529 | this.attach_file_list = []; 1530 | /** @type {MessageData | null} */ 1531 | this.reTweet = null; 1532 | /** @type {MessageData | null} */ 1533 | this.parentTweet = null; 1534 | /** @type {HTMLElement | null} */ 1535 | this.menuDOM = null; 1536 | 1537 | this._nsOneLoadFlag = false; 1538 | this._notTextDiv = false; 1539 | } 1540 | 1541 | /** 1542 | * カードデータ取得 1543 | * @returns {Promise | false} 1544 | */ 1545 | cardDataGet() { 1546 | const article = this._getArticle(); 1547 | if (!article) { 1548 | return false; 1549 | } 1550 | 1551 | let nameSpace_div = article.querySelectorAll(EX_QUERY_DICT.NAME_SPACE_QUERY); 1552 | nameSpace_div.forEach((div) => { 1553 | // 2回目以降はリツイート 1554 | if (this._nsOneLoadFlag) { 1555 | this._addReTweet(); 1556 | } 1557 | 1558 | if (internalDataRefFlag) { 1559 | try { 1560 | const toolbar = this.card.querySelector(`div[id][aria-label][role="group"]`); 1561 | const pr_list = Object.getOwnPropertyNames(toolbar); 1562 | const pr_name = pr_list.find((input)=>input.includes('__reactProps$')); 1563 | const data = toolbar[pr_name]; 1564 | this.internalData = data?.children[1]?.props?.retweetWithCommentLink?.state?.quotedStatus; 1565 | } catch (e){ 1566 | console.warn(`内部データ取得失敗`); 1567 | } 1568 | } 1569 | 1570 | // ユーザー名(id)取得 1571 | let name_span = div.querySelector(EX_QUERY_DICT.NAME_QUERY); 1572 | if (this._nsOneLoadFlag) { 1573 | this.reTweet._setName(name_span?.innerText); 1574 | } else { 1575 | this._setName(name_span?.innerText); 1576 | // フルネーム取得 1577 | if (this.internalData){ 1578 | this.fullName = this.internalData.user.name; 1579 | } 1580 | } 1581 | 1582 | // id取得(ついでに認証マーク判定) 1583 | let id_span = div.querySelectorAll(EX_QUERY_DICT.ID_QUERY); 1584 | id_span.forEach((span) => { 1585 | let fc = span.querySelector(EX_QUERY_DICT.VERIFY_FORMALITY_QUERY); 1586 | if (fc != null) { 1587 | if (this._nsOneLoadFlag) { 1588 | this.reTweet.formality = true; 1589 | } else { 1590 | this.formality = true; 1591 | } 1592 | } 1593 | fc = span.querySelector(EX_QUERY_DICT.VERIFY_QUERY); 1594 | if (fc != null) { 1595 | if (this._nsOneLoadFlag) { 1596 | this.reTweet.verify = true; 1597 | } else { 1598 | this.verify = true; 1599 | } 1600 | } else { 1601 | let tmp = span.innerText.trim(); 1602 | if (tmp.startsWith("@")) { 1603 | if (this._nsOneLoadFlag) { 1604 | this.reTweet.id = tmp; 1605 | } else { 1606 | this.id = tmp; 1607 | } 1608 | } 1609 | } 1610 | }); 1611 | 1612 | this._nsOneLoadFlag = true; 1613 | }); 1614 | 1615 | // 投稿時間取得 1616 | let time_elem = article.querySelector("time"); 1617 | if (!time_elem) { 1618 | return false; 1619 | } 1620 | if (!this._setDate(time_elem.dateTime)) return false; 1621 | 1622 | const pro = [ 1623 | // 画像添付確認 1624 | this._imgCheck(article), 1625 | // メニュー取得(...のこと) 1626 | this._getMenu(article), 1627 | ]; 1628 | 1629 | this._text_divs = article.querySelectorAll(EX_QUERY_DICT.TEXT_DIV_QUERY); 1630 | let text_div = this._text_divs?.[0]; 1631 | 1632 | let fullStr = ""; 1633 | let str = ""; 1634 | let emojiLst = []; 1635 | if (text_div) { 1636 | let tmp; 1637 | text_div.childNodes.forEach((elem) => { 1638 | if (useOldTweetDeck) { 1639 | if (elem.nodeType === Node.TEXT_NODE) { 1640 | tmp = elem.nodeValue; 1641 | str += tmp; 1642 | fullStr += tmp; 1643 | } else if (elem.tagName === "IMG") { 1644 | tmp = elem.alt; 1645 | emojiLst.push(tmp); 1646 | fullStr += tmp; 1647 | } 1648 | } else { 1649 | switch (elem.tagName) { 1650 | case "SPAN": 1651 | tmp = elem.innerText; 1652 | str += tmp; 1653 | fullStr += tmp; 1654 | break; 1655 | case "DIV": 1656 | let a = elem.querySelector(`a[href]`); 1657 | tmp = a?.innerText; 1658 | if (!tmp || !tmp.length || !a.href.startsWith("http")){ 1659 | break; 1660 | } 1661 | const hld = this.has_link_dict; 1662 | const ld = new LinkData(a.href, tmp); 1663 | switch (tmp.slice(0, 1)) { 1664 | case "@": // メンション 1665 | hld.mention.push(ld); 1666 | break; 1667 | case "#": // ハッシュタグ 1668 | hld.hashtag.push(ld); 1669 | break; 1670 | case "$": // シンボルタグ 1671 | hld.symboltag.push(ld); 1672 | break; 1673 | default: 1674 | hld.url.push(ld); 1675 | } 1676 | fullStr += ld.value; 1677 | break; 1678 | case "IMG": 1679 | tmp = elem.alt; 1680 | emojiLst.push(tmp); 1681 | fullStr += tmp; 1682 | break; 1683 | } 1684 | } 1685 | }); 1686 | } else { 1687 | this._notTextDiv = true; 1688 | } 1689 | 1690 | this._setFullMessage(fullStr); 1691 | this._setMessage(str); 1692 | this.emoji = emojiLst; 1693 | 1694 | return Promise.all(pro); 1695 | } 1696 | 1697 | /** 1698 | * 処理対象か判定/取得 1699 | * @returns {HTMLElement} 1700 | */ 1701 | _getArticle() { 1702 | let article; 1703 | if (useOldTweetDeck) { 1704 | article = this.card; 1705 | } else { 1706 | article = this.card.firstChild?.firstChild?.firstChild; 1707 | } 1708 | if (article?.tagName != "ARTICLE") { 1709 | return null; 1710 | } 1711 | return article; 1712 | } 1713 | 1714 | /** 1715 | * ReTweetである場合 1716 | * @returns {MessageData} 1717 | */ 1718 | _addReTweet() { 1719 | const md = new MessageData(this.base_url, null); 1720 | this.reTweet = md; 1721 | md.parentTweet = this; 1722 | return md; 1723 | } 1724 | 1725 | /** 1726 | * 名前設定 1727 | * @param {string} name 1728 | * @returns {undefined} 1729 | */ 1730 | _setName(name = "") { 1731 | this.name = name; 1732 | this.cleanName = normalize(name).replace(CrLfReg, " "); 1733 | } 1734 | 1735 | /** 1736 | * メッセージ設定 1737 | * @param {string} message 1738 | * @returns {undefined} 1739 | */ 1740 | _setMessage(message = "") { 1741 | this.message = message; 1742 | this.cleanMessage = normalize(message); 1743 | this.message_len = this.cleanMessage.length; 1744 | } 1745 | 1746 | /** 1747 | * メッセージ設定 1748 | * @param {string} full_message 1749 | * @returns {undefined} 1750 | */ 1751 | _setFullMessage(full_message = "") { 1752 | this.fullMessage = full_message; 1753 | this.cleanFullMessage = normalize(full_message); 1754 | this.full_message_len = this.cleanFullMessage.length; 1755 | } 1756 | 1757 | /** 1758 | * 日時データ設定 1759 | * @param {string} date 1760 | * @returns {boolean} 1761 | */ 1762 | _setDate(date) { 1763 | try { 1764 | this.dateTime = new Date(date); 1765 | } catch (e) { 1766 | console.error(e); 1767 | return false; 1768 | } 1769 | if (this.dateTime.toString() == "Invalid Date") { 1770 | log("日付変換失敗"); 1771 | return false; 1772 | } 1773 | this.time_value = this.dateTime.getTime(); 1774 | return true; 1775 | } 1776 | 1777 | /** 1778 | * 画像添付確認 1779 | * @param {HTMLElement} article 1780 | * @returns {Promise} 1781 | */ 1782 | _imgCheck(article) { 1783 | const this_ = this; 1784 | return new Promise((resolve) => { 1785 | setTimeout(() => { 1786 | let attach_img = article.querySelectorAll(EX_QUERY_DICT.IMAGE_QUERY); 1787 | //console.log(attach_img) 1788 | if (attach_img) { 1789 | for (let img of attach_img) { 1790 | const src = img.src; 1791 | if (tweetUrlImgReg.test(src)) { 1792 | // 画像 1793 | this_.attach_img = true; 1794 | this_.attach_file_list.push(src); 1795 | } else if (tweetUrlVideoReg.test(src)) { 1796 | // 動画(Gif含む) 1797 | this_.attach_img = true; 1798 | this_.attach_file_list.push(src); 1799 | } 1800 | } 1801 | } 1802 | resolve(); 1803 | }, 1000); 1804 | }); 1805 | } 1806 | 1807 | /** 1808 | * メニュー取得 1809 | * @param {HTMLElement} article 1810 | * @returns {Promise} 1811 | */ 1812 | _getMenu(article) { 1813 | const this_ = this; 1814 | return new Promise((resolve) => { 1815 | setTimeout(() => { 1816 | let menuDOMs = article.querySelectorAll(EX_QUERY_DICT.MENU_BUTTON_QUERY); 1817 | if (menuDOMs.length >= 3) { 1818 | this_.menuDOM = menuDOMs[0]; 1819 | } 1820 | resolve(); 1821 | }, 1000); 1822 | }); 1823 | } 1824 | } 1825 | 1826 | // ========================================================================================== 1827 | // 汎用 便利関数 1828 | // ========================================================================================== 1829 | /** 1830 | * ログを判別しやすく 1831 | * @param {string} str 1832 | * @returns {undefined} 1833 | */ 1834 | function log(str) { 1835 | if (DEBUG) { 1836 | console.log(`[${PRO_NAME}]`, str); 1837 | } 1838 | } 1839 | 1840 | /** 1841 | * DOMが設置されるまで待機 1842 | * @param {string} selectorTxt 1843 | * @param {Function} actionFunction 1844 | * @param {boolean} [bWaitOnce=true] 1845 | * @param {string} [actionFunction] 1846 | * @returns {undefined} 1847 | */ 1848 | function waitForKeyElements( 1849 | selectorTxt, //クエリセレクター 1850 | actionFunction, //実行関数 1851 | bWaitOnce = true, //要素が見つかっても検索を続ける 1852 | iframeName = null //iframeの中の要素の場合はiframeのidを書く 1853 | ) { 1854 | var targetNodes, btargetsFound; 1855 | var iframeDocument = document; 1856 | if (iframeName !== null) { 1857 | let iframeElem = document.getElementById(iframeName); 1858 | 1859 | if (!iframeElem) { 1860 | doRetry(); 1861 | return; 1862 | } 1863 | iframeDocument = iframeElem.contentDocument || iframeElem.contentWindow.document; 1864 | } 1865 | targetNodes = iframeDocument.querySelectorAll(selectorTxt); 1866 | 1867 | if (targetNodes && targetNodes.length > 0) { 1868 | btargetsFound = true; 1869 | targetNodes.forEach(function (element) { 1870 | var alreadyFound = element.dataset.found == "alreadyFound" ? "alreadyFound" : false; 1871 | 1872 | if (!alreadyFound) { 1873 | var cancelFound; 1874 | if (iframeName !== null) { 1875 | cancelFound = actionFunction(element, iframeDocument); 1876 | } else { 1877 | cancelFound = actionFunction(element); 1878 | } 1879 | if (cancelFound) { 1880 | btargetsFound = false; 1881 | } else { 1882 | element.dataset.found = "alreadyFound"; 1883 | } 1884 | } 1885 | }); 1886 | } else { 1887 | btargetsFound = false; 1888 | } 1889 | 1890 | if (btargetsFound && bWaitOnce) { 1891 | //終了 1892 | } else { 1893 | doRetry(); 1894 | } 1895 | 1896 | function doRetry() { 1897 | setTimeout(function () { 1898 | waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeName); 1899 | }, 300); 1900 | } 1901 | } 1902 | 1903 | /** 1904 | * 不明な空白を半角スペースに 1905 | * @param {string} str 1906 | * @returns {string} 1907 | */ 1908 | function unifiedSpace(str) { 1909 | str = str.toString(); 1910 | spaceRegList.forEach((reg) => { 1911 | str = str.replace(reg, " "); 1912 | }); 1913 | return str; 1914 | } 1915 | 1916 | /** 1917 | * 全ての文字を共通化 1918 | * @param {string} str 1919 | * @param {boolean} [useLowerCase=true] - 小文字に統一するか 1920 | * @returns {string} 1921 | */ 1922 | function normalize(str, useLowerCase = true) { 1923 | str = unifiedSpace(str).normalize("NFKC"); 1924 | normalizeRegList.forEach((regs) => { 1925 | str = str.replace(...regs); 1926 | }); 1927 | if (useLowerCase) { 1928 | str = str.toLowerCase(); 1929 | } 1930 | return str; 1931 | } 1932 | 1933 | /** 1934 | * 困った時のレーベンシュタイン距離 1935 | * @param {string} str1 1936 | * @param {string} str2 1937 | * @returns {number} 1938 | */ 1939 | function levenshteinDistance(str1, str2) { 1940 | let r, 1941 | c, 1942 | cost, 1943 | lr = str1.length, 1944 | lc = str2.length, 1945 | d = []; 1946 | 1947 | for (r = 0; r <= lr; r++) { 1948 | d[r] = [r]; 1949 | } 1950 | for (c = 0; c <= lc; c++) { 1951 | d[0][c] = c; 1952 | } 1953 | for (r = 1; r <= lr; r++) { 1954 | for (c = 1; c <= lc; c++) { 1955 | cost = str1.charCodeAt(r - 1) == str2.charCodeAt(c - 1) ? 0 : 1; 1956 | d[r][c] = Math.min(d[r - 1][c] + 1, d[r][c - 1] + 1, d[r - 1][c - 1] + cost); 1957 | } 1958 | } 1959 | return 1 - d[lr][lc] / Math.max(lr, lc); 1960 | } 1961 | 1962 | /** 1963 | * unicodeを復元 1964 | * @param {string} str 1965 | * @returns {string} 1966 | */ 1967 | function reRegExpStr(str) { 1968 | return unifiedSpace(str).replace(reRegExpReg, function (f, a, b, c) { 1969 | let str = a ?? b ?? c ?? null; 1970 | if (str == null) { 1971 | return f; 1972 | } 1973 | return String.fromCodePoint(parseInt(str, 16)); 1974 | }); 1975 | } 1976 | 1977 | // ========================================================================================== 1978 | // 便利関数 GM 1979 | // ========================================================================================== 1980 | 1981 | /** 1982 | * GMからjsonを取得 1983 | * @param {string} key 1984 | * @returns {Promise} 1985 | * @async 1986 | */ 1987 | async function getGM_json(key) { 1988 | let data = null; 1989 | try { 1990 | data = await GM.getValue(key, null); 1991 | } catch (e) { 1992 | console.error(e); 1993 | return null; 1994 | } 1995 | if (data != null) { 1996 | let jsonData = null; 1997 | try { 1998 | jsonData = JSON.parse(data); 1999 | } catch (e) { 2000 | console.error(e); 2001 | } 2002 | return jsonData; 2003 | } 2004 | return null; 2005 | } 2006 | 2007 | /** 2008 | * GMにjsonを保存 2009 | * @param {string} key 2010 | * @param {Object} obj 2011 | * @returns {Promise} 2012 | * @async 2013 | */ 2014 | async function setGM_json(key, obj) { 2015 | try { 2016 | await GM.setValue(key, JSON.stringify(obj)); 2017 | log(obj); 2018 | } catch (e) { 2019 | console.error(e); 2020 | } 2021 | } 2022 | 2023 | /** 2024 | * GMのjsonをリセット 2025 | * @param {string} key 2026 | * @param {boolean} [useConf=true] - 確認画面を強制するか 2027 | * @returns {undefined} 2028 | */ 2029 | async function resetGM_json(key, useConf = true) { 2030 | let cf = lang_dict.sureReset; 2031 | if (useConf && confirm(cf)) { 2032 | try { 2033 | await GM.deleteValue(key); 2034 | } catch (e) { 2035 | console.error(e); 2036 | } 2037 | location.reload(); 2038 | } 2039 | } 2040 | 2041 | // ========================================================================================== 2042 | // 便利関数 2043 | // ========================================================================================== 2044 | /** 2045 | * 正規表現リスト復元 2046 | * @param {string} tag 2047 | * @returns {undefined} 2048 | */ 2049 | function regRestoration(tag) { 2050 | let setting_data = SETTING_LIST[tag]; 2051 | if (!setting_data) { 2052 | console.warn("不明なtag:" + tag); 2053 | return; 2054 | } 2055 | let spText = setting_data.data.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n"); 2056 | setting_data.regexp_list = []; 2057 | spText.forEach((row) => { 2058 | if (row.trim().length && !row.startsWith("!#")) { 2059 | let tmpReg = reRegExpStr(normalize(row, false)); 2060 | try { 2061 | setting_data.regexp_list.push([new RegExp(tmpReg, useRegMode), row]); 2062 | } catch (e) { 2063 | console.error(`[${PRO_NAME}]`, tmpReg, e); 2064 | setting_data.isError = true; 2065 | } 2066 | } 2067 | }); 2068 | } 2069 | 2070 | /** 2071 | * 正規表現使用モード設定 2072 | * @returns {undefined} 2073 | */ 2074 | function setRegMode(){ 2075 | useRegModeList.s = SETTING_LIST.useRegModeDotAll.data; 2076 | 2077 | let tmp = ""; 2078 | for (key in useRegModeList) { 2079 | if (useRegModeList[key]){ 2080 | tmp += key; 2081 | } 2082 | } 2083 | useRegMode = tmp; 2084 | } 2085 | 2086 | /** 2087 | * メニュー項目にイベント設定 2088 | * @param {string} id 2089 | * @param {Function} callback 2090 | * @param {string} [type="click"] 2091 | * @returns {undefined} 2092 | */ 2093 | function setEvent(id, callback, type = "click") { 2094 | document.getElementById(ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + id)?.addEventListener(type, callback); 2095 | } 2096 | 2097 | // ========================================================================================== 2098 | // 初期設定関連 2099 | // ========================================================================================== 2100 | /** 2101 | * 初期設定(1度しか実行するな) 2102 | * @returns {Promise} 2103 | * @async 2104 | */ 2105 | async function init() { 2106 | // 親id取得 2107 | setParentId(); 2108 | 2109 | // 設定呼び出し 2110 | log("設定読み込み...開始"); 2111 | { 2112 | let jsonData = await getGM_json(SETTING_SAVE_KEY); 2113 | if (jsonData != null) { 2114 | for (let key in SETTING_LIST) { 2115 | if (key in jsonData) { 2116 | SETTING_LIST[key].data = jsonData[key]; 2117 | } 2118 | } 2119 | } 2120 | } 2121 | lang_dict = LANGUAGE_DICT[SETTING_LIST.language.data ?? "ja"]; 2122 | setRegMode(); 2123 | log("設定読み込み...完了"); 2124 | 2125 | //検知id再取得 2126 | if (SETTING_LIST.blackMemory.data) { 2127 | log("検知済id読み込み...開始"); 2128 | let jsonData = await getGM_json(BLACK_MEMORY_KEY); 2129 | if (jsonData != null) { 2130 | for (let i = 0, li = jsonData.length; i < li; i++) { 2131 | let id = jsonData[i]; 2132 | if (id.length > 1 && id.startsWith("@")) { 2133 | blacklist_id.add(id); 2134 | } else { 2135 | log("破損id:" + id); 2136 | } 2137 | } 2138 | } 2139 | log("検知済id読み込み...完了"); 2140 | } 2141 | 2142 | // フィルター正規表現設定 2143 | { 2144 | // ブラック表現リスト 2145 | regRestoration("blackTextReg"); 2146 | // ブラックフル表現リスト 2147 | regRestoration("blackFullTextReg"); 2148 | // ホワイト表現リスト 2149 | regRestoration("whiteTextReg"); 2150 | // ブラックRT表現リスト 2151 | //regRestoration("blackRtTextReg"); 2152 | // ブラック名前リスト 2153 | regRestoration("blackNameReg"); 2154 | // サブ垢定義用表現リスト 2155 | regRestoration("subDefinitionReg"); 2156 | // プラットフォーム表現リスト 2157 | regRestoration("platformBlackReg"); 2158 | 2159 | // 除外idリスト 2160 | let spText = SETTING_LIST.excludedUsers.data.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n"); 2161 | 2162 | spText.forEach((row) => { 2163 | if (row.trim().length && !row.startsWith("!#")) { 2164 | if (!row.startsWith("@")) { 2165 | row = "@" + row; 2166 | } 2167 | excludedUsersSet.add(row); 2168 | blacklist_id.delete(row); 2169 | } 2170 | }); 2171 | 2172 | // 投稿の言語を制限 2173 | const allowLang = SETTING_LIST.allowLang; 2174 | try { 2175 | let text = allowLang.data.trim(); 2176 | if (text.length) { 2177 | allowLang.regexp = new RegExp(text, "i"); 2178 | } 2179 | } catch (e) { 2180 | console.error(e); 2181 | allowLang.isError = true; 2182 | } 2183 | } 2184 | 2185 | // 画面移管時対応 2186 | const body_observer = new MutationObserver(bodyChangeEvent); 2187 | body_observer.observe(document.body, { 2188 | subtree: true, 2189 | childList: true, 2190 | }); 2191 | 2192 | document.addEventListener('click', () => { 2193 | const htsc = ELEM_NAME_DICT.HIDE_TITLE_SHOW_CLASS; 2194 | for (elem of document.getElementsByClassName(htsc)) { 2195 | elem.classList.remove(htsc); 2196 | } 2197 | }); 2198 | 2199 | // カスタムcss設定 2200 | try { 2201 | GM.addStyle(BASE_CSS); 2202 | GM.addStyle(SETTING_LIST.customCss.data); 2203 | } catch (e) { 2204 | console.error(e); 2205 | SETTING_LIST.customCss.isError = true; 2206 | } 2207 | 2208 | // 文章類似比較を実行するか 2209 | if (!SETTING_LIST.maxSaveTextSize.data || SETTING_LIST.maxSaveTextSize.data < SETTING_LIST.minSaveTextSize.data) { 2210 | levenshteinDistanceUseFlag = false; 2211 | } 2212 | // OldTweetDeckを使用するか 2213 | if (SETTING_LIST.enableOldTweetDeckMode.data && isPageOldTweetDeck) { 2214 | useOldTweetDeck = true; 2215 | document.body.classList.add(ELEM_NAME_DICT.USE_TWEET_DECK_CLASS); 2216 | } 2217 | // 2218 | if(!SETTING_LIST.useTwitterInternalData.data || useOldTweetDeck){ 2219 | internalDataRefFlag = false; 2220 | } 2221 | 2222 | 2223 | card_init(); 2224 | // 自動で設定画面を開く 2225 | if (SETTING_LIST.debug_viewSettingMenu.data) { 2226 | menuOpen(); 2227 | } 2228 | 2229 | if (useOldTweetDeck && SETTING_LIST.autoLoadJQuery.data && typeof $ === "undefined") { 2230 | let script = document.createElement("script"); 2231 | script.type = "text/javascript"; 2232 | script.onload = function () { 2233 | log("jQuery " + $.fn.jquery + " loaded successfully!"); 2234 | }; 2235 | script.src = "https://code.jquery.com/jquery-3.7.1.min.js"; 2236 | document.head.appendChild(script); 2237 | } 2238 | } 2239 | 2240 | // ========================================================================================== 2241 | // イベント関連 2242 | // ========================================================================================== 2243 | 2244 | /** 2245 | * メッセージの親を取得 2246 | * @returns {undefined} 2247 | */ 2248 | function setParentId() { 2249 | let url = oldUrl.replace(/https?:\/\/.*?\.com/, ""); 2250 | if (url.startsWith("/")) { 2251 | let urls = url.replace(/\?/, "/").split("/"); 2252 | let uid = urls[1] ?? urls[0]; 2253 | if (ALLOW_PAGE_SET.has(uid)) { 2254 | stopFlag = true; 2255 | return; 2256 | } 2257 | const isStatusType = urls[2] ?? ""; 2258 | console.log(`isStatusType: ${isStatusType}`); 2259 | if (!ALLOW_STATUS_SET.has(isStatusType)) { 2260 | stopFlag = true; 2261 | return; 2262 | } 2263 | isPageOldTweetDeck = isStatusType === "tweetdeck"; 2264 | if (uid) { 2265 | uid = "@" + uid; 2266 | log(`親投稿者: ${uid}`); 2267 | parent_id = uid; 2268 | stopFlag = false; 2269 | // 気分で消しとく 2270 | blacklist_id.delete(uid); 2271 | } 2272 | } 2273 | } 2274 | 2275 | /** 2276 | * 画面移管対応 2277 | * @returns {undefined} 2278 | */ 2279 | function bodyChangeEvent() { 2280 | // 更新過多で重くなるので同時実行禁止 2281 | if (body_isWait) { 2282 | body_isReservation = true; 2283 | return; 2284 | } 2285 | body_isWait = true; 2286 | // 反応しない場合用に一瞬待機 2287 | setTimeout(function () { 2288 | // URL変更時のみ 2289 | if (oldUrl !== location.href) { 2290 | oldUrl = location.href; 2291 | setParentId(); 2292 | } 2293 | if (!document.querySelector("." + ELEM_NAME_DICT.PARENT_CLASS)) { 2294 | // class 検知 2295 | if (parent_observer) { 2296 | parent_observer.disconnect(); 2297 | parent_observer = null; 2298 | } 2299 | card_init(); 2300 | } 2301 | body_isWait = false; 2302 | // 一応再実行 2303 | if (body_isReservation) { 2304 | body_isReservation = false; 2305 | bodyChangeEvent(); 2306 | } 2307 | }, SETTING_LIST.bodyObsTimeout.data); 2308 | } 2309 | 2310 | /** 2311 | * tabをtextareaで入力可能に 2312 | * @param {Object} e 2313 | * @returns {undefined} 2314 | */ 2315 | function OnTabKey(e) { 2316 | if (e.keyCode != 9) { 2317 | return; 2318 | } 2319 | e.preventDefault(); 2320 | 2321 | let obj = e.target; 2322 | 2323 | // 現在のカーソルの位置と、カーソルの左右の文字列を取得 2324 | var cursorPosition = obj.selectionStart; 2325 | var cursorLeft = obj.value.substr(0, cursorPosition); 2326 | var cursorRight = obj.value.substr(cursorPosition, obj.value.length); 2327 | 2328 | obj.value = cursorLeft + "\t" + cursorRight; 2329 | 2330 | // カーソルの位置を入力したタブの後ろにする 2331 | obj.selectionEnd = cursorPosition + 1; 2332 | } 2333 | 2334 | // ========================================================================================== 2335 | // メニュー関連 2336 | // ========================================================================================== 2337 | /** 2338 | * メニュー初期設定(実質1度しか実行するな) 2339 | * @returns {undefined} 2340 | */ 2341 | function menu_init() { 2342 | const w_exMenuDOM = document.createElement("div"); 2343 | const sub_details_list = {}; 2344 | for (let k in MENU_GROUP_TYPE) { 2345 | const v = MENU_GROUP_TYPE[k]; 2346 | if (v !== MENU_GROUP_TYPE.basic) { 2347 | const e = document.createElement("details"); 2348 | sub_details_list[v] = e; 2349 | e.innerHTML = `${lang_dict[`menu_${v}`]}`; 2350 | } 2351 | } 2352 | w_exMenuDOM.innerHTML = lang_dict.menu_warn; 2353 | for (let key in SETTING_LIST) { 2354 | let item = SETTING_LIST[key]; 2355 | // 入力欄作成 2356 | let inputType = item?.input ?? ""; 2357 | let input_elem = document.createElement("input"); 2358 | input_elem.type = inputType; 2359 | input_elem.id = ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + key; 2360 | let add_elem = null; 2361 | switch (inputType) { 2362 | case MENU_INPUT_TYPE.text: 2363 | input_elem.value = item.data; 2364 | break; 2365 | case MENU_INPUT_TYPE.num: 2366 | input_elem.value = item.data; 2367 | if (item?.min != null) { 2368 | input_elem.min = item.min; 2369 | } 2370 | if (item?.max != null) { 2371 | input_elem.max = item.max; 2372 | } 2373 | if (item?.step != null) { 2374 | input_elem.step = item.step; 2375 | } 2376 | break; 2377 | case MENU_INPUT_TYPE.check: 2378 | input_elem.checked = item?.data ?? false; 2379 | add_elem = document.createElement("label"); 2380 | add_elem.htmlFor = input_elem.id; 2381 | break; 2382 | case MENU_INPUT_TYPE.btn: 2383 | input_elem.value = item.value; 2384 | break; 2385 | case MENU_INPUT_TYPE.textarea: 2386 | input_elem = document.createElement("textarea"); 2387 | input_elem.value = item.data; 2388 | break; 2389 | case MENU_INPUT_TYPE.select: 2390 | input_elem = document.createElement("select"); 2391 | if (item?.select) { 2392 | let tmp = ""; 2393 | for (let key in item.select) { 2394 | tmp += ``; 2395 | } 2396 | input_elem.innerHTML = tmp; 2397 | } 2398 | break; 2399 | default: 2400 | console.warn("対応していない形式", item); 2401 | continue; 2402 | } 2403 | 2404 | // 項目を囲うdiv 2405 | let div = document.createElement("div"); 2406 | // 名前 2407 | const trans_name = lang_dict[MENU_LANG_KEY + key + MENU_LANG_KEY_NAME]; 2408 | if (trans_name) { 2409 | let name_elem = document.createElement("p"); 2410 | name_elem.textContent = trans_name; 2411 | name_elem.classList.add(ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + "_name"); 2412 | div.appendChild(name_elem); 2413 | } else if (trans_name == null) { 2414 | console.warn(`翻訳情報の欠落: ${MENU_LANG_KEY + key + MENU_LANG_KEY_NAME}`); 2415 | } 2416 | // 説明 2417 | const trans_explanation = lang_dict[MENU_LANG_KEY + key + MENU_LANG_KEY_EXPLANATION]; 2418 | if (trans_explanation) { 2419 | let ex_elem = document.createElement("p"); 2420 | ex_elem.innerHTML = trans_explanation.replace(/\n/g, "
"); 2421 | div.appendChild(ex_elem); 2422 | } else if (trans_name == null) { 2423 | console.warn(`翻訳情報の欠落: ${MENU_LANG_KEY + key + MENU_LANG_KEY_EXPLANATION}`); 2424 | } 2425 | 2426 | div.appendChild(input_elem); 2427 | if (add_elem) { 2428 | div.appendChild(add_elem); 2429 | } 2430 | 2431 | if (item.isError) { 2432 | let errDOM = document.createElement("p"); 2433 | errDOM.classList.add(EX_MENU_ITEM_ERROR_CLASS); 2434 | errDOM.textContent = lang_dict.menu_error; 2435 | div.appendChild(errDOM); 2436 | } 2437 | 2438 | if (MENU_GROUP_TYPE.basic == item.group) { 2439 | w_exMenuDOM.appendChild(div); 2440 | } else { 2441 | let e = sub_details_list[item.group]; 2442 | if (e) { 2443 | e.appendChild(div); 2444 | } else { 2445 | console.warn("存在しないグループ:", item.group); 2446 | } 2447 | } 2448 | } 2449 | for (let k in sub_details_list) { 2450 | w_exMenuDOM.appendChild(sub_details_list[k]); 2451 | } 2452 | // 画面右下のボタン系 2453 | { 2454 | let div = document.createElement("div"); 2455 | div.id = ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + "__btns"; 2456 | let btn_elem = document.createElement("input"); 2457 | btn_elem.type = "button"; 2458 | btn_elem.value = lang_dict.save; 2459 | btn_elem.id = ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + "__save"; 2460 | div.appendChild(btn_elem); 2461 | btn_elem = document.createElement("input"); 2462 | btn_elem.type = "button"; 2463 | btn_elem.value = lang_dict.close; 2464 | btn_elem.id = ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + "__close"; 2465 | div.appendChild(btn_elem); 2466 | w_exMenuDOM.appendChild(div); 2467 | } 2468 | exMenuDOM = document.createElement("div"); 2469 | exMenuDOM.id = EX_MENU_ID; 2470 | exMenuDOM.lang = SETTING_LIST.language.data; 2471 | if (isMobile) { 2472 | exMenuDOM.classList.add(ELEM_NAME_DICT.MOBILE_FLAG_CLASS); 2473 | } else { 2474 | exMenuDOM.classList.add(ELEM_NAME_DICT.PC_FLAG_CLASS); 2475 | } 2476 | exMenuDOM.appendChild(w_exMenuDOM); 2477 | } 2478 | 2479 | /** 2480 | * メニューを開く 2481 | * @returns {undefined} 2482 | */ 2483 | function menuOpen() { 2484 | log("メニュー表示...開始"); 2485 | if (!exMenuDOM) { 2486 | menu_init(); 2487 | } 2488 | 2489 | // DOM 取得 2490 | let menu_elem = document.getElementById(EX_MENU_ID); 2491 | if (!menu_elem) { 2492 | // なければ複製して追加 2493 | menu_elem = exMenuDOM.cloneNode(true); 2494 | document.body.appendChild(menu_elem); 2495 | setEvent("__save", menuSave); 2496 | setEvent("__close", menuClose); 2497 | 2498 | setEvent("customCss", OnTabKey, "keydown"); 2499 | setEvent("resetSetting", menuReset); 2500 | setEvent("resetBlackMemory", blacklistReset); 2501 | setEvent("debug_viewBlacklist", function () { 2502 | console.log(blacklist_id); 2503 | }); 2504 | setEvent("debug_viewMsgDB", function () { 2505 | console.log(msgDB_id, msgDB); 2506 | }); 2507 | setEvent("debug_reInit", card_init); 2508 | } 2509 | menu_elem.classList.add(ELEM_NAME_DICT.EX_MENU_OPEN_CLASS); 2510 | log("メニュー表示...完了"); 2511 | } 2512 | 2513 | /** 2514 | * メニューを閉じる 2515 | * @returns {undefined} 2516 | */ 2517 | function menuClose() { 2518 | log("メニュー非表示"); 2519 | let menu_elem = document.getElementById(EX_MENU_ID); 2520 | if (menu_elem) { 2521 | menu_elem.classList.remove(ELEM_NAME_DICT.EX_MENU_OPEN_CLASS); 2522 | } 2523 | } 2524 | 2525 | /** 2526 | * 設定項目保存 2527 | * @returns {Promise} 2528 | * @async 2529 | */ 2530 | async function menuSave() { 2531 | log("設定保存...開始"); 2532 | for (let key in SETTING_LIST) { 2533 | let item = SETTING_LIST[key]; 2534 | 2535 | let elem = document.getElementById(ELEM_NAME_DICT.EX_MENU_ITEM_BASE_ID + key); 2536 | if (elem) { 2537 | let data = null; 2538 | switch (item.input) { 2539 | case MENU_INPUT_TYPE.text: 2540 | case MENU_INPUT_TYPE.textarea: 2541 | data = elem.value; 2542 | break; 2543 | case MENU_INPUT_TYPE.num: 2544 | data = parseFloat(elem.value); 2545 | if (item?.min != null && item.min > data) { 2546 | data = item.min; 2547 | } 2548 | if (item?.max != null && item.max < data) { 2549 | data = item.max; 2550 | } 2551 | break; 2552 | case MENU_INPUT_TYPE.check: 2553 | data = elem.checked; 2554 | break; 2555 | case MENU_INPUT_TYPE.select: 2556 | for (let i = 0; i < elem.length; i++) { 2557 | if (elem[i]?.selected) { 2558 | data = elem[i].value; 2559 | break; 2560 | } 2561 | } 2562 | break; 2563 | default: 2564 | continue; 2565 | } 2566 | if (data == null) { 2567 | continue; 2568 | } 2569 | item.data = data; 2570 | } 2571 | } 2572 | let dic = {}; 2573 | for (let key in SETTING_LIST) { 2574 | let d = SETTING_LIST[key].data; 2575 | let _d = SETTING_LIST[key].initData; 2576 | if (d != null && d != _d) { 2577 | dic[key] = d; 2578 | } 2579 | } 2580 | await setGM_json(SETTING_SAVE_KEY, dic); 2581 | log("設定保存...完了"); 2582 | menuClose(); 2583 | } 2584 | 2585 | /** 2586 | * 設定項目リセット 2587 | * @returns {undefined} 2588 | */ 2589 | async function menuReset() { 2590 | resetGM_json(SETTING_SAVE_KEY); 2591 | } 2592 | 2593 | // ========================================================================================== 2594 | // カード 関連 2595 | // ========================================================================================== 2596 | 2597 | /** 2598 | * カード初期化 2599 | * @returns {undefined} 2600 | */ 2601 | function card_init() { 2602 | log("初期化中..."); 2603 | 2604 | let tmp = document.querySelector(EX_QUERY_DICT.OBS_QUERY); 2605 | if (tmp && tmp.classList.contains(ELEM_NAME_DICT.PARENT_CLASS)) { 2606 | console.log("MutationObserverはすでに設定されています!"); 2607 | return; 2608 | } 2609 | // もっと見るフラグ初期化 2610 | existMoreTweet = false; 2611 | 2612 | // 表示待機 2613 | waitForKeyElements(EX_QUERY_DICT.OBS_QUERY, function () { 2614 | // (投稿リストの)親を取得 2615 | parentDOM = document.querySelector(EX_QUERY_DICT.OBS_QUERY); 2616 | if (parentDOM == null) { 2617 | log(`(${EX_QUERY_DICT.OBS_QUERY})が見つけれませんでした`); 2618 | return; 2619 | } 2620 | parentDOM.classList.add(ELEM_NAME_DICT.PARENT_CLASS); 2621 | 2622 | // DOM変更検知(イベント) 2623 | parent_observer = new MutationObserver((records) => { 2624 | records.forEach((record) => { 2625 | let addNodes = record.addedNodes; 2626 | if (addNodes.length) { 2627 | addNodes.forEach((addNode) => { 2628 | cardCheck(addNode); 2629 | }); 2630 | } 2631 | }); 2632 | }); 2633 | const observe_setting = { 2634 | childList: true, 2635 | }; 2636 | if (useOldTweetDeck) { 2637 | observe_setting.subtree = true; 2638 | } 2639 | parent_observer.observe(parentDOM, observe_setting); 2640 | 2641 | // 先頭部分が取得出来ていないので再実行 2642 | parentDOM.querySelectorAll(EX_QUERY_DICT.RE_QUERY).forEach((elem) => { 2643 | cardCheck(elem); 2644 | }); 2645 | }); 2646 | } 2647 | 2648 | /** 2649 | * 処理対象判定&処理実行(疑似的に非同期処理に) 2650 | * @param {HTMLElement} card_elem 2651 | * @returns {undefined} 2652 | */ 2653 | function cardCheck(card_elem) { 2654 | if (useOldTweetDeck && !(card_elem instanceof HTMLElement)) { 2655 | return; 2656 | } 2657 | 2658 | // 処理は1度のみ 2659 | const CHECK_CLASS = ELEM_NAME_DICT.CHECK_CLASS; 2660 | if (card_elem.classList.contains(CHECK_CLASS)) { 2661 | return; 2662 | } 2663 | card_elem.classList.add(CHECK_CLASS); 2664 | 2665 | // もっと見るが判定されてしまう問題をゴリ押しで対処 2666 | if (existMoreTweet) { 2667 | const MORE_TWEET_CLASS = ELEM_NAME_DICT.MORE_TWEET_CLASS; 2668 | let tmp_elem = card_elem; 2669 | for (let i = 0; i < 5; i++) { 2670 | tmp_elem = tmp_elem.previousElementSibling; 2671 | if (!tmp_elem) { 2672 | break; 2673 | } 2674 | if (tmp_elem.classList.contains(MORE_TWEET_CLASS)) { 2675 | card_elem.classList.add(MORE_TWEET_CLASS); 2676 | return; 2677 | } 2678 | } 2679 | } else { 2680 | if (card_elem.querySelector("h2")) { 2681 | existMoreTweet = true; 2682 | card_elem.classList.add(ELEM_NAME_DICT.MORE_TWEET_CLASS); 2683 | } 2684 | } 2685 | 2686 | const md = new MessageData(oldUrl, card_elem); 2687 | // 有効なデータか判定 2688 | let getPro = md.cardDataGet(); 2689 | if (getPro === false) return; 2690 | getPro 2691 | .then(() => { 2692 | let ret = commentFilter(md); 2693 | let id; 2694 | 2695 | switch (ret[0]) { 2696 | case FILTED_HIDDEN_ID.processed: 2697 | // 処理済 2698 | return; 2699 | case FILTED_HIDDEN_ID.evaluated: 2700 | // 取得,判定済投稿 2701 | return; 2702 | case FILTED_HIDDEN_ID.newEntry: 2703 | // 問題なし 2704 | addDB(md); 2705 | return; 2706 | case FILTED_HIDDEN_ID.commentFilterDetection: 2707 | // コメントフィルターに反応 2708 | hideComment(md, lang_dict.filterDetection, `comment_${lang_dict.filter}「/${ret[1]}/${useRegMode}」`); 2709 | return; 2710 | case FILTED_HIDDEN_ID.fullCommentFilterDetection: 2711 | // フルコメントフィルターに反応 2712 | hideComment(md, lang_dict.filterDetection, `full_comment_${lang_dict.filter}「/${ret[1]}/${useRegMode}」`); 2713 | return; 2714 | case FILTED_HIDDEN_ID.commentEmojiOnly: 2715 | // 絵文字のみ(スパム) 2716 | hideComment(md, lang_dict.emojiOnly, "comment"); 2717 | return; 2718 | case FILTED_HIDDEN_ID.textDuplication: 2719 | // コピペ 2720 | hideComment(md, lang_dict.textDuplication, `${lang_dict.similarity}:${((ret[1] * 10000) | 0) / 100}%`); 2721 | return; 2722 | case FILTED_HIDDEN_ID.highUsage: 2723 | // 異常なハッシュタグの使用 2724 | hideComment(md, lang_dict.highUsage, `${lang_dict.usageCount}: ${ret[1]}`); 2725 | return; 2726 | case FILTED_HIDDEN_ID.selfCitation: 2727 | // 自分自身の引用 2728 | hideComment(md, lang_dict.selfCitation); 2729 | return; 2730 | case FILTED_HIDDEN_ID.nameFilterDetection: 2731 | // 名前フィルターに反応 2732 | hideComment(md, lang_dict.filterDetection, `name_${lang_dict.filter}「/${ret[1]}/${useRegMode}」`); 2733 | return; 2734 | case FILTED_HIDDEN_ID.nameEmojiOnly: 2735 | // 名前が絵文字のみ 2736 | hideComment(md, lang_dict.emojiOnly, "name"); 2737 | return; 2738 | case FILTED_HIDDEN_ID.verifyRtBlock: 2739 | // 認証済アカウントをRTするな 2740 | hideComment(md, lang_dict.verifyRtBlock); 2741 | return; 2742 | case FILTED_HIDDEN_ID.symbolUsage: 2743 | // 異常なシンボルタグの使用 2744 | hideComment(md, lang_dict.symbolUsage, `${lang_dict.usageCount}: ${ret[1]}`); 2745 | return; 2746 | case FILTED_HIDDEN_ID.detectedElsewhere: 2747 | // 他で検出済 2748 | hideComment(md, lang_dict.detectedElsewhere); 2749 | return; 2750 | case FILTED_HIDDEN_ID.authenticatedAccount: 2751 | // 認証済アカウント 2752 | hideComment(md, lang_dict.authenticatedAccount); 2753 | return; 2754 | case FILTED_HIDDEN_ID.unauthorizedLanguage: 2755 | // 投稿言語の制限 2756 | hideComment(md, lang_dict.unauthorizedLanguage, `${ret[1]}`); 2757 | return; 2758 | case FILTED_HIDDEN_ID.selfCitationSub: 2759 | // サブ垢で己をRTすんな 2760 | hideComment(md, lang_dict.selfCitationSub, `${lang_dict.filter}「/${ret[1]}/${useRegMode}」`); 2761 | return; 2762 | case FILTED_HIDDEN_ID.contributtonCount: 2763 | // 連投検出 2764 | hideComment(md, lang_dict.contributtonCount); 2765 | return; 2766 | case FILTED_HIDDEN_ID.rtContributtonCount: 2767 | // RT連投検出 2768 | hideComment(md, lang_dict.rtContributtonCount); 2769 | return; 2770 | case FILTED_HIDDEN_ID.rtSharingSeries: 2771 | // 同一ユーザーRT検出 2772 | for (let msgData of ret[1]) { 2773 | hideComment(msgData, lang_dict.rtSharingSeries); 2774 | } 2775 | return; 2776 | case FILTED_HIDDEN_ID.platformFilterDetection: 2777 | // プラットフォームフィルター検出 2778 | hideComment(md, lang_dict.platformFilterDetection, `platform_${lang_dict.filter}「/${ret[1]}/${useRegMode}」`) 2779 | return; 2780 | } 2781 | }) 2782 | .catch(console.warn); 2783 | } 2784 | 2785 | // ========================================================================================== 2786 | // フィルター 2787 | // ========================================================================================== 2788 | /** 2789 | * コメントをフィルタリング 2790 | * @param {MessageData} md 2791 | * @returns {number} 2792 | */ 2793 | function commentFilter(md) { 2794 | // log(md); 2795 | 2796 | // 投稿主保護 2797 | if (md.id == parent_id) { 2798 | addDB(md); 2799 | return [FILTED_HIDDEN_ID.processed]; 2800 | } 2801 | // 除外ユーザー保護 2802 | if (excludedUsersSet.has(md.id)) { 2803 | addDB(md); 2804 | return [FILTED_HIDDEN_ID.processed]; 2805 | } 2806 | // 認証公式アカウント保護 2807 | if (SETTING_LIST.formalityCare.data && md.formality) { 2808 | addDB(md); 2809 | return [FILTED_HIDDEN_ID.processed]; 2810 | } 2811 | // blacklist_id比較 2812 | if (blacklist_id.has(md.id)) { 2813 | return [FILTED_HIDDEN_ID.detectedElsewhere]; 2814 | } 2815 | // 認証済アカウント強制ブロック 2816 | if (SETTING_LIST.verifyBlock.data && md.verify) { 2817 | return [FILTED_HIDDEN_ID.authenticatedAccount]; 2818 | } 2819 | // 投稿プラットフォームフィルター 2820 | if (internalDataRefFlag && md.internalData?.source_name) { 2821 | const source_name = normalize(md.internalData.source_name); 2822 | for (let reg of SETTING_LIST.platformBlackReg.regexp_list) { 2823 | if (reg[0].test(source_name)) { 2824 | return [FILTED_HIDDEN_ID.platformFilterDetection, reg[1]]; 2825 | } 2826 | } 2827 | } 2828 | // 投稿言語の制限 2829 | if (!useOldTweetDeck) { 2830 | for (let div of md._text_divs) { 2831 | if (!SETTING_LIST.allowLang.regexp.test(div.lang)) { 2832 | return [FILTED_HIDDEN_ID.unauthorizedLanguage, div.lang]; 2833 | } 2834 | } 2835 | } 2836 | 2837 | // 無言で無言の引用リツイートしている場合 2838 | if (md.reTweet && md._notTextDiv) { 2839 | // 自分自身の場合 2840 | if (SETTING_LIST.oneselfRetweetBlock.data && md.reTweet.id == md.id) { 2841 | return [FILTED_HIDDEN_ID.selfCitation]; 2842 | } 2843 | // 認証済アカウントをRTするな 2844 | if (SETTING_LIST.verifyRtBlock.data && md.reTweet.verify) { 2845 | return [FILTED_HIDDEN_ID.verifyRtBlock]; 2846 | } 2847 | //サブ垢判定 2848 | if (SETTING_LIST.oneselfSubRetweetBlock) { 2849 | for (let reg of SETTING_LIST.subDefinitionReg.regexp_list) { 2850 | if (md.cleanName.replace(reg[0], "") == md.reTweet.cleanName.replace(reg[0], "")) { 2851 | return [FILTED_HIDDEN_ID.selfCitationSub, reg[1]]; 2852 | } 2853 | } 2854 | } 2855 | } 2856 | const message = md.cleanMessage; 2857 | if (SETTING_LIST.emojiOnryBlock.data && !message.replace(spaceReg, "").length && !md.attach_img) { 2858 | return [FILTED_HIDDEN_ID.commentEmojiOnly]; 2859 | } 2860 | if (SETTING_LIST.emojiOnryNameBlock.data && !md.name.replace(spaceReg, "")?.length) { 2861 | md.name = md.id; 2862 | return [FILTED_HIDDEN_ID.nameEmojiOnly]; 2863 | } 2864 | 2865 | // 引用リツイートしている場合 2866 | if (md.reTweet && !md._notTextDiv) { 2867 | // 自分自身の場合 2868 | if (SETTING_LIST.oneselfRetweetBlock.data && md.reTweet.id == md.id) { 2869 | return [FILTED_HIDDEN_ID.selfCitation]; 2870 | } 2871 | // 認証済アカウントをRTするな 2872 | if (SETTING_LIST.verifyRtBlock.data && md.reTweet.verify) { 2873 | return [FILTED_HIDDEN_ID.verifyRtBlock]; 2874 | } 2875 | //サブ垢判定 2876 | if (SETTING_LIST.oneselfSubRetweetBlock) { 2877 | for (let reg of SETTING_LIST.subDefinitionReg.regexp_list) { 2878 | if (md.cleanName.replace(reg[0], "") == md.reTweet.cleanName.replace(reg[0], "")) { 2879 | return [FILTED_HIDDEN_ID.selfCitationSub, reg[1]]; 2880 | } 2881 | } 2882 | } 2883 | } 2884 | 2885 | // コメントフィルターによる検出 2886 | for (let reg of SETTING_LIST.blackTextReg.regexp_list) { 2887 | if (reg[0].test(message)) { 2888 | return [FILTED_HIDDEN_ID.commentFilterDetection, reg[1]]; 2889 | } 2890 | } 2891 | // フルコメントフィルターによる検出 2892 | const full_message = md.cleanFullMessage; 2893 | for (let reg of SETTING_LIST.blackFullTextReg.regexp_list) { 2894 | if (reg[0].test(full_message)) { 2895 | return [FILTED_HIDDEN_ID.fullCommentFilterDetection, reg[1]]; 2896 | } 2897 | } 2898 | // 名前フィルターによる検出 2899 | const username = md.cleanName; 2900 | for (let reg of SETTING_LIST.blackNameReg.regexp_list) { 2901 | if (reg[0].test(username)) { 2902 | return [FILTED_HIDDEN_ID.nameFilterDetection, reg[1]]; 2903 | } 2904 | } 2905 | 2906 | // 異常なハッシュタグの使用回数 2907 | let hashtagCou = message.match(/#[^ ]+/g)?.length ?? 0; 2908 | if (hashtagCou >= SETTING_LIST.maxHashtagCount.data) { 2909 | return [FILTED_HIDDEN_ID.highUsage, hashtagCou]; 2910 | } 2911 | // 異常なシンボルタグの使用回数 2912 | let symboltagCou = message.match(/\$[^ ]+/g)?.length ?? 0; 2913 | if (symboltagCou >= SETTING_LIST.maxSymboltagCount.data) { 2914 | return [FILTED_HIDDEN_ID.symbolUsage, symboltagCou]; 2915 | } 2916 | 2917 | // 短い文字列は比較しない(誤爆対処) 2918 | let min_sts = SETTING_LIST.minSaveTextSize.data; 2919 | if (levenshteinDistanceUseFlag && md.message_len >= min_sts) { 2920 | // コピぺチェック 2921 | let max_sts = SETTING_LIST.maxSaveTextSize.data; 2922 | let al = md.message_len; 2923 | let am = md.time_value; 2924 | for (let msgData of msgDB) { 2925 | let a = message; 2926 | let b = msgData.cleanMessage; 2927 | let bl = msgData.message_len; 2928 | let m = Math.min(al, bl, max_sts); 2929 | if (m < min_sts) { 2930 | continue; 2931 | } 2932 | if (m != al) { 2933 | a = a.substring(0, m); 2934 | } 2935 | if (m != bl) { 2936 | b = b.substring(0, m); 2937 | } 2938 | 2939 | // 一度取得したツイートだった場合 2940 | let bm = msgData.time_value; 2941 | if (am == bm && md.id == msgData.id && md.cleanMessage == msgData.cleanMessage) { 2942 | return [FILTED_HIDDEN_ID.evaluated]; 2943 | } 2944 | 2945 | let ld = levenshteinDistance(a, b); 2946 | if (ld >= SETTING_LIST.msgResemblance.data) { 2947 | if (am > bm) { 2948 | return [FILTED_HIDDEN_ID.textDuplication, ld]; 2949 | } else { 2950 | blacklist_id.add(msgData.id); 2951 | break; 2952 | } 2953 | } 2954 | } 2955 | } else { 2956 | // 比較が行われない場合の代替処理 2957 | let am = md.time_value; 2958 | for (let msgData of msgDB) { 2959 | let bm = msgData.time_value; 2960 | if (am == bm && md.id == msgData.id && md.cleanMessage == msgData.cleanMessage) { 2961 | return [FILTED_HIDDEN_ID.evaluated]; 2962 | } 2963 | } 2964 | } 2965 | 2966 | let id = md.id; 2967 | if (!useOldTweetDeck && msgDB_id.has(id)) { 2968 | let bu = md.base_url; 2969 | // 連投検出 2970 | if (SETTING_LIST.maxContributtonCount.data > 0) { 2971 | let cou = 0; 2972 | for (let msgData of msgDB) { 2973 | if (msgData.id == id && msgData.base_url == bu) { 2974 | cou++; 2975 | } 2976 | } 2977 | if (SETTING_LIST.maxContributtonCount.data <= cou) { 2978 | return [FILTED_HIDDEN_ID.contributtonCount]; 2979 | } 2980 | } 2981 | // RT連投検出 2982 | if (SETTING_LIST.maxRtCount.data > 0 && md.reTweet) { 2983 | let cou = 0; 2984 | let rtl = new Set(md.reTweet.id); 2985 | for (let msgData of msgDB) { 2986 | if (msgData.id == id && msgData.base_url == bu && msgData.reTweet) { 2987 | cou++; 2988 | rtl.add(msgData.reTweet.id); 2989 | } 2990 | } 2991 | if (SETTING_LIST.maxRtCount.data <= cou) { 2992 | // 引用先も一応抹消 2993 | for (let rt of rtl) { 2994 | blacklist_id.add(rt); 2995 | } 2996 | return [FILTED_HIDDEN_ID.rtContributtonCount]; 2997 | } 2998 | } 2999 | // 同一ユーザーRT検出 3000 | if (SETTING_LIST.maxSameRtCount.data > 0 && md.reTweet) { 3001 | let rt = md.reTweet.id; 3002 | let cou = 0; 3003 | let us = new Set(id); 3004 | let usd = [md]; 3005 | for (let msgData of msgDB) { 3006 | if (msgData.base_url == bu && msgData.reTweet?.id == rt) { 3007 | cou++; 3008 | if (!us.has(msgData.id)) { 3009 | us.add(msgData.id); 3010 | usd.push(msgData); 3011 | } 3012 | } 3013 | } 3014 | if (SETTING_LIST.maxRtCount.data <= cou) { 3015 | // 引用先も一応抹消 3016 | blacklist_id.add(rt); 3017 | return [FILTED_HIDDEN_ID.rtSharingSeries, usd]; 3018 | } 3019 | } 3020 | } 3021 | 3022 | return [FILTED_HIDDEN_ID.newEntry]; 3023 | } 3024 | 3025 | // ========================================================================================== 3026 | // tweet操作 3027 | // ========================================================================================== 3028 | 3029 | /** 3030 | * ツイート非表示 3031 | * @param {MessageData} md 3032 | * @param {string} reason - 理由 3033 | * @param {boolean} [ch=true] - 2重参照回避 3034 | * @returns {undefined} 3035 | */ 3036 | function hideComment(md, reason, title="", ch = true) { 3037 | // TLTW以外では大人しく 3038 | if (stopFlag) { 3039 | addDB(md); 3040 | return; 3041 | } 3042 | // 認証済アカウントのみ判定 3043 | if (SETTING_LIST.verifyOnryFilter.data && !md.verify) { 3044 | addDB(md); 3045 | return; 3046 | } 3047 | blacklist_id.add(md.id); 3048 | 3049 | // フィルターによる検出 3050 | for (let reg of SETTING_LIST.whiteTextReg.regexp_list) { 3051 | if (reg[0].test(md.cleanMessage)) { 3052 | return; 3053 | } 3054 | } 3055 | 3056 | md.card.classList.add(ELEM_NAME_DICT.HIDE_CLASS); 3057 | 3058 | if (SETTING_LIST.visibleLog.data) { 3059 | let div = document.createElement("div"); 3060 | div.classList.add(ELEM_NAME_DICT.LOG_CLASS); 3061 | 3062 | let bstw = lang_dict.viewOriginalTweet; 3063 | 3064 | let isVerify = ""; 3065 | if (SETTING_LIST.visibleVerifyLog.data && md.verify) { 3066 | isVerify = VERIFY_SVG; 3067 | } 3068 | 3069 | // フルネームが取得できたならそれを使用 3070 | let name = md.name; 3071 | if(md.fullName){ 3072 | name = md.fullName; 3073 | } 3074 | 3075 | div.innerHTML = /* html */ ` 3076 | ${name} ${isVerify} 3077 | 3078 | 3079 | `; 3080 | { 3081 | const titleSpan = document.createElement("span"); 3082 | titleSpan.classList.add(ELEM_NAME_DICT.HIDE_TITLE_CLASS); 3083 | titleSpan.textContent = `[${reason}]`; 3084 | div.firstElementChild.prepend(titleSpan); 3085 | 3086 | if(title){ 3087 | titleSpan.title = title; 3088 | 3089 | const bubble = document.createElement('div'); 3090 | bubble.classList.add(ELEM_NAME_DICT.HIDE_TITLE_BUBBLE_CLASS); 3091 | bubble.textContent = title; 3092 | titleSpan.appendChild(bubble); 3093 | 3094 | const parentDiv = document.getElementsByClassName(ELEM_NAME_DICT.PARENT_CLASS)[0]; 3095 | const parentDivRect = parentDiv.getBoundingClientRect() 3096 | 3097 | function toggleTooltip(titleSpan, bubble) { 3098 | titleSpan.classList.toggle(ELEM_NAME_DICT.HIDE_TITLE_SHOW_CLASS); 3099 | 3100 | const bs = bubble.style; 3101 | if (titleSpan.classList.contains(ELEM_NAME_DICT.HIDE_TITLE_SHOW_CLASS)) { 3102 | // 画面端にはみ出ていないかチェックして位置調整 3103 | const bubbleRect = bubble.getBoundingClientRect(); 3104 | const padding = 5; 3105 | 3106 | if (bubbleRect.left - parentDivRect.left < padding) { 3107 | bs.left = `${padding}px`; 3108 | bs.transform = "translateX(0)"; 3109 | } else if (bubbleRect.right - parentDivRect.right > parentDiv.innerWidth - padding) { 3110 | bs.left = "auto"; 3111 | bs.right = `${padding}px`; 3112 | bs.transform = "translateX(0)"; 3113 | } else { 3114 | bs.left = "50%"; 3115 | bs.right = "auto"; 3116 | bs.transform = "translateX(-50%)"; 3117 | } 3118 | } else { 3119 | bs.left = ""; 3120 | bs.right = ""; 3121 | bs.transform = ""; 3122 | } 3123 | } 3124 | 3125 | titleSpan.addEventListener('click', (e) => { 3126 | e.stopPropagation(); 3127 | toggleTooltip(titleSpan, bubble); 3128 | }); 3129 | } 3130 | } 3131 | if (!useOldTweetDeck){ 3132 | if (SETTING_LIST.visibleBlockButton.data) { 3133 | const blockBtn = document.createElement("input"); 3134 | blockBtn.type = "button"; 3135 | blockBtn.value = "Block"; 3136 | div.firstElementChild.appendChild(blockBtn); 3137 | blockBtn.addEventListener("click", function () { 3138 | twitterMenuClicker(EX_QUERY_LIST_DICT.BLOCK_QUERY_LIST, md); 3139 | }); 3140 | } 3141 | if (SETTING_LIST.visibleReportButton.data) { 3142 | const reportBtn = document.createElement("input"); 3143 | reportBtn.type = "button"; 3144 | reportBtn.value = "Report"; 3145 | div.firstElementChild.appendChild(reportBtn); 3146 | reportBtn.addEventListener("click", function () { 3147 | twitterMenuClicker(EX_QUERY_LIST_DICT.REPORT_QUERY_LIST, md); 3148 | }); 3149 | } 3150 | // デバッグ 3151 | if (SETTING_LIST.debug_visibleCardDebugButton.data) { 3152 | const devBtn = document.createElement("input"); 3153 | devBtn.type = "button"; 3154 | devBtn.value = "dev"; 3155 | div.firstElementChild.appendChild(devBtn); 3156 | devBtn.addEventListener("click", function () { 3157 | console.log(md); 3158 | }); 3159 | } 3160 | } 3161 | md.card.prepend(div); 3162 | } 3163 | // 無駄な比較をしないように 3164 | if (ch) { 3165 | dbCommentBlock(md.id); 3166 | 3167 | if (!useOldTweetDeck && SETTING_LIST.autoBlock.data) { 3168 | console.log(`自動ブロック: ${md.name}(${md.id}) 3169 | 理由: ${reason}`); 3170 | 3171 | twitterMenuClicker(EX_QUERY_LIST_DICT.BLOCK_QUERY_LIST, md); 3172 | } 3173 | 3174 | // 検知済id保存 3175 | blacklistSave(); 3176 | } 3177 | } 3178 | 3179 | // -------------------------------------------------- 3180 | /** 3181 | * twitterのメニューを開くやつ 3182 | * @param {string[]} list 3183 | * @param {Object} mesData 3184 | * @returns {undefined} 3185 | */ 3186 | function twitterMenuClicker(list, mesData) { 3187 | if (!mesData.menuDOM) { 3188 | return; 3189 | } 3190 | mesData.menuDOM.click(); 3191 | blacklist_id.delete(mesData.id); 3192 | autoClick(list); 3193 | } 3194 | 3195 | // -------------------------------------------------- 3196 | /** 3197 | * 自動クリック 3198 | * @param {string[]} list 3199 | * @param {HTMLElement} [par=document.body] 3200 | * @param {Object} [i=0] 3201 | * @returns {undefined} 3202 | */ 3203 | function autoClick(list, par = document.body, i = 0) { 3204 | if (list.length <= i) { 3205 | return; 3206 | } 3207 | let q = list[i]; 3208 | let j = 0; 3209 | if (Array.isArray(q)) { 3210 | j = q[1]; 3211 | q = q[0]; 3212 | } 3213 | if (q === "__wait__") { 3214 | setTimeout(function () { 3215 | autoClick(list, par, i + 1); 3216 | }, j); 3217 | return; 3218 | } 3219 | let elem = par.querySelectorAll(q)?.[j]; 3220 | // console.log(q,elem) 3221 | if (elem) { 3222 | elem.click(); 3223 | autoClick(list, par, i + 1); 3224 | return; 3225 | } 3226 | setTimeout(function () { 3227 | autoClick(list, par, i); 3228 | }, 100); 3229 | } 3230 | 3231 | // ========================================================================================== 3232 | // msgDB操作 3233 | // ========================================================================================== 3234 | 3235 | /** 3236 | * msgDBに追加 3237 | * @param {MessageData} md 3238 | * @returns {undefined} 3239 | */ 3240 | function addDB(md) { 3241 | msgDB_id.add(md.id); 3242 | /*// 短いと誤爆するため 3243 | if (md.message_len < SETTING_LIST.minSaveTextSize.data) { 3244 | return; 3245 | }*/ 3246 | if (msgDB.length > SETTING_LIST.maxSaveLogSize.data) { 3247 | msgDB.shift(); 3248 | } 3249 | msgDB.push(md); 3250 | log(msgDB.length); 3251 | } 3252 | 3253 | /** 3254 | * 後からblacklist_idに登録された場合 3255 | * @param {string} id 3256 | * @returns {undefined} 3257 | */ 3258 | function dbCommentBlock(id) { 3259 | if (msgDB_id.has(id)) { 3260 | for (let i = msgDB.length - 1; i >= 0; i--) { 3261 | const md = msgDB[i]; 3262 | if (md?.id == id) { 3263 | msgDB.splice(i, 1); 3264 | if (md.base_url == oldUrl) { 3265 | hideComment(md, lang_dict.recursiveDetection, null, false); 3266 | } 3267 | } 3268 | } 3269 | msgDB_id.delete(id); 3270 | } 3271 | } 3272 | 3273 | /** 3274 | * 検知済id保存 3275 | * @returns {Promise} 3276 | * @async 3277 | */ 3278 | async function blacklistSave() { 3279 | if (SETTING_LIST.blackMemory.data) { 3280 | log("検知済id保存...開始"); 3281 | await setGM_json(BLACK_MEMORY_KEY, Array.from(blacklist_id)); 3282 | log("検知済id保存...完了"); 3283 | } 3284 | } 3285 | 3286 | /** 3287 | * 検知済idリセット 3288 | * @returns {undefined} 3289 | */ 3290 | function blacklistReset() { 3291 | resetGM_json(BLACK_MEMORY_KEY); 3292 | } 3293 | 3294 | // ========================================================================================== 3295 | // 本体 3296 | // ========================================================================================== 3297 | log("起動中..."); 3298 | 3299 | init(); 3300 | 3301 | // メニュー表示ボタン定義 3302 | if (GM?.registerMenuCommand && !isMobile) { 3303 | const menu_command_id_1 = GM.registerMenuCommand( 3304 | "Settings", 3305 | function (event) { 3306 | menuOpen(); 3307 | }, 3308 | { 3309 | accessKey: "s", 3310 | autoClose: true, 3311 | } 3312 | ); 3313 | } else { 3314 | let btn = document.createElement("input"); 3315 | btn.id = EX_MENU_OPEN_BUTTON; 3316 | btn.addEventListener("click", () => { 3317 | menuOpen(); 3318 | }); 3319 | btn.type = "button"; 3320 | btn.value = "disp:menu"; 3321 | waitForKeyElements(`body`, (e) => { 3322 | e.appendChild(btn); 3323 | }); 3324 | } 3325 | })(); 3326 | --------------------------------------------------------------------------------