Passed
Push — master ( 18a643...7d2313 )
by Ian
07:58 queued 11s
created

build.rsudp.c_tweet.Tweeter._when_img()   B

Complexity

Conditions 6

Size

Total Lines 49
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 39
nop 2
dl 0
loc 49
rs 8.0106
c 0
b 0
f 0
1
import os, sys, platform
2
import pkg_resources as pr
3
import time
4
import math
5
import numpy as np
6
from datetime import datetime, timedelta
7
import rsudp.raspberryshake as rs
8
from rsudp import printM, printW, printE, helpers
9
import rsudp
10
from twython import Twython
11
12
13
class Tweeter(rs.ConsumerThread):
14
	'''
15
	 .. versionadded:: 0.4.1
16
17
	.. |raspishakeq| raw:: html
18
19
		<a href="https://twitter.com/raspishakeq" target="_blank">Raspberry Shake</a>
20
21
	.. |usgsbigquakes| raw:: html
22
23
		<a href="https://twitter.com/USGSBigQuakes" target="_blank">USGS</a>
24
25
	.. |emsc_lastquake| raw:: html
26
27
		<a href="https://twitter.com/LastQuake" target="_blank">EMSC</a>
28
29
	Twitter is a social media platform sometimes used for quickly
30
	distributing public alert information. It is used by many agencies
31
	including |raspishakeq|, |usgsbigquakes|, and |emsc_lastquake|.
32
33
	.. |tw_api_bots| raw:: html
34
35
		<a href="https://developer.twitter.com/apps" target="_blank">API bots</a>
36
37
	.. note::
38
		Twitter is more difficult and stricter when it comes to making
39
		|tw_api_bots| than many services.
40
		First, you must go through a relatively rigorous process of applying for
41
		a developer account, then making a Twitter "app", and then giving the app
42
		permission to post on your behalf. See :ref:`setting-up-twitter` for details.
43
44
		Once you've gone through that process, Twitter limits posting and makes
45
		its rules on rate limiting relatively difficult to nail down.
46
		Generally, the early 2020 rate limit for posting is 300 in a 3-hour span,
47
		however that can vary depending on whether or not Twitter thinks there is
48
		suspicious activity coming from your account.
49
50
		In general, if you are looking for a simple multi-platform notification
51
		service, it may be easier and more reliable to use the Telegram service
52
		instead. rsudp has a telegram module at
53
		:py:class:rsudp.c_telegram.Telegram:. See :ref:`setting-up-telegram` for details.
54
55
	:param str consumer_key: Twitter calls this the "consumer key"
56
	:param str consumer_secret: Twitter calls this the "consumer key secret"
57
	:param str access_token: Twitter calls this the "consumer access token"
58
	:param str access_secret: Twitter calls this the "consumer access secret"
59
	:param bool tweet_images: whether or not to send images. if False, only alerts will be sent.
60
	:type extra_text: bool or str
61
	:param extra_text: approximately 180 characters to post .
62
	:param queue.Queue q: queue of data and messages sent by :class:`rsudp.c_consumer.Consumer`
63
64
65
	'''
66
	def __init__(self, consumer_key, consumer_secret, access_token, access_token_secret,
67
				 q=False, tweet_images=False, extra_text=False,
68
				 ):
69
		"""
70
		Initialize the process
71
		"""
72
		super().__init__()
73
		self.sender = 'Tweeter'
74
		self.alive = True
75
		self.tweet_images = tweet_images
76
		self.fmt = '%Y-%m-%d %H:%M:%S.%f'
77
		self.region = ' - region: %s' % rs.region.title() if rs.region else ''
78
		self.consumer_key = consumer_key
79
		self.consumer_secret = consumer_secret
80
		self.access_token = access_token
81
		self.access_token_secret = access_token_secret
82
83
		self._resolve_extra_text(extra_text)
84
85
		if q:
86
			self.queue = q
87
		else:
88
			printE('no queue passed to consumer! Thread will exit now!', self.sender)
89
			sys.stdout.flush()
90
			self.alive = False
91
			sys.exit()
92
93
		self.twitter = Twython(
94
			consumer_key,
95
			consumer_secret,
96
			access_token,
97
			access_token_secret
98
		)
99
		self.livelink = 'live feed ➡️ https://stationview.raspberryshake.org/#?net=%s&sta=%s' % (rs.net, rs.stn)
100
		self.message0 = '(#RaspberryShake station %s.%s%s) Event detected at' % (rs.net, rs.stn, self.region)
101
		self.message1 = '(#RaspberryShake station %s.%s%s) Image of event detected at' % (rs.net, rs.stn, self.region)
102
103
		printM('Starting.', self.sender)
104
105
106
	def _resolve_extra_text(self, extra_text):
107
		if ((extra_text == '') or (extra_text == None) or (extra_text == False)):
108
			self.extra_text = ''
109
		else:
110
			extra_text = str(extra_text)
111
			len_ex_txt = len(extra_text)
112
113
			if len_ex_txt > 143:
114
				printW('extra_text parameter is longer than allowable (%s chars) and will be truncated. Please keep extra_text at or below 143 characters.' % len_ex_txt, sender=self.sender)
115
				extra_text = extra_text[:143]
116
117
			self.extra_text =  ' %s' % (extra_text)
118
119
120
	def auth(self):
121
		self.twitter = Twython(
122
			self.consumer_key,
123
			self.consumer_secret,
124
			self.access_token,
125
			self.access_token_secret
126
		)
127
128
129
	def getq(self):
130
		d = self.queue.get()
131
		self.queue.task_done()
132
133
		if 'TERM' in str(d):
134
			self.alive = False
135
			printM('Exiting.', self.sender)
136
			sys.exit()
137
		else:
138
			return d
139
140
141
	def _when_alarm(self, d):
142
		'''
143
		Send a tweet when you get an ``ALARM`` message.
144
145
		:param bytes d: queue message
146
		'''
147
		event_time = helpers.fsec(helpers.get_msg_time(d))
148
		self.last_event_str = '%s' % (event_time.strftime(self.fmt)[:22])
149
		message = '%s %s UTC%s - %s' % (self.message0, self.last_event_str, self.extra_text, self.livelink)
150
		response = None
151
		try:
152
			printM('Sending tweet...', sender=self.sender)
153
			response = self.twitter.update_status(status=message, lat=rs.inv[0][0].latitude,
154
													long=rs.inv[0][0].longitude,
155
													geo_enabled=True, display_coordinates=True)
156
													# location will only stick to tweets on accounts that have location enabled in Settings
157
			printM('Tweeted: %s' % (message), sender=self.sender)
158
			url = 'https://twitter.com/%s/status/%s' % (response['user']['screen_name'], response['id_str'])
159
			printM('Tweet URL: %s' % url)
160
161
		except Exception as e:
162
			printE('could not send alert tweet - %s' % (e))
163
			try:
164
				printE('Waiting 5 seconds and trying to send tweet again...', sender=self.sender, spaces=True)
165
				time.sleep(5.1)
166
				self.auth()
167
				response = self.twitter.update_status(status=message, lat=rs.inv[0][0].latitude,
168
														long=rs.inv[0][0].longitude,
169
														geo_enabled=True, display_coordinates=True)
170
														# location will only stick to tweets on accounts that have location enabled in Settings
171
				printM('Tweeted: %s' % (message), sender=self.sender)
172
				url = 'https://twitter.com/%s/status/%s' % (response['user']['screen_name'], response['id_str'])
173
				printM('Tweet URL: %s' % url)
174
			except Exception as e:
175
				printE('could not send alert tweet - %s' % (e))
176
				response = None
177
178
179
	def _when_img(self, d):
180
		'''
181
		Send a tweet with an image in when you get an ``IMGPATH`` message.
182
183
		:param bytes d: queue message
184
		'''
185
		if self.tweet_images:
186
			imgpath = helpers.get_msg_path(d)
187
			imgtime = helpers.fsec(helpers.get_msg_time(d))
188
			message = '%s %s UTC%s' % (self.message1, imgtime.strftime(self.fmt)[:22], self.extra_text)
189
			response = None
190
			if os.path.exists(imgpath):
191
				with open(imgpath, 'rb') as image:
192
					try:
193
						printM('Uploading image to Twitter %s' % (imgpath), self.sender)
194
						response = self.twitter.upload_media(media=image)
195
						time.sleep(5.1)
196
						printM('Sending tweet...', sender=self.sender)
197
						response = self.twitter.update_status(status=message, media_ids=response['media_id'],
198
																lat=rs.inv[0][0].latitude, long=rs.inv[0][0].longitude,
199
																geo_enabled=True, display_coordinates=True)
200
																# location will only stick to tweets on accounts that have location enabled in Settings
201
						printM('Tweeted with image: %s' % (message), sender=self.sender)
202
						url = 'https://twitter.com/%s/status/%s' % (response['user']['screen_name'], response['id_str'])
203
						printM('Tweet URL: %s' % url)
204
					except Exception as e:
205
						printE('could not send multimedia tweet - %s' % (e))
206
						try:
207
							printM('Waiting 5 seconds and trying to send tweet again...', sender=self.sender)
208
							time.sleep(5.1)
209
							self.auth()
210
							printM('Uploading image to Twitter (2nd try) %s' % (imgpath), self.sender)
211
							response = self.twitter.upload_media(media=image)
212
							time.sleep(5.1)
213
							printM('Sending tweet...', sender=self.sender)
214
							response = self.twitter.update_status(status=message, media_ids=response['media_id'],
215
																	lat=rs.inv[0][0].latitude, long=rs.inv[0][0].longitude,
216
																	geo_enabled=True, display_coordinates=True)
217
																	# location will only stick to tweets on accounts that have location enabled in Settings
218
							printM('Tweeted with image: %s' % (message), sender=self.sender)
219
							url = 'https://twitter.com/%s/status/%s' % (response['user']['screen_name'], response['id_str'])
220
							printM('Tweet URL: %s' % url)
221
222
						except Exception as e:
223
							printE('could not send multimedia tweet (2nd try) - %s' % (e))
224
							response = None
225
226
			else:
227
				printM('Could not find image: %s' % (imgpath), sender=self.sender)
228
229
230 View Code Duplication
	def run(self):
231
		"""
232
		Reads data from the queue and tweets a message if it sees an ALARM or IMGPATH message
233
		"""
234
		while True:
235
			d = self.getq()
236
237
			if 'ALARM' in str(d):
238
				self._when_alarm(d)
239
240
			elif 'IMGPATH' in str(d):
241
				self._when_img(d)
242