From 46f61b35166a8c6597264fd828b0034755d4ecbb Mon Sep 17 00:00:00 2001 From: Vincent A Date: Tue, 27 Aug 2013 22:58:52 +0200 Subject: [PATCH] new messages module: ovs --- modules/ovs/__init__.py | 24 ++++++ modules/ovs/backend.py | 104 +++++++++++++++++++++++ modules/ovs/browser.py | 102 +++++++++++++++++++++++ modules/ovs/favicon.png | Bin 0 -> 3495 bytes modules/ovs/ovsparse.py | 121 +++++++++++++++++++++++++++ modules/ovs/pages.py | 177 ++++++++++++++++++++++++++++++++++++++++ modules/ovs/test.py | 28 +++++++ 7 files changed, 556 insertions(+) create mode 100644 modules/ovs/__init__.py create mode 100644 modules/ovs/backend.py create mode 100644 modules/ovs/browser.py create mode 100644 modules/ovs/favicon.png create mode 100644 modules/ovs/ovsparse.py create mode 100644 modules/ovs/pages.py create mode 100644 modules/ovs/test.py diff --git a/modules/ovs/__init__.py b/modules/ovs/__init__.py new file mode 100644 index 00000000..fcbe95dc --- /dev/null +++ b/modules/ovs/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from .backend import OvsBackend + + +__all__ = ['OvsBackend'] diff --git a/modules/ovs/backend.py b/modules/ovs/backend.py new file mode 100644 index 00000000..a3c7a844 --- /dev/null +++ b/modules/ovs/backend.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +from weboob.tools.backend import BaseBackend, BackendConfig +from weboob.tools.browser import BrowserForbidden +from weboob.tools.value import Value, ValueBackendPassword +from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Message + +from .browser import OvsBrowser + + +__all__ = ['OvsBackend'] + + +class OvsBackend(BaseBackend, ICapMessages, ICapMessagesPost): + NAME = 'ovs' + DESCRIPTION = u'OnVaSortir website. Handles private messages only' + MAINTAINER = u'Vincent A' + EMAIL = 'dev@indigo.re' + VERSION = '0.h' + + CONFIG = BackendConfig(Value('username', label='Username', default=''), + ValueBackendPassword('password', label='Password', default=''), + Value('city', label='City (subdomain)', default='paris')) + # TODO keep list of possible cities + + BROWSER = OvsBrowser + + STORAGE = {'seen': {}} + + def create_default_browser(self): + return self.create_browser(self.config['city'].get(), + self.config['username'].get(), + self.config['password'].get(), + parser='raw') + + def iter_threads(self): + with self.browser: + for thread in self.browser.iter_threads_list(): + yield thread + + def get_thread(self, id): + with self.browser: + thread = self.browser.get_thread(id) + + messages = [thread.root] + thread.root.children + for message in messages: + if not self.storage.get('seen', message.full_id, default=False): + message.flags |= Message.IS_UNREAD + + return thread + + def iter_unread_messages(self): + with self.browser: + for thread in self.iter_threads(): + # TODO reuse thread object? + thread2 = self.get_thread(thread.id) + messages = [thread2.root] + thread2.root.children + for message in messages: + if message.flags & Message.IS_UNREAD: + yield message + # TODO implement more efficiently by having a "last weboob seen" for + # a thread and query a thread only if "last activity" returned by web + # is later than "last weboob seen" + + def set_message_read(self, message): + self.storage.set('seen', message.full_id, True) + self.storage.save() + + def post_message(self, message): + if not self.browser.username: + raise BrowserForbidden() + + with self.browser: + thread = message.thread + + if message.parent: + # ovs.@* + self.browser.post_to_thread(thread.id, message.title, message.content) + else: + # ovs.@* + self.browser.create_thread(thread.id, message.title, message.content) + +# FIXME known bug: parsing is done in "boosted mode" which is automatically disable after some time, the "boosted mode" should be re-toggled often + +# TODO support outing comments, forum messages +# TODO make an ICapOuting? + diff --git a/modules/ovs/browser.py b/modules/ovs/browser.py new file mode 100644 index 00000000..eda55f24 --- /dev/null +++ b/modules/ovs/browser.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword +from weboob.tools.parsers.iparser import IParser +import BeautifulSoup + +from .pages import PagePrivateThreadsList, PagePrivateThread, PageLogin, PageIndex, DummyPage, PageUserProfile + + +__all__ = ['OvsBrowser'] + + +class SoupParser(IParser): + def parse(self, data, encoding=None): + return BeautifulSoup.BeautifulSoup(data.read().decode(encoding or 'utf-8'), convertEntities=BeautifulSoup.BeautifulStoneSoup.ALL_ENTITIES) + + +class OvsBrowser(BaseBrowser): + PROTOCOL = 'http' + DOMAIN = 'paris.onvasortir.com' + ENCODING = 'cp1252' + + def __init__(self, city, username, password, *a, **kw): + self.DOMAIN = '%s.onvasortir.com' % city + self.PAGES = { + '%s://%s/' % (self.PROTOCOL, self.DOMAIN): PageIndex, + + r'%s://%s/message_read.php\?Id=.+' % (self.PROTOCOL, self.DOMAIN): PagePrivateThread, + + '%s://%s/vue_messages_recus.php' % (self.PROTOCOL, self.DOMAIN): PagePrivateThreadsList, + '%s://%s/vue_messages_envoyes.php' % (self.PROTOCOL, self.DOMAIN): PagePrivateThreadsList, + + '%s://%s/page_action_connect.php' % (self.PROTOCOL, self.DOMAIN): PageLogin, + + r'%s://%s/\?Langue=EN' % (self.PROTOCOL, self.DOMAIN): DummyPage, + '%s://%s/page_action_boost.php' % (self.PROTOCOL, self.DOMAIN): DummyPage, + '%s://%s/vue_profil_all.php.php' % (self.PROTOCOL, self.DOMAIN): DummyPage, + r'%s://%s/message_msg_envoi_ok.php\?.*' % (self.PROTOCOL, self.DOMAIN): DummyPage, + '%s://%s/message_action_envoi.php' % (self.PROTOCOL, self.DOMAIN): DummyPage, + + r'%s://%s/profil_read.php\?.+' % (self.PROTOCOL, self.DOMAIN): PageUserProfile, + } + + kw['parser'] = SoupParser() + BaseBrowser.__init__(self, username, password, *a, **kw) + + def iter_threads_list(self): + self.location('/vue_messages_recus.php') + assert self.is_on_page(PagePrivateThreadsList) + for thread in self.page.iter_threads_list(): + yield thread + + self.location('/vue_messages_envoyes.php') + assert self.is_on_page(PagePrivateThreadsList) + for thread in self.page.iter_threads_list(): + yield thread + + def get_thread(self, _id): + self.location('/message_read.php?Id=%s&AffMsg=all' % _id) + assert self.is_on_page(PagePrivateThread) + return self.page.get_thread(_id) + + def login(self): + assert not self.is_logged() + + self.page.login(self.username, self.password) + if not self.is_logged(): + raise BrowserIncorrectPassword() + self.location('/?Langue=EN') + self.location('/page_action_boost.php') + self.location('/') + + def is_logged(self): + return (self.is_on_page(DummyPage) or self.page.is_logged()) + + def post_to_thread(self, thread_id, subject, body): + self.location('/message_read.php?Id=%s' % thread_id.encode(self.ENCODING)) # FIXME + assert self.is_on_page(PagePrivateThread) + self.page.post_to_thread(thread_id, subject, body) + + def create_thread(self, recipient, subject, body): + self.location('/profil_read.php?%s' % recipient.encode(self.ENCODING)) # FIXME + assert self.is_on_page(PageUserProfile) + self.page.create_thread(recipient, subject, body) diff --git a/modules/ovs/favicon.png b/modules/ovs/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0783b9ce955bc96e773db5e9ec572dd24dff4420 GIT binary patch literal 3495 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. + + +import BeautifulSoup +import datetime + + +def nearest_parent(node, expected): + return node.findParent(expected) + + while node and node.name != expected: + node = node.parent + return node + +def all_text_recursive(node): + return ''.join(node.findAll(text=True)) + +def all_next_siblings(node): + ret = [] + while node: + ret.append(node) + node = node.nextSibling + return ret + +def parse_date_from_site(sitedate): + parts = sitedate.split() # [d, m, Y, '-', 'H:M:S'] + if len(parts[0]) == 1: + parts[0] = '0%s' % parts[0] + months = {'january': '01', 'february': '02', 'march': '03', 'april': '04', 'may': '05', 'june': '06', 'july': '07', 'august': '08', 'september': '09', 'october': '10', 'november': '11', 'december': '12'} + parts[1] = months[parts[1].lower()] + del parts[3] + return datetime.datetime.strptime(' '.join(parts), '%d %m %Y %H:%M:%S') + +def image_to_text(src): + smileys = {'chat/e/grin.gif': ':D', + 'chat/e/unsure.gif': ':s', + 'chat/e/smile.gif': ':)', + 'chat/e/shocked.gif': ':|', + 'chat/e/sad.gif': ':(', + 'chat/e/huh.gif': ':h', + 'chat/e/suprised.gif': ':o', + 'chat/e/cool.gif': 'B)', + 'chat/e/redface.gif': ':red', + 'chat/e/confused.gif': ':=', + 'chat/e/razz.gif': ':p', + 'chat/e/wink.gif': ';)', + 'chat/e/mad.gif': ':x', + 'chat/e/rolleyes.gif': ':b', + 'chat/e/lol.gif': ':lol', + 'chat/e/wub.gif': ':$', + 'chat/e/bouche.gif': ':K', + 'chat/e/sick.gif': '+o('} + + return smileys.get(src) + +def html_message_to_text(nodes): + parts = [] + + for node in nodes: + if isinstance(node, BeautifulSoup.NavigableString): + parts.append(unicode(node).replace('\r', '')) + elif node.name == 'img': + parts.append(image_to_text(node['src'])) + elif node.name == 'a': + parts.append(node['href']) + elif node.name == 'br': + parts.append('\n') + else: + assert not ('%s not supported' % node.name) + + return ''.join(parts) + +def create_unique_id(proposed_id, used_ids): + if proposed_id not in used_ids: + return proposed_id + + def make_id(base, index): + return '%s-%s' % (base, index) + + index = 1 + while make_id(proposed_id, index) in used_ids: + index += 1 + + return make_id(proposed_id, index) + +# public + +def private_message_form_fields(document): + ret = {} + form = document.find('form', attrs={'name': 'envoimail'}) + + def set_if_present(name): + item = form.find('input', attrs={'name': name}) + if item: + ret[name] = item['value'] + + set_if_present('Pere') + set_if_present('Sortie') + set_if_present('Dest') + set_if_present('Titre') + return ret + +def is_logged(document): + return (not document.find('form', attrs={'name': 'connection'})) diff --git a/modules/ovs/pages.py b/modules/ovs/pages.py new file mode 100644 index 00000000..bfa75645 --- /dev/null +++ b/modules/ovs/pages.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +import datetime +import re +import urllib +from weboob.tools.browser import BasePage +from weboob.capabilities.messages import Message, Thread + +import ovsparse + +__all__ = ['PagePrivateThreadsList', 'PagePrivateThread', 'PageLogin', 'PageIndex', 'DummyPage', 'PagePostMessage', 'PageUserProfile'] + + +class OvsPage(BasePage): + def is_logged(self): + return ovsparse.is_logged(self.document) + + def login(self, username, password): + self.browser.select_form(name='connection') + self.browser['Pseudo'] = username.encode(self.browser.ENCODING) + self.browser['Password'] = password.encode(self.browser.ENCODING) + self.browser['Retenir'] = ['ok'] + self.browser.submit(nologin=True) + + +class PagePrivateThreadsList(OvsPage): + def iter_threads_list(self): + # site is sorted from latest to oldest + for message_a in reversed(self.document.findAll('a', href=re.compile(r'message_read.php\?'))): + ovs_id = re.search(r'Id=(\d+)', message_a["href"]).group(1) + id_ = ovs_id + + thread = Thread(id_) + thread.title = ovsparse.all_text_recursive(message_a) + thread.flags = Thread.IS_DISCUSSION + + #~ parent_tr = message_a.findParent('tr') + #~ username = all_text_recursive(parent_tr.find('a', href=re.compile(r'profil_read.php\?.*'))) + #~ notread_self = (parent_tr.get('class') == 'newmails') + #~ notread_other = (parent_tr.find('span', **{'class': 'new_sortiepartenaire'}) is not None) + + yield thread + + +class PagePrivateThread(OvsPage): + def get_thread(self, _id): + thread = Thread(_id) + + thread.title = self.document.find('div', 'PADtitreBlanc_txt').find('center').string + thread.flags = Thread.IS_DISCUSSION + root = True + + for message in self._get_messages(thread): + if root: + message.children = [] + thread.root = message + thread.date = message.date + message.title = thread.title + root = False + else: + message.title = 'Re: %s' % thread.title + message.children = [] + message.parent = thread.root + thread.root.children.append(message) + + return thread + + def _get_messages(self, thread): + thread_div = self.document.find(True, 'PADpost_txt') + used_ids = set() + + rcpt = self.document.find('input', attrs={'type': 'hidden', 'name': 'Dest'})['value'] + sender_to_receiver = {rcpt: self.browser.username, self.browser.username: rcpt} + # site is sorted from latest to oldest message + for message_table in reversed(thread_div.findAll('table')): + for td in message_table.findAll('td'): + profile_a = td.find('a', href=re.compile(r'profil_read.php\?.*')) + if not profile_a: + continue + + first_br = td.find('br') + assert first_br.nextSibling.name == 'br' + text_nodes = ovsparse.all_next_siblings(first_br.nextSibling.nextSibling) # TODO + #~ print text_nodes + + # date will be used as id + sitedate = profile_a.findParent('div').find(text=re.compile(',.*')).replace(', ', '') + sysdate = ovsparse.parse_date_from_site(sitedate) + compactdate = datetime.datetime.strftime(sysdate, '%Y%m%dT%H%M%S') + + # but make it unique + msg_id = ovsparse.create_unique_id(compactdate, used_ids) + used_ids.add(msg_id) + + message = Message(thread, msg_id) + + message.sender = re.search(r'\?(.+)', profile_a['href']).group(1) + message.receivers = [sender_to_receiver[message.sender]] + + message.date = sysdate + + message.content = ovsparse.html_message_to_text(text_nodes) + + notread_self = bool(td.find('span', 'ColorSurligne')) + notread_other = bool(td.find('span', 'new_sortiepartenaire')) + if notread_other or notread_self: + message.flags |= Message.IS_NOT_RECEIVED + else: + message.flags |= Message.IS_RECEIVED + + yield message + + def post_to_thread(self, thread_id, subject, body): + form = ovsparse.private_message_form_fields(self.document) + recode_dict(form, self.browser.ENCODING) + form['Message'] = body.encode(self.browser.ENCODING) + self.browser.location('/message_action_envoi.php', urllib.urlencode(form)) + + # html code is so broken that mechanize won't parse the forms + #~ self.browser.select_form('envoimail') + #~ self.browser['Message'] = body.encode(self.browser.ENCODING) + #~ self.browser['Pere'] = thread_id.encode(self.browser.ENCODING) + #~ self.browser['Titre'] = subject.encode(self.browser.ENCODING) + #~ self.browser.submit() + + +class PageLogin(BasePage): + pass + + +class PageIndex(OvsPage): + pass + + +class DummyPage(BasePage): + pass + + +class PagePostMessage(OvsPage): + pass + + +class PageUserProfile(OvsPage): + def create_thread(self, recipient, subject, body): + form = ovsparse.private_message_form_fields(self.document) + recode_dict(form, self.browser.ENCODING) + form['Message'] = body.encode(self.browser.ENCODING) + form['Titre'] = subject.encode(self.browser.ENCODING) + self.browser.location('/message_action_envoi.php', urllib.urlencode(form)) + + #~ self.browser.select_form('envoimail') + #~ self.browser['Titre'] = subject.encode(self.browser.ENCODING) + #~ self.browser['Message'] = body.encode(self.browser.ENCODING) + #~ self.browser.submit() + + +def recode_dict(dict_, encoding): + for k in dict_: + dict_[k] = dict_[k].encode(encoding) diff --git a/modules/ovs/test.py b/modules/ovs/test.py new file mode 100644 index 00000000..b479641a --- /dev/null +++ b/modules/ovs/test.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.test import BackendTest + + +class OvsTest(BackendTest): + BACKEND = 'ovs' + + def test_ovs(self): + raise NotImplementedError()