wantzel.py 24.9 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
10 11
import sqlite3
import time
12 13 14 15

import feedparser
from irc import IrcClientFactory
import MySQLdb
16
from twisted.internet import reactor
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():
    """
81
    This function connects to a MySQL database and returns a usable cursor.
Mindiell's avatar
Mindiell committed
82
    """
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
    )
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

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:
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
152
        # default last_entry_published for tweets
153 154 155 156 157 158 159 160
        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 165 166 167 168 169 170 171 172
        # default last_entry_published for wiki
        self.last_entry_updated = 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_updated FROM wikis"):
            self.last_entry_updated = time.strptime(
                row[0].encode("utf-8"),
                "%Y-%m-%d %H:%M:%S %Z"
            )
        Utils.debug("Dernière mise à jour du wiki: %s" % self.last_entry_updated)

173 174 175
        self.irc = IrcClientFactory(config)
        self.irc.set_privmsg = self.set_privmsg
        reactor.connectTCP(config.server, config.port, self.irc)
176 177 178 179 180 181 182
        # Prepare timer
        reactor.callLater(config.timer, self.timer)

    def timer(self):
        """
        This method launches function regularly (see config.timer).
        """
Mindiell's avatar
Mindiell committed
183
        Utils.debug("Timer called")
184 185 186
        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()
187
        self.op_verification()
188
        self.wiki_updates()
189 190
        # Recalling the timer
        reactor.callLater(config.timer, self.timer)
191 192 193 194 195 196

    def set_privmsg(self):
        """
        This method set the methods to call for each callback received from IRC.
        """
        self.irc.client.privmsg = self.on_privmsg
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
        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
215
        if "o" in modes and self.irc.client.nickname in args:
216 217 218 219 220 221 222 223 224 225 226 227 228
            # 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))
229

230
    def send_message(self, channel, multiline_message):
231 232 233
        """
        Sends a message on specified channel, cutting each line in a new message
        """
234
        for message in multiline_message.splitlines():
235 236 237 238
            self.irc.client.msg(channel, message)

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

302
    def help(self, user, channel, msg):
303 304
        """
        Show global help.
305
        If a known command is behind the ~!help command, an adequate message is
306 307
        returned.
        """
Mindiell's avatar
Mindiell committed
308
        Utils.debug("help command")
Mindiell's avatar
Mindiell committed
309
        # Searching for a command after help keyword
310
        command = re.search("[!~]help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
311 312
        if command:
            command = command.group(1)
313
            self.send_message(user, messages["help_"+command])
Mindiell's avatar
Mindiell committed
314
        else:
315
            self.send_message(user, messages["help"])
316

317
    def rp(self, command, user, channel, msg):
318 319 320
        """
        Adding the article in rp database.
        """
Mindiell's avatar
Mindiell committed
321 322 323 324
        Utils.debug("rp command : %s" % command)
        Utils.debug("rp user : %s" % user)
        Utils.debug("rp channel : %s" % channel)
        Utils.debug("rp msg : %s" % msg)
325
        cite = 0
326
        note = 1
327
        url = get_url(msg)
Mindiell's avatar
Mindiell committed
328
        Utils.debug("url: %s" % url)
329
        if not url:
Mindiell's avatar
Mindiell committed
330
            return
331

332 333 334 335 336 337 338 339 340 341 342
        # 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
343 344
        # Looking for such an article in database
        cursor = get_cursor()
345 346 347 348 349 350 351 352 353 354 355 356
        # We need to be able to retrieve an url with "http" or "https"
        if url.startswith("https"):
            url2 = "http" + url[5:]
        else:
            url2 = "https" + url[4:]
        cursor.execute("""
            SELECT id, note, provenance
            FROM presse
            WHERE url = %s
            OR url = %s""",
            (url, url2)
        )
357 358
        rows = cursor.fetchall()
        if not rows:
Mindiell's avatar
Mindiell committed
359
            Utils.debug("Adding an article by %s: %s" % (user, url))
360 361
            cursor.execute("""
                INSERT INTO presse SET
362
                url=%s, provenance=%s, cite=%s, note=%s, datec=NOW(), title='',
363 364
                lang='', published=0, nid=0, screenshot=0, fetched=0, seemscite=0
                """,
365
                (url, user, cite, note)
366
            )
Mindiell's avatar
Mindiell committed
367
            self.send_message(channel, messages["rp_new_article"] % user)
368
        else:
369
            if rows[0][2] != user:
Mindiell's avatar
Mindiell committed
370
                Utils.debug("Adding a point by %s on %s" % (user, rows[0][0]))
371
                cursor.execute(
372 373 374
                    "UPDATE presse SET note=note+1 WHERE id=%s",
                    (rows[0][0], )
                )
375
            if (rows[0][1]+1) < 3:
Mindiell's avatar
Mindiell committed
376
                self.send_message(channel, messages["rp_known_article"] % user)
377
            else:
Mindiell's avatar
Mindiell committed
378
                self.send_message(channel, messages["rp_taken_article"] % user)
379 380
                # Update number of articles to do
                self.count_articles()
381

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
    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()
397 398 399 400 401
        # We need to be able to retrieve an url with "http" or "https"
        if url.startswith("https"):
            url2 = "http" + url[5:]
        else:
            url2 = "https" + url[4:]
402
        cursor.execute("""
403
            SELECT cite, nid, note
404
            FROM presse
405 406 407 408
            WHERE url = %s
            OR url = %s""",
            (url, url2)
        )
409 410 411 412
        rows = cursor.fetchall()
        if not rows:
            self.send_message(channel, messages["status_unknown_article"] % user)
        else:
413 414 415 416 417 418 419 420 421
            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]
422
            else:
423 424
                message += "non publié / "
            self.send_message(channel, message[:-3])
425

426 427
    def kill(self, user, channel, msg):
        """
Mindiell's avatar
Mindiell committed
428
        Kill an article by setting its score to -100.
429
        """
Mindiell's avatar
Mindiell committed
430
        Utils.debug("kill command")
431 432
        if is_moderator(user):
            url = get_url(msg)
Mindiell's avatar
Mindiell committed
433
            Utils.debug("url: %s" % url)
434
            if url == "":
435
                return
436
            elif url == "http":
437 438 439 440
                self.send_message(channel, messages["rp_http"] % user)
                return
            # Looking for such an article in database
            cursor = get_cursor()
441 442 443 444 445 446 447 448 449 450 451 452
            # We need to be able to retrieve an url with "http" or "https"
            if url.startswith("https"):
                url2 = "http" + url[5:]
            else:
                url2 = "https" + url[4:]
            cursor.execute("""
                SELECT id, note
                FROM presse
                WHERE url = %s
                OR url = %s""",
                (url, url2)
            )
453 454 455 456 457 458
            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
459
        else:
460
            self.send_message(channel, messages["not_moderator"])
461

462
    def stats(self, channel):
463 464 465
        """
        Returns stats on articles in press review.
        """
Mindiell's avatar
Mindiell committed
466
        Utils.debug("stats command")
467
        cursor = get_cursor()
468
        periods = [1, 3, 7, 15]
469
        notes = [0, 3, 4]
470 471 472 473
        notnull = 0
        somethingatall = 0
        for note in notes:
            notnull = 0
474
            period_result = ""
475
            for period in periods:
476 477 478
                cursor.execute("""
                    SELECT COUNT(id) AS cid
                    FROM presse
479 480 481 482 483 484
                    WHERE nid=0
                    AND datec>(NOW()-INTERVAL %s DAY)
                    AND note>=%s""",
                    (period, note)
                )
                rows = cursor.fetchall()
485
                if rows[0][0] > 0:
486
                    period_result = period_result + "%sj:%s, " % (period, rows[0][0])
487
                    notnull = 1
Mindiell's avatar
Mindiell committed
488
                    somethingatall = 1
489
            if notnull:
490
                self.send_message(channel, "note>=%s: " % note + period_result[:-2])
491
        if somethingatall == 0:
492
            self.send_message(channel, messages["stats_bravo"] % periods[-1])
493

494 495 496
    def admin(self, user, channel, msg):
        """
        Manage moderation.
497
        A sub-command should be behind the !~admin command.
498
        """
Mindiell's avatar
Mindiell committed
499
        Utils.debug("admin command")
500
        # Searching for a command after admin keyword
501
        command = re.search("[~!]admin (list|add|del|timer)", msg)
502 503
        if command:
            command = command.group(1)
504 505 506
            if command == "list":
                self.admin_list(user, channel)
            elif command == "add":
507
                self.admin_add(user, channel, msg)
508
            elif command == "del":
509
                self.admin_del(user, channel, msg)
510 511
            elif command == "timer":
                self.admin_timer(user, channel)
512

513
    def admin_list(self, user, channel):
514 515 516
        """
        List actual moderators.
        """
Mindiell's avatar
Mindiell committed
517
        Utils.debug("admin_list command")
518 519 520 521 522
        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"))
523
            self.send_message(channel, messages["admin_list"] % ", ".join(sorted(names)))
524 525 526 527 528 529 530
        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
531
        Utils.debug("admin_add command")
532 533 534 535
        if is_moderator(user):
            try:
                names = []
                connection = sqlite3.connect(config.sqlite_db)
536
                result = re.search("[!~]admin add (([^,]+, ?)+)?(.*)", msg)
537
                if result.group(1):
538
                    names = [name.strip() for name in result.group(1).split(",") if name.strip() != ""]
539 540 541 542 543
                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"))
544
                names = list(set([name for name in names if name not in moderators]))
545 546 547 548 549 550 551 552
                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"])
553
            except Exception:
554 555 556 557 558 559 560 561
                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
562
        Utils.debug("admin_del command")
563 564 565
        if is_moderator(user):
            try:
                names = []
566
                result = re.search("[!~]admin del (([^,]+, ?)+)?(.*)", msg)
567
                if result.group(1):
568
                    names = [name.strip() for name in result.group(1).split(",") if name.strip() != ""]
569
                names.append(result.group(3))
570
                names = list(set(names))
Mindiell's avatar
Mindiell committed
571
                Utils.debug(names)
572 573 574 575 576
                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))
577
            except Exception:
578 579 580 581
                pass
        else:
            self.send_message(channel, messages["not_moderator"])

582
    def admin_timer(self, user, channel):
583 584 585 586 587 588 589 590
        """
        Relaunch a timer.
        """
        Utils.debug("admin_timer command")
        if is_moderator(user):
            try:
                # Recalling the timer
                reactor.callLater(config.timer, self.timer)
591
            except Exception:
592 593 594 595
                pass
        else:
            self.send_message(channel, messages["not_moderator"])

596 597 598 599 600
    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
601
        Utils.debug("count_articles method")
602 603 604 605 606 607 608
        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])
609
        Utils.debug("Found %s articles." % number)
610
        if self.number != number:
Mindiell's avatar
Mindiell committed
611
            self.irc.client.topic("#lqdn-rp", messages["topic"] % number)
612 613 614 615 616 617
        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
618
        Utils.debug("rp_to_twitter method")
619 620
        now = time.localtime()
        today = time.strptime("%s-%s-%s %s" % (
621 622 623 624 625
            now.tm_year,
            now.tm_mon,
            now.tm_mday,
            time.tzname[0]
            ), "%Y-%m-%d %Z")
Mindiell's avatar
Mindiell committed
626 627 628
        language = "fr"
        if "/en/" in rss:
            language = "en"
629 630
        entries = feedparser.parse(rss)['entries']
        entries.reverse()
631
        Utils.debug(self.last_entry_published)
632 633 634 635 636
        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
637 638 639 640
                    # 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")
641
                    if len(title) + min(len(link),23) > 135:
okhin's avatar
okhin committed
642
                        # What is the number of chars we need to remove
643 644
                        excess = len(title) + min(len(link),23) - 135
                        title = ''.join([title[:-(excess + 4)], ' ...'])
645
                    tweet(messages["tweet_rp_%s" % language] % (
okhin's avatar
okhin committed
646 647
                        title,
                        link
648
                    ))
Mindiell's avatar
Mindiell committed
649
                    Utils.debug(entry.published_parsed)
Mindiell's avatar
Mindiell committed
650
                    Utils.debug(entry.title)
651 652 653 654 655 656 657 658 659 660 661 662 663 664
                    # 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
665 666 667
                else:
                    Utils.debug(entry.title)
                    Utils.debug(entry.published_parsed)
668

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    def wiki_updates(self):
        url = "https://wiki.laquadrature.net/api.php?days=1&limit=50&translations=filter&action=feedrecentchanges&feedformat=atom"
        now = time.localtime()
        today = time.strptime("%s-%s-%s %s" % (
            now.tm_year,
            now.tm_mon,
            now.tm_mday,
            time.tzname[0]
            ), "%Y-%m-%d %Z")
        entries = feedparser.parse(url)['entries']
        for entry in entries:
            # if date of update is greater than today midnight
            if today < entry.updated_parsed:
                if self.last_entry_updated < entry.updated_parsed:
                    # Ecriture de la mise à jour sur le canal de travail
684
                    self.send_message("#lqdn-travail", messages["wiki_update"] % (
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
                        entry.author.encode("utf-8"),
                        entry.title.encode("utf-8"),
                        entry.link.encode("utf-8"),
                    ))
                    # Save last_entry_published
                    self.last_entry_updated = entry.updated_parsed
                    last_entry_updated = time.strftime(
                        "%Y-%m-%d %H:%M:%S %Z",
                        self.last_entry_updated
                    )
                    connection = sqlite3.connect(config.sqlite_db)
                    connection.execute(
                        "UPDATE wikis SET last_entry_updated=?",
                        (last_entry_updated,)
                    )
                    connection.commit()

702 703

if __name__ == '__main__':
704
    Wantzel()
705
    reactor.run()