twitterdm.py 8.24 KB
Newer Older
Hiro's avatar
Hiro committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding: utf-8 -*-
#
# This file is part of GetTor, a Tor Browser distribution system.
#
# :authors: isra <hiro@torproject.org>
#           see also AUTHORS file
#
# :copyright:   (c) 2008-2014, The Tor Project, Inc.
#               (c) 2019, Hiro
#
# :license: This is Free Software. See LICENSE for license information.

from __future__ import absolute_import

import hashlib
Hiro's avatar
Hiro committed
16
import json
17
import time
Hiro's avatar
Hiro committed
18

Hiro's avatar
Hiro committed
19
20
21
22
import configparser

from twisted.internet import defer

Hiro's avatar
Hiro committed
23
24
from ...parse.twitter import TwitterParser
from ...utils.twitter import Twitter
Hiro's avatar
Hiro committed
25
26
27
28
29
30
31
32
33
34
35
36
from ...utils.db import SQLite3 as DB
from ...utils.commons import log
from ...utils import strings

class Twitterdm(object):
    """
    Class for sending twitter replies to `help` and `links` requests.
    """
    def __init__(self, settings):
        """
        Constructor. It opens and stores a connection to the database.
        :dbname: reads from configs
Hiro's avatar
Hiro committed
37

Hiro's avatar
Hiro committed
38
39
40
        """
        self.settings = settings
        dbname = self.settings.get("dbname")
Hiro's avatar
Hiro committed
41
        self.twitter = Twitter(settings)
Hiro's avatar
Hiro committed
42
43
        self.conn = DB(dbname)

44
45
    def __del__(self):
        del self.conn
Hiro's avatar
Hiro committed
46

Hiro's avatar
Hiro committed
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    def get_interval(self):
        """
        Get time interval for service periodicity.

        :return: time interval (float) in seconds.
        """
        return self.settings.get("twitter_interval")


    def twitter_callback(self, message):
        """
        Callback invoked after a message has been sent.

        :param message (string): Success details from the server.
        """
62
        log.debug("Message sent successfully.")
Hiro's avatar
Hiro committed
63

Hiro's avatar
Hiro committed
64

Hiro's avatar
Hiro committed
65
66
67
68
    def twitter_errback(self, error):
        """
        Errback if we don't/can't send the message.
        """
69
        log.warn("Could not send message.")
Hiro's avatar
Hiro committed
70
        raise RuntimeError("{}".format(error))
Hiro's avatar
Hiro committed
71
72


Hiro's avatar
Hiro committed
73
    def twitterdm(self, twitter_id, message):
Hiro's avatar
Hiro committed
74
75
76
77
        """
        Send a twitter message for each message received. It creates a plain
        text message, and sends it via twitter APIs

Hiro's avatar
Hiro committed
78
79
        :param twitter_id (str): twitter_id of the recipient.
        :param message (str): text of the message.
Hiro's avatar
Hiro committed
80
81
82
83

        :return: deferred whose callback/errback will handle the API execution
        details.
        """
84
85
86
        return self.send_tweet(
            twitter_id, message
        ).addCallback(self.twitter_callback).addErrback(self.twitter_errback)
Hiro's avatar
Hiro committed
87

Hiro's avatar
Hiro committed
88

89
    def send_tweet(self, twitter_id, message):
Hiro's avatar
Hiro committed
90
        post_data = self.twitter.post_message(
Hiro's avatar
Hiro committed
91
            twitter_id, message
Hiro's avatar
Hiro committed
92
93
        )
        if post_data.status_code == 200:
Hiro's avatar
Hiro committed
94
            time.sleeps(61)
95

Hiro's avatar
Hiro committed
96
        else:
97
            raise RuntimeError("Error sending message: (%s)" % str(e))
Hiro's avatar
Hiro committed
98

Hiro's avatar
Hiro committed
99
100
        return post_data

Hiro's avatar
Hiro committed
101
102
103
104
105
106
    @defer.inlineCallbacks
    def get_new(self):
        """
        Get new requests to process. This will define the `main loop` of
        the Twitter service.
        """
Hiro's avatar
Hiro committed
107

Hiro's avatar
Hiro committed
108
109
110
111
112
        log.debug("Retrieve list of messages")
        data = self.twitter.twitter_data()

        for e in data['events']:

Hiro's avatar
Hiro committed
113
            message_id = { "id": e['id'], "twitter_handle": e['message_create']['sender_id'] }
Hiro's avatar
Hiro committed
114
115

            log.debug("Parsing message")
Hiro's avatar
Hiro committed
116
            tp = TwitterParser(self.settings, message_id)
Hiro's avatar
Hiro committed
117
118
119
            yield defer.maybeDeferred(
                tp.parse, e['message_create']['message_data']['text'], message_id
            ).addCallback(tp.parse_callback).addErrback(tp.parse_errback)
120
            del tp
Hiro's avatar
Hiro committed
121
122
123
124
125
126
127

        # Manage help and links messages separately
        help_requests = yield self.conn.get_requests(
            status="ONHOLD", command="help", service="twitter"
        )

        link_requests = yield self.conn.get_requests(
Hiro's avatar
Hiro committed
128
            status="ONHOLD", command="links", service="twitter"
Hiro's avatar
Hiro committed
129
130
131
132
133
        )

        if help_requests:
            strings.load_strings("en")
            try:
134
                log.debug("Got new help request.")
Hiro's avatar
Hiro committed
135
136

                for request in help_requests:
Hiro's avatar
Hiro committed
137
                    ids = json.loads("{}".format(request[0].replace("'", '"')))
Hiro's avatar
Hiro committed
138
139
140
141
142
                    message_id = ids['id']
                    twitter_id = ids['twitter_handle']
                    date = request[5]

                    hid = hashlib.sha256(twitter_id.encode('utf-8'))
143
                    log.debug(
Hiro's avatar
Hiro committed
144
145
146
147
148
                        "Sending help message to {}.".format(
                            hid.hexdigest()
                        )
                    )

Hiro's avatar
Hiro committed
149
150
151
152
                    body_msg = strings._("help_body_intro")
                    body_msg += strings._("help_body_paragraph")
                    body_msg += strings._("help_body_support")

Hiro's avatar
Hiro committed
153
154
                    yield self.twitterdm(
                        twitter_id=twitter_id,
Hiro's avatar
Hiro committed
155
                        message=body_msg
Hiro's avatar
Hiro committed
156
157
158
159
160
161
162
163
164
165
166
167
                    )

                    yield self.conn.update_stats(
                        command="help", platform='', language='en',
                        service="twitter"
                    )

                    yield self.conn.update_request(
                        id=request[0], hid=hid.hexdigest(), status="SENT",
                        service="twitter", date=date
                    )

Hiro's avatar
Hiro committed
168
            except RuntimeError as e:
169
                log.error("Error sending twitter message: {}.".format(e))
Hiro's avatar
Hiro committed
170
171
172

        elif link_requests:
            try:
173
                log.debug("Got new links request.")
Hiro's avatar
Hiro committed
174
175

                for request in link_requests:
Hiro's avatar
Hiro committed
176
                    ids = json.loads("{}".format(request[0].replace("'", '"')))
Hiro's avatar
Hiro committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
                    message_id = ids['id']
                    twitter_id = ids['twitter_handle']
                    date = request[5]
                    platform = request[2]
                    language = request[3]

                    if not language:
                        language = 'en'

                    locales = strings.get_locales()

                    strings.load_strings(language)
                    locale = locales[language]['locale']

191
                    log.debug("Getting links for {}.".format(platform))
Hiro's avatar
Hiro committed
192
193
194
195
196
197
                    links = yield self.conn.get_links(
                        platform=platform, language=locale, status="ACTIVE"
                    )

                    # build message
                    link_msg = None
Hiro's avatar
Hiro committed
198
199
                    file = ""

Hiro's avatar
Hiro committed
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
                    for link in links:
                        provider = link[5]
                        version = link[4]
                        arch = link[3]
                        url = link[0]
                        file = link[7]
                        sig_url = url + ".asc"

                        link_str = "Tor Browser {} for {}-{}-{} ({}): {}\n".format(
                            version, platform, locale, arch, provider, url
                        )

                        link_str += "Signature file: {}\n".format(sig_url)

                        if link_msg:
                            link_msg = "{}\n{}".format(link_msg, link_str)
                        else:
                            link_msg = link_str

Hiro's avatar
Hiro committed
219
220
221
222
223
224
225
                        body_msg = strings._("links_body_platform").format(platform)
                        body_msg += trings._("links_body_links").format(link_msg)
                        body_msg += trings._("links_body_archive")
                        body_msg += trings._("links_body_internet_archive")
                        body_msg += trings._("links_body_google_drive")
                        body_msg += trings._("links_body_internet_archive").format(file)
                        body_msg += trings._("links_body_ending")
Hiro's avatar
Hiro committed
226
227

                    hid = hashlib.sha256(twitter_id.encode('utf-8'))
228
                    log.debug(
Hiro's avatar
Hiro committed
229
230
231
232
233
234
                        "Sending links to {}.".format(
                            hid.hexdigest()
                        )
                    )

                    yield self.twitterdm(
Hiro's avatar
Hiro committed
235
                        twitter_id=twitter_id,
Hiro's avatar
Hiro committed
236
                        message=body_msg
Hiro's avatar
Hiro committed
237
238
239
240
241
242
243
244
245
246
247
248
                    )

                    yield self.conn.update_stats(
                        command="links", platform=platform, language=locale,
                        service="twitter"
                    )

                    yield self.conn.update_request(
                        id=request[0], hid=hid.hexdigest(), status="SENT",
                        service="twitter", date=date
                    )

Hiro's avatar
Hiro committed
249
            except RuntimeError as e:
250
                log.error("Error sending message: {}.".format(e))
Hiro's avatar
Hiro committed
251
252
        else:
            log.debug("No pending twitter requests. Keep waiting.")