wantzel.py 22 KB
Newer Older
1
# encoding: utf-8
2
"""
Mindiell's avatar
Mindiell committed
3
Bot Wantzel from La Quadrature du Net.
4

Mindiell's avatar
Mindiell committed
5
6
License : AGPLv3
Doc     : https://wiki.laquadrature.net/Wantzel
7
8
9
"""

import re
Mindiell's avatar
Mindiell committed
10
11
import sqlite3
import time
12
13
14
15

import feedparser
from irc import IrcClientFactory
import MySQLdb
16
from twisted.internet import reactor
Mindiell's avatar
Mindiell committed
17
from twitter import Twitter, OAuth
18
19
20
21

import config
from messages import messages

Mindiell's avatar
Mindiell committed
22
23
24
25
26
27
28
29
LOG_FILE = "wantzel.log"
DEBUG = 3
WARNING = 2
INFO = 1
ERROR = 0
LOG_LEVEL = DEBUG

class Utils(object):
30
31
32
    """
    Simple utility class to log easily.
    """
Mindiell's avatar
Mindiell committed
33
34
    @classmethod
    def log(cls, message):
35
36
37
38
39
        """
        Logging message with timestamp.
        """
        actual_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        with open(LOG_FILE, 'a') as file_handle:
40
            try:
41
                file_handle.write("%s: %s\n" % (actual_time, message.encode("utf-8")))
42
43
            except UnicodeDecodeError:
                file_handle.write("%s: %s\n" % (actual_time, message))
Mindiell's avatar
Mindiell committed
44
45
46

    @classmethod
    def debug(cls, message):
47
48
49
50
        """
        Manage DEBUG level of logging.
        """
        if LOG_LEVEL >= DEBUG:
Mindiell's avatar
Mindiell committed
51
52
53
54
            cls.log("%s: %s" % ("DEBUG", message))

    @classmethod
    def warning(cls, message):
55
56
57
58
        """
        Manage WARNING level of logging.
        """
        if LOG_LEVEL >= WARNING:
Mindiell's avatar
Mindiell committed
59
60
61
62
            cls.log("%s: %s" % ("WARNING", message))

    @classmethod
    def info(cls, message):
63
64
65
66
        """
        Manage INFO level of logging.
        """
        if LOG_LEVEL >= INFO:
Mindiell's avatar
Mindiell committed
67
68
69
70
            cls.log("%s: %s" % ("INFO", message))

    @classmethod
    def error(cls, message):
71
72
73
74
        """
        Manage ERROR level of logging.
        """
        if LOG_LEVEL >= ERROR:
Mindiell's avatar
Mindiell committed
75
76
77
78
            cls.log("%s: %s" % ("ERROR", message))



Mindiell's avatar
Mindiell committed
79
80
def get_cursor():
    """
Mindiell's avatar
Mindiell committed
81
    This function connects to a MySQL database and returns a usable cursor.
Mindiell's avatar
Mindiell committed
82
    """
Mindiell's avatar
Mindiell committed
83
    connection = MySQLdb.connect(
Mindiell's avatar
Mindiell committed
84
85
86
87
88
        host=config.dbserver,
        user=config.dbuser,
        passwd=config.dbpassword,
        db=config.dbname
    )
Mindiell's avatar
Mindiell committed
89
90
    if connection:
        return connection.cursor()
Mindiell's avatar
Mindiell committed
91
92
    return None

93

94
def get_url(message):
Mindiell's avatar
Mindiell committed
95
    """
96
    Retrieve the url in the message.
Mindiell's avatar
Mindiell committed
97
    """
98
99
    # Let's get the url
    result = re.search("(https?[^ ]+)", message)
Mindiell's avatar
Mindiell committed
100
    if not result:
101
        return
102
    url = result.group(1)
Mindiell's avatar
Mindiell committed
103
104
105
106
107
108
109
110
    # Removing anchor if needed
    result = re.search("^([^#]*)", url)
    if result:
        url = result.group(1)
    # Removing trackers
    url = re.sub("[?&](utm_medium|utm_source|utm_campaign|xtor)=[^&]*", "", url)
    return url

111

Mindiell's avatar
Mindiell committed
112
113
114
115
116
117
118
def is_moderator(name):
    """
    This function verify if a user is a moderator.
    """
    connection = sqlite3.connect(config.sqlite_db)
    cursor = connection.cursor()
    cursor.execute("SELECT count(*) FROM moderator WHERE name=?", (name, ))
119
    if int(cursor.fetchone()[0]) == 1:
Mindiell's avatar
Mindiell committed
120
121
122
123
        return True
    return False


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def tweet(message):
    """
    Tweet message on specified account
    """
    Utils.debug("tweet method")
    auth = OAuth(
        config.TOKEN,
        config.TOKENSEC,
        config.CONSKEY,
        config.CONSSEC
    )
    twitter = Twitter(auth=auth)
    try:
        Utils.debug("Tweeting: %s" % message)
        twitter.statuses.update(status=message)
    except Exception:
        pass


143
144
145
146
147
148
149
150
class Wantzel(object):
    """
    Wantzel bot.
    """
    def __init__(self):
        """
        Initialization of bot over IRC.
        """
151
        self.number = None
Mindiell's avatar
Mindiell committed
152
153
154
155
156
157
158
159
160
        # default last_entry_published
        self.last_entry_published = time.strptime("2000-01-01", "%Y-%m-%d")
        # See if there is something in the db
        connection = sqlite3.connect(config.sqlite_db)
        for row in connection.execute("SELECT last_entry_published FROM tweets"):
            self.last_entry_published = time.strptime(
                row[0].encode("utf-8"),
                "%Y-%m-%d %H:%M:%S %Z"
            )
161
        Utils.debug("Dernier tweet: %s" % self.last_entry_published)
162
163
164
        self.irc = IrcClientFactory(config)
        self.irc.set_privmsg = self.set_privmsg
        reactor.connectTCP(config.server, config.port, self.irc)
Mindiell's avatar
Mindiell committed
165
166
167
168
169
170
171
        # Prepare timer
        reactor.callLater(config.timer, self.timer)

    def timer(self):
        """
        This method launches function regularly (see config.timer).
        """
Mindiell's avatar
Mindiell committed
172
        Utils.debug("Timer called")
Mindiell's avatar
Mindiell committed
173
174
175
        self.rp_to_twitter("http://www.laquadrature.net/fr/revue-de-presse/feed")
        self.rp_to_twitter("http://www.laquadrature.net/en/press-review/feed")
        self.count_articles()
176
        self.op_verification()
Mindiell's avatar
Mindiell committed
177
178
        # Recalling the timer
        reactor.callLater(config.timer, self.timer)
179
180
181
182
183
184

    def set_privmsg(self):
        """
        This method set the methods to call for each callback received from IRC.
        """
        self.irc.client.privmsg = self.on_privmsg
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
        self.irc.client.irc_unknown = self.test_unknown
        self.irc.client.modeChanged = self.mode_changed

    def test_unknown(self, prefix, command, params):
        Utils.debug("UNKNOWN %s %s %s" % (prefix, command, params))
        if command=="RPL_NAMREPLY":
            self.names(params)

    def op_verification(self):
        """
        Verify if wantzel is chan operator, if not it complains on the chan <3.
        """
        Utils.debug("op_verification method")
        # On liste les utilisateurs du canal de la rp
        self.irc.client.sendLine("NAMES #lqdn-rp")

    def mode_changed(self, user, channel, flag_set, modes, args):
        Utils.debug("Mode changed : %s %s %s %s %s" % (user, channel, flag_set, modes, args))
Mindiell's avatar
Mindiell committed
203
        if "o" in modes and self.irc.client.nickname in args:
204
205
206
207
208
209
210
211
212
213
214
215
216
            # Cleaning user name
            user = re.search("([^!]*)!", user).group(1)
            if flag_set:
                self.send_message(channel, messages["oped"] % user)
            else:
                self.send_message(channel, messages["deoped"] % user)

    def names(self, params):
        Utils.debug("Names : %s" % params)
        if params[2]=="#lqdn-rp":
            ops = [user[1:] for user in params[3].split() if user[0]=="@"]
            if "@"+self.irc.client.nickname not in params[3]:
                self.send_message(params[2], messages["please_op"] % ", ".join(ops))
217

218
    def send_message(self, channel, multiline_message):
219
220
221
        """
        Sends a message on specified channel, cutting each line in a new message
        """
222
        for message in multiline_message.splitlines():
223
224
225
226
            self.irc.client.msg(channel, message)

    def on_privmsg(self, user, channel, msg):
        """
Mindiell's avatar
Mindiell committed
227
228
        Wantzel can understand a lot of commands. Commands followed by a (*)
        are accessible only to moderators:
229
230
231
        - help
            Returns a message about how to use the bot.
            If a command is passed after help, the message explains how to use
232
            the command.
233
        - rp(acp) <url>
Mindiell's avatar
Mindiell committed
234
            Add an article in the database
235
236
        - status <url>
            Retrieve some informations about an article in the database
237
        - stats
Mindiell's avatar
Mindiell committed
238
            Show some statistics about the RP
Mindiell's avatar
Mindiell committed
239
        - kill (*)
Mindiell's avatar
Mindiell committed
240
            Kill an article by giving it a score of -100
241
        - admin list (*)
Mindiell's avatar
Mindiell committed
242
            List rights in private
243
        - admin add (*)
244
245
246
            Add one or more new moderator to list
        - admin del (*)
            Delete one or more moderator from list
247
        - admin timer
248
            Relaunch a timer
249
250
        """
        # Cleaning user name
Mindiell's avatar
Mindiell committed
251
        user = re.search("([^!]*)!", user).group(1)
Mindiell's avatar
Mindiell committed
252
        Utils.debug("Message received: %s %s %s" % (user, channel, msg))
Mindiell's avatar
Mindiell committed
253
        # Never answer to botself
254
        if user != config.nickname:
Mindiell's avatar
Mindiell committed
255
256
257
258
259
            # If it's a query, bot should answer to the user as the channel
            if "#" not in channel:
                channel = user
            # Help command, specific
            if "wantzel" in msg and ("help" in msg or "aide" in msg):
260
                self.help(user, channel, msg)
Mindiell's avatar
Mindiell committed
261
            # Find known command
262
            command = re.search("[!~](rp[acp]*|status|kill|help|stats|admin)", msg)
Mindiell's avatar
Mindiell committed
263
            Utils.debug("Command: %s" % command)
Mindiell's avatar
Mindiell committed
264
            if command:
Mindiell's avatar
Mindiell committed
265
                Utils.debug("group(0): %s" % command.group(0))
Mindiell's avatar
Mindiell committed
266
                command = command.group(1)
Mindiell's avatar
Mindiell committed
267
                Utils.debug("Command: %s" % command)
Mindiell's avatar
Mindiell committed
268
                if command.startswith("rp"):
Mindiell's avatar
Mindiell committed
269
                    Utils.debug("Calling self.rp")
270
                    self.rp(command, user, channel, msg)
271
272
273
                if command.startswith("status"):
                    Utils.debug("Calling self.status")
                    self.status(command, user, channel, msg)
274
                elif command == "help":
Mindiell's avatar
Mindiell committed
275
                    Utils.debug("Calling self.help")
276
                    self.help(user, channel, msg)
277
                elif command == "kill":
Mindiell's avatar
Mindiell committed
278
                    Utils.debug("Calling self.kill")
Mindiell's avatar
Mindiell committed
279
                    self.kill(user, channel, msg)
280
                elif command == "stats":
Mindiell's avatar
Mindiell committed
281
                    Utils.debug("Calling self.stats")
282
283
                    self.stats(channel)
                elif command == "admin":
Mindiell's avatar
Mindiell committed
284
                    Utils.debug("Calling self.admin")
Mindiell's avatar
Mindiell committed
285
                    self.admin(user, channel, msg)
Mindiell's avatar
Mindiell committed
286
287
288
        # No more giving the title of an url
        #if title and website:
        #    self.send_message(channel, messages["title"] % (title, website))
289

290
    def help(self, user, channel, msg):
291
292
        """
        Show global help.
293
        If a known command is behind the ~help command, an adequate message is
294
295
        returned.
        """
Mindiell's avatar
Mindiell committed
296
        Utils.debug("help command")
Mindiell's avatar
Mindiell committed
297
        # Searching for a command after help keyword
298
        command = re.search("~help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
299
300
        if command:
            command = command.group(1)
301
            self.send_message(user, messages["help_"+command])
Mindiell's avatar
Mindiell committed
302
        else:
303
            self.send_message(user, messages["help"])
304

305
    def rp(self, command, user, channel, msg):
306
307
308
        """
        Adding the article in rp database.
        """
Mindiell's avatar
Mindiell committed
309
310
311
312
        Utils.debug("rp command : %s" % command)
        Utils.debug("rp user : %s" % user)
        Utils.debug("rp channel : %s" % channel)
        Utils.debug("rp msg : %s" % msg)
313
        cite = 0
314
        note = 1
315
        url = get_url(msg)
Mindiell's avatar
Mindiell committed
316
        Utils.debug("url: %s" % url)
317
        if not url:
Mindiell's avatar
Mindiell committed
318
            return
319

320
321
322
323
324
325
326
327
328
329
330
        # Managing flags
        # LQdN is quoted
        if "c" in command:
            cite += 1
        # the article speak about LQdN
        if command.count("p") > 1:
            cite += 2
        # Archive this article
        if "a" in command:
            cite += 4

Mindiell's avatar
Mindiell committed
331
332
        # Looking for such an article in database
        cursor = get_cursor()
333
334
335
        cursor.execute("SELECT id, note, provenance FROM presse WHERE url = %s", (url, ))
        rows = cursor.fetchall()
        if not rows:
Mindiell's avatar
Mindiell committed
336
            Utils.debug("Adding an article by %s: %s" % (user, url))
337
338
            cursor.execute(
                """INSERT INTO presse SET
339
                url=%s, provenance=%s, cite=%s, note=%s, datec=NOW(), title='',
340
341
                lang='', published=0, nid=0, screenshot=0, fetched=0, seemscite=0
                """,
342
                (url, user, cite, note)
343
            )
Mindiell's avatar
Mindiell committed
344
            self.send_message(channel, messages["rp_new_article"] % user)
345
        else:
346
            if rows[0][2] != user:
Mindiell's avatar
Mindiell committed
347
                Utils.debug("Adding a point by %s on %s" % (user, rows[0][0]))
348
                cursor.execute(
349
350
351
                    "UPDATE presse SET note=note+1 WHERE id=%s",
                    (rows[0][0], )
                )
352
            if (rows[0][1]+1) < 3:
Mindiell's avatar
Mindiell committed
353
                self.send_message(channel, messages["rp_known_article"] % user)
354
            else:
Mindiell's avatar
Mindiell committed
355
                self.send_message(channel, messages["rp_taken_article"] % user)
Mindiell's avatar
Mindiell committed
356
357
                # Update number of articles to do
                self.count_articles()
358

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
    def status(self, command, user, channel, msg):
        """
        Retrieving status of the article in rp database.
        """
        Utils.debug("rp command : %s" % command)
        Utils.debug("rp user : %s" % user)
        Utils.debug("rp channel : %s" % channel)
        Utils.debug("rp msg : %s" % msg)
        url = get_url(msg)
        Utils.debug("url: %s" % url)
        if not url:
            return

        # Looking for such an article in database
        cursor = get_cursor()
        cursor.execute("""
375
            SELECT cite, nid, note
376
377
378
379
380
381
            FROM presse
            WHERE url = %s""", (url, ))
        rows = cursor.fetchall()
        if not rows:
            self.send_message(channel, messages["status_unknown_article"] % user)
        else:
382
383
384
385
386
387
388
389
390
            message = "%s: note %s / " % (user, rows[0][2])
            if rows[0][0] & 1:
                message += "cite LQdN / "
            if rows[0][0] & 2:
                message += "parle de LQdN / "
            if rows[0][0] & 4:
                message += "archivé / "
            if rows[0][1] > 0:
                message += "publié (https://laquadrature.net/node/%s) / " % rows[0][1]
391
            else:
392
393
                message += "non publié / "
            self.send_message(channel, message[:-3])
394

395
396
    def kill(self, user, channel, msg):
        """
Mindiell's avatar
Mindiell committed
397
        Kill an article by setting its score to -100.
398
        """
Mindiell's avatar
Mindiell committed
399
        Utils.debug("kill command")
Mindiell's avatar
Mindiell committed
400
401
        if is_moderator(user):
            url = get_url(msg)
Mindiell's avatar
Mindiell committed
402
            Utils.debug("url: %s" % url)
403
            if url == "":
Mindiell's avatar
Mindiell committed
404
                return
405
            elif url == "http":
Mindiell's avatar
Mindiell committed
406
407
408
409
410
411
412
413
414
415
416
                self.send_message(channel, messages["rp_http"] % user)
                return
            # Looking for such an article in database
            cursor = get_cursor()
            cursor.execute("SELECT id, note FROM presse WHERE url=%s", (url, ))
            rows = cursor.fetchall()
            if not rows:
                self.send_message(channel, messages["kill_none"] % url)
            else:
                cursor.execute("UPDATE presse SET note=-100 WHERE id=%s", (rows[0][0], ))
                self.send_message(channel, messages["kill_done"] % url)
Mindiell's avatar
Mindiell committed
417
        else:
Mindiell's avatar
Mindiell committed
418
            self.send_message(channel, messages["not_moderator"])
419

420
    def stats(self, channel):
421
422
423
        """
        Returns stats on articles in press review.
        """
Mindiell's avatar
Mindiell committed
424
        Utils.debug("stats command")
Mindiell's avatar
Mindiell committed
425
        cursor = get_cursor()
426
        periods = [1, 3, 7, 15]
427
        notes = [0, 3, 4]
428
429
430
431
        notnull = 0
        somethingatall = 0
        for note in notes:
            notnull = 0
432
            period_result = ""
433
434
            for period in periods:
                cursor.execute(
435
                    """SELECT COUNT(id) AS cid FROM presse
436
437
438
439
440
441
                    WHERE nid=0
                    AND datec>(NOW()-INTERVAL %s DAY)
                    AND note>=%s""",
                    (period, note)
                )
                rows = cursor.fetchall()
442
                if rows[0][0] > 0:
443
                    period_result = period_result + "%sj:%s, " % (period, rows[0][0])
444
                    notnull = 1
Mindiell's avatar
Mindiell committed
445
                    somethingatall = 1
446
            if notnull:
447
                self.send_message(channel, "note>=%s: " % note + period_result[:-2])
448
        if somethingatall == 0:
449
            self.send_message(channel, messages["stats_bravo"] % periods[-1])
450

Mindiell's avatar
Mindiell committed
451
452
453
    def admin(self, user, channel, msg):
        """
        Manage moderation.
454
        A sub-command should be behind the ~admin command.
Mindiell's avatar
Mindiell committed
455
        """
Mindiell's avatar
Mindiell committed
456
        Utils.debug("admin command")
Mindiell's avatar
Mindiell committed
457
        # Searching for a command after admin keyword
Mindiell's avatar
Mindiell committed
458
        command = re.search("~admin (list|add|del|timer)", msg)
Mindiell's avatar
Mindiell committed
459
460
        if command:
            command = command.group(1)
461
462
463
            if command == "list":
                self.admin_list(user, channel)
            elif command == "add":
Mindiell's avatar
Mindiell committed
464
                self.admin_add(user, channel, msg)
465
            elif command == "del":
Mindiell's avatar
Mindiell committed
466
                self.admin_del(user, channel, msg)
467
468
            elif command == "timer":
                self.admin_timer(user, channel)
Mindiell's avatar
Mindiell committed
469

470
    def admin_list(self, user, channel):
Mindiell's avatar
Mindiell committed
471
472
473
        """
        List actual moderators.
        """
Mindiell's avatar
Mindiell committed
474
        Utils.debug("admin_list command")
Mindiell's avatar
Mindiell committed
475
476
477
478
479
        if is_moderator(user):
            connection = sqlite3.connect(config.sqlite_db)
            names = []
            for row in connection.execute("SELECT name FROM moderator"):
                names.append(row[0].encode("utf-8"))
480
            self.send_message(channel, messages["admin_list"] % ", ".join(sorted(names)))
Mindiell's avatar
Mindiell committed
481
482
483
484
485
486
487
        else:
            self.send_message(channel, messages["not_moderator"])

    def admin_add(self, user, channel, msg):
        """
        Add some new moderators if not existing yet.
        """
Mindiell's avatar
Mindiell committed
488
        Utils.debug("admin_add command")
Mindiell's avatar
Mindiell committed
489
490
491
492
        if is_moderator(user):
            try:
                names = []
                connection = sqlite3.connect(config.sqlite_db)
493
                result = re.search("~admin add (([^,]+, ?)+)?(.*)", msg)
Mindiell's avatar
Mindiell committed
494
                if result.group(1):
495
                    names = [name.strip() for name in result.group(1).split(",") if name.strip() != ""]
Mindiell's avatar
Mindiell committed
496
497
498
499
500
                names.append(result.group(3))
                # Do not add actual moderators
                moderators = []
                for row in connection.execute("SELECT name FROM moderator"):
                    moderators.append(row[0].encode("utf-8"))
501
                names = list(set([name for name in names if name not in moderators]))
Mindiell's avatar
Mindiell committed
502
503
504
505
506
507
508
509
                if names:
                    # Converting set in list of tuples
                    values = [(name,) for name in names]
                    connection.executemany("INSERT INTO moderator (name) VALUES (?)", values)
                    connection.commit()
                    self.send_message(channel, messages["admin_add"] % ", ".join(names))
                else:
                    self.send_message(channel, messages["admin_add_empty"])
510
            except Exception:
Mindiell's avatar
Mindiell committed
511
512
513
514
515
516
517
518
                pass
        else:
            self.send_message(channel, messages["not_moderator"])

    def admin_del(self, user, channel, msg):
        """
        Delete a moderator from list.
        """
Mindiell's avatar
Mindiell committed
519
        Utils.debug("admin_del command")
Mindiell's avatar
Mindiell committed
520
521
522
        if is_moderator(user):
            try:
                names = []
523
                result = re.search("~admin del (([^,]+, ?)+)?(.*)", msg)
Mindiell's avatar
Mindiell committed
524
                if result.group(1):
525
                    names = [name.strip() for name in result.group(1).split(",") if name.strip() != ""]
Mindiell's avatar
Mindiell committed
526
                names.append(result.group(3))
527
                names = list(set(names))
Mindiell's avatar
Mindiell committed
528
                Utils.debug(names)
Mindiell's avatar
Mindiell committed
529
530
531
532
533
                connection = sqlite3.connect(config.sqlite_db)
                for name in names:
                    connection.execute("DELETE FROM moderator WHERE name=?", (name, ))
                connection.commit()
                self.send_message(channel, messages["admin_del"] % ", ".join(names))
534
            except Exception:
Mindiell's avatar
Mindiell committed
535
536
537
538
                pass
        else:
            self.send_message(channel, messages["not_moderator"])

539
    def admin_timer(self, user, channel):
540
541
542
543
544
545
546
547
        """
        Relaunch a timer.
        """
        Utils.debug("admin_timer command")
        if is_moderator(user):
            try:
                # Recalling the timer
                reactor.callLater(config.timer, self.timer)
548
            except Exception:
549
550
551
552
                pass
        else:
            self.send_message(channel, messages["not_moderator"])

Mindiell's avatar
Mindiell committed
553
554
555
556
557
    def count_articles(self):
        """
        Count number of articles not done in RP and updates the topic of the
        press review channel if needed.
        """
Mindiell's avatar
Mindiell committed
558
        Utils.debug("count_articles method")
Mindiell's avatar
Mindiell committed
559
560
561
562
563
564
565
        cursor = get_cursor()
        cursor.execute("""SELECT COUNT(*) FROM presse
            WHERE DATE_SUB(NOW(), INTERVAL 2 MONTH)<datec
            AND note > 2
            AND nid = 0""")
        rows = cursor.fetchall()
        number = int(rows[0][0])
566
        Utils.debug("Found %s articles." % number)
567
        if self.number != number:
Mindiell's avatar
Mindiell committed
568
            self.irc.client.topic("#lqdn-rp", messages["topic"] % number)
Mindiell's avatar
Mindiell committed
569
570
571
572
573
574
        self.number = number

    def rp_to_twitter(self, rss):
        """
        By parsing the RSS feed of the press-review, we know what to tweet.
        """
Mindiell's avatar
Mindiell committed
575
        Utils.debug("rp_to_twitter method")
Mindiell's avatar
Mindiell committed
576
577
        now = time.localtime()
        today = time.strptime("%s-%s-%s %s" % (
578
579
580
581
582
            now.tm_year,
            now.tm_mon,
            now.tm_mday,
            time.tzname[0]
            ), "%Y-%m-%d %Z")
Mindiell's avatar
Mindiell committed
583
584
585
        language = "fr"
        if "/en/" in rss:
            language = "en"
Mindiell's avatar
Mindiell committed
586
587
        entries = feedparser.parse(rss)['entries']
        entries.reverse()
588
        Utils.debug(self.last_entry_published)
Mindiell's avatar
Mindiell committed
589
590
591
592
593
        for entry in entries:
            # if date of publication is greater than today, midnight, and
            # lesser than future
            if today < entry.published_parsed < now:
                if self.last_entry_published < entry.published_parsed:
okhin's avatar
okhin committed
594
595
596
597
                    # Let's see if we can truncate the lenght of the tweet
                    # We have 5 chars for the language, so max-length is 135
                    title = entry.title.encode("utf-8")
                    link = entry.link.encode("utf-8")
598
                    if len(title) + min(len(link),23) > 135:
okhin's avatar
okhin committed
599
                        # What is the number of chars we need to remove
600
601
                        excess = len(title) + min(len(link),23) - 135
                        title = ''.join([title[:-(excess + 4)], ' ...'])
602
                    tweet(messages["tweet_rp_%s" % language] % (
okhin's avatar
okhin committed
603
604
                        title,
                        link
Mindiell's avatar
Mindiell committed
605
                    ))
Mindiell's avatar
Mindiell committed
606
                    Utils.debug(entry.published_parsed)
Mindiell's avatar
Mindiell committed
607
                    Utils.debug(entry.title)
Mindiell's avatar
Mindiell committed
608
609
610
611
612
613
614
615
616
617
618
619
620
621
                    # Save last_entry_published
                    self.last_entry_published = entry.published_parsed
                    last_entry_published = time.strftime(
                        "%Y-%m-%d %H:%M:%S %Z",
                        self.last_entry_published
                    )
                    connection = sqlite3.connect(config.sqlite_db)
                    connection.execute(
                        "UPDATE tweets SET last_entry_published=?",
                        (last_entry_published,)
                    )
                    connection.commit()
                    # Tweet only one message in order not to spam
                    return
622
623
624
                else:
                    Utils.debug(entry.title)
                    Utils.debug(entry.published_parsed)
Mindiell's avatar
Mindiell committed
625

626
627

if __name__ == '__main__':
628
    Wantzel()
629
    reactor.run()