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)
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
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()