support repositories to manage backends (closes #747)

This commit is contained in:
Romain Bignon 2012-01-03 12:10:21 +01:00
commit 14a7a1d362
410 changed files with 1079 additions and 297 deletions

932
modules/aum/API.txt Normal file
View file

@ -0,0 +1,932 @@
Adopte un Mec API
------------------
Constants:
APIKEY = fb0123456789abcd
URL = http://api.adopteunmec.com/api.php
ME Commands
===========
me.login
---------
Parameters:
- login
- pass
Errors:
- 1.1.1 : invalid login
Return value:
{u'errors': [],
u'result': {u'baskets': u'384',
u'events': {u'lastBasket': {u'alert': u'1',
u'birthday': u'1989-02-04',
u'city': u'Paris',
u'country': u'fr',
u'cover': u'1',
u'id': u'14471939',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-09-20 13:56:36',
u'list5': u'0',
u'login': u'kloo',
u'mod_level': u'0',
u'path': u'9/3/9/1/7/4/4/',
u'pseudo': u'Kloolloo',
u'region': u'11',
u'sex': 1,
u'shard': 9,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14471939/Kloolloo',
u'zip': u'75001'},
u'lastChat': {u'alert': u'1',
u'birthday': u'1987-10-10',
u'city': u'Paris 18e Arrondissement',
u'country': u'fr',
u'cover': u'0',
u'id': u'13280274',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2010-11-20 03:37:00',
u'list5': u'0',
u'login': u'#c1a63bda81cc03ccdf080ca6e003919e',
u'mod_level': u'0',
u'path': u'4/7/2/0/8/2/3/',
u'pseudo': u'Katie Lee',
u'region': u'11',
u'sex': 1,
u'shard': 4,
u'style': u'0',
u'table': u'adopteun.girls_coma',
u'url': u'/api.php?member/view/13280274/KatieLee',
u'zip': u'75018'},
u'lastFlash': {u'alert': u'1',
u'birthday': u'1983-12-20',
u'city': u'Longjumeau',
u'country': u'fr',
u'cover': u'1',
u'id': u'13883475',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-20 18:52:13',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'5/7/4/3/8/8/3/',
u'pseudo': u'Pas Tjs Sage',
u'region': u'11',
u'sex': 1,
u'shard': 5,
u'style': u'2',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13883475/PasTjsSage',
u'zip': u'91160'},
u'lastMail': {u'alert': u'1',
u'birthday': u'1989-04-28',
u'city': u'Paris 8e Arrondissement',
u'country': u'fr',
u'cover': u'5',
u'id': u'13268738',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-20 19:24:14',
u'list5': u'0',
u'login': u'@13268738',
u'mod_level': u'0',
u'path': u'8/3/7/8/6/2/3/',
u'pseudo': u'Th\xe9na',
u'region': u'11',
u'sex': 1,
u'shard': 8,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13268738/Thna',
u'zip': u'75008'},
u'lastVisit': {u'alert': u'1',
u'birthday': u'1988-02-27',
u'city': u'Paris',
u'country': u'fr',
u'cover': u'1',
u'id': u'14477637',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-20 18:31:00',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'7/3/6/7/7/4/4/',
u'pseudo': u'Lolly',
u'region': u'11',
u'sex': 1,
u'shard': 7,
u'style': u'4',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14477637/Lolly',
u'zip': u'75005'}},
u'flashs': 10,
u'mails': u'731',
u'me': {u'about1': u"Je n'ai pas de temps &agrave; perdre, je n'ai ni MSN ni Facebook, je consid&egrave;re qu'on appr&eacute;cie davantage la discussion face &agrave; face autour d'un verre que dans les yeux de son &eacute;cran.\r<br>\r<br>Bon et ne venez que si vous avez quelque chose &agrave; me dire, je n'envoie jamais de charmes.",
u'about2': u'',
u'admin': u'0',
u'alert': u'0',
u'alert_add': u'6',
u'birthday': u'1986-08-13',
u'books': u"Orwell (1984, La ferme des animaux)<br>Barjavel (La nuit des temps, Le voyageur imprudent, Ravage, \x85)<br>Boris Vian (J'irai cracher sur vos tombes, L'&eacute;cume des jours\x85)<br>Bukowski, Desproges, San Antonio<br>Sartre, Le Canard",
u'cat': u'1',
u'checks1': u'0',
u'checks2': u'16686',
u'checks3': u'0',
u'checks4': u'0',
u'checks5': u'0',
u'checks6': u'2',
u'checks7': u'0',
u'cinema': u'Le Grand D&eacute;tournement \x97 La Classe Am&eacute;ricaine<br>V pour Vendetta, Pulp Fiction, The Truman Show<br>Eternal Sunshine of the Spotless Mind, Match Point<br>Idiocracy, The Big Lebowski, La cit&eacute; de la peur<br>Sin City, Orange Mecanique, Buffet Froid, L',
u'city': u'Paris',
u'country': u'fr',
u'cover': u'5',
u'drink': u'2',
u'email': u'tesiruna@parano.me',
u'eyes': u'3',
u'f': u'',
u'first_cnx': u'2010-05-16 09:13:53',
u'first_ip': u'81.57.125.104',
u'food': u'1',
u'godfather': u'0',
u'hair_color': u'5',
u'hair_size': u'3',
u'hobbies': u'',
u'id': u'22450639',
u'img_count': u'5',
u'isBan': False,
u'isOnline': True,
u'job': u'Dieu',
u'last_chat': u'-0001-11-29 23:09:21',
u'last_cnx': u'2011-09-20 19:24:25',
u'last_ip': u'88.161.27.232',
u'lat': u'48.861961',
u'latR': u'0.852802098431',
u'list1': u'2',
u'list2': u'2',
u'list3': u'2',
u'list4': u'3',
u'list5': u'0',
u'list6': u'0',
u'lng': u'2.33594',
u'lngR': u'0.040769844129',
u'login': u'@22450639',
u'mod_level': u'1',
u'music': u"Pink Floyd, Scorpions, Emperor<br>Metallica, Iron Maiden, Accept<br>Slash's Snakepit, Queen, Deep Purple<br>Led Zeppelin, Rolling Stones<br>Brassens, Souchon, Brel, Vian",
u'origins': u'1',
u'pass': u'8f3fa83cec9a243ae53c1337d2b5e1cf',
u'path': u'9/3/6/0/5/4/2/',
u'phone': u'-',
u'pictures': [{u'file': u'5',
u'height': u'427',
u'id': u'7315391',
u'md5': u'859fcd2e425617c33c16d6a1bc510ad3',
u'member': u'22450639',
u'rank': u'1',
u'valid': u'1578737',
u'width': u'500'}],
u'pseudo': u'Nazification',
u'region': u'11',
u'sex': 0,
u'shape': u'1',
u'shard': 9,
u'size': u'175',
u'smoke': u'2',
u'style': u'0',
u'subregion': u'76',
u'table': u'adopteun.boys',
u'texts1': u'',
u'texts2': u'',
u'texts3': u'',
u'texts4': u'',
u'texts5': u'',
u'texts6': u'',
u'title': u'',
u'tvs': u'Je ne poss&egrave;de pas la TV<br><br><br><br>',
u'url': u'/api.php?member/view/22450639/Nazification',
u'validated': True,
u'visites': u'0',
u'w': u'',
u'warn': u'0',
u'weight': u'55',
u'zip': u'75000'},
u'news': {u'newBaskets': 3, u'newMails': 1, u'newVisits': 113},
u'popu': u'71540',
u'subMobile': False,
u'subWebsite': False,
u'token': u'ea7bac8837f6e5bb1e2a3267d6a08b32e388d593',
u'visites': u'958'}}
me.[default]
------------
Return value:
{u'errors': [],
u'result': {u'events': {u'lastBasket': {u'alert': u'1',
u'birthday': u'1989-02-04',
u'city': u'Paris',
u'country': u'fr',
u'cover': u'1',
u'id': u'14471939',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-09-20 13:56:36',
u'list5': u'0',
u'login': u'kloo',
u'mod_level': u'0',
u'path': u'9/3/9/1/7/4/4/',
u'pseudo': u'Kloolloo',
u'region': u'11',
u'sex': 1,
u'shard': 9,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14471939/Kloolloo',
u'zip': u'75001'},
u'lastChat': {u'alert': u'1',
u'birthday': u'1987-10-10',
u'city': u'Paris 18e Arrondissement',
u'country': u'fr',
u'cover': u'0',
u'id': u'13280274',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2010-11-20 03:37:00',
u'list5': u'0',
u'login': u'#c1a63bda81cc03ccdf080ca6e003919e',
u'mod_level': u'0',
u'path': u'4/7/2/0/8/2/3/',
u'pseudo': u'Katie Lee',
u'region': u'11',
u'sex': 1,
u'shard': 4,
u'style': u'0',
u'table': u'adopteun.girls_coma',
u'url': u'/api.php?member/view/13280274/KatieLee',
u'zip': u'75018'},
u'lastFlash': {u'alert': u'1',
u'birthday': u'1983-12-20',
u'city': u'Longjumeau',
u'country': u'fr',
u'cover': u'1',
u'id': u'13883475',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-20 18:52:13',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'5/7/4/3/8/8/3/',
u'pseudo': u'Pas Tjs Sage',
u'region': u'11',
u'sex': 1,
u'shard': 5,
u'style': u'2',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13883475/PasTjsSage',
u'zip': u'91160'},
u'lastMail': {u'alert': u'1',
u'birthday': u'1989-04-28',
u'city': u'Paris 8e Arrondissement',
u'country': u'fr',
u'cover': u'5',
u'id': u'13268738',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-20 19:47:28',
u'list5': u'0',
u'login': u'@13268738',
u'mod_level': u'0',
u'path': u'8/3/7/8/6/2/3/',
u'pseudo': u'Th\xe9na',
u'region': u'11',
u'sex': 1,
u'shard': 8,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13268738/Thna',
u'zip': u'75008'},
u'lastVisit': {u'alert': u'1',
u'birthday': u'1988-02-27',
u'city': u'Paris',
u'country': u'fr',
u'cover': u'1',
u'id': u'14477637',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-09-20 19:09:00',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'7/3/6/7/7/4/4/',
u'pseudo': u'Lolly',
u'region': u'11',
u'sex': 1,
u'shard': 7,
u'style': u'4',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14477637/Lolly',
u'zip': u'75005'}},
u'news': {u'newBaskets': 0, u'newMails': 1, u'newVisits': 113},
u'token': u'9a97a03774c9f440e676c78f48794a7221a67285'}}
me.basket
----------
Return value:
{u'errors': [],
u'popu': 71840,
u'result': {u'basket': [{u'alert': u'1',
u'birthday': u'1989-02-04',
u'city': u'Paris',
u'country': u'fr',
u'cover': u'1',
u'date': u'2011-09-19 01:52:29',
u'id': u'14471939',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-09-20 13:56:36',
u'list5': u'0',
u'login': u'kloo',
u'mod_level': u'0',
u'path': u'9/3/9/1/7/4/4/',
u'pseudo': u'Kloolloo',
u'region': u'11',
u'sex': 1,
u'shard': 9,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14471939/Kloolloo',
u'zip': u'75001'}],
u'inBasket': 57,
u'token': u'ea7bac8837f6e5bb1e2a3267d6a08b32e388d593'}}
me.flashs
---------
Return value:
{u'errors': [],
u'result': {u'all': [{u'date': u'2011-10-19 10:50:43',
u'fid': u'41311269',
u'id': u'12656592',
u'member': {u'alert': u'1',
u'birthday': u'1986-08-08',
u'city': u'Armes',
u'country': u'fr',
u'cover': u'3',
u'id': u'12656592',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-10-19 17:28:00',
u'list5': u'0',
u'login': u'@12656592',
u'mod_level': u'0',
u'path': u'2/9/5/6/5/6/2/',
u'pseudo': u'Yayanne',
u'region': u'5',
u'sex': 1,
u'shard': 2,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/12656592/Yayanne',
u'zip': u'58500'},
u'seen': u'2011-10-19 10:54:09'}],
u'count': 1467,
u'news': [],
u'olds': [],
u'popu': u'110350',
u'token': u'3af90dd563431fe6b4c65930d18337c997fac34e'}}
me.visits
---------
Return value:
{u'errors': [],
u'result': {u'count': u'607',
u'news': [],
u'offset': 0,
u'olds': [{u'alert': u'0',
u'birthday': u'1985-01-29',
u'city': u'Albi',
u'country': u'fr',
u'cover': u'11',
u'date': u'2011-10-19 17:40:43',
u'id': u'13461054',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-10-19 17:47:25',
u'list5': u'0',
u'login': u'@13461054',
u'mod_level': u'0',
u'path': u'4/5/0/1/6/4/3/',
u'pseudo': u'Bruume',
u'region': u'15',
u'seen': u'2011-10-19 17:42:55',
u'sex': 1,
u'shard': 4,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13461054/Bruume',
u'vid': u'199226074',
u'zip': u'81000'}],
u'popu': u'110350',
u'token': u'd7380871794008c94971acec82b7b679dd800307'}}
MESSAGE Commands
================
message.[default]
-----------------
Arguments:
- P=<NB_ENTRIES>,<START>
Return value:
{u'errors': [],
u'result': {u'count': 1,
u'threads': [{u'cat': u'0',
u'date': u'2011-09-20 19:22:11',
u'id': u'11132125',
u'id_from': u'13268738',
u'id_to': u'22450639',
u'member': {u'alert': u'1',
u'birthday': u'1989-04-28',
u'city': u'Paris 8e Arrondissement',
u'country': u'fr',
u'cover': u'5',
u'id': u'13268738',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-20 19:54:15',
u'list5': u'0',
u'login': u'@13268738',
u'mod_level': u'0',
u'path': u'8/3/7/8/6/2/3/',
u'pseudo': u'Th\xe9na',
u'region': u'11',
u'sex': 1,
u'shard': 8,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13268738/Thna',
u'zip': u'75008'},
u'message': u'0',
u'status': u'0',
u'title': u"J'aime bien les \xe9l\xe9phants. Toi ?"}],
u'token': u'0f70c31bc6d05d45bee64e6f2eab9b537640f2f8'}}
message.thread
--------------
Parameters:
- memberId
- count
Return value:
{u'result': {u'popu': u'15910',
u'thread': {u'isNew': False,
u'member': {u'alert': u'3',
u'birthday': u'1987-04-22',
u'city': u'Maisons-Alfort',
u'country': u'fr',
u'cover': u'6',
u'id': u'14022243',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-09-20 18:18:00',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'3/4/2/2/2/0/4/',
u'pseudo': u'Sophkipeut',
u'region': u'11',
u'sex': 1,
u'shard': 3,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14022243/Sophkipeut',
u'zip': u'94700'},
u'messages': [{u'date': u'2011-09-19 17:52:03',
u'id': u'46583458',
u'id_from': u'14022243',
u'id_to': u'23185402',
u'message': u"Lol, moi j'ai fait attention, mais bon &ccedil;a n'emp&ecirc;che pas son bidou, ceci dit c'est planqu&eacute; par ses loooooooooooong poils ^^",
u'src': u'',
u'title': u"Lol, moi j'ai fait attention, mais bon \xe7a n'emp\xea..."},
{u'date': u'2011-09-19 17:49:39',
u'id': u'47467748',
u'id_from': u'23185402',
u'id_to': u'14022243',
u'message': u"Ah oui, justement le v&eacute;t&eacute;rinaire m'avait dit apr&egrave;s la castration de Futex qu'il fallait faire attention &agrave; son poids, du coup &ccedil;a m'a tellement vex&eacute; que j'ai fais attention au point qu'il est sans doute m&ecirc;me trop maigre.",
u'src': u'',
u'title': u"Ah oui, justement le v\xe9t\xe9rinaire m'avait dit apr\xe8..."}],
u'remoteStatus': u'2',
u'status': u'1',
u'warning': 0},
u'token': u'dbeccf96256d4f11991626707881fdba28f54d73'}}
message.new
-----------
Parameters:
- memberId
- message
Return value:
{u'errors': [],
u'result': {u'thread': {u'isNew': False,
u'member': {u'alert': u'1',
u'birthday': u'1986-12-08',
u'city': u'Rosny-sous-Bois',
u'country': u'fr',
u'cover': u'6',
u'id': u'11099536',
u'isBan': False,
u'isOnline': False,
u'last_cnx': u'2011-09-20 16:26:32',
u'list5': u'0',
u'login': u'@11099536',
u'mod_level': u'0',
u'path': u'6/3/5/9/9/0/1/',
u'pseudo': u'Debo',
u'region': u'11',
u'sex': 1,
u'shard': 6,
u'style': u'0',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/11099536/Debo',
u'zip': u'93110'},
u'messages': [{u'date': u'2011-09-20 20:09:07',
u'id': u'46588573',
u'id_from': u'23185402',
u'id_to': u'11099536',
u'message': u'Coucou',
u'src': u'iphone',
u'title': u'Coucou'},
{u'date': u'2011-09-20 16:27:34',
u'id': u'46638469',
u'id_from': u'11099536',
u'id_to': u'23185402',
u'message': u"Coucou pour tout t'avouer je ne m'y etais pas connect&eacute; depuis septembre ! un peu moins de boulot alors j'y traine !!\r\n\r\nEt toi &ccedil;a va? tu devais pas partir au canada? tu es deja revenu.?",
u'src': u'',
u'title': u"Coucou pour tout t'avouer je ne m'y etais pas co..."}],
u'remoteStatus': u'0',
u'status': u'2',
u'warning': 0},
u'token': u'2491f8ccb6741ceee2a461dc523939796904a0fa'}}
message.delete
--------------
Parameters:
- id_user
Return Value:
Unknown
MEMBER Commands
===============
member.view
-----------
Parameters:
- id
Return value:
{u'errors': [],
u'result': {u'member': {u'about1': u"comment te dire.. j'ai autant envie te laisser boire dans ma bouteille que mettre ma langue dans ta bouche !",
u'about2': u"LES BISOUS C'EST BIEN LES BIJOUX C'EST MIEUX!\r<br>\r<br>Et l'humour encore plus, mouhaha\r<br>\r<br>PS: Si vous &ecirc;tes le sosie de Pharell Williams, adoptez moi ;)",
u'admin': u'0',
u'alert': u'1',
u'alert_add': u'0',
u'birthday': u'1991-05-31',
u'books': u"L'Analphab&egrave;te - Rendell<br>Etat limite - Assouline<br>Marie-Antoinette - Zweig<br><br>",
u'cat': u'2',
u'checks1': u'2',
u'checks2': u'1588',
u'checks3': u'0',
u'checks4': u'0',
u'checks5': u'64',
u'checks6': u'192',
u'checks7': u'0',
u'cinema': u"My Blueberry Night<br>Shinning - L'exorciste - Ester - Gothika<br>How High ! - Requiem for a dream<br>Remember Me - trainspotting<br>He gots game - Buffet froid",
u'city': u'Paris',
u'country': u'fr',
u'cover': u'1',
u'drink': u'2',
u'eyes': u'5',
u'f': u'',
u'first_cnx': u'2011-09-17 00:18:59',
u'first_ip': u'82.120.134.233',
u'food': u'3',
u'godfather': u'0',
u'hair_color': u'4',
u'hair_size': u'3',
u'hobbies': u'Le th&eacute;&acirc;tre d&eacute;finitivement ! et le sport !',
u'id': u'14465370',
u'img_count': u'6',
u'isBan': False,
u'isOnline': False,
u'job': u'etudiante',
u'last_chat': u'0000-00-00 00:00:00',
u'last_cnx': u'2011-09-24 00:09:29',
u'last_ip': u'92.151.177.164',
u'lat': u'48.8814',
u'latR': u'0.853141372984',
u'list1': u'0',
u'list2': u'42',
u'list3': u'0',
u'list4': u'0',
u'list5': u'0',
u'list6': u'0',
u'lng': u'2.3365',
u'lngR': u'0.0407796179728',
u'login': u'@',
u'mailable': True,
u'mod_level': u'0',
u'music': u'Daft Punk - Bloody Beetrots - Kid Cudi - Jamiroquai<br>Red Hot - Ray Charles - BEP - Gorillaz - Birdy nam nam<br>Citizen Cope - Angus &amp; Julia Stone - Portishead<br>50cent - Sia - Ben Harper - Busta Rhymes<br>Musiques de gansta ! ET Debussy',
u'origins': u'1',
u'path': u'0/7/3/5/6/4/4/',
u'phone': u'-',
u'popu': {u'bonus': u'6',
u'contacts': u'1',
u'flashs': u'246',
u'id': u'14465370',
u'invits': u'0',
u'mails': u'39',
u'popu': u'11345',
u'visites': u'645'},
u'pseudo': u'Ruslana',
u'region': u'11',
u'sex': 1,
u'shape': u'1',
u'shard': 0,
u'size': u'170',
u'smoke': u'2',
u'style': u'4',
u'subregion': u'76',
u'table': u'adopteun.girls',
u'texts1': u'',
u'texts2': u'',
u'texts3': u'',
u'texts4': u'',
u'texts5': u'',
u'texts6': u'MON SOURIRE HAHAHA',
u'title': u'',
u'tvs': u'Dexter<br>OC<br>True blood<br>Envoy&eacute; sp&eacute;cial ;) - Arte !<br>',
u'url': u'/api.php?member/view/14465370/Ruslana',
u'visites': u'0',
u'w': u'',
u'warn': u'0',
u'weight': u'50',
u'zip': u'75008'},
u'token': u'e0247704012e01bc32756b357b010e5206ac9c76'}}
member.pictures
---------------
Parameters:
- id
Return value:
{u'errors': [],
u'result': {u'pictures': [{u'id': u'12363004',
u'rating': 4.4473684210500002,
u'url': u'http://s0.adopteunmec.com/0/6/0/1/6/image7.jpg'}],
u'token': u'e131f1b194f2a19337882398b10b79457a638252'}}
member.addBasket
----------------
Parameters:
- id
Errors:
- 5.1.1 : member does not exist
- 5.1.5 : already sent charm to this one
- 5.1.6 : no enough charms available
Return value:
{u'errors': u'0',
u'flashs': 4,
u'result': {u'token': u'55039d0557393bb7c5e4381792143d003f0e60c0'}}
SEARH Commands
==============
search.[default]
----------------
Return value:
{u'errors': [],
u'result': {u'qsearch': {u'ageMax': u'25',
u'ageMin': u'18',
u'dist': u'0',
u'new': u'0',
u'query': u'{"sex":1,"ageMin":"18","ageMax":"25","region":"fr","new":"0","dist":"0"}',
u'region': u'fr',
u'sex': 1},
u'search': {u'ageMax': u'27',
u'ageMin': u'20',
u'checks1': u'0',
u'checks2': u'0',
u'country': u'fr',
u'dist': u'50',
u'drink': u'0',
u'eyes': u'0',
u'food': u'0',
u'hair_color': u'0',
u'hair_size': u'0',
u'origins': u'0',
u'pseudo': u'',
u'query': u'{"ageMin":"20","ageMax":"27","country":"fr","region":"11","subregion":"0","dist":"50","pseudo":"","sex":"1","sizeMin":"0","sizeMax":"0","weightMin":"0","weightMax":"75","shape":"0","hair_size":"0","hair_color":"0","eyes":"0","origins":"0","style":"0","checks1":"0","checks2":"0","smoke":"0","drink":"0","food":"0","search":"true"}',
u'region': u'11',
u'search': u'true',
u'sex': u'1',
u'shape': u'0',
u'sizeMax': u'0',
u'sizeMin': u'0',
u'smoke': u'0',
u'style': u'0',
u'subregion': u'0',
u'weightMax': u'75',
u'weightMin': u'0'},
u'token': u'3196a3365d927f2ee8738ec8dfc4a5abd75e3ee3'}}
search.quick
------------
Parameters:
- sex (int[0,1])
- ageMin (int)
- ageMax (int)
- region (str)
- new (int)
- dist (int)
Return Value:
{u'errors': [],
u'result': {u'regions': {u'be': None,
u'be_24': u' - wallonie',
u'be_25': u' - bruxelles capitale',
u'be_26': u' - flandre',
u'ca': None,
u'ca_34': u' - canada',
u'ca_35': u' - quebec',
u'ch': None,
u'ch_27': u' - r\xe9gion l\xe9manique',
u'ch_28': u' - espace Mittelland',
u'ch_29': u' - suisse du nord-ouest',
u'ch_30': u' - zurich',
u'ch_31': u' - suisse orientale',
u'ch_32': u' - suisse centrale',
u'ch_33': u' - tessin',
u'fr': None,
u'fr_1': u' - alsace',
u'fr_10': u' - haute-normandie',
u'fr_11': u' - ile-de-france',
u'fr_12': u' - languedoc-roussillon',
u'fr_13': u' - limousin',
u'fr_14': u' - lorraine',
u'fr_15': u' - midi-pyr\xe9n\xe9es',
u'fr_16': u' - nord-pas-de-calais',
u'fr_17': u' - paca',
u'fr_18': u' - pays de la loire',
u'fr_19': u' - picardie',
u'fr_2': u' - aquitaine',
u'fr_20': u' - poitou-charentes',
u'fr_21': u' - rh\xf4ne-alpes',
u'fr_22': u' - corse',
u'fr_23': u' - dom+tom',
u'fr_3': u' - auvergne',
u'fr_4': u' - basse-normandie',
u'fr_5': u' - bourgogne',
u'fr_6': u' - bretagne',
u'fr_7': u' - centre',
u'fr_8': u' - champagne-ardenne',
u'fr_9': u' - franche-comt\xe9'},
u'search': [{u'alert': u'2',
u'birthday': u'1985-11-21',
u'city': u'Boulogne-Billancourt',
u'country': u'fr',
u'cover': u'12',
u'id': u'14252744',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-24 15:19:48',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'4/4/7/2/5/2/4/',
u'pseudo': u'Birdy',
u'region': u'11',
u'sex': 1,
u'shard': 4,
u'style': u'5',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/14252744/Birdy',
u'zip': u'92100'}],
u'token': u'ef78ac6d812ab15f2f8efd578f4da4ef2e23aa71'}}
search.advanced
---------------
Parameters:
- ageMin (int)
- ageMax (int)
- country (str)
- region (int)
- subregion (int)
- dist (int)
- pseudo (str)
- sex (int[0,1])
- sizeMin (int)
- sizeMax (int)
- weightMin (int)
- weightMax (int)
- shape (int)
- hair_size (int)
- hair_color (int)
- eyes (int)
- origins (int)
- style (int)
- checks1 (int)
- checks2 (int)
- smoke (int)
- drink (int)
- food (int)
- search (bool)
Return Value:
{u'errors': [],
u'regions': {u'be': None,
u'be_24': u' - wallonie',
u'be_25': u' - bruxelles capitale',
u'be_26': u' - flandre',
u'ca': None,
u'ca_34': u' - canada',
u'ca_35': u' - quebec',
u'ch': None,
u'ch_27': u' - r\xe9gion l\xe9manique',
u'ch_28': u' - espace Mittelland',
u'ch_29': u' - suisse du nord-ouest',
u'ch_30': u' - zurich',
u'ch_31': u' - suisse orientale',
u'ch_32': u' - suisse centrale',
u'ch_33': u' - tessin',
u'fr': None,
u'fr_1': u' - alsace',
u'fr_10': u' - haute-normandie',
u'fr_11': u' - ile-de-france',
u'fr_12': u' - languedoc-roussillon',
u'fr_13': u' - limousin',
u'fr_14': u' - lorraine',
u'fr_15': u' - midi-pyr\xe9n\xe9es',
u'fr_16': u' - nord-pas-de-calais',
u'fr_17': u' - paca',
u'fr_18': u' - pays de la loire',
u'fr_19': u' - picardie',
u'fr_2': u' - aquitaine',
u'fr_20': u' - poitou-charentes',
u'fr_21': u' - rh\xf4ne-alpes',
u'fr_22': u' - corse',
u'fr_23': u' - dom+tom',
u'fr_3': u' - auvergne',
u'fr_4': u' - basse-normandie',
u'fr_5': u' - bourgogne',
u'fr_6': u' - bretagne',
u'fr_7': u' - centre',
u'fr_8': u' - champagne-ardenne',
u'fr_9': u' - franche-comt\xe9'},
u'result': {u'search': [{u'alert': u'1',
u'birthday': u'1988-04-07',
u'city': u'Dammartin',
u'country': u'fr',
u'cover': u'25',
u'id': u'13579115',
u'isBan': False,
u'isOnline': True,
u'last_cnx': u'2011-09-24 15:17:22',
u'list5': u'0',
u'login': u'@',
u'mod_level': u'0',
u'path': u'5/1/1/9/7/5/3/',
u'pseudo': u"S\xe9 's\xe9",
u'region': u'11',
u'sex': 1,
u'shard': 5,
u'style': u'1',
u'table': u'adopteun.girls',
u'url': u'/api.php?member/view/13579115/Ss',
u'zip': u'77230'}],
u'token': u'f92aede46118dcdba6d484146b4627777fbe7188'}}

24
modules/aum/__init__.py Normal file
View file

@ -0,0 +1,24 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from .browser import AuMBrowser
from .backend import AuMBackend
__all__ = ['AuMBrowser', 'AuMBackend']

135
modules/aum/antispam.py Normal file
View file

@ -0,0 +1,135 @@
# -*- 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 <http://www.gnu.org/licenses/>.
import re
__all__ = ['AntiSpam']
class AntiSpam(object):
def check_thread(self, thread):
resume = thread['title']
# Check if there is an email address in the offer.
if re.match('^[\w\d\.\-_]+@[\w\d\.]+ vous offre la pos', resume):
return False
if thread['member']['pseudo'] == 'Ekaterina':
return False
return True
def check_profile(self, profile):
# The name of profile is in form #123456789
if profile['pseudo'] == '':
return False
if profile['about1'].startswith('salut! je te donne mon msn'):
return False
if profile['about2'].startswith('cam to cam'):
return False
if profile['about2'].startswith('je suis une femme tres tres belle et je recherche un homme qui aime le sexe'):
return False
if profile['about2'].endswith('mmmmmmmmmmmmmmmm'):
return False
return True
# ipaddr is not available anymore.
for ipaddr in (profile['last_ip'], profile['first_ip']):
if ipaddr.startswith('41.202.'):
return False
if ipaddr.startswith('41.250.'):
return False
if ipaddr.startswith('41.251.'):
return False
if ipaddr.startswith('41.141.'):
return False
if ipaddr.startswith('194.177.'):
return False
if ipaddr.startswith('41.85.'):
return False
if ipaddr.startswith('41.86.'):
return False
if ipaddr.startswith('196.47.'):
return False
if re.match('105\.13\d.*', ipaddr):
return False
if ipaddr in ('62.157.186.18', '198.36.222.8', '212.234.67.61', '203.193.158.210', '41.189.34.180', '41.66.12.36', '196.47.137.21', '213.136.125.122', '41.191.87.188'):
return False
return True
def check_contact(self, contact):
if contact.id == 1:
return True
if not self.check_profile(contact.aum_profile):
return False
return True
# ipaddr is not available anymore.
first_ip = contact.profile['info']['IPaddr'].value.split(' ')[0]
last_ip = contact.profile['info']['IPaddr'].value.rstrip(')')
for ipaddr in (first_ip, last_ip):
if ipaddr.endswith('.afnet.net'):
return False
if ipaddr.endswith('.iam.net.ma'):
return False
if ipaddr.endswith('.amsterdam.ananoos.net'):
return False
if ipaddr.endswith('.tedata.net'):
return False
if ipaddr.endswith('kupo.fr'):
return False
if ipaddr.endswith('.static.virginmedia.com'):
return False
if ipaddr.endswith('frozenway.com'):
return False
if ipaddr.endswith('.rev.bgtn.net'):
return False
if ipaddr.endswith('real-vpn.com'):
return False
if ipaddr.endswith('.nl.ipodah.net'):
return False
if ipaddr.endswith('.wanamaroc.com'):
return False
if ipaddr.endswith('.ukservers.com'):
return False
if ipaddr.endswith('.startdedicated.com'):
return False
if ipaddr.endswith('.clients.your-server.de'):
return False
if ipaddr.endswith('.cba.embratel.net.br'):
return False
if ipaddr.endswith('.idstelcom.com'):
return False
if ipaddr.endswith('proxy.chg-support.com'):
return False
if ipaddr.endswith('.sprintsvc.net'):
return False
if ipaddr.endswith('.relakks.com'):
return False
return True
def check_mail(self, mail):
# Spambot with a long first-message.
if mail['message'].find('Je veux que vous m\'ayez ecrit directement sur le mon e-mail') >= 0:
return False
if mail['message'].find('ilusa12010@live.fr') >= 0:
return False
return True

555
modules/aum/backend.py Normal file
View file

@ -0,0 +1,555 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import email
import time
import re
import datetime
from html2text import unescape
from dateutil import tz
from dateutil.parser import parse as _parse_dt
from weboob.capabilities.base import NotLoaded
from weboob.capabilities.chat import ICapChat
from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Message, Thread
from weboob.capabilities.dating import ICapDating, OptimizationNotFound, Event
from weboob.capabilities.contact import ICapContact, ContactPhoto, Query, QueryError
from weboob.capabilities.account import ICapAccount, StatusField
from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.browser import BrowserUnavailable
from weboob.tools.value import Value, ValuesDict, ValueBool, ValueBackendPassword
from weboob.tools.log import getLogger
from weboob.tools.misc import local2utc
from .contact import Contact
from .captcha import CaptchaError
from .antispam import AntiSpam
from .browser import AuMBrowser
from .optim.profiles_walker import ProfilesWalker
from .optim.visibility import Visibility
from .optim.queries_queue import QueriesQueue
__all__ = ['AuMBackend']
def parse_dt(s):
d = _parse_dt(s)
return local2utc(d)
class AuMBackend(BaseBackend, ICapMessages, ICapMessagesPost, ICapDating, ICapChat, ICapContact, ICapAccount):
NAME = 'aum'
MAINTAINER = 'Romain Bignon'
EMAIL = 'romain@weboob.org'
VERSION = '0.a'
LICENSE = 'AGPLv3+'
DESCRIPTION = u"“Adopte un mec” french dating website"
CONFIG = BackendConfig(Value('username', label='Username'),
ValueBackendPassword('password', label='Password'),
ValueBool('antispam', label='Enable anti-spam', default=False),
ValueBool('baskets', label='Get baskets with new messages', default=True))
STORAGE = {'profiles_walker': {'viewed': []},
'queries_queue': {'queue': []},
'sluts': {},
'notes': {},
}
BROWSER = AuMBrowser
MAGIC_ID_BASKET = 1
def __init__(self, *args, **kwargs):
BaseBackend.__init__(self, *args, **kwargs)
if self.config['antispam'].get():
self.antispam = AntiSpam()
else:
self.antispam = None
def create_default_browser(self):
return self.create_browser(self.config['username'].get(), self.config['password'].get())
def report_spam(self, id):
with self.browser:
self.browser.delete_thread(id)
# Do not report fakes to website, to let them to other guys :)
#self.browser.report_fake(id)
# ---- ICapDating methods ---------------------
def init_optimizations(self):
self.add_optimization('PROFILE_WALKER', ProfilesWalker(self.weboob.scheduler, self.storage, self.browser))
self.add_optimization('VISIBILITY', Visibility(self.weboob.scheduler, self.browser))
self.add_optimization('QUERIES_QUEUE', QueriesQueue(self.weboob.scheduler, self.storage, self.browser))
def iter_events(self):
all_events = {}
with self.browser:
all_events['baskets'] = (self.browser.get_baskets, 'You were put into %s\'s basket')
all_events['flashs'] = (self.browser.get_flashs, 'You sent a charm to %s')
all_events['visits'] = (self.browser.get_visits, 'Visited by %s')
for type, (events, message) in all_events.iteritems():
for event in events():
try:
e = Event(event['%sid' % type[0]])
except KeyError:
e = Event(event['id'])
e.date = parse_dt(event['date'])
e.type = type
if 'member' in event:
e.contact = self._get_partial_contact(event['member'])
else:
e.contact = self._get_partial_contact(event)
if not e.contact:
continue
e.message = message % e.contact.name
yield e
# ---- ICapMessages 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:
if thread['member'].get('isBan', thread['member'].get('dead', False)):
with self.browser:
self.browser.delete_thread(thread['member']['id'])
continue
if self.antispam and not self.antispam.check_thread(thread):
self.logger.info('Skipped a spam-thread from %s' % thread['pseudo'])
self.report_spam(thread['member']['id'])
continue
t = Thread(int(thread['member']['id']))
t.flags = Thread.IS_DISCUSSION
t.title = 'Discussion with %s' % thread['member']['pseudo']
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:
thread = Thread(int(id))
thread.flags = Thread.IS_DISCUSSION
full = False
else:
full = True
with self.browser:
mails = self.browser.get_thread_mails(id, 100)
my_name = self.browser.get_my_name()
child = None
msg = None
slut = self._get_slut(id)
if contacts is None:
contacts = {}
if not thread.title:
thread.title = u'Discussion with %s' % mails['member']['pseudo']
self.storage.set('sluts', thread.id, 'status', mails['status'])
self.storage.save()
for mail in mails['messages']:
flags = 0
if self.antispam and not self.antispam.check_mail(mail):
self.logger.info('Skipped a spam-mail from %s' % mails['member']['pseudo'])
self.report_spam(thread.id)
break
if parse_dt(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'])
if self.antispam and not self.antispam.check_contact(contacts[mail['id_from']]):
self.logger.info('Skipped a spam-mail-profile from %s' % mails['member']['pseudo'])
self.report_spam(thread.id)
break
if int(mail['id_from']) == self.browser.my_id:
if int(mails['remoteStatus']) == 0 and msg is None:
flags |= Message.IS_NOT_ACCUSED
else:
flags |= Message.IS_ACCUSED
signature = u''
if mail.get('src', None):
signature += u'Sent from my %s\n\n' % mail['src']
if mail['id_from'] in contacts:
signature += contacts[mail['id_from']].get_text()
msg = Message(thread=thread,
id=int(time.strftime('%Y%m%d%H%M%S', parse_dt(mail['date']).timetuple())),
title=thread.title,
sender=my_name if int(mail['id_from']) == self.browser.my_id else mails['member']['pseudo'],
receivers=[my_name if int(mail['id_from']) != self.browser.my_id else mails['member']['pseudo']],
date=parse_dt(mail['date']),
content=unescape(mail['message']).strip(),
signature=signature,
children=[],
flags=flags)
if child:
msg.children.append(child)
child.parent = msg
child = msg
if full and msg:
# If we have get all the messages, replace NotLoaded with None as
# parent.
msg.parent = None
if not full and not msg:
# Perhaps there are hidden messages
msg = NotLoaded
thread.root = msg
return thread
def iter_unread_messages(self, thread=None):
try:
contacts = {}
with self.browser:
threads = self.browser.get_threads_list()
for thread in threads:
if thread['member'].get('isBan', thread['member'].get('dead', False)):
with self.browser:
self.browser.delete_thread(int(thread['member']['id']))
continue
if self.antispam and not self.antispam.check_thread(thread):
self.logger.info('Skipped a spam-unread-thread from %s' % thread['member']['pseudo'])
self.report_spam(thread['member']['id'])
continue
slut = self._get_slut(thread['member']['id'])
if parse_dt(thread['date']) > slut['lastmsg'] or int(thread['status']) != int(slut['status']):
t = self.get_thread(thread['member']['id'], contacts, get_profiles=True)
for m in t.iter_all_messages():
if m.flags & m.IS_UNREAD:
yield m
if not self.config['baskets'].get():
return
# Send mail when someone added me in her basket.
# XXX possibly race condition if a slut adds me in her basket
# between the aum.nb_new_baskets() and aum.get_baskets().
with self.browser:
slut = self._get_slut(-self.MAGIC_ID_BASKET)
new_baskets = self.browser.nb_new_baskets()
if new_baskets > 0:
baskets = self.browser.get_baskets()
my_name = self.browser.get_my_name()
for basket in baskets:
if basket['isBan'] or parse_dt(basket['date']) <= slut['lastmsg']:
continue
contact = self.get_contact(basket['id'])
if self.antispam and not self.antispam.check_contact(contact):
self.logger.info('Skipped a spam-basket from %s' % contact.name)
self.report_spam(basket['id'])
continue
thread = Thread(int(basket['id']))
thread.title = 'Basket of %s' % contact.name
thread.root = Message(thread=thread,
id=self.MAGIC_ID_BASKET,
title=thread.title,
sender=contact.name,
receivers=[my_name],
date=parse_dt(basket['date']),
content='You are taken in her basket!',
signature=contact.get_text(),
children=[],
flags=Message.IS_UNREAD)
yield thread.root
except BrowserUnavailable, e:
self.logger.debug('No messages, browser is unavailable: %s' % e)
pass # don't care about waiting
def set_message_read(self, message):
if message.id == self.MAGIC_ID_BASKET:
# Save the last baskets checks.
slut = self._get_slut(-self.MAGIC_ID_BASKET)
if slut['lastmsg'] < message.date:
slut['lastmsg'] = message.date
self.storage.set('sluts', -self.MAGIC_ID_BASKET, slut)
self.storage.save()
return
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):
id = int(id)
sluts = self.storage.get('sluts')
if not sluts or not id in sluts:
slut = {'lastmsg': datetime.datetime(1970,1,1),
'status': 0}
else:
slut = self.storage.get('sluts', id)
slut['lastmsg'] = slut.get('lastmsg', datetime.datetime(1970,1,1)).replace(tzinfo=tz.tzutc())
slut['status'] = int(slut.get('status', 0))
return slut
# ---- ICapMessagesPost methods ---------------------
def post_message(self, message):
with self.browser:
self.browser.post_mail(message.thread.id, message.content)
# ---- ICapContact 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['pseudo']
else:
contact = Contact(_id, profile['pseudo'], Contact.STATUS_ONLINE)
contact.url = self.browser.id2url(_id)
contact.parse_profile(profile, self.browser.get_consts())
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['pseudo'], 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(count=100)
for thread in threads:
c = self._get_partial_contact(thread['member'])
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 charm has been sent')
else:
return Query(id, 'Unable to send charm: it has been enqueued')
else:
with self.browser:
if not self.browser.send_charm(id):
raise QueryError('No enough charms available')
return Query(id, 'A charm has been sent')
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()
# ---- ICapChat methods ---------------------
def iter_chat_messages(self, _id=None):
with self.browser:
return self.browser.iter_chat_messages(_id)
def send_chat_message(self, _id, message):
with self.browser:
return self.browser.send_chat_message(_id, message)
#def start_chat_polling(self):
#self._profile_walker = ProfilesWalker(self.weboob.scheduler, self.storage, self.browser)
# ---- ICapAccount methods ---------------------
ACCOUNT_REGISTER_PROPERTIES = ValuesDict(
Value('username', label='Email address', regexp='^[^ ]+@[^ ]+\.[^ ]+$'),
Value('password', label='Password', regexp='^[^ ]+$', masked=True),
Value('sex', label='Sex', choices={'m': 'Male', 'f': 'Female'}),
Value('birthday', label='Birthday (dd/mm/yyyy)', regexp='^\d+/\d+/\d+$'),
Value('zipcode', label='Zipcode'),
Value('country', label='Country', choices={'fr': 'France', 'be': 'Belgique', 'ch': 'Suisse', 'ca': 'Canada'}, default='fr'),
Value('godfather',label='Godfather', regexp='^\d*$', default=''),
)
@classmethod
def register_account(klass, account):
"""
Register an account on website
This is a static method, it would be called even if the backend is
instancied.
@param account an Account object which describe the account to create
"""
browser = None
bday, bmonth, byear = account.properties['birthday'].get().split('/', 2)
while not browser:
try:
browser = klass.BROWSER(account.properties['username'].get())
browser.register(password= account.properties['password'].get(),
sex= (0 if account.properties['sex'].get() == 'm' else 1),
birthday_d= int(bday),
birthday_m= int(bmonth),
birthday_y= int(byear),
zipcode= account.properties['zipcode'].get(),
country= account.properties['country'].get(),
godfather= account.properties['godfather'].get())
except CaptchaError:
getLogger('aum').info('Unable to resolve captcha. Retrying...')
browser = None
REGISTER_REGEXP = re.compile('.*http://www.adopteunmec.com/register4.php\?([^\' ]*)\'')
def confirm_account(self, mail):
msg = email.message_from_string(mail)
content = u''
for part in msg.walk():
s = part.get_payload(decode=True)
content += unicode(s, 'iso-8859-15')
url = None
for s in content.split():
m = self.REGISTER_REGEXP.match(s)
if m:
url = '/register4.php?' + m.group(1)
break
if url:
browser = self.create_browser('')
browser.openurl(url)
return True
return False
def get_account(self):
"""
Get the current account.
"""
raise NotImplementedError()
def update_account(self, account):
"""
Update the current account.
"""
raise NotImplementedError()
def get_account_status(self):
with self.browser:
return (
StatusField('myname', 'My name', self.browser.get_my_name()),
StatusField('score', 'Score', self.browser.score()),
StatusField('avcharms', 'Available charms', self.browser.nb_available_charms()),
StatusField('godchilds', 'Number of godchilds', self.browser.nb_godchilds()),
)
OBJECTS = {Thread: fill_thread,
Contact: fill_contact,
ContactPhoto: fill_photo
}

419
modules/aum/browser.py Normal file
View file

@ -0,0 +1,419 @@
# -*- coding: utf-8 -*-
# Copyright(C) 2008-2011 Romain Bignon, Christophe Benz
#
# 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 <http://www.gnu.org/licenses/>.
import math
import re
import datetime
import random
import urllib
from htmlentitydefs import codepoint2name
try:
import json
except ImportError:
import simplejson as json
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword, BrowserUnavailable, BrowserHTTPNotFound
from weboob.capabilities.chat import ChatException, ChatMessage
from weboob.capabilities.messages import CantSendMessage
__all__ = ['AuMBrowser']
class AuMException(Exception):
ERRORS = {"0.0.0": "Bad signature",
"0.0.1": "Malformed request",
"0.0.2": "Not logged",
"1.1.1": "No member has this login",
"1.1.2": "Password don't match",
"1.1.3": "User has been banned",
"1.12.1": "Invalid country",
"1.12.1": "Invalid region",
"4.0.1": "Member not found",
"4.1.1": "Thread doesn't exist",
"4.1.2": "Cannot write to this member",
"5.1.1": "Member tergeted doesn't exist",
"5.1.2": "Sex member targeted is not the opposite of the member logged",
"5.1.3": "Not possible to send a charm",
"5.1.4": "Not possible to send a charm because the 5 charms has been already used",
"5.1.5": "Not possible because the guy has already send a charm to this girl",
"5.1.6": "No more money",
"5.1.7": "Not possible to add to basket",
"5.2.1": "Member doesn't exist",
"5.3.1": "Member doesn't exist",
}
def __init__(self, code):
Exception.__init__(self, self.ERRORS.get(code, code))
self.code = code
class AuMBrowser(BaseBrowser):
DOMAIN = 'api.adopteunmec.com'
APIKEY = 'fb0123456789abcd'
consts = None
search_query = None
my_sex = 0
my_id = 0
my_name = u''
my_coords = (0,0)
def id2url(self, id):
return 'http://www.adopteunmec.com/index.php/profile/%s' % id
def url2id(func):
def inner(self, id, *args, **kwargs):
m = re.match('^http://.*adopteunmec.com.*/(\d+)$', str(id))
if m:
id = int(m.group(1))
else:
m = re.match('^http://.*adopteunmec.com/index.php/profile/(\d+).*', str(id))
if m:
id = int(m.group(1))
return func(self, id, *args, **kwargs)
return inner
def api_request(self, command, action, parameter='', data=None, nologin=False):
if data is None:
# Always do POST requests.
data = ''
elif isinstance(data, (list,tuple,dict)):
data = urllib.urlencode(data)
elif isinstance(data, unicode):
data = data.encode('utf-8')
url = self.buildurl(self.absurl('/api.php'), S=self.APIKEY,
C=command,
A=action,
P=parameter,
O='json')
buf = self.openurl(url, data)
try:
r = json.load(buf)
except ValueError:
buf.seek(0)
raise ValueError(buf.read())
if 'errors' in r and r['errors'] != '0' and len(r['errors']) > 0:
code = r['errors'][0]
if code in (u'0.0.2', u'1.1.1', u'1.1.2'):
if not nologin:
self.login()
return self.api_request(command, action, parameter, data, nologin=True)
else:
raise BrowserIncorrectPassword(AuMException.ERRORS[code])
else:
raise AuMException(code)
return r
def login(self):
r = self.api_request('me', 'login', data={'login': self.username,
'pass': self.password,
}, nologin=True)
self.my_sex = r['result']['me']['sex']
self.my_id = int(r['result']['me']['id'])
self.my_name = r['result']['me']['pseudo']
self.my_coords = (float(r['result']['me']['lat']), float(r['result']['me']['lng']))
return r
#def register(self, password, sex, birthday_d, birthday_m, birthday_y, zipcode, country, godfather=None):
# if not self.is_on_page(RegisterPage):
# self.location('http://www.adopteunmec.com/register2.php')
# self.page.register(password, sex, birthday_d, birthday_m, birthday_y, zipcode, country)
# if godfather:
# if not self.is_on_page(AccountPage):
# self.location('http://www.adopteunmec.com/account.php')
# self.page.set_godfather(godfather)
#@pageaccess
#def add_photo(self, name, f):
# if not self.is_on_page(EditPhotoPage):
# self.location('/edit.php?type=1')
# return self.page.add_photo(name, f)
#@pageaccess
#def set_nickname(self, nickname):
# if not self.is_on_page(EditAnnouncePage):
# self.location('/edit.php?type=2')
# return self.page.set_nickname(nickname)
#@pageaccess
#def set_announce(self, title=None, description=None, lookingfor=None):
# if not self.is_on_page(EditAnnouncePage):
# self.location('/edit.php?type=2')
# return self.page.set_announce(title, description, lookingfor)
#@pageaccess
#def set_description(self, **args):
# if not self.is_on_page(EditDescriptionPage):
# self.location('/edit.php?type=3')
# return self.page.set_description(**args)
def check_login(func):
def inner(self, *args, **kwargs):
if self.my_id == 0:
self.login()
return func(self, *args, **kwargs)
return inner
def get_consts(self):
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'])
@check_login
def get_my_name(self):
return self.my_name
@check_login
def get_my_id(self):
return self.my_id
@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):
r = self.api_request('me', 'visits')
return r['result']['news'] + r['result']['olds']
@check_login
def get_threads_list(self, count=30):
r = self.api_request('message', '[default]', '%d,0' % count)
return r['result']['threads']
@check_login
@url2id
def get_thread_mails(self, id, count=30):
r = self.api_request('message', 'thread', data={'memberId': id, 'count': count})
return r['result']['thread']
@check_login
@url2id
def post_mail(self, id, content):
new_content = u''
for c in content:
try:
new_content += '&%s;' % codepoint2name[ord(c)]
except KeyError:
new_content += c
content = new_content.replace('\n', '\r\n').encode('Windows-1252', 'replace')
try:
self.api_request('message', 'new', data={'memberId': id, 'message': content})
except AuMException, e:
raise CantSendMessage(unicode(e))
@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
@url2id
def add_basket(self, id):
try:
self.api_request('member', 'addBasket', data={'id': id})
except AuMException:
return False
else:
return True
@url2id
def deblock(self, id):
self.readurl('http://www.adopteunmec.com/fajax_postMessage.php?action=deblock&to=%s' % id)
return True
@url2id
def report_fake(self, id):
return self.readurl('http://www.adopteunmec.com/fake.php', 'id=%s' % id)
@url2id
def rate(self, id, what, rating):
result = self.openurl('http://www.adopteunmec.com/fajax_vote.php', 'member=%s&what=%s&rating=%s' % (id, what, rating)).read()
return float(result)
def search_profiles(self, **kwargs):
if self.search_query is None:
r = self.api_request('searchs', '[default]')
self.search_query = r['result']['search']['query']
params = {}
for key, value in json.loads(self.search_query).iteritems():
if isinstance(value, dict):
for k, v in value.iteritems():
params['%s%s' % (key, k.capitalize())] = v
else:
params[key] = value or ''
r = self.api_request('searchs', 'advanced', '30,0', params)
ids = [s['id'] for s in r['result']['search']]
return set(ids)
@url2id
def get_profile(self, id, with_pics=True):
r = self.api_request('member', 'view', data={'id': id})
if not 'result' in r:
print r
profile = r['result']['member']
# Calculate distance in km.
profile['dist'] = 0.0
if 'lat' in profile and 'lng' in profile:
coords = (float(profile['lat']), float(profile['lng']))
R = 6371
lat1 = math.radians(self.my_coords[0])
lat2 = math.radians(coords[0])
lon1 = math.radians(self.my_coords[1])
lon2 = math.radians(coords[1])
dLat = lat2 - lat1
dLong = lon2 - lon1
a= pow(math.sin(dLat/2), 2) + math.cos(lat1) * math.cos(lat2) * pow(math.sin(dLong/2), 2)
c= 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
profile['dist'] = R * c
if with_pics:
r = self.api_request('member', 'pictures', data={'id': id})
profile['pictures'] = []
for pic in r['result']['pictures']:
d = {'hidden': False}
d.update(pic)
profile['pictures'].append(d)
base_url = 'http://s%s.adopteunmec.com/%s' % (profile['shard'], profile['path'])
if len(profile['pictures']) > 0:
pic_regex = re.compile('(?P<base_url>http://.+\.adopteunmec\.com/.+/)image(?P<id>.+)\.jpg')
pic_max_id = max((int((lambda m: m and m.groupdict()['id'] or 0)(pic_regex.match(pic['url'])))) for pic in profile['pictures'])
for id in xrange(1, pic_max_id + 1):
url = u'%simage%s.jpg' % (base_url, id)
if not url in [pic['url'] for pic in profile['pictures']]:
profile['pictures'].append({'url': url, u'hidden': True, 'id': u'0', 'rating': 0.0})
else:
url = '%simage1.jpg' % base_url
try:
self.openurl(url)
except BrowserHTTPNotFound:
pass
else:
profile['pictures'].append({'url': url, u'hidden': True, 'id': u'0', 'rating': 0.0})
return profile
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

204
modules/aum/captcha.py Normal file
View file

@ -0,0 +1,204 @@
# -*- 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 <http://www.gnu.org/licenses/>.
import hashlib
import sys
import Image
class CaptchaError(Exception): pass
class Tile:
hash = {
'bc8d52d96058478a6def26226145d53b': 'A',
'c62ecdfddb72b2feaed96cd9fe7c2802': 'A',
'8b61cda8a3240d8fa5f2610424271300': 'AD',
'f5dc63d37c7ea3375d86180f0ae62d05': 'AE',
'fd562be230da7f767f4454148632201d': 'AF',
'1860de576d8b0d1d87edc9dcb0b2a64c': 'AG',
'53afa108d36186e6bd23631711ec3d8c': 'AJ',
'6f2f9a1082a9230272c45117320f159d': 'AL',
'e14249a774d24bacc6e2bcadd7f3df65': 'AM',
'389330dbf3d85dea2dc40c6f9cf77d52': 'AN',
'17526a3c2261b55f9cd237c4aa195099': 'AQ',
'7e4820a9cc6c83a9fa60ff73ceb52157': 'AW',
'90690d1209753a2bcfeafa890082a585': 'B',
'2cf22e9ceace03a5f8ed3999e92d877e': 'C',
'a1d0bf1a29600a82a6aa2b8b21651b0f': 'D',
'9bb6909d647a0be3b2e7352d37374228': 'E',
'38120c8346f16cd07a9194283787ee5e': 'F',
'd41ff948fbc50a628c858b8e3e9e931c': 'G',
'4cc9322d3361eb3f9fea7fc83579e40f': 'H',
'837cd0f04e2d47ca6975745bdd0da640': 'I',
'da0204fa51b38414051376cc1c27ba72': 'J',
'199b1a9f9e1df1c2eddadcc4582957d7': 'JW',
'5e8d3d5bd5f683d84b089f2cecc1e196': 'JX',
'bc1fcf3546057d40d2db5454caacb3a5': 'JZ',
'c2f5866ba3bf799ece8b202492d199bf': 'K',
'7abe4091e11921afe6dac16509999010': 'KT',
'281ef08e623184e5621a73b9ccec7c9a': 'KX',
'b28e3fc06411de2ac7f53569bc3b42db': 'L',
'd58a6c26649926f1145fb4b7b42d0554': 'LT',
'4add630c6d124899fef814211975e344': 'M',
'9740cefe1629d6bc149a72d5f2a4586d': 'N',
'396f816f7e78e5c98de6404f8c4bd2ee': 'O',
'31ae7c9536b6c6a96e30a77b70e4b2fd': 'P',
'98ad9b1c32c05e6efc06637a166e4c42': 'PA',
'a05cce33683025fb2c6708ee06f6028e': 'Q',
'2852f51e8939bf9664fe064f7dacf310': 'R',
'3798513fe87e786faa67552a140fd86f': 'S',
'350b13811e34eeb63e3d7fb4b5eade5b': 'T',
'a01b186cbc767e17d948ed04eff114a1': 'U',
'8405f4d80ce80c4e6e9680fcfac4fe40': 'V',
'17ed80e9cb9a585098ae6a55d8d1f5c0': 'W',
'ae54ca77be5561330781a08dfbaff7a7': 'W',
'bbded6a2ba5f521bba276bb843bf4c98': 'WXT',
'ea662dd25fc528b84b832ce71ae3de61': 'WZ',
'4eb23916138e7c01714431dbecfe8b96': 'X',
'c02093d35d852339ff34f2b26873bf5a': 'XW',
'65744e0c6ce0c56d04873dfd732533a7': 'Y',
'315fb7dba7032004bd362cf0bb076733': 'YA',
'ce12a68a4f15657bc5297a6cf698bc0a': 'YAQ',
'275478ea2280351f7433a0606f962175': 'Z',
}
def __init__(self):
self.map = []
def append(self, pxls):
self.map.append(pxls)
def display(self):
print '-' * (len(self.map) * 2 + 2)
for y in xrange(len(self.map[0])):
sys.stdout.write('|')
for x in xrange(len(self.map)):
sys.stdout.write('%s' % ('XX' if self.map[x][y] else ' '))
print '|'
print '-' * (len(self.map) * 2 + 2)
def checksum(self):
s = ''
for pxls in self.map:
for pxl in pxls:
s += '%d' % (1 if pxl else 0)
return hashlib.md5(s).hexdigest()
@property
def letter(self):
checksum = self.checksum()
try:
return self.hash[checksum]
except KeyError:
print 'Unable te resolve:'
self.display()
print 'hash: %s' % checksum
raise CaptchaError()
class Captcha:
def __init__(self, f):
self.img = Image.open(f)
self.w, self.h = self.img.size
self.map = self.img.load()
self.tiles = []
tile = None
for x in xrange(self.w):
blank = True
pxls = []
for y in xrange(self.h):
pxls.append(self[x,y])
if self[x,y] != 0:
blank = False
if tile:
if blank:
tile = None
else:
tile.append(pxls)
elif not blank:
tile = Tile()
tile.append(pxls)
self.tiles.append(tile)
def __getitem__(self, (x, y)):
return self.map[x % self.w, y % self.h]
def __iter__(self):
for tile in self.tiles:
yield tile
@property
def text(self):
s = ''
for tile in self.tiles:
s += tile.letter
return s
class Decoder:
def __init__(self):
self.hash = {}
def process(self):
from aum.browser import AuMBrowser
browser = AuMBrowser('')
browser.openurl('/register2.php')
c = Captcha(browser.openurl('/captcha.php'))
for tile in c:
checksum = tile.checksum()
if checksum in self.hash:
print 'Skipping %s' % self.hash[checksum]
continue
tile.display()
print 'Checksum: %s' % checksum
ntry = 2
while ntry:
sys.stdout.write('Enter the letter: ')
l = sys.stdin.readline().strip()
ntry -= 1
if len(l) != 1:
print 'Error: please enter only one letter'
elif l in self.hash.itervalues():
print 'Warning! This letter has already been catched!'
else:
ntry = 0
self.hash[checksum] = l
def main(self):
try:
while 1:
self.process()
except KeyboardInterrupt:
print ''
print 'hash = {'
l = sorted(self.hash.iteritems(), key=lambda (k,v): (v,k))
for hash, value in l:
print ' \'%s\': %s' % (hash, value)
print '}'
if __name__ == '__main__':
d = Decoder()
d.main()

281
modules/aum/contact.py Normal file
View file

@ -0,0 +1,281 @@
# -*- coding: utf-8 -*-
# Copyright(C) 2008-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 <http://www.gnu.org/licenses/>.
import socket
from datetime import datetime
from dateutil.parser import parse as parse_dt
from weboob.tools.ordereddict import OrderedDict
from weboob.capabilities.contact import Contact as _Contact, ProfileNode
from weboob.tools.misc import html2text
class FieldBase:
def __init__(self, key, key2=None):
self.key = key
self.key2 = key2
def get_value(self, value, consts):
raise NotImplementedError
class FieldStr(FieldBase):
def get_value(self, profile, consts):
return html2text(unicode(profile[self.key])).strip()
class FieldBool(FieldBase):
def get_value(self, profile, consts):
return bool(int(profile[self.key]))
class FieldDist(FieldBase):
def get_value(self, profile, consts):
return '%.2f km' % float(profile[self.key])
class FieldIP(FieldBase):
def get_hostname(self, s):
try:
return socket.gethostbyaddr(s)[0]
except (socket.gaierror, socket.herror):
return s
def get_value(self, profile, consts):
s = self.get_hostname(profile[self.key])
if profile[self.key] != profile[self.key2]:
s += ' (first %s)' % self.get_hostname(profile[self.key2])
return s
class FieldProfileURL(FieldBase):
def get_value(self, profile, consts):
id = int(profile[self.key])
if id > 0:
return 'http://www.adopteunmec.com/index.php/profile/%d' % id
else:
return ''
class FieldPopu(FieldBase):
def get_value(self, profile, consts):
return unicode(profile['popu'][self.key])
class FieldPopuRatio(FieldBase):
def get_value(self, profile, consts):
v1 = float(profile['popu'][self.key])
v2 = float(profile['popu'][self.key2])
if v2 == 0.0:
return 'NaN'
else:
return '%.2f' % (v1 / v2)
class FieldOld(FieldBase):
def get_value(self, profile, consts):
birthday = parse_dt(profile[self.key])
return int((datetime.now() - birthday).days / 365.25)
class FieldSplit(FieldBase):
def get_value(self, profile, consts):
return [html2text(s).strip() for s in profile[self.key].split(self.key2) if len(s.strip()) > 0]
class FieldBMI(FieldBase):
def __init__(self, key, key2, fat=False):
FieldBase.__init__(self, key, key2)
self.fat = fat
def get_value(self, profile, consts):
height = int(profile[self.key])
weight = int(profile[self.key2])
if height == 0 or weight == 0:
return ''
bmi = (weight/float(pow(height/100.0, 2)))
if not self.fat:
return bmi
elif bmi < 15.5:
return 'severely underweight'
elif bmi < 18.4:
return 'underweight'
elif bmi < 24.9:
return 'normal'
elif bmi < 30:
return 'overweight'
else:
return 'obese'
class FieldFlags(FieldBase):
def get_value(self, profile, consts):
i = int(profile[self.key])
labels = []
for d in consts[self.key]:
if i & (1 << int(d['value'])):
labels.append(html2text(d['label']).strip())
return labels
class FieldList(FieldBase):
def get_value(self, profile, consts):
i = int(profile[self.key])
for d in consts[self.key]:
if i == int(d['value']):
return html2text(d['label']).strip()
return ''
class Contact(_Contact):
TABLE = OrderedDict((
('_info', OrderedDict((
('title', FieldStr('title')),
# ipaddr is not available anymore.
#('IPaddr', FieldIP('last_ip', 'first_ip')),
('admin', FieldBool('admin')),
('ban', FieldBool('isBan')),
('first', FieldStr('first_cnx')),
('godfather', FieldProfileURL('godfather')),
))),
('_stats', OrderedDict((
('mails', FieldPopu('mails')),
('baskets', FieldPopu('contacts')),
('charms', FieldPopu('flashs')),
('visites', FieldPopu('visites')),
('invits', FieldPopu('invits')),
('bonus', FieldPopu('bonus')),
('score', FieldPopu('popu')),
('ratio', FieldPopuRatio('mails', 'flashs')),
))),
('details', OrderedDict((
('old', FieldOld('birthday')),
('birthday', FieldStr('birthday')),
('zipcode', FieldStr('zip')),
('location', FieldStr('city')),
('distance', FieldDist('dist')),
('country', FieldStr('country')),
('phone', FieldStr('phone')),
('eyes', FieldList('eyes')),
('hair_color', FieldList('hair_color')),
('hair_size', FieldList('hair_size')),
('height', FieldList('size')),
('weight', FieldList('weight')),
('BMI', FieldBMI('size', 'weight')),
('fat', FieldBMI('size', 'weight', fat=True)),
('shape', FieldList('shape')),
('origins', FieldList('origins')),
('signs', FieldFlags('checks1')),
('job', FieldStr('job')),
('style', FieldList('style')),
('food', FieldList('food')),
('drink', FieldList('drink')),
('smoke', FieldList('smoke')),
))),
('tastes', OrderedDict((
('hobbies', FieldStr('hobbies')),
('music', FieldSplit('music', '<br>')),
('cinema', FieldSplit('cinema', '<br>')),
('books', FieldSplit('books', '<br>')),
('tv', FieldSplit('tvs', '<br>')),
))),
('sex', OrderedDict((
('underwear', FieldFlags('checks7')),
('practices', FieldFlags('checks5')),
('favorite', FieldFlags('checks3')),
('toys', FieldFlags('checks6')),
))),
('personality', OrderedDict((
('snap', FieldStr('texts1')),
('exciting', FieldStr('texts2')),
('hate', FieldStr('texts3')),
('vices', FieldStr('texts4')),
('assets', FieldStr('texts6')),
('fantasies', FieldStr('texts5')),
('is', FieldFlags('checks2')),
)))
))
def parse_profile(self, profile, consts):
cat = int(profile.get('cat', 3))
if cat == 1:
self.status = Contact.STATUS_ONLINE
self.status_msg = u'online'
self.status_msg = u'since %s' % profile['last_cnx']
elif cat == 2:
self.status = Contact.STATUS_AWAY
self.status_msg = u'away'
self.status_msg = u'connection at %s' % profile['last_cnx']
elif cat == 3:
self.status = Contact.STATUS_OFFLINE
self.status_msg = u'last connection %s' % profile['last_cnx']
self.summary = html2text(profile.get('about1', '')).strip().replace('\n\n', '\n')
if len(profile.get('about2', '')) > 0:
self.summary += u'\n\nLooking for:\n%s' % html2text(profile['about2']).strip().replace('\n\n', '\n')
for photo in profile['pictures']:
self.set_photo(photo['url'].split('/')[-1],
url=photo['url'],
thumbnail_url=photo['url'].replace('image', 'thumb1_'),
hidden=photo['hidden'])
self.profile = OrderedDict()
if 'sex' in profile:
for section, d in self.TABLE.iteritems():
flags = ProfileNode.SECTION
if section.startswith('_'):
flags |= ProfileNode.HEAD
section = section.lstrip('_')
s = ProfileNode(section, section.capitalize(), OrderedDict(), flags=flags)
for key, builder in d.iteritems():
value = builder.get_value(profile, consts[int(profile['sex'])])
s.value[key] = ProfileNode(key, key.capitalize().replace('_', ' '), value)
self.profile[section] = s
self.aum_profile = profile
def get_text(self):
def print_node(node, level=1):
result = u''
if node.flags & node.SECTION:
result += u'\t' * level + node.label + '\n'
for sub in node.value.itervalues():
result += print_node(sub, level+1)
else:
if isinstance(node.value, (tuple,list)):
value = ', '.join(unicode(v) for v in node.value)
elif isinstance(node.value, float):
value = '%.2f' % node.value
else:
value = node.value
result += u'\t' * level + u'%-20s %s\n' % (node.label + ':', value)
return result
result = u'Nickname: %s\n' % self.name
if self.status & Contact.STATUS_ONLINE:
s = 'online'
elif self.status & Contact.STATUS_OFFLINE:
s = 'offline'
elif self.status & Contact.STATUS_AWAY:
s = 'away'
else:
s = 'unknown'
result += u'Status: %s (%s)\n' % (s, self.status_msg)
result += u'URL: %s\n' % self.url
result += u'Photos:\n'
for name, photo in self.photos.iteritems():
result += u'\t%s%s\n' % (photo, ' (hidden)' if photo.hidden else '')
result += u'\nProfile:\n'
for head in self.profile.itervalues():
result += print_node(head)
result += u'Description:\n'
for s in self.summary.split('\n'):
result += u'\t%s\n' % s
return result

BIN
modules/aum/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

View file

@ -0,0 +1,185 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import random
from weboob.tools.browser import BrowserUnavailable, BrowserIncorrectPassword
from weboob.capabilities.dating import Optimization
from weboob.capabilities.account import AccountRegisterError
from weboob.tools.log import getLogger
from weboob.tools.value import Value, ValuesDict, ValueInt
from aum.captcha import CaptchaError
from aum.exceptions import AdopteWait, AdopteBanned
from aum.browser import AuMBrowser
__all__ = ['PriorityConnection']
class PriorityConnection(Optimization):
CONFIG = ValuesDict(ValueInt('minimal', label='Minimal of godchilds', default=5),
Value('domain', label='Domain to use for fake accounts emails', default='aum.example.com'),
ValueInt('interval', label='Interval of checks (seconds)', default=3600)
)
def __init__(self, sched, storage, browser):
self.sched = sched
self.storage = storage
self.browser = browser
self.logger = getLogger('priorityconn', browser.logger)
self.config = storage.get('priority_connection', 'config', default=None)
if self.config == {}:
self.config = None
self.check_cron = None
self.activity_cron = None
def start(self):
if self.config is None:
return False
self.check_cron = self.sched.repeat(int(self.config['interval']), self.check_godchilds)
self.activity_cron = self.sched.repeat(600, self.activity_fakes)
return True
def stop(self):
self.sched.cancel(self.check_cron)
self.check_cron = None
self.sched.cancel(self.activity_cron)
self.activity_cron = None
return True
def is_running(self):
return self.check_cron is not None
def set_config(self, params):
self.config = params
self.storage.set('priority_connection', 'config', self.config)
self.storage.save()
def get_config(self):
return self.config
def generate_name(self):
login = u''
for x in xrange(8):
if x % 2:
login += random.choice(u'aeiou')
else:
login += random.choice(u'bcdfghjklmnprstv')
fakes = self.storage.get('priority_connection', 'fakes')
while ('%s@%s' % (login, self.config['domain'])) in fakes.iterkeys():
login += '_'
return login
def generate_password(self):
return '%08x' % random.randint(1, int('ffffffff', 16))
def check_godchilds(self):
with self.browser:
try:
my_id = self.browser.get_my_id()
nb_godchilds = self.browser.nb_godchilds()
except AdopteWait:
nb_godchilds = 0
except BrowserUnavailable:
# We'll check later
return
missing_godchilds = int(self.config['minimal']) - nb_godchilds
self.logger.info('Missing godchilds: %s' % missing_godchilds)
if missing_godchilds <= 0:
return
for i in xrange(missing_godchilds):
registered = False
while not registered:
name = self.generate_name()
password = self.generate_password()
browser = AuMBrowser('%s@%s' % (name, self.config['domain']), proxy=self.browser.proxy)
try:
browser.register(password= password,
sex= 1, #slut
birthday_d= random.randint(1,28),
birthday_m= random.randint(1,12),
birthday_y= random.randint(1975, 1990),
zipcode= 75001,
country= 'fr',
godfather= my_id)
except AccountRegisterError, e:
self.logger.warning('Unable to register account: %s' % e)
except CaptchaError:
self.logger.warning('Unable to solve captcha... Retrying')
else:
registered = True
# set nickname
browser.set_nickname(name.strip('_').capitalize())
# rate my own profile with good score
for i in xrange(4):
browser.rate(my_id, i, 5.0)
# save fake in storage
fake = {'username': browser.username,
'password': password}
self.storage.set('priority_connection', 'fakes', name, fake)
self.storage.save()
self.logger.info('Fake account "%s" created (godfather=%s)' % (name, my_id))
def activity_fakes(self):
try:
fakes = self.storage.get('priority_connection', 'fakes', default={})
if len(fakes) == 0:
return
while 1:
name = random.choice(fakes.keys())
fake = fakes[name]
try:
browser = AuMBrowser(fake['username'], fake['password'], proxy=self.browser.proxy)
except (AdopteBanned,BrowserIncorrectPassword), e:
self.logger.warning('Fake %s can\'t login: %s' % (name, e))
continue
profiles = browser.search_profiles(country="fr",
dist='10',
save=True)
if not profiles:
continue
id = profiles.pop()
profile = browser.get_profile(id)
# bad rate
for i in xrange(4):
browser.rate(profile.get_id(), i, 0.6)
# deblock
browser.deblock(profile.get_id())
return
except BrowserUnavailable:
# don't care
pass

View file

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Romain Bignon, Christophe Benz
#
# 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 <http://www.gnu.org/licenses/>.
from __future__ import with_statement
from random import randint
from weboob.tools.browser import BrowserUnavailable
from weboob.capabilities.dating import Optimization
from weboob.tools.log import getLogger
__all__ = ['ProfilesWalker']
class ProfilesWalker(Optimization):
def __init__(self, sched, storage, browser):
self.sched = sched
self.storage = storage
self.browser = browser
self.logger = getLogger('walker', browser.logger)
self.walk_cron = None
self.view_cron = None
self.visited_profiles = set(storage.get('profiles_walker', 'viewed'))
self.logger.info(u'Loaded %d already visited profiles from storage.' % len(self.visited_profiles))
self.profiles_queue = set()
def save(self):
self.storage.set('profiles_walker', 'viewed', list(self.visited_profiles))
self.storage.save()
def start(self):
self.walk_cron = self.sched.repeat(60, self.enqueue_profiles)
self.view_cron = self.sched.schedule(randint(10,40), self.view_profile)
return True
def stop(self):
self.sched.cancel(self.walk_cron)
self.sched.cancel(self.view_cron)
self.walk_cron = None
self.view_cron = None
return True
def is_running(self):
return self.walk_cron is not None
def enqueue_profiles(self):
try:
with self.browser:
profiles_to_visit = self.browser.search_profiles().difference(self.visited_profiles)
self.logger.info(u'Enqueuing profiles to visit: %s' % profiles_to_visit)
self.profiles_queue = set(profiles_to_visit)
self.save()
except BrowserUnavailable:
return
def view_profile(self):
try:
try:
id = self.profiles_queue.pop()
except KeyError:
return # empty queue
try:
with self.browser:
profile = self.browser.get_profile(id)
self.logger.info(u'Visited profile %s (%s)' % (profile['pseudo'], id))
# Get score from the aum_score module
#d = self.nucentral_core.callService(context.Context.fromComponent(self), 'aum_score', 'score', profile)
# d.addCallback(self.score_cb, profile.getID())
# deferredlist.append(d)
# do not forget that we visited this profile, to avoid re-visiting it.
self.visited_profiles.add(id)
self.save()
except BrowserUnavailable:
# We consider this profil hasn't been [correctly] analysed
self.profiles_queue.add(id)
return
except Exception, e:
print e
finally:
if self.view_cron is not None:
self.view_cron = self.sched.schedule(randint(10,40), self.view_profile)

View file

@ -0,0 +1,107 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from __future__ import with_statement
from weboob.tools.browser import BrowserUnavailable
from weboob.capabilities.dating import Optimization
from weboob.capabilities.contact import QueryError
from weboob.tools.log import getLogger
__all__ = ['QueriesQueue']
class QueriesQueue(Optimization):
def __init__(self, sched, storage, browser):
self.sched = sched
self.storage = storage
self.browser = browser
self.logger = getLogger('queriesqueue', browser.logger)
self.queue = storage.get('queries_queue', 'queue', default=[])
self.check_cron = None
def save(self):
self.storage.set('queries_queue', 'queue', self.queue)
self.storage.save()
def start(self):
self.check_cron = self.sched.repeat(3600, self.flush_queue)
return True
def stop(self):
self.sched.cancel(self.check_cron)
self.check_cron = None
return True
def is_running(self):
return self.check_cron is not None
def enqueue_query(self, id, priority=999):
id_queue = [_id[1] for _id in self.queue]
if int(id) in id_queue:
raise QueryError('This id is already queued')
self.queue.append((int(priority), int(id)))
self.save()
# Try to flush queue to send it now.
self.flush_queue()
# Check if the enqueued query has been sent
for p, i in self.queue:
if i == int(id):
return False
return True
def flush_queue(self):
self.queue.sort()
priority = 0
id = None
try:
try:
while len(self.queue) > 0:
priority, id = self.queue.pop()
if not id:
continue
with self.browser:
if self.browser.send_charm(id):
self.logger.info('Charm sent to %s' % id)
else:
self.queue.append((priority, id))
self.logger.info("Charm can't be send to %s" % id)
break
# As the charm has been correctly sent (no exception raised),
# we don't store anymore ID, because if nbAvailableCharms()
# fails, we don't want to re-queue this ID.
id = None
priority = 0
except BrowserUnavailable:
# We consider this profil hasn't been [correctly] analysed
if not id is None:
self.queue.append((priority, id))
finally:
self.save()

View file

@ -0,0 +1,55 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from weboob.tools.browser import BrowserUnavailable
from weboob.capabilities.dating import Optimization
from ..browser import AuMBrowser
__all__ = ['Visibility']
class Visibility(Optimization):
def __init__(self, sched, browser):
self.sched = sched
self.browser = browser
self.cron = None
def start(self):
self.cron = self.sched.repeat(60*5, self.reconnect)
return True
def stop(self):
self.sched.cancel(self.cron)
self.cron = None
return True
def is_running(self):
return self.cron is not None
def reconnect(self):
try:
AuMBrowser(self.browser.username,
self.browser.password,
proxy=self.browser.proxy)
except BrowserUnavailable, e:
print str(e)
pass

49
modules/aum/test.py Normal file
View file

@ -0,0 +1,49 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from weboob.tools.test import BackendTest
from weboob.tools.browser import BrowserUnavailable
__all__ = ['AuMTest']
class AuMTest(BackendTest):
BACKEND = 'aum'
def test_new_messages(self):
try:
for message in self.backend.iter_unread_messages():
pass
except BrowserUnavailable:
# enough frequent to do not care about.
pass
def test_contacts(self):
try:
contacts = list(self.backend.iter_contacts())
if len(contacts) == 0:
# so bad, we can't test that...
return
self.backend.fillobj(contacts[0], ['photos', 'profile'])
except BrowserUnavailable:
# enough frequent to do not care about.
pass