-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathstreambot.py
198 lines (157 loc) · 7.73 KB
/
streambot.py
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
192
193
194
195
196
197
198
import django
import logging
# from nltk import word_tokenize
import os
# import re
from slacker import Slacker
from sutime import SUTime
import tweepy
from tweepy.api import API
from slack_msg import send_slack_message
# need to point Django at the right settings to access pieces of app
os.environ["DJANGO_SETTINGS_MODULE"] = "openchat.settings" # noqa
django.setup() # noqa
from openspaces.bot_utils import db_utils, tweet_utils, time_utils
import openspaces.secrets as s
from openchat.settings import BASE_DIR
class StreamListener(tweepy.StreamListener):
"""Object that defines the callback actions passed to tweepy.Stream"""
def __init__(self, streambot, api=None):
self.api = api or API()
# needed ref to streambot so method can be called
self.streambot = streambot
self.tw_bot_id = 964215184951713792
self.ignored_users = []
def update_ignore_users(self):
"""Check app config table to get list of ignored twitter ids"""
ignore_list = db_utils.get_ignored_users()
ignore_list.append(self.tw_bot_id)
self.ignored_users = ignore_list
def on_status(self, status):
"""Take a tweet with matching keyword save and trigger retweet_logic"""
self.update_ignore_users()
if status.user.id in self.ignored_users:
return
# create or update user and tweet records in Django models
db_utils.get_or_create_user_and_tweet(status)
# trigger logic to handle tweet and decide on response in Streambot
self.streambot.retweet_logic(status.text, status.id_str,
status.user.screen_name, status.user.id)
def on_error(self, status_code):
if status_code == 420:
return False
class Streambot:
""" Stream Twitter and look for tweets that contain targeted words,
when tweets found look for datetime and room, if present save tweet
to OutgoingTweet model.
Ex.
bot = Streambot()
# to run a stream looking for tweets about PyCon
bot.run_stream(["PyCon"])
"""
def __init__(self):
db_utils.setup_outgoing_config() # needs an outgoing config obj to check against
self.api = self.setup_auth()
self.stream_listener = StreamListener(self)
jar_files = os.path.join(BASE_DIR, "python-sutime", "jars")
self.sutime = SUTime(jars=jar_files, mark_time_ranges=True)
self.slacker = Slacker(s.SLACK_TOKEN)
def setup_auth(self):
"""Set up auth stuff for api and return tweepy api object"""
auth = tweepy.OAuthHandler(s.openspacesbot["CONSUMER_KEY"],
s.openspacesbot["CONSUMER_SECRET"])
auth.set_access_token(s.openspacesbot["ACCESS_TOKEN"],
s.openspacesbot["ACCESS_TOKEN_SECRET"])
api = tweepy.API(auth)
return api
def run_stream(self, search_list=None):
""" Start stream, when matching tweet found on_status method called.
search_list arg is a list of terms that will be looked for in tweets
"""
if search_list == None:
raise ValueError("Need a list of search terms as arg to run_stream")
stream = tweepy.Stream(auth=self.api.auth, listener=self.stream_listener)
stream.filter(track=search_list)
def send_mention_tweet(self, screen_name):
"""Mention a user in a tweet from bot letting them know that
their tweet has been recieved and that we will send out reminders
about their event.
"""
hours_mins = time_utils.get_local_clock_time()
mention = "@{} just saw your Open Spaces tweet!"
mention += " I'll retweet a reminder 15 minutes before your event!"
mention = mention.format(screen_name, hours_mins)
try:
self.api.update_status(status=mention)
except:
# if same user tweets valid openspaces tweet at exact same clock time
# it causes a duplicate tweet which bot can't send
pass
def parse_time_room(self, tweet):
"""Get time and room number from a tweet using SUTime and tweet_utils"""
extracted_time = self.sutime.parse(tweet)
time_and_room = tweet_utils.get_time_and_room(tweet, extracted_time)
return time_and_room
def value_check(self, time_room_obj):
"""Returns a tuple with the counts of values extracted from a tweet
in the parse_time_room method. This tuple is used to decide how bot
will respond to tweet.
"""
num_room_values = len(time_room_obj["room"])
num_time_values = len(time_room_obj["date"])
return (num_room_values, num_time_values)
def retweet_logic(self, tweet, tweet_id, screen_name, user_id):
"""Use SUTime to try to parse a datetime out of a tweet, if successful
save tweet to OutgoingTweet to be retweeted
"""
# use SUTime to parse a datetime out of tweet
time_room = self.parse_time_room(tweet)
# make sure both time and room extracted and only one val each
val_check = self.value_check(time_room)
if val_check == (1, 1):
room = time_room["room"][0]
date_mention = tweet_utils.check_date_mention(tweet)
converted_time = time_utils.convert_to_utc(time_room["date"][0], date_mention)
# check for a time and room conflict, only 1 set of retweets per event
# default time range that a room is resrved for is -15 +30 mins
conflict = db_utils.check_time_room_conflict(converted_time, room)
if not conflict:
# This record lets us check that retweets not for same event
event_obj = db_utils.create_event(description=tweet,
start=converted_time,
location=room,
creator=screen_name)
tweet_utils.schedule_tweets(screen_name, tweet, tweet_id, converted_time, event_obj)
slack_msg = "{} \n Tweet from: {}, \n id: {}".format(tweet, screen_name, user_id)
send_slack_message(user_id=user_id,
tweet_id=tweet_id,
screen_name=screen_name,
tweet_created=True,
tweet=tweet,
slack_msg=slack_msg)
self.send_mention_tweet(screen_name)
else:
message = f"Tweet found for an already scheduled event: \n {tweet}"
send_slack_message(user_id=user_id,
tweet_id=tweet_id,
screen_name=screen_name,
tweet_created=False,
tweet=tweet,
slack_msg=message,
channel="conflict")
elif val_check == (0, 0):
# tweet found but without valid time or room extracted, ignore
pass
else:
# tweet with relevant information but not exactly 1 time & 1 room
slack_msg = "Tweet found that needs review: {} \n tweet_id: {} \n screen_name: {} \n user_id: {}"
slack_msg = slack_msg.format(tweet, tweet_id, screen_name, user_id)
send_slack_message(user_id=user_id,
tweet_id=tweet_id,
screen_name=screen_name,
tweet_created=False,
tweet=tweet,
slack_msg=slack_msg)
if __name__ == '__main__':
bot = Streambot()
bot.run_stream(["pyconopenspaces", "pyconopenspace"])