まえがき
Twitterに"#᥋ᵍᶜₒ𝖛ịⅆ"というハッシュタグが流れているのを見た。内部的には"#5GCovid"と等しいようだった。タグの指す内容は置くとして、これが同一視されてるのは奇妙な気もした。
これは、他にも同様のハッシュタグを作れるのでは?と思って、変換器を作った。
nixeneko.github.io
Twitterのハッシュタグの仕様
(2021/01/29現在のため、変更される可能性がある)
ハッシュタグに使える文字
- Unicode 7.0に存在する文字である必要がある(8.0以降で追加された文字はハッシュタグにならない)
- 文字に関する情報はUnicode Character Database (UCD)としてまとまっている
- https://www.unicode.org/Public/7.0.0/ucd/UnicodeData.txt
- 見方は「UnicodeData.txt format」とかで探すと出てくる
- 文字に関する情報はUnicode Character Database (UCD)としてまとまっている
- ハッシュタグに使える文字は、基本的には、次のgeneral categoryをもつUnicode文字であるようだ:
- Lu……Letter, Uppercase
- Ll……Letter, Lowercase
- Lt……Letter, Titlecase
- Lm……Letter, Modifier
- Lo……Letter, Other
- Mn……Mark, Non-Spacing
- Mc……Mark, Spacing Combining
- Me……Mark, Enclosing
- Nd……Number, Decimal Digit
- このほかにも個別に追加されていると思われる記号がある。例:
- 「_」アンダースコア
- 「・」中黒
- 「゠」ダブルハイフン U+30A0 Katakana-Hiragana Double Hyphen
- 数字やマークだけではハッシュタグにはならない
ハッシュタグの同値判定の仕様の推測
- 大文字小文字を区別しない
- 文字を同一視するためにUnicode正規化が施されているはず。
- ある種の結合文字(combining marks)は無視される
- "#天デ部"と"#天テ\u0301\u3099部"(Combining Acute + Combining濁点)が同一視される(\uXXXXはUnicode文字を表す)
- 無視される結合文字の一覧は後掲する
- ダイアクリティカルマーク合成済み文字の場合は、正規化により分解されて、マークが除去される
- ひらがな・カタカナの、濁点・半濁点つきの文字とそうでない清音の文字は区別される。また、ひらがなとカタカナも区別される
- (Unicode正規化の互換分解で半角濁点U+FF9Eは合成用濁点U+3099に変換されるので以下の話は当然なので消した)
清音の仮名の次に半角濁点・半濁点が来ると、濁音・半濁音の仮名として扱われる(ひらがなカタカナともに) (間に一部の結合文字が挟まっててもよさそう)- 仮名と(半角または合成用)濁点・半濁点の間にU+034F COMBINING GRAPHEME JOINERが入ると濁点が消えるっぽい。なぜ…?
- ある種の10進数字は、ASCII文字の数字と同一視される
- Decimal digit valueの示す数字と同一視されるのかと思ったが、Decimal digit valueが存在していてもASCII数字と同一視されないものがあった。次である:
- U+0de6-U+0def SINHALA LITH DIGIT
- U+a9f0-U+a9f9 MYANMAR TAI LAING DIGIT
- U+104a0-U+104a9 OSMANYA DIGIT
- U+11066-U+1106f BRAHMI DIGIT
- U+110f0-U+110f9 SORA SOMPENG DIGIT
- U+11136-U+1113f CHAKMA DIGIT
- U+111d0-111d9 SHARADA DIGIT
- U+112f0-U+112f9 KHUDAWADI DIGIT
- U+114d0-U+114d9 TIRHUTA DIGIT
- U+11650-U+11659 MODI DIGIT
- U+116c0-U+116c9 TAKRI DIGIT
- U+118e0-U+118e9 WARANG CITI DIGIT
- U+16a60-U+16a69 MRO DIGIT
- U+16b50-U+16b59 PAHAWH HMONG DIGIT
- Decimal digit valueの示す数字と同一視されるのかと思ったが、Decimal digit valueが存在していてもASCII数字と同一視されないものがあった。次である:
- 他にも、特殊な同一視がみられる。個別にルールが追加されているのかもしれない。次のような例を見つけた:
無視されるCombining Marksの一覧を次に挙げる。抜けがあるかもしれない。
ignorable_marks = [ "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0305", "\u0306", "\u0307", "\u0308", "\u0309", "\u030a", "\u030b", "\u030c", "\u030d", "\u030e", "\u030f", "\u0310", "\u0311", "\u0312", "\u0313", "\u0314", "\u0315", "\u0316", "\u0317", "\u0318", "\u0319", "\u031a", "\u031b", "\u031c", "\u031d", "\u031e", "\u031f", "\u0320", "\u0321", "\u0322", "\u0323", "\u0324", "\u0325", "\u0326", "\u0327", "\u0328", "\u0329", "\u032a", "\u032b", "\u032c", "\u032d", "\u032e", "\u032f", "\u0330", "\u0331", "\u0332", "\u0333", "\u0334", "\u0335", "\u0336", "\u0337", "\u0338", "\u0339", "\u033a", "\u033b", "\u033c", "\u033d", "\u033e", "\u033f", "\u0340", "\u0341", "\u0342", "\u0343", "\u0344", "\u0345", "\u0346", "\u0347", "\u0348", "\u0349", "\u034a", "\u034b", "\u034c", "\u034d", "\u034e", "\u034f", "\u0350", "\u0351", "\u0352", "\u0353", "\u0354", "\u0355", "\u0356", "\u0357", "\u0358", "\u0359", "\u035a", "\u035b", "\u035c", "\u035d", "\u035e", "\u035f", "\u0360", "\u0361", "\u0362", "\u0363", "\u0364", "\u0365", "\u0366", "\u0367", "\u0368", "\u0369", "\u036a", "\u036b", "\u036c", "\u036d", "\u036e", "\u036f", "\u0610", "\u0611", "\u0612", "\u0613", "\u0614", "\u0615", "\u0616", "\u0617", "\u0618", "\u0619", "\u061a", "\u064b", "\u064c", "\u064d", "\u064e", "\u064f", "\u0650", "\u0651", "\u0652", "\u0653", "\u0654", "\u0655", "\u0656", "\u0657", "\u0658", "\u0659", "\u065a", "\u065b", "\u065c", "\u065d", "\u065e", "\u065f", "\u0670", "\u06d6", "\u06d7", "\u06d8", "\u06d9", "\u06da", "\u06db", "\u06dc", "\u06df", "\u06e0", "\u06e1", "\u06e2", "\u06e3", "\u06e4", "\u06e7", "\u06e8", "\u06ea", "\u06eb", "\u06ec", "\u06ed", "\u093c", "\u094d", "\u3099", "\u309a"]
ひらがな・カタカナ用の合成用濁点・半濁点も無視される。ものの、濁点や半濁点がついた仮名をそうでない仮名と同一視することはしない(「テ」と「デ」は別)。そのため、濁点や半濁点のつき得る仮名についた合成用濁点・半濁点は削除しない。
これは、Unicode正規化により、NFCなどで合成してから合成用濁点・半濁点を除去するという順序にするとうまくいくと思う。
難読化の仕組み
上記したハッシュタグの同値判定を逆方向にたどっていくと、最終的に同値に判定される様々なバリエーションが得られる。詳しくはソースでも見てください。行きあたりばったりなので見てわかるかは知らないですが…
github.com