マヨケーがポアされたため、現在はロシケーがメインとなっています。

「利用者:夜泣き/スクリプト」の版間の差分

提供:唐澤貴洋Wiki
ナビゲーションに移動 検索に移動
>Fet-Fe
>Fet-Fe
編集の要約なし
6行目: 6行目:


'''
'''
ver2.1.1 2022/8/13恒心
ver2.1.2 2022/8/29恒心


当コードは恒心停止してしまったhttps://rentry.co/7298gの降臨ショーツイート自動収集スクリプトの復刻改善版です
当コードは恒心停止してしまったhttps://rentry.co/7298gの降臨ショーツイート自動収集スクリプトの復刻改善版です
216行目: 216行目:
   def _check_tor(self) -> None | NoReturn:
   def _check_tor(self) -> None | NoReturn:
     print('Torのチェック中ですを')
     print('Torのチェック中ですを')
     res: Final[Response] = self._request_once('https://check.torproject.org/api/ip') ##リクエスト
     try:
    is_tor: Final[bool] = json.loads(res.text)['IsTor']
      res: Final[Response] = self._request_once('https://check.torproject.org/api/ip') ##リクエスト
    if is_tor:
      is_tor: Final[bool] = json.loads(res.text)['IsTor']
      print('Tor OK')
      if is_tor:
    else:
        print('Tor OK')
      raise RuntimeError('通信がTorを経由していないなりを')
      else:
        raise RuntimeError('サイトにTorのIPでアクセスできていないなりを')
    except requests.exceptions.ConnectionError as e:
      print(e, file=sys.stderr)
      print('通信がTorのSOCKS proxyを経由していないなりを', file=sys.stderr)
      exit(1)


   ##nitterのインスタンスが生きているかチェック
   ##nitterのインスタンスが生きているかチェック
233行目: 238行目:
       res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生
       res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生
     except requests.exceptions.RequestException as e: ##エラー発生時は終了
     except requests.exceptions.RequestException as e: ##エラー発生時は終了
       print('インスタンスが死んでますを')
      print(e, file=sys.stderr)
       exit()
       print('インスタンスが死んでますを', file=sys.stderr)
       exit(1)


   ##ツイート収集するユーザー名を取得
   ##ツイート収集するユーザー名を取得
284行目: 290行目:
       print("取得成功した分だけ発行しますを")
       print("取得成功した分だけ発行しますを")
       self._make_txt()
       self._make_txt()
     exit() ##終了
     else:
      exit(1) ##終了


   ##テキスト発行
   ##テキスト発行
293行目: 300行目:
       f.write(result_txt)
       f.write(result_txt)
     print("テキストファイル手に入ったやで〜")
     print("テキストファイル手に入ったやで〜")
     exit() ##終了
     exit(0) ##終了


   ##記録を中断するツイート
   ##記録を中断するツイート
328行目: 335行目:
         else:
         else:
           print('https://pbs.twimg.com/media/' + media_name + ' をアップロードしなければない。')
           print('https://pbs.twimg.com/media/' + media_name + ' をアップロードしなければない。')
        # 動画についてはm3u8で落ちてきて面倒臭いため取得しない
      # 動画についてはm3u8で落ちてきて面倒臭いため取得しない
       """
       """
       for video in tweet_media.select('.attachment.video-container video'):
       for video in tweet_media.select('.attachment.video-container video'):
344行目: 351行目:
       link = re.sub('#.*$', '', link)
       link = re.sub('#.*$', '', link)
       link = self.TWITTER_URL + link
       link = self.TWITTER_URL + link
       quote_txt: str = '<br>\n' + self._archive_url(link, link)
       quote_txt = '<br>\n' + self._archive_url(link, link)
     return quote_txt
     return quote_txt


362行目: 369行目:
       media_txt: str = self._get_tweet_media(tweet) ##ツイートに画像などのメディアを追加
       media_txt: str = self._get_tweet_media(tweet) ##ツイートに画像などのメディアを追加
       quote_txt: str = self._get_tweet_quote(tweet) ##引用リツイートの場合、元ツイートを追加
       quote_txt: str = self._get_tweet_quote(tweet) ##引用リツイートの場合、元ツイートを追加
       self._txt_data = '!' + archived_tweet_url + '\n|-\n|\n' + tweet_content.get_text().replace('\n', '<br>\n').replace('#', '<nowiki>#</nowiki>') + quote_txt + media_txt + '\n|-\n' + self._txt_data ##wikiの文法に変化
       self._txt_data = '!' + archived_tweet_url + '\n|-\n|\n' \
        + self._escape_wiki_reserved_words(tweet_content.get_text()) \
        + quote_txt + media_txt + '\n|-\n' \
        + self._txt_data ##wikiの文法に変化
       self._limit_count += 1 ##記録回数をカウント
       self._limit_count += 1 ##記録回数をカウント
       if self._limit_count % self.REPORT_INTERVAL == 0:
       if self._limit_count % self.REPORT_INTERVAL == 0:
372行目: 382行目:
         print(f"{self.LIMIT_N_TWEETS}件も記録している。もうやめにしませんか。")
         print(f"{self.LIMIT_N_TWEETS}件も記録している。もうやめにしませんか。")
         self._make_txt()
         self._make_txt()
  #テンプレート外の#をnowikiで囲む
  def _escape_hash(self, text: str) -> str:
    archive_begin: Final[str] = '{{Archive|'
    callinshowlink_begin: Final[str] = '{{CallinShowLink|'
    archive_end: Final[str] = '}}'
    current_depth: int = 0
    new_text: str = ''
    for i in range(len(text)):
      if text[i:i+len(archive_begin)] == archive_begin or text[i:i+len(callinshowlink_begin)] == callinshowlink_begin:
        current_depth += 1
      elif text[i:i+len(archive_end)] == archive_end:
        current_depth = max(current_depth-1, 0)
      if current_depth == 0 and text[i] == '#':
        new_text += '<nowiki>#</nowiki>'
      else:
        new_text += text[i]
    return new_text
  #MediaWiki文法と衝突する文字を無効化する
  def _escape_wiki_reserved_words(self, text: str) -> str:
    text = text.replace('\n', '<br>\n')
    text = re.sub(r'^ ', '&nbsp;', text, flags=re.MULTILINE)
    text = self._escape_hash(text)
    return text


   #tagをテンプレートArchiveの文字列に変化させる
   #tagをテンプレートArchiveの文字列に変化させる
380行目: 415行目:
         if re.match('https' + self.NITTER_INSTANCE[4:], url.get('href')):
         if re.match('https' + self.NITTER_INSTANCE[4:], url.get('href')):
           #Nitter上のTwitterへのリンクを直す
           #Nitter上のTwitterへのリンクを直す
           url_link = re.sub('https' + self.NITTER_INSTANCE[4:], self.TWITTER_URL + '/', url.get('href'))
           url_link: str = url.get('href').replace('https' + self.NITTER_INSTANCE[4:], self.TWITTER_URL + '/')
           url_link = re.sub('\?.*$', '', url_link)
           url_link = re.sub('\?.*$', '', url_link)
           url.replace_with(self._archive_url(url_link, url_link)) ##テンプレートArchiveに変化
           url.replace_with(self._archive_url(url_link, url_link)) ##テンプレートArchiveに変化
         elif re.match('piped.kavin.rocks/', url.text) or re.match('invidio.us/', url.text):
         elif re.match('piped.kavin.rocks/', url.text) or re.match('invidio.us/', url.text):
           #Nitter上のYouTubeへのリンクを直す
           #Nitter上のYouTubeへのリンクを直す
           url_link = url.get('href')
           url_link: str = url.get('href')
           url_link = re.sub('piped.kavin.rocks/', 'youtu.be/', url_link)
           url_link = url_link.replace('piped.kavin.rocks/', 'youtu.be/')
           url_link = re.sub('invidio.us/', 'youtu.be/', url_link)
           url_link = url_link.replace('invidio.us/', 'youtu.be/')
           url.replace_with(self._archive_url(url_link, url_link)) ##テンプレートArchiveに変化
           url.replace_with(self._archive_url(url_link, url_link)) ##テンプレートArchiveに変化
         else:
         else:
394行目: 429行目:
   #URLをテンプレートArchiveに変化させる
   #URLをテンプレートArchiveに変化させる
   def _archive_url(self, url: Final[str], text: Final[str]) -> str:
   def _archive_url(self, url: Final[str], text: Final[str]) -> str:
     return '{{Archive|1=' + url + '|2=' + self._archive(url) + '|3=' + text  + '}}' ##テンプレートArchiveの文字列返す
     return '{{Archive|1=' + unquote(url) + '|2=' + self._archive(url) + '|3=' + unquote(text) + '}}' ##テンプレートArchiveの文字列返す


   #URLをテンプレートCallinShowlinkに変化させる
   #URLをテンプレートCallinShowlinkに変化させる
402行目: 437行目:
   ##URLから魚拓返す
   ##URLから魚拓返す
   def _archive(self, url: Final[str]) -> str:
   def _archive(self, url: Final[str]) -> str:
     archive_url: str = self.ARCHIVE_TODAY_STANDARD + re.sub('#', '%23', url) ##wikiに載せるとき用URLで失敗するとこのままhttps://archive.ph/https://xxxxxxxxの形で返される
     archive_url: str = self.ARCHIVE_TODAY_STANDARD + url.replace('#', '%23') ##wikiに載せるとき用URLで失敗するとこのままhttps://archive.ph/https://xxxxxxxxの形で返される
     res: Final[Response | None] = self._request(self.ARCHIVE_TODAY + re.sub('#', '%23', url)) ##アクセス用URL使って結果を取得
     res: Final[Response | None] = self._request(self.ARCHIVE_TODAY + url.replace('#', '%23')) ##アクセス用URL使って結果を取得
     if res is None : ##魚拓接続失敗時処理
     if res is None : ##魚拓接続失敗時処理
       print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。')
       print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。')
435行目: 470行目:


if __name__ == '__main__':
if __name__ == '__main__':
  if sys.version_info.major < 3 or (sys.version_info.major == 3 and sys.version_info.minor < 10):
    print('Pythonのバージョンを3.10以上に上げて下さい', file=sys.stderr)
    exit(1)
   krsw: Final[bool] = len(sys.argv) > 1 and sys.argv[1] == 'krsw' ##コマンドライン引数があるかどうかのフラグ
   krsw: Final[bool] = len(sys.argv) > 1 and sys.argv[1] == 'krsw' ##コマンドライン引数があるかどうかのフラグ
   twitter_archiver: TwitterArchiver = TwitterArchiver(krsw)
   twitter_archiver: TwitterArchiver = TwitterArchiver(krsw)
447行目: 485行目:
20件での実行例。
20件での実行例。
{|class="wikitable" style="text-align: left;"
{|class="wikitable" style="text-align: left;"
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537795802302918656|2=https://archive.ph/AuE5x}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563171863043325954|2=https://archive.ph/jkfiW}}
|-
|
明日は明日の風が吹く!!!<br>
{{Archive|1=https://twitter.com/rumaimai288/status/1563136444230926341|2=https://archive.ph/eA4bp|3=https://twitter.com/rumaimai288/status/1563136444230926341}}
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563179306477449216|2=https://archive.ph/7GnmJ}}
|-
|-
|
|
テレビ局なんか既得権益だろ。<br>
そうなるよな。<br>
<br>
{{Archive|1=https://twitter.com/itm_nlab/status/1563041010963296261|2=https://archive.ph/xPWIY|3=https://twitter.com/itm_nlab/status/1563041010963296261}}
在野の声をSNSを使って行っていくしかない。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537796046780497920|2=https://archive.ph/OlZiU}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563184989369315329|2=https://archive.ph/J4P2z}}
|-
|-
|
|
オレたちはもう騙されないんだ。<br>
昔、渋谷の東宝の映画館で見たな。<br>
<br>
{{Archive|1=https://twitter.com/kinro_ntv/status/1562741577184731136|2=https://archive.ph/o3p1C|3=https://twitter.com/kinro_ntv/status/1562741577184731136}}
全てを変えるんだ。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537796546263416832|2=https://archive.ph/dZjIg}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563406043119783936|2=https://archive.ph/e7Bcv}}
|-
|-
|
|
なあ、この国は良くなってるのか。<br>
これは良くない。<br>
<br>
若い人は安く使われ、<br>
物価は上がり、<br>
公共事業だけはお金を垂れ流していく。<br>
少子化が進み、年金も当てにならない。<br>
<br>
<br>
何でこんなに若い人が苦しまないといけないんだ。
日中、会社の車だろ。<br>
{{Archive|1=https://twitter.com/pachipachisukki/status/1558433177408274432|2=https://archive.ph/r3Hzf|3=https://twitter.com/pachipachisukki/status/1558433177408274432}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537796983578333184|2=https://archive.ph/e1YIX}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563469935099011073|2=https://archive.ph/eon5H}}
|-
|-
|
|
若い人が全員選挙行ったらこの国は変わるぜ。<br>
政権を担った全ての政党に責任。<br>
<br>
民主党だった人たちも含めてだ。<br>
<br>
放置してきたんだ。<br>
解散総選挙をしよう。<br>
<br>
<br>
ガチャのチケットもってるのに使わない手はないだろ。
民意をもう一度問うために。<br>
{{Archive|1=https://twitter.com/tbs_houtoku/status/1563429510682669058|2=https://archive.ph/WUk14|3=https://twitter.com/tbs_houtoku/status/1563429510682669058}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537797605308395520|2=https://archive.ph/sTiWk}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563471832316923904|2=https://archive.ph/ci34j}}
|-
|-
|
|
👍
立憲民主党はこの14人が議員辞職してみるくらいかっこいいことしてから追及してほしい。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537798386619133952|2=https://archive.ph/VECnL}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563472582229209089|2=https://archive.ph/DhMyI}}
|-
|-
|
|
頼むよ!君にかかってる。
二大政党制は期待できない。<br>
<br>
今は第三極が必要なんだ。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537799090326863872|2=https://archive.ph/OIfjs}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563477985893658624|2=https://archive.ph/pqsll}}
|-
|-
|
|
引きこもりの人は引きこもりになりたくてなったんじゃない。<br>
{{Archive|1=https://www.iza.ne.jp/article/20220825-GBGV6NOE5BNWJHT3MSN47LXYCA/photo/E7XLVXDSQJJEXEG7AUTFABQ2GI/?utm_source=yahoo news feed&utm_medium=referral&utm_campaign=related_link|2=https://archive.ph/UKMYM|3=https://www.iza.ne.jp/article/20220825-GBGV6NOE5BNWJHT3MSN47LXYCA/photo/E7XLVXDSQJJEXEG7AUTFABQ2GI/?utm_source=yahoo news feed&utm_medium=referral&utm_campaign=related_link}}<br>
苦しんでるんだ。<br>
<br>
誰かがその声を聞く必要がある。<br>
身の丈にあわないものを求めるとかっこ悪いっていう象徴。<br>
強制的に連れ出すとかやばいだろ。<br>
<br>
法規制する必要あるでしょ。<br>
背景に写っている山の山頂は見えない。<br>
強要罪だぜ。<br>
<br>
全ては見過ごされてるんだよ。
五里霧中。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537803303752388608|2=https://archive.ph/M0BdR}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563480030466154497|2=https://archive.ph/by77Y}}
|-
|-
|
|
この国は<br>
金を貯め込む人がいる。<br>
<br>
<br>
やばいだろ。<br>
人のために使った方がいいよ。<br>
<br>
<br>
引用リスペクト 梅野源治選手
金を抱いて死ぬことはできないんだぜ。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538070416488792064|2=https://archive.ph/9d43q}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563514805356756992|2=https://archive.ph/vAqUv}}
|-
|-
|
|
本当、そうだと思う。<br>
弁護士唐澤貴洋への匿名のメッセージを募集中!<br>
<br>
<nowiki>#</nowiki>マシュマロを投げ合おう<br>
貴重な意見ありがとう。
{{Archive|1=https://marshmallow-qa.com/apt/e52b0b30-c815-4fdb-8a9c-409a6b8a650b|2=https://archive.ph/O6g3k|3=https://marshmallow-qa.com/apt/e52b0b30-c815-4fdb-8a9c-409a6b8a650b}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538070983827165186|2=https://archive.ph/nz7k9}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563518352777445378|2=https://archive.ph/GPVPd}}
|-
|-
|
|
この国は円安が進み、外国旅行に行ったことのない若者も多く。<br>
コパノリッキーを獲得🏇<br>
どうなるんだ。<br>
[[ファイル:FbK7jfiagAADYSm.jpg|240px]]
<br>
ライジングサンってどうなってるんだ。<br>
<br>
今は戦後の混乱期と同じだ。<br>
<br>
若い人たちで国を作るんだ。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538071752802467840|2=https://archive.ph/ON3Hx}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563709530797527040|2=https://archive.ph/y7fte}}
|-
|-
|
|
立花先生は、いつも一緒懸命だ。<br>
本当その通りだ。<br>
<br>
信念は変わらない。<br>
<br>
理不尽との闘い。<br>
<br>
立花先生が裁判を起こすことは至極まともなことだ。<br>
<br>
<br>
だって最後は司法なはずだろ。<br>
銀座に飲みに行ってみろ。<br>
<br>
<br>
でも、裁判は、ひどい判決も多い。<br>
そんな飲み方しているやつなんかみたことない。<br>
<br>
<br>
権力に擦り寄ることもある。<br>
遊び方を知らない奴が、嫉妬妄想で仕方ないよねとか言ってるんだ。<br>
<br>
<br>
そんな全てを曝け出しているのが立花先生だ。<br>
性的被害に遭うのは仕方がないとか、人権意識ゼロの発言。<br>
<br>
<br>
こんな人今までいたか。
接待を伴う飲食業は、立派な仕事だ。<br>
{{Archive|1=https://twitter.com/hone_hone_kotu/status/1562287607681146881|2=https://archive.ph/5I5HF|3=https://twitter.com/hone_hone_kotu/status/1562287607681146881}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538072997734793216|2=https://archive.ph/AoMxP}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563713254584553472|2=https://archive.ph/ZNeTh}}
|-
|-
|
|
立花先生はいつも現場主義。<br>
今回問題になった香川照之さんみたいな飲み方は、カッコ悪くて、そんなの真似してたら嘲笑、出禁、通報。
<br>
人々の声を拾い。<br>
<br>
それを手練手管で伝えいく。<br>
<br>
時に道化を案じるのは、みんなに問題を気づいてほしいからだ。<br>
<br>
この3年間、少数政党でこれだけ目立ち続けた政党があるか。<br>
<br>
それは全て、NHKを変えたい、人々に良くなってもらいたい。<br>
<br>
その一心でしかないと思う。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538073614540754944|2=https://archive.ph/SYHKj}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563822049453256705|2=https://archive.ph/1udCZ}}
|-
|-
|
|
全てをめくっていくんだよ。<br>
接待を伴う飲食店についての考え方だったり、店の中での振る舞いでわかる。<br>
<br>
<br>
ガーシーさんは、その第一章だ。<br>
お金を使うのが無駄っていう人は、単なるケチ、女性にお金を使ったら負けくらいの精神の持ち主、一緒にいても楽しくない。<br>
<br>
<br>
許せないことを言うことは大切だと。<br>
振る舞いが悪いのは、自己評価が過大な人間。<br>
<br>
過大なので、更衣室のネタ。
ガーシー党は、言わば情報公開政党ってことだろ。<br>
<br>
いいじゃないか、澱んだ永田町が少しは澄んでくるんじゃないか。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538074524721491968|2=https://archive.ph/vqHml}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563822588249272320|2=https://archive.ph/4CcE4}}
|-
|-
|
|
{{Archive|1=https://piped.kavin.rocks/nrJqzmMWUCU|2=https://archive.ph/https://piped.kavin.rocks/nrJqzmMWUCU|3=piped.kavin.rocks/nrJqzmMWUCU}}<br>
接待を伴う飲食店の全ての痛客に向けて。<br>
<br>
<br>
澱んだ街ごと食い荒らすんだ。
{{Archive|1=https://note.com/takahirokarasawa/n/nd7f01ba99c74|2=https://archive.ph/Hte73|3=https://note.com/takahirokarasawa/n/nd7f01ba99c74}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538582552423780352|2=https://archive.ph/https://twitter.com/CallinShow/status/1538582552423780352}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563833420492316673|2=https://archive.ph/cOFo8}}
|-
|-
|
|
弁護士唐澤貴洋への匿名のメッセージを募集中!<br>
ドンマイ川端!<br>
<nowiki>#</nowiki>マシュマロを投げ合おう<br>
{{Archive|1=https://twitter.com/KAWABATA1129/status/1563706260645871616|2=https://archive.ph/OFcSs|3=https://twitter.com/KAWABATA1129/status/1563706260645871616}}
{{Archive|1=https://marshmallow-qa.com/apt/21e00e4b-e468-42e9-bd14-44c2bf9b37d0|2=https://archive.ph/https://marshmallow-qa.com/apt/21e00e4b-e468-42e9-bd14-44c2bf9b37d0|3=marshmallow-qa.com/apt/21e00…}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539223436337516548|2=https://archive.ph/tBmP8}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563890352960204800|2=https://archive.ph/pUjWT}}
|-
|-
|
|
{{Archive|1=https://www.fsa.go.jp/sesc/news/c_2022/2022/20220621-3.html|2=https://archive.ph/EyPMc|3=fsa.go.jp/sesc/news/c_2022/2…}}<br>
{{Archive|1=https://marshmallow-qa.com/callinshow?utm_medium=url_text&utm_source=promotion|2=https://archive.ph/2BvG5|3=https://marshmallow-qa.com/callinshow?utm_medium=url_text&utm_source=promotion}}<br>
<br>
<br>
エクシアさん、こういった問題について、どうお考えなんですかね。
フォロワーの人で何か質問がある人待っています😊
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539223981106302976|2=https://archive.ph/6XxFe}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563891616901111808|2=https://archive.ph/QAjmW}}
|-
|-
|
|
エクシアの関戸さんの経営されている会社等のご存じの方いらっしゃいましたらご連絡お願い申し上げます。
このツィートが意味がわからない人へ。<br>
<br>
川端さんが試合で負けたことに対して、<br>
一ファンとして<br>
<br>
ドンマイ + 川端<br>
<br>
という意味でツイートしたものです。<br>
{{Archive|1=https://twitter.com/CallinShow/status/1563833420492316673|2=https://archive.ph/cOFo8|3=https://twitter.com/CallinShow/status/1563833420492316673}}
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539224957829672960|2=https://archive.ph/iJxRd}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563913356511428608|2=https://archive.ph/u8HnK}}
|-
|-
|
|
そちらは法人名でしょうか?
アーシュラ・K・ル・グィンの「オメラスから歩み去る人々」を読む。<br>
<br>
ネットの中で誹謗中傷する人たちは、理想郷とも思えるオメラスに住み、オメラスの地下牢にいる子どもを作ることで、安定を得ようとする。
|-
|-
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539237355701379075|2=https://archive.ph/https://twitter.com/CallinShow/status/1539237355701379075}}
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1563913838881554432|2=https://archive.ph/BgbEh}}
|-
|-
|
|
菊地さんは虎ノ門ヒルズいくらで買ったんだろ。<br>
現代社会においてオメラスは入れ子構造になっている。<br>
キャッシュで
<br>
|-
オメラスとされるところは、実は地下牢なんだと気付けるか。<br>
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539237797281816576|2=https://archive.ph/https://twitter.com/CallinShow/status/1539237797281816576}}
<br>
|-
オメラスを去ることはできるのか。<br>
|
{{Archive|1=https://twitter.com/CallinShow/status/1563913356511428608|2=https://archive.ph/u8HnK|3=https://twitter.com/CallinShow/status/1563913356511428608}}
仮に3億だとして、これを買うのに、所得としては6億くらいの報酬を得ないとダメだよな。
|-
|-
|}
|}

2022年8月29日 (月) 01:09時点における版

‎とりあえず取り急ぎ。バグ報告は利用者・トーク:夜泣き

コード

#!/usr/bin/env python3

'''
ver2.1.2 2022/8/29恒心

当コードは恒心停止してしまったhttps://rentry.co/7298gの降臨ショーツイート自動収集スクリプトの復刻改善版です
前開発者との出会いに感謝

〜〜〜〜〜〜〜〜〜〜〜〜〜【使い方】〜〜〜〜〜〜〜〜〜〜〜〜〜

・terminalに $ python3 (ファイル名) で作動します
・定数類は状況に応じて変えてください
・$ python3 (ファイル名) krsw コマンドライン引数をkrswとつけると自動モードです
・自動モードではユーザーは降臨ショー、クエリはなし、取得上限まで自動です
・つまりユーザー入力が要りません

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

ーーーーーーーーーーーーー【注意点】ーーーーーーーーーーーーー

・Pythonのバージョンは3.10以上
・環境は玉葱前提です。
・Whonix-Workstation, MacOSで動作確認済
  ・MacOSの場合はbrewでtorコマンドを導入し、実行
・PySocks, bs4はインストールしないと標準で入ってません
・requestsも環境によっては入っていないかもしれない
・$ pip install bs4 requests PySocks
・pipも入っていなければ$ sudo apt install pip
・バグ報告はhttps://krsw-wiki.org/wiki/?curid=15799#利用者:夜泣き/スクリプトについて

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
'''

#インポート類
import sys
import os
import codecs
import re
import json
from time import sleep
from typing import Final, NoReturn, TypeAlias
from urllib.parse import quote, unquote
import warnings

import requests
import bs4
from bs4 import BeautifulSoup

##おそらくはツイートの内容によってMarkupResemblesLocatorWarningが吐き出されることがあるので無効化
warnings.simplefilter('ignore')

##型エイリアス
Response: TypeAlias = requests.models.Response

class TwitterArchiver:
  #定数・設定類

  ##nitterのインスタンス
  ##生きているのはhttps://github.com/zedeus/nitter/wiki/Instancesで確認
  ##末尾にスラッシュ必須
  NITTER_INSTANCE: Final[str] = 'http://ibsboeui2im5o7dxnik3s5yghufumgy5abevtij5nbizequfpu4qi4ad.onion/'

  ##archive.todayの魚拓
  ##実際にアクセスして魚拓があるか調べるのにはonionを使用
  ##末尾にスラッシュ必須
  ARCHIVE_TODAY: Final[str] = 'http://archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion/'

  ##archive.todayの魚拓
  ##記事の文章に使うのはクリアネット
  ##末尾にスラッシュ必須
  ARCHIVE_TODAY_STANDARD: Final[str] = 'https://archive.ph/'

  ##twitterのURL
  ##末尾にスラッシュ禁止
  TWITTER_URL: Final[str] = 'https://twitter.com'

  ##降臨ショーのユーザーネーム
  CALLINSHOW: Final[str] = 'CallinShow'

  ##HTTPリクエストのタイムアウト秒数
  REQUEST_TIMEOUT: Final[int] = 30

  ##取得するツイート数の上限
  LIMIT_N_TWEETS: Final[int] = 100

  ##記録件数を報告するインターバル
  REPORT_INTERVAL: Final[int] = 5

  ##HTTPリクエスト失敗時の再試行回数
  LIMIT_N_REQUESTS: Final[int] = 5

  ##HTTPリクエスト失敗時にさらに追加する待機時間
  WAIT_TIME_FOR_ERROR: Final[int] = 4

  ##HTTPリクエスト成功失敗関わらず待機時間
  ##1秒待つだけで行儀がいいクローラーだそうなので既定では1秒
  ##しかし日本のポリホーモは1秒待っていても捕まえてくるので注意
  ##https://ja.wikipedia.org/wiki/?curid=2187212
  WAIT_TIME: Final[int] = 1

  ##nitterのURLのドメインとユーザーネーム部分の接続部品
  SEARCH_QUERY: Final[str] = 'search?q=from%3A'

  ##HTTPリクエスト時のユーザーエージェントとヘッダ
  HEADERS: Final[dict[str, str]] = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0'
  }

  ##HTTP
  PROXIES: Final[dict[str, str]] = {
    'http': 'socks5h://127.0.0.1:9050',
    'https': 'socks5h://127.0.0.1:9050'
  }

  ##nitterでユーザーがいなかったとき返ってくるページのタイトル
  ##万が一仕様変更で変わったとき用
  NITTER_ERROR_TITLE: Final[str] = 'Error|nitter'

  ##archive.todayで魚拓がなかったときのレスポンス
  ##万が一仕様変更で変わったとき用
  NO_ARCHIVE: Final[str] = 'No results'

  ##nitterで次ページ遷移時のURLから抜け落ちてる部分
  SEARCH: Final[str] = 'search'

  ##nitterの前ページ読み込み部分の名前
  ##万が一仕様変更で変わったとき用
  NEWEST: Final[str] = 'Load newest'

  ##画像などのツイートメディアをダウンロードするディレクトリ
  MEDIA_DIR: Final[str] = 'tweet_media'

  #関数類
  def __init__(self, krsw: bool=False):
    self._txt_data: str = ''
    self._limit_count: int = 0 ##記録数

    self._check_slash() ##スラッシュが抜けてないかチェック
    self._check_tor() ##Torが使えているかチェック
    self._check_instance() ##インスタンスが死んでないかチェック

    ##ユーザー名取得
    if krsw:
      print('名前は自動的に' + self.CALLINSHOW + 'にナリます')
      name: Final[str] = self.CALLINSHOW
    else:
      name: Final[str] = self._get_name()

    ##検索クエリとページ取得
    if krsw:
      print('クエリは自動的になしにナリます')
      self._page: Response | None = self._request(self.NITTER_INSTANCE + self.SEARCH_QUERY + name)
      if self._page is None:
        self._fail()
    else:
      self._page: Response = self._get_query(name)

    ##終わりにするツイート取得
    if krsw:
      print('終わりにするツイートは自動的になしにナリます')
      self._stop: Final[str] = ''
    else:
      self._stop: Final[str] = self._stop_word()

    print()


  ##坂根輝美に場所を知らせます
  def _pickup_counter(self) -> None:
    print('ピックアップカウンター付近でふ')

  ##引数のURLにHTTP接続します
  ##失敗かどうかは呼出側で要判定
  def _request_once(self, url: Final[str]) -> Response:
    res: Response = requests.get(url, timeout=self.REQUEST_TIMEOUT, headers=self.HEADERS, allow_redirects=False, proxies=self.PROXIES)
    sleep(self.WAIT_TIME) ##DoS対策で待つ
    return res

  ##HTTP接続を再試行回数まで試します
  ##成功すると結果を返します
  ##接続失敗が何度も起きるとNoneを返します
  ##呼出側で要None判定
  def _request(self, url: Final[str]) -> Response | None:
    counter: int = 1 ##リクエスト挑戦回数を記録
    while True:
      try:
        res: Response = self._request_once(url) ##リクエスト
        res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生
      except requests.exceptions.RequestException as e:
        print(url + 'への通信失敗ナリ  ' + f"{counter}/{self.LIMIT_N_REQUESTS}回")
        if counter < self.LIMIT_N_REQUESTS: ##エラー発生時上限まで再挑戦
          counter += 1 ##現在の試行回数1回増やす
          sleep(self.WAIT_TIME_FOR_ERROR) ##失敗時は長めに待つ
        else:
          return None ##失敗したらNone返却し呼出側で対処してもらう
      else:
        return res ##リクエストの結果返す

  ##URLの最後にスラッシュ付いていなければ付ける
  ##Twitterだけスラッシュ付いていないほうが都合いいので抜く
  def _check_slash(self) -> None | NoReturn:
    if self.NITTER_INSTANCE[-1] != '/':
      raise RuntimeError('NITTER_INSTANCEの末尾には/が必須です')
    if self.ARCHIVE_TODAY[-1] != '/':
      raise RuntimeError('ARCHIVE_TODAYの末尾には/が必須です')
    if self.ARCHIVE_TODAY_STANDARD[-1] != '/':
      raise RuntimeError('ARCHIVE_TODAY_STANDARDの末尾には/が必須です')
    if self.TWITTER_URL[-1] == '/':
      raise RuntimeError('TWITTER_URLの末尾には/があってはいけません')

  ##Torが使えているかチェック
  def _check_tor(self) -> None | NoReturn:
    print('Torのチェック中ですを')
    try:
      res: Final[Response] = self._request_once('https://check.torproject.org/api/ip') ##リクエスト
      is_tor: Final[bool] = json.loads(res.text)['IsTor']
      if is_tor:
        print('Tor OK')
      else:
        raise RuntimeError('サイトにTorのIPでアクセスできていないなりを')
    except requests.exceptions.ConnectionError as e:
      print(e, file=sys.stderr)
      print('通信がTorのSOCKS proxyを経由していないなりを', file=sys.stderr)
      exit(1)

  ##nitterのインスタンスが生きているかチェック
  ##死んでいたらそこで終了
  ##接続を一回しか試さない_request_onceを使っているのは
  ##激重インスタンスが指定されたとき試行回数増やして偶然成功してそのまま実行されるのを躱すため
  def _check_instance(self) -> None | NoReturn:
    print("nitterのインスタンスチェック中ですを")
    try:
      res: Final[Response] = self._request_once(self.NITTER_INSTANCE) ##リクエスト
      res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生
    except requests.exceptions.RequestException as e: ##エラー発生時は終了
      print(e, file=sys.stderr)
      print('インスタンスが死んでますを', file=sys.stderr)
      exit(1)

  ##ツイート収集するユーザー名を取得
  ##何も入力しないと尊師を指定するよう改良
  def _get_name(self) -> str | NoReturn:
    while True:
      print('アカウント名を入れなければない。空白だと自動的に' + self.CALLINSHOW + 'になりますを')
      account_str: str = input() ##ユーザー入力受付
      ##空欄で降臨ショー
      if account_str == '':
        return self.CALLINSHOW
      else:
        res: Response | None = self._request(self.NITTER_INSTANCE + account_str) ##リクエストして結果取得
        if res is None : ##リクエスト失敗判定
          self._fail()
        soup: BeautifulSoup = BeautifulSoup(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
        if soup.title == self.NITTER_ERROR_TITLE: ##タイトルがエラーでないか判定
          print(account_str + "は実在の人物ではありませんでした") ##エラー時ループに戻る
        else:
          print("最終的に出会ったのが@" + account_str + "だった。")
          return account_str ##成功時アカウント名返す

  ##検索クエリを取得
  def _get_query(self, name: Final[str]) -> Response | NoReturn:
    while True:
      print("検索クエリを入れるナリ。複数ある時は一つの塊ごとに入力するナリ。空欄を入れると検索開始ナリ。")
      print("例:「無能」→改行→「脱糞」→改行→「弟殺し」→改行→空欄で改行")
      query_str: list[str] = [name] ##検索クエリ文字列をquery_strに収納。ユーザー名を先に含めておく
      query_input: str = input() ##ユーザー入力受付
      ##空欄が押されるまでユーザー入力受付
      while query_input != '':
        query_str.append(quote(query_input))
        query_input = input()
      res: Response | None = self._request(self.NITTER_INSTANCE + self.SEARCH_QUERY + '+'.join(query_str)) ##リクエストして結果取得
      if res is None : ##リクエスト失敗判定
        self._fail()
      soup: BeautifulSoup = BeautifulSoup(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
      ##1件もないときはHTMLにtimeline-noneがあるから判定
      if soup.find(class_='timeline-none') is None:
        print("クエリのピースが埋まっていく。")
        return res ##成功したリクエストのレスポンスを返す
      else:
        print("適切なクエリを入力することを切に望む。")

  ##接続失敗時処理
  def _fail(self) -> NoReturn:
    print("接続失敗しすぎで強制終了ナリ")
    if self._txt_data != '': ##取得成功したデータがあれば発行
      print("取得成功した分だけ発行しますを")
      self._make_txt()
    else:
      exit(1) ##終了

  ##テキスト発行
  def _make_txt(self) -> NoReturn:
    result_txt: Final[str] = '{|class="wikitable" style="text-align: left;"\n' + self._txt_data + '|}' ##wikiの表の最初と最後
    ##ファイル出力
    with codecs.open('tweet.txt', 'w', 'utf-8') as f:
      f.write(result_txt)
    print("テキストファイル手に入ったやで〜")
    exit(0) ##終了

  ##記録を中断するツイート
  def _stop_word(self) -> str:
    print(f"ここにツイートの本文を入れる!すると・・・・なんと一致するツイートで記録を中断する!(これは本当の仕様です。)ちなみに、空欄だと{self.LIMIT_N_TWEETS}件まで終了しない。")
    end_str: Final[str] = input() ##ユーザー入力受付
    return end_str

  ##Twitterの画像をダウンロード
  def _download_media(self, media_name: Final[str]) -> bool:
    os.makedirs(self.MEDIA_DIR, exist_ok=True)
    url: Final[str] = 'https://pbs.twimg.com/media/' + media_name
    res: Final[Response | None] = self._request(url)
    if res is not None:
      if 'image' not in res.headers['content-type']:
        return False
      with open(self.MEDIA_DIR + '/' + media_name, "wb") as f:
        f.write(res.content)
      return True
    else:
      return False

  def _get_tweet_media(self, tweet: bs4.element.Tag) -> str:
    tweet_media: bs4.element.Tag | None = tweet.select_one('.tweet-body > .attachments') # 引用リツイート内のメディアを選択しないように.tweet-body直下の.attachmentsを選択
    media_txt: str = ''
    if tweet_media is not None:
      media_list: list[str] = []
      # ツイートの画像の取得
      for image_a in tweet_media.select('.attachment.image a'):
        media_name: str = re.search(r'%2F([^%]*\.jpg)|%2F([^%]*\.jpeg)|%2F([^%]*\.png)|%2F([^%]*\.gif)', image_a.get('href')).group(1)
        media_list.append(f"[[ファイル:{media_name}|240px]]")
        if self._download_media(media_name):
          print(self.MEDIA_DIR + '/' + media_name + ' をアップロードしなければない。')
        else:
          print('https://pbs.twimg.com/media/' + media_name + ' をアップロードしなければない。')
      # 動画についてはm3u8で落ちてきて面倒臭いため取得しない
      """
      for video in tweet_media.select('.attachment.video-container video'):
        media_url: str = unquote(re.search(r'[^\/]+$', video.get('data-url')).group(0))
        print(media_url)
      """
      media_txt = '<br>\n' + ' '.join(media_list)
    return media_txt

  def _get_tweet_quote(self, tweet: bs4.element.Tag) -> str:
    tweet_quote: Final[bs4.element.Tag | None] = tweet.select_one('.tweet-body > .quote.quote-big') # 引用リツイートを選択
    quote_txt: str = ''
    if tweet_quote is not None:
      link: str = tweet_quote.select_one('.quote-link').get('href')
      link = re.sub('#.*$', '', link)
      link = self.TWITTER_URL + link
      quote_txt = '<br>\n' + self._archive_url(link, link)
    return quote_txt

  #ページからツイート本文をself._txt_dataに収めていく
  def get_tweet(self) -> None | NoReturn:
    soup: Final[BeautifulSoup] = BeautifulSoup(self._page.text, 'html.parser') ##beautifulsoupでレスポンス解析
    tweets: Final[bs4.element.ResultSet] = soup.find_all(class_='timeline-item') ##一ツイートのブロックごとにリストで取得
    for tweet in tweets: ##一ツイート毎に処理
      if tweet.a.text == self.NEWEST: ##Load Newestのボタンは処理しない
        continue
      if tweet.find(class_='retweet-header') is not None: ##retweet-headerはリツイートを示すので入っていれば処理しない
        continue
      tweet_url: str = self.TWITTER_URL + re.sub('#[^#]*$', '', tweet.find(class_='tweet-link').get('href')) ##ツイートのURL作成
      archived_tweet_url: str = self._callinshowlink_url(tweet_url) ##ツイートURLをテンプレートCallinShowlinkに変化
      tweet_content: bs4.element.Tag = tweet.find(class_='tweet-content media-body') ##ツイートの中身だけ取り出す
      self._archive_soup(tweet_content) ##ツイートの中身のリンクをテンプレートArchiveに変化
      media_txt: str = self._get_tweet_media(tweet) ##ツイートに画像などのメディアを追加
      quote_txt: str = self._get_tweet_quote(tweet) ##引用リツイートの場合、元ツイートを追加
      self._txt_data = '!' + archived_tweet_url + '\n|-\n|\n' \
        + self._escape_wiki_reserved_words(tweet_content.get_text()) \
        + quote_txt + media_txt + '\n|-\n' \
        + self._txt_data ##wikiの文法に変化
      self._limit_count += 1 ##記録回数をカウント
      if self._limit_count % self.REPORT_INTERVAL == 0:
        print(f"ツイートを{self._limit_count}件も記録したンゴwwwwwwwwwww")
      if self._stop != '' and self._stop in tweet_content.get_text(): ##目的ツイートか判定
        print("目的ツイート発見でもう尾張屋根")
        self._make_txt()
      if self._limit_count >= self.LIMIT_N_TWEETS: ##上限達成か判定
        print(f"{self.LIMIT_N_TWEETS}件も記録している。もうやめにしませんか。")
        self._make_txt()

  #テンプレート外の#をnowikiで囲む
  def _escape_hash(self, text: str) -> str:
    archive_begin: Final[str] = '{{Archive|'
    callinshowlink_begin: Final[str] = '{{CallinShowLink|'
    archive_end: Final[str] = '}}'
    current_depth: int = 0
    new_text: str = ''
    for i in range(len(text)):
      if text[i:i+len(archive_begin)] == archive_begin or text[i:i+len(callinshowlink_begin)] == callinshowlink_begin:
        current_depth += 1
      elif text[i:i+len(archive_end)] == archive_end:
        current_depth = max(current_depth-1, 0)
      if current_depth == 0 and text[i] == '#':
        new_text += '<nowiki>#</nowiki>'
      else:
        new_text += text[i]
    return new_text

  #MediaWiki文法と衝突する文字を無効化する
  def _escape_wiki_reserved_words(self, text: str) -> str:
    text = text.replace('\n', '<br>\n')
    text = re.sub(r'^ ', '&nbsp;', text, flags=re.MULTILINE)
    text = self._escape_hash(text)
    return text

  #tagをテンプレートArchiveの文字列に変化させる
  def _archive_soup(self, tag: bs4.element.Tag) -> None:
    urls_in_tweet: Final[bs4.element.ResultSet] = tag.find_all('a')
    for url in urls_in_tweet:
      if re.match('^https?://', url.get('href')) is not None: ##先頭にhttpが付いていない物はハッシュタグの検索ページへのリンクなので処理しない
        if re.match('https' + self.NITTER_INSTANCE[4:], url.get('href')):
          #Nitter上のTwitterへのリンクを直す
          url_link: str = url.get('href').replace('https' + self.NITTER_INSTANCE[4:], self.TWITTER_URL + '/')
          url_link = re.sub('\?.*$', '', url_link)
          url.replace_with(self._archive_url(url_link, url_link)) ##テンプレートArchiveに変化
        elif re.match('piped.kavin.rocks/', url.text) or re.match('invidio.us/', url.text):
          #Nitter上のYouTubeへのリンクを直す
          url_link: str = url.get('href')
          url_link = url_link.replace('piped.kavin.rocks/', 'youtu.be/')
          url_link = url_link.replace('invidio.us/', 'youtu.be/')
          url.replace_with(self._archive_url(url_link, url_link)) ##テンプレートArchiveに変化
        else:
          url.replace_with(self._archive_url(url.get('href'), url.get('href'))) ##テンプレートArchiveに変化

  #URLをテンプレートArchiveに変化させる
  def _archive_url(self, url: Final[str], text: Final[str]) -> str:
    return '{{Archive|1=' + unquote(url) + '|2=' + self._archive(url) + '|3=' + unquote(text)  + '}}' ##テンプレートArchiveの文字列返す

  #URLをテンプレートCallinShowlinkに変化させる
  def _callinshowlink_url(self, url: Final[str]) -> str:
    return '{{CallinShowLink|1=' + url + '|2=' + self._archive(url) + '}}' ##テンプレートCallinShowlinkの文字列返す

  ##URLから魚拓返す
  def _archive(self, url: Final[str]) -> str:
    archive_url: str = self.ARCHIVE_TODAY_STANDARD + url.replace('#', '%23') ##wikiに載せるとき用URLで失敗するとこのままhttps://archive.ph/https://xxxxxxxxの形で返される
    res: Final[Response | None] = self._request(self.ARCHIVE_TODAY + url.replace('#', '%23')) ##アクセス用URL使って結果を取得
    if res is None : ##魚拓接続失敗時処理
      print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。')
    else:
      soup: Final[BeautifulSoup] = BeautifulSoup(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
      content: bs4.element.Tag = soup.find(id="CONTENT") ##archive.todayの魚拓一覧ページの中身だけ取得
      if content.get_text()[:len(self.NO_ARCHIVE)] == self.NO_ARCHIVE: ##魚拓があるかないか判定
        print(url + "の魚拓がない。これはいけない。")
      else:
        archive_url = content.find('a').get('href').replace(self.ARCHIVE_TODAY, self.ARCHIVE_TODAY_STANDARD)
    return archive_url

  ##新しいページを取得
  def go_to_new_page(self) -> None | NoReturn:
    soup: Final[BeautifulSoup] = BeautifulSoup(self._page.text, 'html.parser') ##beautifulsoupでレスポンス解析
    show_mores: Final[bs4.element.ResultSet] = soup.find_all(class_="show-more")
    for show_more in show_mores: ##show-moreに次ページへのリンクか前ページへのリンクがある
      if show_more.text != self.NEWEST:  ##前ページへのリンクではないか判定
        new_url: str = self.NITTER_INSTANCE + self.SEARCH + show_more.a.get('href') ##直下のaタグのhrefの中身取ってURL頭部分と合体
    res: Final[Response | None] = self._request(new_url) ##接続してHTML取ってくる
    if res is None:
      self._fail()
    new_page_soup: Final[BeautifulSoup] = BeautifulSoup(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
    if new_page_soup.find(class_="timeline-end") is None: ##ツイートの終端ではtimeline-endだけのページになるので判定
      print(res.url + 'に移動しますを')
      self._page = res ##まだ残りツイートがあるのでページを返して再度ツイート本文収集
    else:
      print("急に残りツイートが無くなったな終了するか")
      self._make_txt()


if __name__ == '__main__':
  if sys.version_info.major < 3 or (sys.version_info.major == 3 and sys.version_info.minor < 10):
    print('Pythonのバージョンを3.10以上に上げて下さい', file=sys.stderr)
    exit(1)
  krsw: Final[bool] = len(sys.argv) > 1 and sys.argv[1] == 'krsw' ##コマンドライン引数があるかどうかのフラグ
  twitter_archiver: TwitterArchiver = TwitterArchiver(krsw)

  ##ツイートを取得し終えるまでループ
  while True:
    twitter_archiver.get_tweet() ##self._txt_dataにページ内のリツイート以外の全ツイートの中身突っ込んでいく
    twitter_archiver.go_to_new_page() ##新しいページ取得

実行例

20件での実行例。

https://twitter.com/CallinShow/status/1563171863043325954(魚拓)

明日は明日の風が吹く!!!
https://twitter.com/rumaimai288/status/1563136444230926341(魚拓)

https://twitter.com/CallinShow/status/1563179306477449216(魚拓)

そうなるよな。
https://twitter.com/itm_nlab/status/1563041010963296261(魚拓)

https://twitter.com/CallinShow/status/1563184989369315329(魚拓)

昔、渋谷の東宝の映画館で見たな。
https://twitter.com/kinro_ntv/status/1562741577184731136(魚拓)

https://twitter.com/CallinShow/status/1563406043119783936(魚拓)

これは良くない。

日中、会社の車だろ。
https://twitter.com/pachipachisukki/status/1558433177408274432(魚拓)

https://twitter.com/CallinShow/status/1563469935099011073(魚拓)

政権を担った全ての政党に責任。

民主党だった人たちも含めてだ。

放置してきたんだ。
解散総選挙をしよう。

民意をもう一度問うために。
https://twitter.com/tbs_houtoku/status/1563429510682669058(魚拓)

https://twitter.com/CallinShow/status/1563471832316923904(魚拓)

立憲民主党はこの14人が議員辞職してみるくらいかっこいいことしてから追及してほしい。

https://twitter.com/CallinShow/status/1563472582229209089(魚拓)

二大政党制は期待できない。

今は第三極が必要なんだ。

https://twitter.com/CallinShow/status/1563477985893658624(魚拓)

news feed&utm_medium=referral&utm_campaign=related_link https://www.iza.ne.jp/article/20220825-GBGV6NOE5BNWJHT3MSN47LXYCA/photo/E7XLVXDSQJJEXEG7AUTFABQ2GI/?utm_source=yahoo news feed&utm_medium=referral&utm_campaign=related_link(魚拓)

身の丈にあわないものを求めるとかっこ悪いっていう象徴。

背景に写っている山の山頂は見えない。

五里霧中。

https://twitter.com/CallinShow/status/1563480030466154497(魚拓)

金を貯め込む人がいる。

人のために使った方がいいよ。

金を抱いて死ぬことはできないんだぜ。

https://twitter.com/CallinShow/status/1563514805356756992(魚拓)

弁護士唐澤貴洋への匿名のメッセージを募集中!
#マシュマロを投げ合おう
https://marshmallow-qa.com/apt/e52b0b30-c815-4fdb-8a9c-409a6b8a650b(魚拓)

https://twitter.com/CallinShow/status/1563518352777445378(魚拓)

コパノリッキーを獲得🏇
FbK7jfiagAADYSm.jpg

https://twitter.com/CallinShow/status/1563709530797527040(魚拓)

本当その通りだ。

銀座に飲みに行ってみろ。

そんな飲み方しているやつなんかみたことない。

遊び方を知らない奴が、嫉妬妄想で仕方ないよねとか言ってるんだ。

性的被害に遭うのは仕方がないとか、人権意識ゼロの発言。

接待を伴う飲食業は、立派な仕事だ。
https://twitter.com/hone_hone_kotu/status/1562287607681146881(魚拓)

https://twitter.com/CallinShow/status/1563713254584553472(魚拓)

今回問題になった香川照之さんみたいな飲み方は、カッコ悪くて、そんなの真似してたら嘲笑、出禁、通報。

https://twitter.com/CallinShow/status/1563822049453256705(魚拓)

接待を伴う飲食店についての考え方だったり、店の中での振る舞いでわかる。

お金を使うのが無駄っていう人は、単なるケチ、女性にお金を使ったら負けくらいの精神の持ち主、一緒にいても楽しくない。

振る舞いが悪いのは、自己評価が過大な人間。
過大なので、更衣室のネタ。

https://twitter.com/CallinShow/status/1563822588249272320(魚拓)

接待を伴う飲食店の全ての痛客に向けて。

https://note.com/takahirokarasawa/n/nd7f01ba99c74(魚拓)

https://twitter.com/CallinShow/status/1563833420492316673(魚拓)

ドンマイ川端!
https://twitter.com/KAWABATA1129/status/1563706260645871616(魚拓)

https://twitter.com/CallinShow/status/1563890352960204800(魚拓)

https://marshmallow-qa.com/callinshow?utm_medium=url_text&utm_source=promotion(魚拓)

フォロワーの人で何か質問がある人待っています😊

https://twitter.com/CallinShow/status/1563891616901111808(魚拓)

このツィートが意味がわからない人へ。

川端さんが試合で負けたことに対して、
一ファンとして

ドンマイ + 川端

という意味でツイートしたものです。
https://twitter.com/CallinShow/status/1563833420492316673(魚拓)

https://twitter.com/CallinShow/status/1563913356511428608(魚拓)

アーシュラ・K・ル・グィンの「オメラスから歩み去る人々」を読む。

ネットの中で誹謗中傷する人たちは、理想郷とも思えるオメラスに住み、オメラスの地下牢にいる子どもを作ることで、安定を得ようとする。

https://twitter.com/CallinShow/status/1563913838881554432(魚拓)

現代社会においてオメラスは入れ子構造になっている。

オメラスとされるところは、実は地下牢なんだと気付けるか。

オメラスを去ることはできるのか。
https://twitter.com/CallinShow/status/1563913356511428608(魚拓)