twitterdm.py 7.33 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
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)

Hiro's avatar
Hiro committed
44

Hiro's avatar
Hiro committed
45
46
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.
        """
        log.info("Message sent successfully.")

Hiro's avatar
Hiro committed
62

Hiro's avatar
Hiro committed
63
64
65
66
67
    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
68
        raise RuntimeError("{}".format(error))
Hiro's avatar
Hiro committed
69
70


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

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

Hiro's avatar
Hiro committed
84
85

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

Hiro's avatar
Hiro committed
94
95
        return post_data

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

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

        for e in data['events']:

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

            log.debug("Parsing message")
Hiro's avatar
Hiro committed
111
            tp = TwitterParser(self.settings, message_id)
Hiro's avatar
Hiro committed
112
113
114
115
116
117
118
119
120
121
            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
122
            status="ONHOLD", command="links", service="twitter"
Hiro's avatar
Hiro committed
123
124
125
126
127
128
129
130
        )

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

                for request in help_requests:
Hiro's avatar
Hiro committed
131
                    ids = json.loads("{}".format(request[0]))
Hiro's avatar
Hiro committed
132
133
134
135
136
137
138
139
140
141
142
143
144
                    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
145
                        message=strings._("help_body")
Hiro's avatar
Hiro committed
146
147
148
149
150
151
152
153
154
155
156
157
                    )

                    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
158
            except RuntimeError as e:
Hiro's avatar
Hiro committed
159
160
161
162
163
164
165
                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
166
                    ids = json.loads("{}".format(request[0]))
Hiro's avatar
Hiro committed
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
                    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
                    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(
                        email_addr=twitter_id,
                        body=body_msg
                    )

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