Commit 815db623 authored by Mindiell's avatar Mindiell

Added tweeting, parsing rss feed

parent fb7e703d
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
Configuration for IRC bot Wantzel. Configuration for IRC bot Wantzel.
""" """
# IRC access
server = 'irc.freenode.net' server = 'irc.freenode.net'
port = 6667 port = 6667
nickname = 'testBot' nickname = 'testBot'
...@@ -10,7 +11,22 @@ password = '', ...@@ -10,7 +11,22 @@ password = '',
channels = [ channels = [
'#testchannel' '#testchannel'
] ]
# External drupal db, MySQL
dbuser = "root" dbuser = "root"
dbpassword = "" dbpassword = ""
dbserver = "localhost" dbserver = "localhost"
dbname = "db" dbname = "db"
# Internal db, sqlite is sufficient
sqlite_db = "db.sqlite3"
# Timer for chrono-methods, in seconds (float)
timer = 180
# Twitter account
TOKEN = ""
TOKENSEC = ""
CONSKEY = ""
CONSSEC = ""
...@@ -9,7 +9,7 @@ messages = { ...@@ -9,7 +9,7 @@ messages = {
"""Bonjour, je suis le bot de la Quadrature du Net, vous pouvez me demander de l'aide si besoin. (wantzel help)""", """Bonjour, je suis le bot de la Quadrature du Net, vous pouvez me demander de l'aide si besoin. (wantzel help)""",
"help": "help":
"""Mes commandes sont : !help !rp(cpa) !kill !stats. """Mes commandes sont : !help !rp(cpa) !kill !stats et !admin.
Pour plus d'informations, voir ici: https://wiki.laquadrature.net/Wantzel Pour plus d'informations, voir ici: https://wiki.laquadrature.net/Wantzel
Pour obtenir de l'aide sur une commande en particulier, il suffit de taper !help <commande>""", Pour obtenir de l'aide sur une commande en particulier, il suffit de taper !help <commande>""",
...@@ -28,10 +28,18 @@ L'utilisation se fait sous la forme: !rp <url de l'article à ajouter>""", ...@@ -28,10 +28,18 @@ L'utilisation se fait sous la forme: !rp <url de l'article à ajouter>""",
Les statistiques sont calculées sur des notes supérieurs ou égales à 0, 3, et 4. Et sur les 1, 3, 7, et 15 derniers jours.""", Les statistiques sont calculées sur des notes supérieurs ou égales à 0, 3, et 4. Et sur les 1, 3, 7, et 15 derniers jours.""",
"help_kill": "help_kill":
"""Fixe la note de l'article donné en paramètre à -100. """*Attention* seuls les vrais rp-jedis ont accès à cette commande <3
Fixe la note de l'article donné en paramètre à -100.
Utile en cas d'erreur ou pour s'assurer que l'article ne sera pas publié dans la RP Utile en cas d'erreur ou pour s'assurer que l'article ne sera pas publié dans la RP
Utilisation: !kill <url de l'article> Utilisation: !kill <url de l'article>""",
*Attention* seuls les vrais rp-jedis ont accès à cette commande <3""",
"help_admin":
"""*Attention* seuls les vrais rp-jedis ont accès à cette commande <3
Permet de gérer la liste des utilisateurs ayant un accès privilégié. Il n'y a qu'un seul niveau de privilège.
Utilisations:
!admin list => Fournit la liste des utilisateurs privilégiés
!admin add user[, user]> => Ajoute un ou plusieurs utilisateurs à la liste
!admin del user[, user] => Supprime un ou plusieurs utilisateurs de la liste""",
"rp_http": "rp_http":
"""Merci %s, mais je prends en compte uniquement les adresses internet qui commencent par http ou https""", """Merci %s, mais je prends en compte uniquement les adresses internet qui commencent par http ou https""",
...@@ -57,4 +65,27 @@ Utilisation: !kill <url de l'article> ...@@ -57,4 +65,27 @@ Utilisation: !kill <url de l'article>
"title": "title":
"""Titre: %s (à %s)""", """Titre: %s (à %s)""",
"admin_list":
"""Liste des modérateurs actuels: %s""",
"admin_add":
"""%s a(ont) été ajouté(s) à la liste des modérateurs""",
"admin_add_empty":
"""Ces utilisateurs sont déjà modérateurs""",
"admin_del":
"""%s a(ont) été retiré(s) de la liste des modérateurs.""",
"not_moderator":
"""Désolé, il ne semble pas que vous ayez les droits pour cette commande.""",
"topic":
"""Canal de la revue de presse de La Quadrature du Net ~ %s articles en attente ~ Mode d'emploi https://wiki.laquadrature.net/Revue_de_presse ~ Une arme, le savoir est. Le diffuser, notre devoir c'est.""",
"tweet_rp":
"""[La Quadrature du Net - Revue de presse] %s — %s""",
"reload":
"""Configuration à jour""", #La configuration a été mise à jour, merci <3""",
} }
...@@ -6,15 +6,22 @@ License : AGPLv3 ...@@ -6,15 +6,22 @@ License : AGPLv3
Doc : https://wiki.laquadrature.net/Wantzel Doc : https://wiki.laquadrature.net/Wantzel
TODO: TODO:
- Ajouter la gestion des droits pour certaines commandes - Ajouter des commandes permettant de gérer le mediakit (moderators only)
- Mettre une valeur par défaut pour les champs concernés - Parser les urls et voir ce qu'on peut faire:
- Afficher les titres des urls fournies sur le canal - upload de vidéo
- tag de vidéo
- obtenir un lien vers une vidéo
""" """
import feedparser
import importlib
from irc import IrcClientFactory from irc import IrcClientFactory
import MySQLdb import MySQLdb
import re import re
import sqlite3
import time
from twisted.internet import reactor from twisted.internet import reactor
from twitter import Twitter, OAuth
import urllib import urllib
import config import config
...@@ -22,16 +29,16 @@ from messages import messages ...@@ -22,16 +29,16 @@ from messages import messages
def get_cursor(): def get_cursor():
""" """
This function connects to a database and returns a usable cursor. This function connects to a MySQL database and returns a usable cursor.
""" """
db = MySQLdb.connect( connection = MySQLdb.connect(
host=config.dbserver, host=config.dbserver,
user=config.dbuser, user=config.dbuser,
passwd=config.dbpassword, passwd=config.dbpassword,
db=config.dbname db=config.dbname
) )
if db: if connection:
return db.cursor() return connection.cursor()
return None return None
def get_url(message): def get_url(message):
...@@ -70,6 +77,18 @@ def get_title(message): ...@@ -70,6 +77,18 @@ def get_title(message):
title = re.sub("&amp;", "&", title) title = re.sub("&amp;", "&", title)
return (title, website) return (title, website)
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, ))
if int(cursor.fetchone()[0])==1:
return True
return False
class Wantzel(object): class Wantzel(object):
""" """
Wantzel bot. Wantzel bot.
...@@ -78,9 +97,32 @@ class Wantzel(object): ...@@ -78,9 +97,32 @@ class Wantzel(object):
""" """
Initialization of bot over IRC. Initialization of bot over IRC.
""" """
self.number = 0
# 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"
)
self.irc = IrcClientFactory(config) self.irc = IrcClientFactory(config)
self.irc.set_privmsg = self.set_privmsg self.irc.set_privmsg = self.set_privmsg
reactor.connectTCP(config.server, config.port, self.irc) reactor.connectTCP(config.server, config.port, self.irc)
# Prepare timer
reactor.callLater(config.timer, self.timer)
def timer(self):
"""
This method launches function regularly (see config.timer).
"""
print("Timer called")
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()
# Recalling the timer
reactor.callLater(config.timer, self.timer)
def set_privmsg(self): def set_privmsg(self):
""" """
...@@ -98,7 +140,8 @@ class Wantzel(object): ...@@ -98,7 +140,8 @@ class Wantzel(object):
def on_privmsg(self, user, channel, msg): def on_privmsg(self, user, channel, msg):
""" """
Wantzel can understand some commands : Wantzel can understand a lot of commands. Commands followed by a (*)
are accessible only to moderators:
- help - help
Returns a message about how to use the bot. Returns a message about how to use the bot.
If a command is passed after help, the message explains how to use If a command is passed after help, the message explains how to use
...@@ -107,8 +150,14 @@ class Wantzel(object): ...@@ -107,8 +150,14 @@ class Wantzel(object):
Add an article in the database Add an article in the database
- stats - stats
Show some statistics about the RP Show some statistics about the RP
- kill - kill (*)
Kill an article by giving it a score of -100 Kill an article by giving it a score of -100
- moderate list (*)
List rights in private
- moderate add (*)
Add a new moderator to list
- moderate remove (*)
Remove a moderator from list
""" """
# Cleaning user name # Cleaning user name
user = re.search("([^!]*)!", user).group(1) user = re.search("([^!]*)!", user).group(1)
...@@ -126,7 +175,7 @@ class Wantzel(object): ...@@ -126,7 +175,7 @@ class Wantzel(object):
if "wantzel" in msg and ("help" in msg or "aide" in msg): if "wantzel" in msg and ("help" in msg or "aide" in msg):
self.help(user, channel, msg) self.help(user, channel, msg)
# Find known command # Find known command
command = re.search("!(rp[acp]*|kill|help|stats)", msg) command = re.search("!(rp[acp]*|kill|help|stats|admin)", msg)
if command: if command:
command = command.group(1) command = command.group(1)
print("Command: %s" % command) print("Command: %s" % command)
...@@ -138,6 +187,8 @@ class Wantzel(object): ...@@ -138,6 +187,8 @@ class Wantzel(object):
self.kill(user, channel, msg) self.kill(user, channel, msg)
elif command=="stats": elif command=="stats":
self.stats(user, channel, msg) self.stats(user, channel, msg)
elif command=="admin":
self.admin(user, channel, msg)
if title and website: if title and website:
self.send_message(channel, messages["title"] % (title, website)) self.send_message(channel, messages["title"] % (title, website))
...@@ -160,7 +211,7 @@ class Wantzel(object): ...@@ -160,7 +211,7 @@ class Wantzel(object):
""" """
print("help command") print("help command")
# Searching for a command after help keyword # Searching for a command after help keyword
command = re.search("!help (stats|rp|help|kill)", msg) command = re.search("!help (stats|rp|help|kill|admin)", msg)
if command: if command:
command = command.group(1) command = command.group(1)
self.send_message(user, messages["help_"+command]) self.send_message(user, messages["help_"+command])
...@@ -196,8 +247,6 @@ class Wantzel(object): ...@@ -196,8 +247,6 @@ class Wantzel(object):
# Archive this article # Archive this article
if "a" in command: if "a" in command:
note -= 2 note -= 2
#TODO: Gérer les autres champs qui n'ont pas de valeur par défaut
# lang, published, nid, screenshot, title, fetched, seemscite
print("Adding an article by %s: %s" % (user, url)) print("Adding an article by %s: %s" % (user, url))
result = cursor.execute( result = cursor.execute(
"""INSERT INTO presse SET """INSERT INTO presse SET
...@@ -218,37 +267,40 @@ class Wantzel(object): ...@@ -218,37 +267,40 @@ class Wantzel(object):
self.send_message(channel, messages["rp_known_article"] % user) self.send_message(channel, messages["rp_known_article"] % user)
else: else:
self.send_message(channel, messages["rp_taken_article"] % user) self.send_message(channel, messages["rp_taken_article"] % user)
# Update number of articles to do
self.count_articles()
def kill(self, user, channel, msg): def kill(self, user, channel, msg):
""" """
Kill an article by setting its score to -100. Kill an article by setting its score to -100.
""" """
#TODO: Gérer les droits de cette commande
print("kill command") print("kill command")
url = get_url(msg) if is_moderator(user):
print("url: %s" % url) url = get_url(msg)
if url=="": print("url: %s" % url)
return if url=="":
elif url=="http": return
self.send_message(channel, messages["rp_http"] % user) elif url=="http":
return self.send_message(channel, messages["rp_http"] % user)
# Looking for such an article in database return
cursor = get_cursor() # Looking for such an article in database
cursor.execute("SELECT id, note FROM presse WHERE url=%s", (url, )) cursor = get_cursor()
rows = cursor.fetchall() cursor.execute("SELECT id, note FROM presse WHERE url=%s", (url, ))
if not rows: rows = cursor.fetchall()
self.send_message(channel, messages["kill_none"] % url) 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)
else: else:
cursor.execute("UPDATE presse SET note=-100 WHERE id=%s", (rows[0][0], )) self.send_message(channel, messages["not_moderator"])
self.send_message(channel, messages["kill_done"] % url)
def stats(self, user, channel, msg): def stats(self, user, channel, msg):
""" """
Returns stats on articles in press review. Returns stats on articles in press review.
""" """
print("stats command") print("stats command")
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="site") cursor = get_cursor()
cursor = db.cursor()
periods = [1, 3, 7, 15] periods = [1, 3, 7, 15]
notes = [0, 3 ,4] notes = [0, 3 ,4]
notnull = 0 notnull = 0
...@@ -276,6 +328,169 @@ class Wantzel(object): ...@@ -276,6 +328,169 @@ class Wantzel(object):
result = messages["stats_bravo"] % periods[-1] result = messages["stats_bravo"] % periods[-1]
self.send_message(channel, result) self.send_message(channel, result)
def admin(self, user, channel, msg):
"""
Manage moderation.
A sub-command should be behind the !admin command.
"""
print("admin command")
# Searching for a command after admin keyword
command = re.search("!admin (list|add|del)", msg)
if command:
command = command.group(1)
if command=="list":
self.admin_list(user, channel, msg)
elif command=="add":
self.admin_add(user, channel, msg)
elif command=="del":
self.admin_del(user, channel, msg)
def admin_list(self, user, channel, msg):
"""
List actual moderators.
"""
print("admin_list command")
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"))
self.send_message(channel, messages["admin_list"] % ", ".join(names))
else:
self.send_message(channel, messages["not_moderator"])
def admin_add(self, user, channel, msg):
"""
Add some new moderators if not existing yet.
"""
print("admin_add command")
if is_moderator(user):
try:
names = []
connection = sqlite3.connect(config.sqlite_db)
result = re.search("!admin add (([^,]+, ?)+)?(.*)", msg)
if result.group(1):
names = [name for name in result.group(1).split(", ") if name!=""]
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"))
names = set([name for name in names if name not in moderators])
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"])
except:
pass
else:
self.send_message(channel, messages["not_moderator"])
def admin_del(self, user, channel, msg):
"""
Delete a moderator from list.
"""
print("admin_del command")
if is_moderator(user):
try:
names = []
result = re.search("!admin del (([^,]+, ?)+)?(.*)", msg)
if result.group(1):
names = [name for name in result.group(1).split(", ") if name!=""]
names.append(result.group(3))
names = set(names)
print(names)
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))
except:
pass
else:
self.send_message(channel, messages["not_moderator"])
def count_articles(self):
"""
Count number of articles not done in RP and updates the topic of the
press review channel if needed.
"""
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])
if self.number!=number:
self.irc.client.topic("#mytipy", messages["topic"] % number)
pass
self.number = number
def rp_to_twitter(self, rss):
"""
By parsing the RSS feed of the press-review, we know what to tweet.
"""
print("rp_to_twitter method")
now = time.localtime()
today = time.strptime("%s-%s-%s %s" % (
now.tm_year,
now.tm_mon,
now.tm_mday,
tm_isdst
),
"%Y-%m-%d %Z"
)
entries = feedparser.parse(rss)['entries']
entries.reverse()
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:
self.tweet(messages["tweet_rp"] % (
entry.title.encode("utf-8"),
entry.link.encode("utf-8")
))
print(entry.published_parsed)
print(entry.title)
# 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
def tweet(self, message):
"""
Tweet message on specified account
"""
print("tweet method")
auth = OAuth(
config.TOKEN,
config.TOKENSEC,
config.CONSKEY,
config.CONSSEC
)
twitter = Twitter(auth=auth)
try:
print("Tweeting: %s", message)
#twitter.statuses.update(status=message)
except:
pass
if __name__ == '__main__': if __name__ == '__main__':
wantzel = Wantzel() wantzel = Wantzel()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment