Macも絵文字対応の5ch専ブラが欲しい…

私事なのですが、去年のApple Silicon(M1)がファンレスにも関わらず高性能な点で一目惚れし,
ずーっとこれまでWindowsしか使ってことなかった人間が思い切ってMacへと乗り換えてしまいました。
Windowsで良く使っていた5ch専ブラが使えないため、自作を始めたという記事です。
![スクリーンショット 2021-09-20 3.28.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537256/e30bac95-16c1-d514-cfd6-504cb739e55d.png)
（全然まだまだ未完成）

##記事概要
<dl>
  <dt>ポエム</dt>
  <dd>5ch専ブラを作り出した経緯理由</dd>
  <dt>専ブラ作成中にハマった点（現在進行系）</dt>
  <dd>SwiftUI、文字コード、など…</dd>
  <dt>今時点の開発状況</dt>
  <dd>スレ表示までは動いた…</dd>
</dl>

##Macに乗り換えて困った…
実は昔から色々な2ch(5ch)の板を見てました。
しかも私はオールドタイプなのでスマホではなく今では逆に珍しくなったPC用の専ブラ勢です。
そこでMac用の専ブラとして色々調べました。

| 名前 | dat形式 | API対応 | プラットフォーム | 絵文字 | 備考 |
|:-----------|:------------:|:----------:|:-----------|:----------:|:-----------|
| BathyScaphe | No  | Yes | Mac      | No  | - |
| V2C-R       | Yes | Yes | Java     | No  | - |
| Twinkle     | ?   | Yes | iOS      | Yes | ARM Macのみ利用可 |
| Siki        | No  | No  | Electron | ?  | Proxy経由利用 |
| B5S Viewer  | ?   | No  | Flutter  | ?  | APIキー入力利用 |

長年、2ch(5ch)をやってきて収集癖が祟り約10年分ほどのログをJane Styleで溜め込んでいました。
そのため.dat形式のログが個人的には必須。現状ではV2-Rを利用しています。
加えて近年ではスマホユーザーの増加に伴い絵文字利用が活発になっている板も多いです。
しかし、.dat形式と絵文字の双方に対応した専ブラがMac環境では（私の知っている限り）存在せず、
暇潰しを兼ねて自作を始めたところ泥沼へとハマっていきました…。

##どんな5ch専ブラにしよう…
完成形のイメージを考えました。

1. ペイン形式（[板一覧][スレ一覧][スレ表示]）
1. 板やスレッドをタブ形式表示
1. API無しでのスレデータ取得
1. ログは.dat形式で保存
1. 絵文字表示対応

軽く触った感じや開発前に下調べした結果、環境はmacOS SwiftUIを採用しました。
しかし、2番のタブ形式は参考になるものが見つからず難儀しそうと開発前から感じ…。
まぁその通りだったんですけど。

####ペイン形式（[板一覧][スレ一覧][スレ表示]）
初めてSwiftUIを触って驚きました。簡単に作れました。
NavigationLinkからスレ一覧Viewを呼び出すと、View2のところに表示されます。
同様にスレ一覧ViewからNavigationLinkで更にスレ表示Viewを呼び出すと
View3のところに表示されます。
これで３ペイン形式をあっさりと作れました。

```swift:ContentView.swift
var body: some View {
  NavigationView {
   List {
    NavigationLink(destination:スレ一覧View(表示スレID等)){}
   }.listStyle(SidebarListStyle())
 }
 View2()
 View3()
}
```

####板やスレッドをタブ形式表示
これがよく分からず未だに上手く動いていません。
Tabview等では上手くいかなそうと思い以下のように自前でタブっぽく作りました。
（上手く行かない部分は後半の作成状況の欄で説明）

```swift:ThreadListView.swift
ScrollView (.horizontal) {
 HStack {
   ForEach() {
    Button(
    action: {@Sateの値を変更しスレ一覧やスレ内容を再描画}
    ){板名やスレ名
    }.buttonStyle(BorderlessButtonStyle())
   Divider()
 }
List {スレ一覧やスレ内容表示処理} 
```
ボタンの枠を無くし水平表示のScrollViewで表示し何となくタブっぽくしました。
タブ（ボタン）が押されたときのアクションとして@State変数を変更し、
スレ一覧データやスレデータを取得する関数呼び出しを更新しデータを取得しています。
また、その変数の変更やタブ名として表示するためにタブ用のデータも作っておきました。
（板やスレが開かれるとタブデータとして追加）

####API無しでのスレデータ取得
前にpythonでHTMLをパースしデータ取得をやっていたので直ぐに出来ると思っていました。
しかし、大きな落とし穴が…。

```swift:ThreadListView.swift
 _ = AF.request(URL, method:HTTPMethod.get, )
 .response(queue: queue) { response in
 switch response.result {
 case .success(let value):
  if let doc = try? HTML(html: value!, encoding: .shiftJIS) {
   for log in doc.xpath("hogehoge") {}
  }
```

こんな感じでAlamofireでHTML Dataを取得し、HTMLパーサー(Kanna）へと渡すという王道な処理です。
しかし、mac?では.shiftJISのエンコード処理でDataに他の文字コードが含まれているとnilを返すという問題が生じます。
そのためスレッドタイトルにおかしな文字が含まれているとスレ一覧が全て取得出来ず、
また、スレッドのレスに含まれていればスレデータが丸ごと表示出来ません。

それでググるとNSAttributedStringを使えばdataの文字コードに混在しても文字列をそのまま抜き出せることが分かりました。

```swift:ThreadListView.swift
 _ = AF.request(URL, method:HTTPMethod.get, )
 .response(queue: queue) { response in
 switch response.result {
 case .success(let value):
  if let attributedString = try? NSAttributedString(data: value!,
  options: [.documentType:NSAttributedString.DocumentType.html], 
  documentAttributes: nil){ }
```

HTMLパースより強引過ぎますが表示出来るだけマシと思い実装を進めます。

```swift:ThreadListView.swift
##以下のスレよりデータ取得
# https://swallow.5ch.net/test/read.cgi/livejupiter/1632068890/

attributedString.string.enumerateLines{
 line, stop in
 print(line)
}

##NSAttributedStringで1行ずつ表示したデータ(一部省略）
# 
# ￼検索
# ￼￼
# 【悲報】黒ひげ海賊団のNo2弱すぎる
# 		20コメント2KB
# 		全部
# 1-100
# 最新50
# ￼￼￼
# 1風吹けば名無し2021/09/20(月) 01:28:10.10ID:11pNzpT20
# サボに完封されてた
# 
# 2風吹けば名無し2021/09/20(月) 01:28:44.09ID:qLezje0F0
# チャンピオンやぞ？
# 
# 		20コメント2KB
# 
# ■掲示板に戻る■
# ★ULA版★
# ￼￼￼￼￼
# レスを投稿する
# 
# read.cgi ver 07.2.9 2021/08 Walang Kapalit ★
# Cipher Simian ★
```

表示したデータはヘッダーとフッターの余計な情報がつくものの
「レス番号+名前+日付+ID」「書き込み内容」とString形式にてキレイに取得出来ます。
そのため「レス番号+名前+日付」を区切りとして正規表現にて処理し、スレデータを処理出来るようにしました。

##今時点の開発状況

挙動を簡単にgifにしました。
![ezgif-2-ee9f45ec6c4f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537256/70f2fbf7-6c0d-6462-fea8-dcae738ed5ac.gif)
上で上げていた５つの機能はこんな状況です。

- [x] ペイン形式（[板一覧][スレ一覧][スレ表示]）
- [ ] 板やスレッドをタブ形式表示
- [ ] API無しでのスレデータ取得
- [x] ログは.dat形式で保存
- [x] 絵文字表示対応

####板やスレッドをタブ形式表示
ログとは別に.onAppear{}を使いスクロール位置を保存しています。[^1]
タブをクリックした際は保存されているログデータ及びスクロール位置を読み込み描画＆その位置まで移動しています。
しかし、別スレッドを開くとどうもそのスクロール位置が初期化され保存されています。
自分でも何言っているかよくわからないですが、とりあえず挙動がおかしいです。
同様に前のスレに戻るとlistの高さがおかしくなることもあります。
こんな変な自作タブより、WindowGroupを使いたいんですが3ペイン形式だとスレ表示部だけ標準のTab Barと組み合わせるのも無理そうでお手上げ状態です:open_mouth:

![ezgif-2-9651681bba1f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537256/b5971134-db21-0aa1-8d9b-ab941bef855d.gif)

####API無しでのスレデータ取得
VIP板のスレやID無しスレ？が何故かデータ取得出来ていないときがあります。
レス番号と名前、日付は必須のはずなので正規表現パターンのミスなのか、もっと調べて修正したいです。

####機能面はまだまだ足りない
「書き込み」「レス参照・ジャンプ」「URLリンク化」「画像ビューワ」「検索・フィルタ」など必須機能もまだまだ無いです。
先は長い…


##まとめ
<dl>
  <dt>5ch専ブラ作成開始</dt>
  <dd>難しい…</dd>
  <dd>誠心誠意作成中（笑）</dd>
</dl>

[^1]: [Save ScrollViews position and scroll back to it later (offset to position)](https://stackoverflow.com/questions/65870891/save-scrollviews-position-and-scroll-back-to-it-later-offset-to-position)