Twitter Botで長い文章をツイート可能ギリギリの長さに調節する

# はじめに
TwitterのBotを作っていると、ツイートしようとしたときに次のようなエラーが返ってくることがあります。

```text
[{'code': 186, 'message': 'Tweet needs to be a bit shorter.'}]
```

これは、Twitterの文字制限をオーバーしている、というエラーで、
日本語では140文字、英語だと280文字までしかツイートできません。
さらに、urlは22文字として換算される、とか、まあいろいろな例外もあるのですが、
そのような解説は[他の記事](https://qiita.com/PND/items/17e87b8839c9099d2e70#url-%E6%8A%BD%E5%87%BA)に譲るとして、
実運用ではどのようにして文字数制限を回避すればよいでしょうか。

例えば、青空文庫に掲載されている、日本の名作とその書き出しをツイートするボットを開発する場合、
下の画像のように、書き出しを制限ギリギリにする方法を本記事では紹介します。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565132/91a9e1c2-e306-b60d-6790-02499bb90eb9.png)



# twitter-text-parserについて

ツイッターの文字数制限のルールは複雑です。
単純にpythonのlen関数だと、英語（1バイト文字）、日本語（2バイト文字）どちらも1文字としてカウントされていまうので、使えません。
そこで、twitterでツイート可能かどうかの判定をしてくれるtwitter-text-parserというパッケージがあるので、それを利用しましょう。

■twitter-text-parser [公式ドキュメント](https://pypi.org/project/twitter-text-parser/)

コマンド画面で

```terminal
pip install twitter-text-parser
```

をします。
実際に使ってみたのが以下になります。

```python:test1.py
from twitter_text import parse_tweet

text_ok = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
print(parse_tweet(text_ok))

text_ng = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901"
print(parse_tweet(text_ng))
```

```text:実行結果
ParsedResult(valid=True, weightedLength=280, permillage=1000, validRangeStart=0, validRangeEnd=279, displayRangeStart=0, displayRangeEnd=279)
ParsedResult(valid=False, weightedLength=281, permillage=1003, validRangeStart=0, validRangeEnd=279, displayRangeStart=0, displayRangeEnd=280)
```

のように、ParsedResult型で出力されます。
この型の中のvalidが、ツイート可能かどうかを表していて、
Trueならばツイート可能、Falseならばツイート不可能です。
ですので、次のようなツイート可能/不可能ジャッジが可能です。

```test2.py
from twitter_text import parse_tweet

text_ok = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
text_ng = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901"

def judge_tweetable(text):
    if parse_tweet(text).valid: # validがTrueであれば
        return "ツイート可能です"
    else:
        return "ツイート不可能です"

print(f"text_okは{judge_tweetable(text_ok)}")
print(f"text_okは{judge_tweetable(text_ng)}")
```

```text:実行結果
text_okはツイート可能です
text_okはツイート不可能です
```

# ツイート可能な長さになるまで書き出しを短くする
前座はここまで、ツイート可能な長さまでツイートをそろえる方法を紹介します。

アルゴリズムとしては簡単で、書き出しの文章をだんだん短くしていって、ツイート可能な長さになるまで行います。

```python 
from twitter_text import parse_tweet

def judge_tweetable(text):
    if parse_tweet(text).valid: # validがTrueであれば
        return "ツイート可能です"
    else:
        return "ツイート不可能です"

def change_tweet_len(tweet_format, target):
    target = target[:280] # 長すぎると処理に時間がかかるので、前半280文字にカット
    for i in range(len(target)):
        if i == 0:
            tweet_txt = tweet_format.format(target)
        else:
            tweet_txt = tweet_format.format(target[:-i] + "…")
        if parse_tweet(tweet_txt).valid:
            return tweet_txt

    # ここまで処理が来た場合は、書き出しがなくてもtweet_formatが文字数を超えていた場合
    return 0

# 書籍データ
author = "夏目 漱石"
title = "吾輩は猫である"
url = r"https://www.aozora.gr.jp/cards/000148/card789.html"
kakidashi = """
　吾輩は猫である。名前はまだ無い。
　どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。この書生というのは時々我々を捕えて煮て食うという話である。
"""

# ツイートフォーマット
tweet_format = author + "の" + "『" + title + "』\n{}\n\n続きは青空文庫で！\n" + url

tweet_text_changed = change_tweet_len(tweet_format, target=kakidashi)

# 一応の確認
print(f"以下の文章は{judge_tweetable(tweet_text_changed)}")
print("----------------------------------")
print(tweet_text_changed)
print("----------------------------------")
```

```text:実行結果
以下の文章はツイート可能です
----------------------------------
夏目 漱石の『吾輩は猫である』

　吾輩は猫である。名前はまだ無い。
　どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそ…

続きは青空文庫で！
https://www.aozora.gr.jp/cards/000148/card789.html
----------------------------------
```

関数change_tweet_lenでツイートの長さを調節しています。
引数のtweet_formatに、pythonのフォーマット文を指定し、targetに書き出しの文章を指定します。
関数内で、targetで指定した文章を、ツイート可能な長さになるまで、targetの文章を短くします。

念のため、ツイート可能か確認したものが以下になります。(冒頭の画像と同じやつ)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565132/45df8977-9e6f-b718-fd1d-91495dd5c66c.png)

はい、ぴったりですね。