pax_global_header 0000666 0000000 0000000 00000000064 12356015330 0014510 g ustar 00root root 0000000 0000000 52 comment=e025fb0b2040e76d68512fca33d3483aa63d925d
woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/ 0000775 0000000 0000000 00000000000 12356015330 0021600 5 ustar 00root root 0000000 0000000 woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/modules/ 0000775 0000000 0000000 00000000000 12356015330 0023250 5 ustar 00root root 0000000 0000000 woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/modules/okc/ 0000775 0000000 0000000 00000000000 12356015330 0024024 5 ustar 00root root 0000000 0000000 woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/modules/okc/__init__.py 0000664 0000000 0000000 00000001510 12356015330 0026132 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Romain Bignon
#
# 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 .browser import OkCBrowser
from .backend import OkCBackend
__all__ = ['OkCBrowser', 'OkCBackend']
woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/modules/okc/backend.py 0000664 0000000 0000000 00000032613 12356015330 0025772 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2012 Roger Philibert
#
# 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 time
import datetime
from dateutil import tz
from dateutil.parser import parse as _parse_dt
from weboob.capabilities.messages import CapMessages, CapMessagesPost, Message, Thread
from weboob.capabilities.dating import CapDating, OptimizationNotFound, Event
from weboob.capabilities.contact import CapContact, ContactPhoto, Contact, Query, QueryError
from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.value import Value, ValueBackendPassword
from weboob.tools.misc import local2utc
from .browser import OkCBrowser
from .optim.profiles_walker import ProfilesWalker
__all__ = ['OkCBackend']
def parse_dt(s):
now = datetime.datetime.now()
if s is None:
return local2utc(now)
if 'minutes ago' in s:
m = int(s.split()[0])
d = now - datetime.timedelta(minutes=m)
elif u'–' in s:
# Date in form : "Yesterday – 20:45"
day, hour = s.split(u'–')
day = day.strip()
hour = hour.strip()
if day == 'Yesterday':
d = now - datetime.timedelta(days=1)
elif day == 'Today':
d = now
hour = _parse_dt(hour)
d = datetime.datetime(d.year, d.month, d.day, hour.hour, hour.minute)
else:
#if ',' in s:
# Date in form : "Dec 28, 2011")
d = _parse_dt(s)
return local2utc(d)
class OkCBackend(BaseBackend, CapMessages, CapContact, CapMessagesPost, CapDating):
NAME = 'okc'
MAINTAINER = u'Roger Philibert'
EMAIL = 'roger.philibert@gmail.com'
VERSION = '0.j'
LICENSE = 'AGPLv3+'
DESCRIPTION = u'OkCupid dating website'
CONFIG = BackendConfig(Value('username', label='Username'),
ValueBackendPassword('password', label='Password'))
STORAGE = {'profiles_walker': {'viewed': []},
'queries_queue': {'queue': []},
'sluts': {},
#'notes': {},
}
BROWSER = OkCBrowser
def create_default_browser(self):
return self.create_browser(self.config['username'].get(), self.config['password'].get())
# ---- CapDating methods ---------------------
def init_optimizations(self):
self.add_optimization('PROFILE_WALKER', ProfilesWalker(self.weboob.scheduler, self.storage, self.browser))
def iter_events(self):
all_events = {}
with self.browser:
all_events[u'visits'] = (self.browser.get_visits, 'Visited by %s')
for type, (events, message) in all_events.iteritems():
for event in events():
e = Event(event['who']['id'])
e.date = parse_dt(event['date'])
e.type = type
# if 'who' in event:
# e.contact = self._get_partial_contact(event['who'])
# else:
# e.contact = self._get_partial_contact(event)
# if not e.contact:
# continue
# e.message = message % e.contact.name
yield e
# ---- CapMessages methods ---------------------
def fill_thread(self, thread, fields):
return self.get_thread(thread)
def iter_threads(self):
with self.browser:
threads = self.browser.get_threads_list()
for thread in threads:
# Remove messages from user that quit
#if thread['member'].get('isBan', thread['member'].get('dead', False)):
# with self.browser:
# self.browser.delete_thread(thread['member']['id'])
# continue
t = Thread(int(thread['id']))
t.flags = Thread.IS_DISCUSSION
t.title = u'Discussion with %s' % thread['username']
yield t
def get_thread(self, id, contacts=None, get_profiles=False):
"""
Get a thread and its messages.
The 'contacts' parameters is only used for internal calls.
"""
thread = None
if isinstance(id, Thread):
thread = id
id = thread.id
if not thread and isinstance(id, basestring) and not id.isdigit():
for t in self.browser.get_threads_list():
if t['username'] == id:
id = t['id']
break
else:
return None
if not thread:
thread = Thread(int(id))
thread.flags = Thread.IS_DISCUSSION
with self.browser:
mails = self.browser.get_thread_mails(id)
my_name = self.browser.get_my_name()
child = None
msg = None
slut = self._get_slut(thread.id)
if contacts is None:
contacts = {}
if not thread.title:
thread.title = u'Discussion with %s' % mails['member']['pseudo']
for mail in mails['messages']:
flags = 0
if mail['date'] > slut['lastmsg']:
flags |= Message.IS_UNREAD
if get_profiles:
if not mail['id_from'] in contacts:
with self.browser:
contacts[mail['id_from']] = self.get_contact(mail['id_from'])
signature = u''
if mail.get('src', None):
signature += u'Sent from my %s\n\n' % mail['src']
if contacts.get(mail['id_from'], None) is not None:
signature += contacts[mail['id_from']].get_text()
msg = Message(thread=thread,
id=int(time.strftime('%Y%m%d%H%M%S', mail['date'].timetuple())),
title=thread.title,
sender=mail['id_from'],
receivers=[my_name if mail['id_from'] != my_name else mails['member']['pseudo']],
date=mail['date'],
content=mail['message'],
signature=signature,
children=[],
flags=flags)
if child:
msg.children.append(child)
child.parent = msg
child = msg
if msg:
msg.parent = None
thread.root = msg
return thread
def iter_unread_messages(self, thread=None):
contacts = {}
for thread in self.browser.get_threads_list():
t = self.get_thread(thread['username'], contacts, get_profiles=True)
for m in t.iter_all_messages():
if m.flags & m.IS_UNREAD:
yield m
def set_message_read(self, message):
slut = self._get_slut(message.thread.id)
if slut['lastmsg'] < message.date:
slut['lastmsg'] = message.date
self.storage.set('sluts', message.thread.id, slut)
self.storage.save()
def _get_slut(self, id):
sluts = self.storage.get('sluts')
if not sluts or not id in sluts:
slut = {'lastmsg': datetime.datetime(1970,1,1)}
else:
slut = self.storage.get('sluts', id)
slut['lastmsg'] = slut.get('lastmsg', datetime.datetime(1970,1,1)).replace(tzinfo=tz.tzutc())
return slut
# ---- CapMessagesPost methods ---------------------
def post_message(self, message):
content = message.content.replace('\n', '\r\n').encode('utf-8', 'replace')
with self.browser:
# Check wether we already have a thread with this user
threads = self.browser.get_threads_list()
for thread in threads:
if thread['id'] == message.thread.id:
self.browser.post_reply(thread['id'], content)
break
else:
self.browser.post_mail(message.thread.id, content)
# ---- CapContact methods ---------------------
def fill_contact(self, contact, fields):
if 'profile' in fields:
contact = self.get_contact(contact)
if contact and 'photos' in fields:
for name, photo in contact.photos.iteritems():
with self.browser:
if photo.url and not photo.data:
data = self.browser.openurl(photo.url).read()
contact.set_photo(name, data=data)
if photo.thumbnail_url and not photo.thumbnail_data:
data = self.browser.openurl(photo.thumbnail_url).read()
contact.set_photo(name, thumbnail_data=data)
def fill_photo(self, photo, fields):
with self.browser:
if 'data' in fields and photo.url and not photo.data:
photo.data = self.browser.readurl(photo.url)
if 'thumbnail_data' in fields and photo.thumbnail_url and not photo.thumbnail_data:
photo.thumbnail_data = self.browser.readurl(photo.thumbnail_url)
return photo
def get_contact(self, contact):
with self.browser:
if isinstance(contact, Contact):
_id = contact.id
elif isinstance(contact, (int,long,basestring)):
_id = contact
else:
raise TypeError("The parameter 'contact' isn't a contact nor a int/long/str/unicode: %s" % contact)
profile = self.browser.get_profile(_id)
if not profile:
return None
_id = profile['id']
if isinstance(contact, Contact):
contact.id = _id
contact.name = profile['id']
else:
contact = Contact(_id, profile['id'], Contact.STATUS_OFFLINE)
contact.url = 'http://%s/profile/%s' % (self.browser.DOMAIN, _id)
contact.profile = profile['data']
contact.summary = profile.get('summary', '')
if contact.profile['details']['last_online'].value == u'Online now!':
contact.status = Contact.STATUS_ONLINE
else:
contact.status = Contact.STATUS_OFFLINE
contact.status_msg = contact.profile['details']['last_online'].value
for no, photo in enumerate(self.browser.get_photos(_id)):
contact.set_photo(u'image_%i' % no, url=photo, thumbnail_url=photo)
return contact
#def _get_partial_contact(self, contact):
# if contact.get('isBan', contact.get('dead', False)):
# with self.browser:
# self.browser.delete_thread(int(contact['id']))
# return None
# s = 0
# if contact.get('isOnline', False):
# s = Contact.STATUS_ONLINE
# else:
# s = Contact.STATUS_OFFLINE
# c = Contact(contact['id'], contact['id'], s)
# c.url = self.browser.id2url(contact['id'])
# if 'birthday' in contact:
# birthday = _parse_dt(contact['birthday'])
# age = int((datetime.datetime.now() - birthday).days / 365.25)
# c.status_msg = u'%s old, %s' % (age, contact['city'])
# if contact['cover'].isdigit() and int(contact['cover']) > 0:
# url = 'http://s%s.adopteunmec.com/%s%%(type)s%s.jpg' % (contact['shard'], contact['path'], contact['cover'])
# else:
# url = 'http://s.adopteunmec.com/www/img/thumb0.gif'
# c.set_photo('image%s' % contact['cover'],
# url=url % {'type': 'image'},
# thumbnail_url=url % {'type': 'thumb0_'})
# return c
def iter_contacts(self, status=Contact.STATUS_ALL, ids=None):
with self.browser:
threads = self.browser.get_threads_list()
for thread in threads:
c = self.get_contact(thread['username'])
if c and (c.status & status) and (not ids or c.id in ids):
yield c
def send_query(self, id):
if isinstance(id, Contact):
id = id.id
queries_queue = None
try:
queries_queue = self.get_optimization('QUERIES_QUEUE')
except OptimizationNotFound:
pass
if queries_queue and queries_queue.is_running():
if queries_queue.enqueue_query(id):
return Query(id, 'A profile was visited')
else:
return Query(id, 'Unable to visit profile: it has been enqueued')
else:
with self.browser:
if not self.browser.visit_profile(id):
raise QueryError('Could not visit profile')
return Query(id, 'Profile was visited')
#def get_notes(self, id):
# if isinstance(id, Contact):
# id = id.id
# return self.storage.get('notes', id)
#def save_notes(self, id, notes):
# if isinstance(id, Contact):
# id = id.id
# self.storage.set('notes', id, notes)
# self.storage.save()
OBJECTS = {Thread: fill_thread,
Contact: fill_contact,
ContactPhoto: fill_photo
}
woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/modules/okc/browser.py 0000664 0000000 0000000 00000021505 12356015330 0026064 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2012 Roger Philibert
#
# 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 urllib
from weboob.tools.browser import BaseBrowser, BasePage
from weboob.tools.ordereddict import OrderedDict
from .pages import LoginPage, ThreadPage, MessagesPage, PostMessagePage, ProfilePage, PhotosPage, VisitsPage, QuickMatchPage, SentPage
__all__ = ['OkCBrowser']
class OkCException(Exception):
pass
def check_login(func):
def inner(self, *args, **kwargs):
if not self.logged_in:
self.login()
return func(self, *args, **kwargs)
return inner
class OkCBrowser(BaseBrowser):
DOMAIN = 'm.okcupid.com'
PROTOCOL = 'https'
ENCODING = 'UTF-8'
PAGES = OrderedDict((
('https://%s/login.*' % DOMAIN, LoginPage),
('http://%s/home' % DOMAIN, BasePage),
('http://%s/messages' % DOMAIN, ThreadPage),
('http://%s/messages\?compose=1' % DOMAIN, PostMessagePage),
('http://\w+.okcupid.com/messages\?.*', MessagesPage),
('http://%s/profile/.*/photos' % DOMAIN, PhotosPage),
('http://%s/profile/[^/]*' % DOMAIN, ProfilePage),
('http://%s/visitors' % DOMAIN, VisitsPage),
('http://%s/quickmatch' % DOMAIN, QuickMatchPage),
('http://%s/mailbox' % DOMAIN, SentPage),
))
logged_in = False
def home(self):
self.location(self.absurl('/home'))
def login(self):
self.location(self.absurl('/login'), no_login=True)
self.page.login(self.username, self.password)
self.logged_in = True
def is_logged(self):
return self.logged_in
def get_consts(self):
return { 'conts' : 'blah' }
# if self.consts is not None:
# return self.consts
# self.consts = []
# for i in xrange(2):
# r = self.api_request('me', 'all_values', data={'sex': i})
# self.consts.append(r['result']['values'])
# return self.consts
#@check_login
#def score(self):
# #r = self.api_request('member', 'view', data={'id': self.my_id})
# return int(r['result']['member']['popu']['popu'])
def get_my_name(self):
return self.username
#@check_login
#def nb_new_mails(self):
# r = self.api_request('me', '[default]')
# return r['result']['news']['newMails']
#@check_login
#def nb_new_baskets(self):
# r = self.api_request('me', '[default]')
# return r['result']['news']['newBaskets']
#@check_login
#def nb_new_visites(self):
# r = self.api_request('me', '[default]')
# return r['result']['news']['newVisits']
#@check_login
#def nb_available_charms(self):
# r = self.login()
# return r['result']['flashs']
#@check_login
#def nb_godchilds(self):
# r = self.api_request('member', 'view', data={'id': self.my_id})
# return int(r['result']['member']['popu']['invits'])
#@check_login
#def get_baskets(self):
# r = self.api_request('me', 'basket')
# return r['result']['basket']
#@check_login
#def get_flashs(self):
# r = self.api_request('me', 'flashs')
# return r['result']['all']
@check_login
def get_visits(self):
self.location('http://m.okcupid.com/visitors')
return self.page.get_visits()
@check_login
def get_threads_list(self):
self.location('http://m.okcupid.com/messages')
return self.page.get_threads()
@check_login
def get_thread_mails(self, id):
id = int(id)
self.location('http://www.okcupid.com/messages?readmsg=true&threadid=%i&folder=1' % id)
return self.page.get_thread_mails()
@check_login
def post_mail(self, id, content):
self.location(self.absurl('/messages?compose=1'))
self.page.post_mail(id, content)
@check_login
def post_reply(self, thread_id, content):
self.location(self.absurl('/messages?readmsg=true&threadid=%s&folder=1' % thread_id))
username, key = self.page.get_post_params()
data = urllib.urlencode({
'ajax' : 1,
'sendmsg' : 1,
'r1' : username,
'subject' : '',
'body' : content,
'threadid' : thread_id,
'authcode' : key,
'reply' : 1,
})
self.addheaders = [('Referer', self.page.url), ('Content-Type', 'application/x-www-form-urlencoded')]
self.open('http://m.okcupid.com/mailbox', data=data)
#@check_login
#@url2id
#def delete_thread(self, id):
# r = self.api_request('message', 'delete', data={'id_user': id})
# self.logger.debug('Thread deleted: %r' % r)
#@check_login
#@url2id
#def send_charm(self, id):
# try:
# self.api_request('member', 'addBasket', data={'id': id})
# except AuMException:
# return False
# else:
# return True
@check_login
def find_match_profile(self, **kwargs):
self.location(self.absurl('/quickmatch'))
user_id = self.page.get_id()
return user_id
@check_login
def get_profile(self, id):
self.location(self.absurl('/profile/%s' % id))
profile = self.page.get_profile()
return profile
@check_login
def get_photos(self, id):
self.location(self.absurl('/profile/%s/photos' % id))
return self.page.get_photos()
#def _get_chat_infos(self):
# try:
# data = json.load(self.openurl('http://www.adopteunmec.com/1.1_cht_get.php?anticache=%f' % random.random()))
# except ValueError:
# raise BrowserUnavailable()
# if data['error']:
# raise ChatException(u'Error while getting chat infos. json:\n%s' % data)
# return data
#def iter_contacts(self):
# def iter_dedupe(contacts):
# yielded_ids = set()
# for contact in contacts:
# if contact['id'] not in yielded_ids:
# yield contact
# yielded_ids.add(contact['id'])
# data = self._get_chat_infos()
# return iter_dedupe(data['contacts'])
#def iter_chat_messages(self, _id=None):
# data = self._get_chat_infos()
# if data['messages'] is not None:
# for message in data['messages']:
# yield ChatMessage(id_from=message['id_from'], id_to=message['id_to'], message=message['message'], date=message['date'])
#def send_chat_message(self, _id, message):
# url = 'http://www.adopteunmec.com/1.1_cht_send.php?anticache=%f' % random.random()
# data = dict(id=_id, message=message)
# headers = {
# 'Content-type': 'application/x-www-form-urlencoded',
# 'Accept': 'text/plain',
# 'Referer': 'http://www.adopteunmec.com/chat.php',
# 'Origin': 'http://www.adopteunmec.com',
# }
# request = self.request_class(url, urllib.urlencode(data), headers)
# response = self.openurl(request).read()
# try:
# datetime.datetime.strptime(response, '%Y-%m-%d %H:%M:%S')
# return True
# except ValueError:
# return False
@check_login
def visit_profile(self, id):
self.location(self.absurl('/profile/%s' % id))
stalk, u, tuid = self.page.get_visit_button_params()
if stalk and u and tuid:
# Premium users, need to click on "visit button" to let the other person know his profile was visited
data = urllib.urlencode({
'ajax' : 1,
'stalk': stalk,
'u':u,
'tuid': tuid
})
self.addheaders = [('Referer', self.page.url), ('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8')]
self.open('http://m.okcupid.com/profile', data=data)
return True
@check_login
def do_rate(self, id):
# Need to be in quickmatch page
abs_url, rating,params = self.page.get_rating_params()
# print abs_url, rating, params
data = urllib.urlencode(params)
self.addheaders = [('Referer', self.page.url), ('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8')]
self.open('http://m.okcupid.com%s' %abs_url, data=data)
return True
woob-e025fb0b2040e76d68512fca33d3483aa63d925d-modules-okc/modules/okc/favicon.png 0000664 0000000 0000000 00000005032 12356015330 0026157 0 ustar 00root root 0000000 0000000 PNG
IHDR @ @ iq sRGB bKGD pHYs
B(x tIMEk tEXtComment Created with GIMPW uIDATxIoqHdQŶ+41D 9hX[uΗ(K@k1;%;^Id-4IqiZDrI ݞ>;g;vixxm|M "w
0#oC |?i@HM_@C_7𑐚P :w҇4l! K1 e3IXR{ 4-oqx&RLu