twitterdm.py 7.48 KB
Newer Older
Hiro's avatar
Hiro committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- 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 gettext
import hashlib
Hiro's avatar
Hiro committed
17
import json
18
import time
Hiro's avatar
Hiro committed
19

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

from twisted.internet import defer

Hiro's avatar
Hiro committed
24
25
from ...parse.twitter import TwitterParser
from ...utils.twitter import Twitter
Hiro's avatar
Hiro committed
26
27
28
29
30
31
32
33
34
35
36
37
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
38

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

Hiro's avatar
Hiro committed
45

Hiro's avatar
Hiro committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    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.
        """
        log.info("Message sent successfully.")

Hiro's avatar
Hiro committed
63

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


Hiro's avatar
Hiro committed
72
    def twitterdm(self, twitter_id, message):
Hiro's avatar
Hiro committed
73
74
75
76
        """
        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
77
78
        :param twitter_id (str): twitter_id of the recipient.
        :param message (str): text of the message.
Hiro's avatar
Hiro committed
79
80
81
82

        :return: deferred whose callback/errback will handle the API execution
        details.
        """
83
        return self.send_tweet(twitter_id, message)
Hiro's avatar
Hiro committed
84

Hiro's avatar
Hiro committed
85

86
    def send_tweet(self, twitter_id, message):
Hiro's avatar
Hiro committed
87
        post_data = self.twitter.post_message(
Hiro's avatar
Hiro committed
88
            twitter_id, message
Hiro's avatar
Hiro committed
89
90
        )
        if post_data.status_code == 200:
91
            time.sleeps(30)
Hiro's avatar
Hiro committed
92
93
94
            self.twitter_callback
        else:
            self.twitter_errback
Hiro's avatar
Hiro committed
95

Hiro's avatar
Hiro committed
96
97
        return post_data

Hiro's avatar
Hiro committed
98
99
100
101
102
103
    @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
104

Hiro's avatar
Hiro committed
105
106
107
108
109
        log.debug("Retrieve list of messages")
        data = self.twitter.twitter_data()

        for e in data['events']:

Hiro's avatar
Hiro committed
110
            message_id = { "id": e['id'], "twitter_handle": e['message_create']['sender_id'] }
Hiro's avatar
Hiro committed
111
112

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

        # 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
124
            status="ONHOLD", command="links", service="twitter"
Hiro's avatar
Hiro committed
125
126
127
128
129
130
131
132
        )

        if help_requests:
            strings.load_strings("en")
            try:
                log.info("Got new help request.")

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

                    hid = hashlib.sha256(twitter_id.encode('utf-8'))
                    log.info(
                        "Sending help message to {}.".format(
                            hid.hexdigest()
                        )
                    )

                    yield self.twitterdm(
                        twitter_id=twitter_id,
Hiro's avatar
Hiro committed
147
                        message=strings._("help_body")
Hiro's avatar
Hiro committed
148
149
150
151
152
153
154
155
156
157
158
159
                    )

                    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
160
            except RuntimeError as e:
Hiro's avatar
Hiro committed
161
162
163
164
165
166
167
                log.info("Error sending twitter message: {}.".format(e))

        elif link_requests:
            try:
                log.info("Got new links request.")

                for request in link_requests:
Hiro's avatar
Hiro committed
168
                    ids = json.loads("{}".format(request[0].replace("'", '"')))
Hiro's avatar
Hiro committed
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
                    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']

                    log.info("Getting links for {}.".format(platform))
                    links = yield self.conn.get_links(
                        platform=platform, language=locale, status="ACTIVE"
                    )

                    # build message
                    link_msg = None
Hiro's avatar
Hiro committed
190
191
                    file = ""

Hiro's avatar
Hiro committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
                    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

                    body_msg = strings._("links_body").format(platform, link_msg, file)

                    hid = hashlib.sha256(twitter_id.encode('utf-8'))
                    log.info(
                        "Sending links to {}.".format(
                            hid.hexdigest()
                        )
                    )

                    yield self.twitterdm(
Hiro's avatar
Hiro committed
221
                        twitter_id=twitter_id,
Hiro's avatar
Hiro committed
222
                        message=body_msg
Hiro's avatar
Hiro committed
223
224
225
226
227
228
229
230
231
232
233
234
                    )

                    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
235
            except RuntimeError as e:
Hiro's avatar
Hiro committed
236
237
238
                log.info("Error sending message: {}.".format(e))
        else:
            log.debug("No pending twitter requests. Keep waiting.")