リツイート企画botを作って twitterBot⑦

· ☕ 8

フォロワー様5000人を記念して、企画botを作りました。

企画ツイートにリプしてくれたら、御礼のリプライと、その人の最近のツイートを3件リツイートするよー!って企画w

面白いって参加してくださる方がたくさんいて、もうすごい嬉しい//

開始から3時間くらいだけど、20人くらいの方に参加いただけて…。

ありがとうございます!

ここまでの作成過程なんかはこちら

で、実際に使ってもらうとバグがでるでるww

知ってたけどw

なので、調整入れていきます!

各種エラーの対応

名前の切り抜き

twitterって140文字しかつぶやけないんですね。

なので、それ以上のツイートをしようとすると、こんなエラーが返ってくる

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

御礼のリプライメッセージはこちら。

1
status = f'{reply["name"]}さん、企画に参加ありがとうございます!\nリプの御礼に{reply["name"]}さんのツイートを最新から3件ほど、RTさせて頂きます♡\n何回でも参加可能なので、またのご参加お待ちしております!\n\n※このツイートはbotからの自動送信です'

名前抜きで、108文字もある💦

なので、許容できる文字数は15文字ってところですねww

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    #名前の切り抜き
    for idx,r in enumerate(replyList):
        name = replyList[idx]["name"]
        name = name.split("@")[0]
        name = name.split(" ")[0]
        name = name.split(" ")[0]
        name = name.split("")[0]
        
        #空になってしまったら、元々の名前を復活
        if len(name) == 0:
            name = replyList[idx]["name"]

        #切り抜いた結果of切り抜けなかった結果15文字以内なら文字数で切り取り
        if len(name) <= 15:
            replyList[idx]["name"] = name
        else:
            replyList[idx]["name"] = f"{name[0:15]}…"

で、こんなのを追加してみました。

やっぱり名前の区切り文字で一番使われてるのは@ですよね。

あとスペースとか、メジャーな区切り文字の方はしっかり区切ってあげます。

で、切り取れなかった人や、切り取っても長い人は、15文字でバサッと切って、略してるよーって伝えるためにw点をくっつけておきます!

はい、完成。

ツイートが検出出来ない人への対応

Twitterって莫大な量のツイートが蓄積されるから、私たち一般人がapiを使うと最近一週間分のデータしか取れない。みたいな制限があるんですって。

あんまりツイートしない人だと、3ツイートも取得できないww

で、うん、これはもはやただのバグですが…。

Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 165, in lambda_handler
    main()
  File "/var/task/lambda_function.py", line 155, in main
    print(f'{reply["tweetIdList"][idx]}をリツイート')
IndexError: list index out of range

はぁ…。

ない配列取ろうとしてますからね…。素人かっていうね…。

1
2
    if idx >= len(reply["tweetIdList"]):
        break

はい。制御かけましたよ…。

各種改善

wait追加

リツイートとリプライをしているわけなのですが、waitもいれずにバシバシリツイートするのはどうなのよ?

って思ったので、wait追加しました。

    sleepTime = random.random() * 20
    print(sleepTime)
    time.sleep(sleepTime)

ざつーな感じw

0~1の乱数に20掛けてるから、0秒から20秒waitします。

ngになった人へのリプライ

変なツイートをリプライしたくないので、最低限のチェックを掛けていたんですが、そうすると、チェックに引っかかってリプライされてないのか、なんらかのえらーなのかが分からない…。

困るので、あなたはngのためリツイートできませんっていうリプライをすることにした。

1
2
3
4
    if reply["ng"]:
        status = f'{reply["name"]}さん、企画に参加ありがとうございます!\n大変申し訳ありませんが、{reply["ngReason"]}のため、{reply["name"]}さんのツイートはRTできません。\n\n※このツイートはbotからの自動送信です'
    else:
        status = f'{reply["name"]}さん、企画に参加ありがとうございます!\nリプの御礼に{reply["name"]}さんのツイートを最新から3件ほど、RTさせて頂きます♡\n何回でも参加可能なので、またのご参加お待ちしております!\n\n※このツイートはbotからの自動送信です'

こんな感じ

完成!

githubにも後で上げますー

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
from requests_oauthlib import OAuth1Session, OAuth1
import requests
import json
import codecs
import time
import os
import datetime
import collections
import re
import random

OAUTH1 = os.environ['OAUTH1']
OAUTH2 = os.environ['OAUTH2']
OAUTH3 = os.environ['OAUTH3']
OAUTH4 = os.environ['OAUTH4']
SCREEN_NAME = os.environ['SCREEN_NAME']

def getOauth():
    return OAuth1(OAUTH1, OAUTH2, OAUTH3, OAUTH4)

def main():

    ngList=["融資",'裏垢','xxx','yyy'] #ブログを汚したくないから書いていないけど、実際には、あと100ワードくらいありますwww

    #自分宛のリプライ最新から10件を全て取得
    response = requests.get(
        f'https://api.twitter.com/1.1/statuses/mentions_timeline.json?count=20',
        auth=getOauth()
    ).json()

    #その中から、指定した元ツイートに対してのリプライのみに絞り込む
    in_reply_to_status_id=1231358267516809216
    response = list(filter(lambda x:x["in_reply_to_status_id"] == in_reply_to_status_id,response))

    #未リプライの物のみに絞り込む
    doneList = requests.get(
        f'https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={SCREEN_NAME}&count=100',
        auth=getOauth()
    ).json()
    doneList = list(map(lambda x:x["in_reply_to_status_id"],doneList))
    print(doneList)
    
    response = list(filter(lambda x:x["id"] not in doneList,response))
    print(response)

    #扱いやすいように成型
    replyList = list(map(lambda x:{"id":x["user"]["id"],"name":x["user"]["name"],"profile":x["user"]["description"],"replyId":x["id"],"ng":False},response))
    print(replyList)
    
    #名前の切り抜き
    for idx,r in enumerate(replyList):
        name = replyList[idx]["name"]
        name = name.split("@")[0]
        name = name.split("")[0]
        
        #空になってしまったら、元々の名前を復活
        if len(name) == 0:
            name = replyList[idx]["name"]

        #切り抜いた結果of切り抜けなかった結果15文字以内なら文字数で切り取り
        if len(name) <= 15:
            replyList[idx]["name"] = name
        else:
            replyList[idx]["name"] = f"{name[0:15]}…"

    #最新のツイートを10件取得する
    option = 'count=10&exclude_replies=true&include_rts=false'
    for idx,r in enumerate(replyList):
        user_timeline = requests.get(
            f'https://api.twitter.com/1.1/statuses/user_timeline.json?id={r["id"]}&{option}',
            auth=getOauth()
        ).json()
        replyList[idx]["tweetList"] = list(map(lambda x:x["text"],user_timeline))
        replyList[idx]["tweetIdList"] = list(map(lambda x:x["id"],user_timeline))
    print(replyList)

    #NGワードチェック
    print("NGワードチェック")
    for idx,reply in enumerate(replyList):
        #プロフィールをチェック
        if len(list(filter(lambda ng:ng in reply["profile"],ngList))):
            print(f"{reply['name']}:ng")
            print(list(filter(lambda ng:ng in reply["profile"],ngList)))
            replyList[idx]["ng"] = True
            replyList[idx]["ngReason"] = "プロフィールにNGワードが含まれている"
        elif len(list(filter(lambda x:len(list(filter(lambda ng:ng in x,ngList))),reply["tweetList"]))):
            print(f"{reply['name']}:ng")
            print(list(filter(lambda x:len(list(filter(lambda ng:ng in x,ngList))),reply["tweetList"])))
            replyList[idx]["ng"] = True
            replyList[idx]["ngReason"] = "ツイートにNGワードが含まれている"
        else:
            print(f"{reply['name']}:ok")

    #リンク数チェック
    print("リンク数チェック")
    for idx,reply in enumerate(replyList):
        #ツイートをチェック
        if len(list(filter(lambda x:x.count("http")>=2,reply["tweetList"]))):
            print(f"{reply['name']}:ng")
            replyList[idx]["ng"] = True
            replyList[idx]["ngReason"] = "ツイートに過剰なリンクがある"
        else:
            print(f"{reply['name']}:ok")

    #重複リンク数チェック
    print("重複リンク数チェック")
    pattern = "https?://[\w/:%#\$&\?\(\)~\.=\+\-]+"
    for idx,reply in enumerate(replyList):
        #urlを抽出 ※既に1ツイート内に複数urlある場合は除外しているので、ここに来た時には、必ず1ツイート0~1url
        #まずurlを含むものだけに絞る
        urlTweetList =list(filter(lambda x:x.count("http")==1,reply["tweetList"]))
        print(f"urlTweetList:{urlTweetList}")
        #urlを抽出
        urlList = list(map(lambda x:re.findall(pattern, x)[0],urlTweetList))
        print(f"urlList:{urlList}")
        #urlListのそれぞれのurlの出現回数をカウントしてくれる ※要import collections
        counter = collections.Counter(urlList)
        print(f"counter:{counter}")

        if len(list(filter(lambda x:x >= 3,counter.values()))):
            print(f"{reply['name']}:ng")
            replyList[idx]["ng"] = True
            replyList[idx]["ngReason"] = "ツイートに過剰な重複リンクがある"
        else:
            print(f"{reply['name']}:ok")

    warnList = ["RT企画","固定ツイート","固ツイ","リツイート"]
    #RT企画数チェック
    print("RT企画数チェック")
    for idx,reply in enumerate(replyList):
        if len(list(filter(lambda x:len(list(filter(lambda warn:warn in x,warnList))),reply["tweetList"]))) >= 3:
            print(f"{reply['name']}:ng")
            replyList[idx]["ng"] = True
            replyList[idx]["ngReason"] = "過剰なRT企画ツイートがある"
        else:
            print(f"{reply['name']}:ok")

    #日本語チェック
    print("日本語チェック")
    pattern = "[\u3041-\u309F]+" #ひらがな
    for idx,reply in enumerate(replyList):
        print(re.findall(pattern, reply["profile"]))
        if len(re.findall(pattern, reply["profile"])) == 0:
            print(f"{reply['name']}:ng")
            replyList[idx]["ng"] = True
            replyList[idx]["ngReason"] = "プロフィールから日本語が検出できない"
        else:
            print(f"{reply['name']}:ok")

    #リプライする
    for reply in replyList:
        if reply["ng"]:
            status = f'{reply["name"]}さん、企画に参加ありがとうございます!\n大変申し訳ありませんが、{reply["ngReason"]}のため、{reply["name"]}さんのツイートはRTできません。\n\n※このツイートはbotからの自動送信です'
        else:
            status = f'{reply["name"]}さん、企画に参加ありがとうございます!\nリプの御礼に{reply["name"]}さんのツイートを最新から3件ほど、RTさせて頂きます♡\n何回でも参加可能なので、またのご参加お待ちしております!\n\n※このツイートはbotからの自動送信です'
        in_reply_to_status_id = reply["replyId"]
        print(f"in_reply_to_status_id:{in_reply_to_status_id} exec")
        replyResponse = requests.post(
            f'https://api.twitter.com/1.1/statuses/update.json',
            data={"status":status
                ,"in_reply_to_status_id":in_reply_to_status_id
                ,"auto_populate_reply_metadata":True},
            auth=getOauth()
        ).json()
        print(replyResponse)

        #リツイートする
        if not reply["ng"]:
            for idx in range(3):
                if idx >= len(reply["tweetIdList"]):
                    break
                sleepTime = random.random() * 20
                print(sleepTime)
                time.sleep(sleepTime)

                print(f'{reply["tweetIdList"][idx]}をリツイート')
                response = requests.post(
                    f'https://api.twitter.com/1.1/statuses/retweet/{reply["tweetIdList"][idx]}.json',
                    auth=getOauth()
                ).json()
                print(response)

def lambda_handler(event, context):

    try:
        main()

    except:
        import traceback
        traceback.print_exc()

まだまだ、リプお待ちしてますー//


るな
るな
エンジニア