pax_global_header 0000666 0000000 0000000 00000000064 12117742547 0014524 g ustar 00root root 0000000 0000000 52 comment=05fab92a48047cc78177f37583b6955b0fdc2bca
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/ 0000775 0000000 0000000 00000000000 12117742547 0021150 5 ustar 00root root 0000000 0000000 woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/ 0000775 0000000 0000000 00000000000 12117742547 0022620 5 ustar 00root root 0000000 0000000 woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/.keys/ 0000775 0000000 0000000 00000000000 12117742547 0023651 5 ustar 00root root 0000000 0000000 woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/.keys/florent.asc 0000664 0000000 0000000 00000017520 12117742547 0026017 0 ustar 00root root 0000000 0000000 -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.12 (GNU/Linux)
mQGiBEZvDQMRBACcSc5xvGE7EnAFonjB9P2U66xk3kL7grVJTvxdiY6ErwaqfRU0
XFSzwTnRoaQQFk//l+ctVaZ/Ex39y9lmivJLF4ME2RghIXTenJqbr6NQfnYIcPbx
fKo+SUt91Yf7cei1j22Qz6UdfdFgbllJ/JHcNMA0Qvjn+lLhk+YaB34rswCgqU2p
EoVlGtdUpx2/5yJh2PGqAe0D+QHf4gq5ZGKT8zF6HdySHvFaBZVwaymtxlaHINkE
SpLjhlEr2h3TKpBqZKS1W4njDpszMkgVxmjZPKLSkNkn1Mh8JISvv/6R5MOCZW9k
k+AnUhzvYLieM4WaBFPU9JgSb5ChVzuIgdQk+9w4gHYpkzBW91mnaPcdgZ2NTysB
HvFLA/98qxGdvSQGxw6wL05BUV0Qh6boeUyrzmV0rx+C0arVt3WXBf5He4HimioA
Lnq5ZfH7eqh1Ix+b4GcVeG3vQkOhbLqL73OnQ5B+lCFjM0UqvhtKlDBr0oTIUT4w
u/VlycwaZUKoRyd/ugA/mJjCZw4urN5Jpqn3bIO8CMIiZPpEwLQgRmxvcmVudCBG
b3VyY290IDxmbG9AZm91cmNvdC5mcj6IYgQTEQIAIgUCSz/zoQIbAwYLCQgHAwIG
FQgCCQoLBBYCAwECHgECF4AACgkQ5Y0WuYomQc7jyACgmI/LjLyq9OTkLiXEJznF
Pi/hYF8An21akzCCIZt8JrI9btSCyZOmu7LEtDVGbG9yZW50IEZvdXJjb3QgPGZs
b3JlbnQuZm91cmNvdEB0ZWxlY29tLWJyZXRhZ25lLmV1PohgBBMRAgAgBQJHsXTR
AhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ5Y0WuYomQc4RigCeLDV6E4bs
dJY2RBBE1UaG6qBa3TMAnjM/1O4kGN73RxrpD7LIrtp+Y0jNiEYEEBECAAYFAkex
esEACgkQLxDn0gRI4Xmr4wCeKZpgCaLOAliSwQwIQBntaeiSF8oAnRfOpFDVRiJC
1LY7fUl+w9135swEiEYEEBECAAYFAkex4jkACgkQ0fXg8CRaFsL/5ACglmyhRf/0
KmFCBGNhsDmkE5CcM24AnRKPqCjK3bE6akMKn/op4YL0wiU+iEYEEBECAAYFAkhU
d88ACgkQeGfVPHR5Nd1fwACfS7k18ddOMcMP9YeJYX+m0RSrbHYAoMlSbWTAIQz0
304hH8MP99vpk+xGtDJGbG9yZW50IEZvdXJjb3QgPGZsb3JlbnQuZm91cmNvdEBl
bnN0LWJyZXRhZ25lLmZyPohgBBMRAgAgBQJGb+ZuAhsDBgsJCAcDAgQVAggDBBYC
AwECHgECF4AACgkQ5Y0WuYomQc4GuQCfSI12zO5BVMvwVj2cOUrIN2GpCHkAn0ns
JTIapEb85AiURSCenZ4tRLNBiQEcBBABAgAGBQJGglehAAoJEESXPSGYpTg5qJgI
AKbj3C0HZZVz0oMgHgBklPjaYtFKdHwwj6d9/zNQ6MVNMya2ihC9uHVoQbEjW3zs
S/GPw1QYhJWDiiyclQCO6hUuy7hTHEVqGlkCrSm1sD86Pqk4hGbCSBj00UdPcHlc
xn50HHEaiLJlzg3Inl1pAu4IfQEyVCQzjjeckEKAGjD1sfr797v9W6wr7n+a2vFp
Yua5Za/j1gL36ATfY3P6msICKmUlbrNFEz4a0830TZwivW1kfFGZZ22EaHqxjZix
D3Im+rjUQG8zloD9aR6ZsoKeUzBG0vaaVkH9YNFoq3SJIQSx0k2ejoB1IztvC5SJ
MCuwwgTG0OLfVgkvR1F6P1OIRgQQEQIABgUCRoAmRAAKCRAht4CnaLJw4SesAJ9C
mKlIGJmspI3toDAXDuf1gWkoEQCfaXCewDZxBG/lEu5hndRA2AoNzdKIRgQQEQIA
BgUCRo0XFwAKCRCmXE4RZxQYm5rEAJ47tRFhlA/yMBb0WV5IB+eIrdCs4ACeJn8I
eE9m9H+uxb2yjDSFupRrgxCIRgQQEQIABgUCRufWiQAKCRBBTIrLI7Otz7KQAJsH
PWejjoymykce47n1a/JAmymr6gCfSdH/yFcsn/r7IboErWirE2L5JfCIRgQQEQIA
BgUCRw5ByQAKCRCY980d79hYDbxnAJ9Y3UbnnGbVM7f3HZQzHyk3DaLGgwCgtEu/
RTDpnTFsX5cSpW+rvZsTk1CIRgQQEQIABgUCRx+8lAAKCRAvEOfSBEjheVwPAJ9v
4tDk2neOHdXKiSnT3ixi+bB6UQCgi74FTQB1j+dSX6D1B4MpzG/xjceIRgQQEQIA
BgUCR7HiOQAKCRDR9eDwJFoWwgH4AJ0bJnwk1xl543nzP9U+3EWRkggHzgCfbSWF
XLq1GilK8L/OIsKhyKPZauiIRgQQEQIABgUCSFR3zwAKCRB4Z9U8dHk13fO5AJwI
ztSS0x5pagQR4cpQOanJxhtWsACg00ksOSBC0cEPnDtMjS32tSdCnNC0OEZvdXJj
b3QgRmxvcmVudCA8ZmxvcmVudC5mb3VyY290QHJlc2VsLmVuc3QtYnJldGFnbmUu
ZnI+iGAEExECACAFAkZvDQMCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRDl
jRa5iiZBzqlMAJ9ZjeJySwq8H0Pgi+OdYYkhV/bGRACbB/nBrBTJq7KrNMjsdlSI
o+/ov2yJARwEEAECAAYFAkaCV6kACgkQRJc9IZilODlvxgf9HcUrqEKaP1ddYCoA
1yfGFmTPcofnSi485ixc8yjyayukLli9LEjQM9sZPZW6ambnFSrurxoJ3NCJZQHf
tGO9/L9zFamNSxJslcGcpBnEbxdGdtuC0CApzYQLR/JgSUNbt0qz8Mj0Vtx5JbTl
1cmXERnOPv3AhTk65RLRZ5Ct3z21f/yZHpn/UD1HkeGf3pzTDgZQKKQgYkFbuX3f
7akhYRGl5CpN0yfdMWqTd39LWdejtmyzxSYeRBskcsVbBeHWtx8QEuH8JZgFijdF
GAUR2NEmg5IkVD4MzdUNm+bPmnxL44HPXLuy9+8O6HN3WOwkZ1EqN5Uh7r/TXbK+
kHUciohGBBARAgAGBQJGgCZPAAoJECG3gKdosnDhTwQAoMZEfIioKkJiiXUYFR/1
1pr0Ih8+AJ4u6ymm/wmcVp1k5g4/s1e5PmIrqYhGBBARAgAGBQJGjRccAAoJEKZc
ThFnFBib/GkAoMCKd3wpyeTq0MHa6zkd9YkoB6tNAJ9VGLXonB/bIoPi2S3Q7urJ
A1z3F4hGBBARAgAGBQJG59aJAAoJEEFMissjs63PXEoAoKX3tIWQe4Bk8oZoFwUC
aXUXOlvVAKDGq+4o8tklek8g1/gD/wod/VE81IhGBBARAgAGBQJHDkHNAAoJEJj3
zR3v2FgN3/kAnjJbKpkVgBubL79pBvgaCq6w+Hk1AKCOZjbJcb1mL97YoFy3CrzH
kVjmPIhGBBARAgAGBQJHH7yYAAoJEC8Q59IESOF5iZMAoKrUUwJo++Lb+QGBUeIM
f1G36q9aAJwNSvjzUxIlobyRZoEy02yRqCBW04hGBBARAgAGBQJHseI5AAoJENH1
4PAkWhbC138AmgK/Os5LefLDTGIhk4YbHSOKDHOKAKCiDRRhB/FFDHmVkN/GkREp
6zucL4hGBBARAgAGBQJIVHfPAAoJEHhn1Tx0eTXdTQcAoJu3wDwGanmYKGtCBOku
H5+U6tdzAJwNnMShHPIEnuSRd+uWKqxv6+9J/LQeRmxvcmVudCBGb3VyY290IDxm
bG9AcmVzZWwuZnI+iGAEExECACAFAkZv5hQCGwMGCwkIBwMCBBUCCAMEFgIDAQIe
AQIXgAAKCRDljRa5iiZBzvTiAJwOyJ1UIi0JyNP2hjdq3I996xo1VQCgpIdutSvQ
HIJK6w6EeXD7aUnRoK2JARwEEAECAAYFAkaCV6EACgkQRJc9IZilODmomAgApuPc
LQdllXPSgyAeAGSU+Npi0Up0fDCPp33/M1DoxU0zJraKEL24dWhBsSNbfOxL8Y/D
VBiElYOKLJyVAI7qFS7LuFMcRWoaWQKtKbWwPzo+qTiEZsJIGPTRR09weVzGfnQc
cRqIsmXODcieXWkC7gh9ATJUJDOON5yQQoAaMPWx+vv3u/1brCvuf5ra8Wli5rll
r+PWAvfoBN9jc/qawgIqZSVus0UTPhrTzfRNnCK9bWR8UZlnbYRoerGNmLEPcib6
uNRAbzOWgP1pHpmygp5TMEbS9ppWQf1g0WirdIkhBLHSTZ6OgHUjO28LlIkwK7DC
BMbQ4t9WCS9HUXo/U4kBHAQQAQIABgUCRoJXqQAKCRBElz0hmKU4OcUyB/9JU1kl
8hveOyUxAOt7ODUOI4cAnAVCcD+YOrw9kqKwoRtdHDNt8GCr+iCuBFpy3RybgH1W
40cFwZORr5vy2/VGdS7Ykah8kB7tj7g4qjQWT6va7fH1mrT/kMH+89CX7LATnozE
qtDvGNLdBJgEvEWtRnTjbEYKWO7nekwJxm6kye0pMG4YxMPGMc/WWF2LiEghJYPS
n62TusUMuci/a4GX/Fu2gKBANWMnkq/WAbcuJNuW8Bue99qBpfQPn9OWvExs9gpL
RnhBKDU1adMLu4kdE/d8UNM942wYFzqSoIORxBWFg89+YO1q/SoCNo9hXOVRq2i4
ncC343cVJbO2tUYAiEYEEBECAAYFAkaAJkQACgkQIbeAp2iycOEnrACfQpipSBiZ
rKSN7aAwFw7n9YFpKBEAn2lwnsA2cQRv5RLuYZ3UQNgKDc3SiEYEEBECAAYFAkaA
Jk8ACgkQIbeAp2iycOFTOgCglTkZSp2+LtivTrIB0JKdy69kf08AoJfa11nDQZUT
Ob1hFdvQ8dhV8VnEiEYEEBECAAYFAkaNFxwACgkQplxOEWcUGJuGZACgy6AQfKqr
Hwg1/3mVv2BSb+jU/EgAnjKWgbCWb6OSkzz+bk+QetalZdlqiEYEEBECAAYFAkbn
1okACgkQQUyKyyOzrc+TdQCfXRKDJYc9iXlOpTuQGctelYRDMFQAoK340FZOkg9l
0H8+gaOXiJ53VqTyiEYEEBECAAYFAkcOQc0ACgkQmPfNHe/YWA39KACeLlx7F63P
Q0Cs+rdgEQZW4GAy9qwAn1/TEvn46TU9jeHM0LyTnU8gjHokiEYEEBECAAYFAkcf
vJgACgkQLxDn0gRI4XlcrgCeJZ+Cs7uAx5ZY77orLzn3JFbaN3oAn2EcHrgt1QYO
ZlF0KeZniIlpGbPPiEYEEBECAAYFAkex4jkACgkQ0fXg8CRaFsL94ACfT2pWwstZ
OKTt8VGpRPhpIgWG1jwAniUTf0daKioAY1Qq9FBVBTD0dG/4iEYEEBECAAYFAkhU
d88ACgkQeGfVPHR5Nd2sHgCgtZ27U4bKtMHbVAY1Kiu0X3mFuKsAn2W2B5gZD+FM
S/rvYZ0jy4zEME3utCpGbG9yZW50IEZvdXJjb3QgPGZsb3JlbnQuZm91cmNvdEBy
ZXNlbC5mcj6IYAQTEQIAIAUCRm/mNwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheA
AAoJEOWNFrmKJkHOBEEAnRzGb0uJXDT486AfdUF7EjzgAdc3AKCQD7n/QRrE4IRC
Dy/GzyKgSrBVeIkBHAQQAQIABgUCRoJXqQAKCRBElz0hmKU4OV54B/9irlMBem4K
uyz01YP8xMFPS53XFp3jhxMPZ6XmuXdzpMTNvkhkzKWPLwuIgCqgGKE+bTHAA5hF
AeZSwhaEuPGbQtM7Y2Cd7wxlqb9ew73jE57MfGiWEOW+ezB84H+fbNmTbLfCP3KP
yBl548i8sI86EwfMhokpGls0eL8EED6j0664JN6hdY7Bc1V9bch3CDIIlVebhSY1
yjqaWbSWFNBsKljITWPYx8WAAyXIuJA2MRnXryxoLlWgbNG9CmnJND1C9lIVkNCT
4ROeKf+7AJrNJzdx280/TM1BCbgr8irKD6zGyr3QNyMTXZ8z3XYLlUIyNn5x/mqR
DhUVy8Ud6RN4iEYEEBECAAYFAkaAJk8ACgkQIbeAp2iycOHxsACeJRU0sztb0OC0
x+6vmrl/Jnyn9ywAoJBOl3gvVeNweb36s62RXYkAAv+LiEYEEBECAAYFAkcOOMIA
CgkQQUuEI2/szeBHhACgkueuwu27O/affl9pYpEUMRZSe+sAoIfP0UgJwq7Cjn8Y
lxdvzUIj7zAwiEYEEBECAAYFAkaNFxwACgkQplxOEWcUGJuk0wCfaUlA+llit0fO
RJKKVQGPJhStaJ0AoJ7Iaf/V4WOYEcZu8wQn74Lnjm+liEYEEBECAAYFAkbn1okA
CgkQQUyKyyOzrc+smACaA2qg2mhssFZGjP4147nDkN3PLY8AnRBJgthy6pBpfv9K
fI1IPaVmCAhriEYEEBECAAYFAkcOQc0ACgkQmPfNHe/YWA1eKwCeLA/Sop/zSHDb
npRtNfGeYFK5920AoKaDf4nbknaBCPvN86skCBXJXLYMiEYEEBECAAYFAkcfvJgA
CgkQLxDn0gRI4XmkygCfZOEW9RocJWLD8vUoBidIg1Jfsv8AoKRSfQzuEXKl1mGR
AZD+wmWENksgiEYEEBECAAYFAkex4jkACgkQ0fXg8CRaFsK0LwCgizpCEZ31sM/d
tZkyNhtcNFLEuT4AoKVDXEJv+r470w9S5nwMVc3blRS+iEYEEBECAAYFAkhUd88A
CgkQeGfVPHR5Nd3D1wCgk6UXR5207eJ8CO3y26xRz7gSE9AAn2G4ZVomp5hTUazi
ygoar9ooELG8tB9GbG9yZW50IDx3ZWJvb2JAZmxvLmZvdXJjb3QuZnI+iGIEExEC
ACIFAlED2+ECGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEOWNFrmKJkHO
E+gAmQGk3HBw3O1rrMVgvPcctuTfio7HAJ9VUI0l3h0m8csf54M9XE35lZIBGLkC
DQRGbw0TEAgAjULPV1tH1DyCHZd9DdJOGXSD3EVGV5dKPolV3C59dcZmUP8z08yQ
h7UKiwv4WbnsjV+kOaORWZxBGp24P+v2Dzqwq0EwiEyFFLD6s0FjWMX0yPqrEHn7
7MzUrxVP2QaVSy6B6WK4K4OleGlrf9tcFyGXjPh8eY8CqrlWgD3KNuY8ShcUfH3g
jBtirfrYgoP1fKmATdCdQAOkv4ygJMoT0SrMpMvHe+yb7VD23vyyFeVESd/tSo3W
92XeAt2672P+rmMpie5olc7W62Mk8UUVnoxcutZAqsUgEMqjWFm5RAn1oxWaS3K2
5jWA3hd6hLHPomLXyoEEByrZ3eLvHVgg2wADBQf/appTR2W/Ikc0hDFVK0bca8RT
h5U31h/uWAoZr9gP/OmT3auFybs5Lplexe+ZtITraGZeO8t6QCQRgfuRXp3D00tf
wOCIImWPQ2MyZsPjBtXD7pDCVJd+ejDdo3NeWp16fKhZm7CUbj0DBOSptWsGRvoy
QaC6i7SaAjzkTm0qufN1ZRSlTjBznpQKP8vSA8TOIhNeuVotyfmUK6JXBeEk/6fn
2nkmh1uKajgoDOS6xp1BeEiqhmWD62DFoMmhmb2zh9ENj0cHR8x0fppWEho/9V/z
ce+5VmdvU+VlbeCxsGxhRfVs+M0H9GXYT2ta6FZ18lASQgorpMiprMtNgOdYp4hJ
BBgRAgAJBQJGbw0TAhsMAAoJEOWNFrmKJkHO1aIAnRv7uliZ9nOUwvlSYTkvCfKo
WP2vAKCc09hTbQK0RRXh+IS6sYPmmNIqsQ==
=MAbt
-----END PGP PUBLIC KEY BLOCK-----
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/.keys/laurentb.asc 0000664 0000000 0000000 00000035644 12117742547 0026171 0 ustar 00root root 0000000 0000000 -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.17 (GNU/Linux)
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
Ae4hpP7W
=DzWq
-----END PGP PUBLIC KEY BLOCK-----
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/.keys/romain.asc 0000664 0000000 0000000 00000003562 12117742547 0025634 0 ustar 00root root 0000000 0000000 -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.17 (GNU/Linux)
mQENBEoOr78BCADAsCwWXDvedstjc9L3YSlXJ6KDd12WVD+iEiVGUqToJkWwp05k
uVvvibhNxjDJzZqfWxBvfhFXkf/APF8PoTvkA4l51WOfPGSZejiKMcenij+3HjKa
GCPjLlY9rlTUjA5Alw533WP4Wja0EILXy0xjS9eXN2/Vbtrh7sbwU3cs9KOgDjEH
dcR/ZgRGrqiLuVd83L2rlyoWZ5KM2KCkMjtpPBFUXOf5pyCV683wd+grdsHzL8Lm
MAyVPPCmwrCKdkrG4/xjlBo0Ghjwq4gH6ShclcdJlwjfBY/CLtv5voM+TxKZ2wg0
6FIwyZdh0F8G4c5PhiL+TXKrAV76WNwgwE1VABEBAAG0I1JvbWFpbiBCaWdub24g
PHJvbWFpbkBwZWVyZnVzZS5vcmc+iQE2BBMBAgAgBQJKDq+/AhsvBgsJCAcDAgQV
AggDBBYCAwECHgECF4AACgkQ8a5sCGs03G3Djwf7B8CKV9ZOvarZlQsaqzzA5AVM
/jB+oi86tjCDvTB4pgdbVUcni4MxWzTCU5OUXPbjzaFr+liB8tsGbSSFCJxMSdMu
DMhCcjU48XB3aSTXfvrKRqf/7kBdIqRqNB4KvmXGQI6NQl68O/BNQ8zX++M39GXR
tCzKS9VcWOdra8KN45ADVjVx3v8MCBguc9GTXM5EXxdoIUjU9ClWAklfWWJZBHGB
9NnPITfup28rjxXoITgXjF5Irk3uOEyleQ3fDM1uzLw5u/EHNh+uQJgV5Y5YHY9g
b9VT97iIfimw4vJ5stCKCwf7wz9VbGdBpNRMNepGV+/wVOg37nl2nFLZV9pCtLQg
Um9tYWluIEJpZ25vbiA8cm9tYWluQGJpZ25vbi5tZT6JATgEEwECACIFAk1ytV4C
Gy8GCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEPGubAhrNNxtHVwH+wTw7zcC
Xx+pQPR+s4gbtKwb0L95psuN5fUl2f9jEnAWwOSvkYoBmbA0+dYCtokFGIbFNmzU
ajBXwFkVEdphyVYxH4DnIwmwmafMA/QjYiQB91zRPpZQKCG1ioKlM/He9wJsrskl
zaqUEs3BuAx4n45vsiuERjJFwejbfqlI3PYSnS1/ncVpvn1pREn3a+QJ2hDMx2Ns
Znh0HkhJ6LLB8F2Z5bFSDcpTAiNgECRc3eleiTQT+89RrbvpPUi8iIFUcWmGxYBU
Atk6miGgjKpy98Ezc1LWfPOSaZjNBxhvtlicx+bDqkcMYPakZ1gLNWWyW2iNljDF
KiLhXstA/eNC3qC0IVJvbWFpbiBCaWdub24gPHJvbWFpbkBzeW1saW5rLm1lPokB
OAQTAQIAIgUCTXK1SgIbLwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ8a5s
CGs03G2b6Qf/VjmEs0pBdpg/Ewj7HmikxIQ7lvX5um1YHjLyLNdp6W6wylmzh3u5
dsaajEWmPbUhaDre7gTtxPNJFs6FXksfquUiZySXA45R+XH8Qh/TWJAY8FjdaZwB
tgOn4rka+n0rQeG1fklHw4Z3AREm/GmfeA63nkmS5SvdXskAz1iXB20Hyb6bGy5b
Y7ZJNhJ8Ocz2THizc2od3Bx8Pzw3DHs+NaW5sK/NJJN9QgpdLA4lo0KeidCALEXF
CReDEaT4Cs/Ds3u8QkuR+s+8qN5BOYcIWz79T/hLFw46SezEz9gjJ+NR1nIMd2um
bwnyciYEGVOfG4DsU/IRwnbk+HWcLNTFnw==
=mTlW
-----END PGP PUBLIC KEY BLOCK-----
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/ 0000775 0000000 0000000 00000000000 12117742547 0023553 5 ustar 00root root 0000000 0000000 woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/__init__.py 0000664 0000000 0000000 00000001433 12117742547 0025665 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 .backend import ArteBackend
__all__ = ['ArteBackend']
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/backend.py 0000664 0000000 0000000 00000006322 12117742547 0025517 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 __future__ import with_statement
from weboob.capabilities.video import ICapVideo, BaseVideo
from weboob.capabilities.collection import ICapCollection, CollectionNotFound
from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.value import Value
from .browser import ArteBrowser
from .video import ArteVideo
__all__ = ['ArteBackend']
class ArteBackend(BaseBackend, ICapVideo, ICapCollection):
NAME = 'arte'
MAINTAINER = u'Romain Bignon'
EMAIL = 'romain@weboob.org'
VERSION = '0.f'
DESCRIPTION = 'Arte French and German TV'
LICENSE = 'AGPLv3+'
CONFIG = BackendConfig(Value('lang', label='Lang of videos',
choices={'fr': 'French', 'de': 'Deutsch', 'en': 'English'}, default='fr'),
Value('quality', label='Quality of videos', choices=['hd', 'sd'], default='hd'))
BROWSER = ArteBrowser
def create_default_browser(self):
return self.create_browser(lang=self.config['lang'].get(), quality=self.config['quality'].get())
def get_video(self, _id):
with self.browser:
return self.browser.get_video(_id)
def search_videos(self, pattern, sortby=ICapVideo.SEARCH_RELEVANCE, nsfw=False, max_results=None):
with self.browser:
return self.browser.search_videos(pattern)
def fill_video(self, video, fields):
if fields != ['thumbnail']:
# if we don't want only the thumbnail, we probably want also every fields
with self.browser:
video = self.browser.get_video(ArteVideo.id2url(video.id), video)
if 'thumbnail' in fields and video.thumbnail:
with self.browser:
video.thumbnail.data = self.browser.readurl(video.thumbnail.url)
return video
def iter_resources(self, objs, split_path):
if BaseVideo in objs:
collection = self.get_collection(objs, split_path)
if collection.path_level == 0:
yield self.get_collection(objs, [u'latest'])
if collection.split_path == [u'latest']:
for video in self.browser.latest_videos():
yield video
def validate_collection(self, objs, collection):
if collection.path_level == 0:
return
if BaseVideo in objs and collection.split_path == [u'latest']:
collection.title = u'Latest Arte videos'
return
raise CollectionNotFound(collection.split_path)
OBJECTS = {ArteVideo: fill_video}
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/browser.py 0000664 0000000 0000000 00000004134 12117742547 0025612 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 weboob.tools.browser import BaseBrowser
from weboob.tools.browser.decorators import id2url
from .pages import IndexPage, VideoPage
from .video import ArteVideo
__all__ = ['ArteBrowser']
class ArteBrowser(BaseBrowser):
DOMAIN = u'videos.arte.tv'
ENCODING = None
PAGES = {r'http://videos.arte.tv/\w+/videos/toutesLesVideos.*': IndexPage,
r'http://videos.arte.tv/\w+/do_search/videos/.*': IndexPage,
r'http://videos.arte.tv/\w+/videos/(?P.+)\.html': VideoPage
}
SEARCH_LANG = {'fr': 'recherche', 'de': 'suche', 'en': 'search'}
def __init__(self, lang, quality, *args, **kwargs):
BaseBrowser.__init__(self, *args, **kwargs)
self.lang = lang
self.quality = quality
@id2url(ArteVideo.id2url)
def get_video(self, url, video=None):
self.location(url)
return self.page.get_video(video, self.lang, self.quality)
def home(self):
self.location('http://videos.arte.tv/fr/videos/toutesLesVideos')
def search_videos(self, pattern):
self.location(self.buildurl('/%s/do_search/videos/%s' % (self.lang, self.SEARCH_LANG[self.lang]), q=pattern.encode('utf-8')))
assert self.is_on_page(IndexPage)
return self.page.iter_videos()
def latest_videos(self):
self.home()
assert self.is_on_page(IndexPage)
return self.page.iter_videos()
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/favicon.png 0000664 0000000 0000000 00000001463 12117742547 0025712 0 ustar 00root root 0000000 0000000 PNG
IHDR @ @ iq sRGB bKGD pHYs tIME5p IDATxOVUϝ?̘\fJ!2n>h+E-"ĕ`
D0(IA
X(Mj9.<afyw=_89y~<}I$D"H$1h]x9]ex
Kww?q
S+YQ՝.!l^m3Fz<p:+[}.VΝI|~Pw|MZw̷ x2>J\A@|xPBG, |^lo^spL*ƱLq;vbK8>_yM38տÜˊ߆N5!|ٰ]i1s!jVT_e>=
sfz֙XQ,0TDuB&Rg{y&dk+Bmk&@]kdyu2߁x;AKZ.B~V|2+,pF8'K!54G_wp E,WN,߸yw;B}L>; br%ɊLplq4y>o]6Eqq)r
ï<{ "~.@̟&P"H$D"H$^ qQc IENDB` woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/pages.py 0000664 0000000 0000000 00000010052 12117742547 0025222 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 .
import datetime
import re
import urllib
from weboob.tools.browser import BasePage, BrokenPageError
from weboob.tools.capabilities.thumbnail import Thumbnail
from weboob.capabilities import NotAvailable
from .video import ArteVideo
__all__ = ['IndexPage', 'VideoPage']
class IndexPage(BasePage):
def iter_videos(self):
videos = self.document.getroot().cssselect("div[class=video]")
for div in videos:
title = div.find('h2').find('a').text
m = re.match(r'/fr/videos/(.*)\.html', div.find('h2').find('a').attrib['href'])
_id = ''
if m:
_id = m.group(1)
rating = rating_max = 0
rates = self.parser.select(div, 'div[class=rateContainer]', 1)
for r in rates.findall('div'):
if 'star-rating-on' in r.attrib['class']:
rating += 1
rating_max += 1
video = ArteVideo(_id)
video.title = unicode(title)
video.rating = rating
video.rating_max = rating_max
thumb = self.parser.select(div, 'img[class=thumbnail]', 1)
video.thumbnail = Thumbnail(u'http://videos.arte.tv' + thumb.attrib['src'])
try:
parts = self.parser.select(div, 'div.duration_thumbnail', 1).text.split(':')
if len(parts) == 2:
hours = 0
minutes, seconds = parts
elif len(parts) == 3:
hours, minutes, seconds = parts
else:
raise BrokenPageError('Unable to parse duration %r' % parts)
except BrokenPageError:
pass
else:
video.duration = datetime.timedelta(hours=int(hours), minutes=int(minutes), seconds=int(seconds))
video.set_empty_fields(NotAvailable, ('url',))
yield video
class VideoPage(BasePage):
def get_video(self, video=None, lang='fr', quality='hd'):
if not video:
video = ArteVideo(self.group_dict['id'])
video.title = unicode(self.get_title())
video.url = unicode(self.get_url(lang, quality))
video.set_empty_fields(NotAvailable)
return video
def get_title(self):
return self.document.getroot().cssselect('h1')[0].text
def get_url(self, lang, quality):
obj = self.parser.select(self.document.getroot(), 'object', 1)
movie_url = self.parser.select(obj, 'param[name=movie]', 1)
xml_url = urllib.unquote(movie_url.attrib['value'].split('videorefFileUrl=')[-1])
doc = self.browser.get_document(self.browser.openurl(xml_url))
videos_list = self.parser.select(doc.getroot(), 'video')
videos = {}
for v in videos_list:
videos[v.attrib['lang']] = v.attrib['ref']
if lang in videos:
xml_url = videos[lang]
else:
xml_url = videos.popitem()[1]
doc = self.browser.get_document(self.browser.openurl(xml_url))
obj = self.parser.select(doc.getroot(), 'urls', 1)
videos_list = self.parser.select(obj, 'url')
urls = {}
for v in videos_list:
urls[v.attrib['quality']] = v.text
if quality in urls:
video_url = urls[quality]
else:
video_url = urls.popitem()[1]
return video_url
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/test.py 0000664 0000000 0000000 00000002670 12117742547 0025111 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 weboob.tools.test import BackendTest
from weboob.capabilities.video import BaseVideo
class ArteTest(BackendTest):
BACKEND = 'arte'
def test_search(self):
l = list(self.backend.search_videos('arte'))
if len(l) > 0:
v = l[0]
self.backend.fillobj(v, ('url',))
self.assertTrue(v.url and v.url.startswith('rtmp://'), 'URL for video "%s" not found: %s' % (v.id, v.url))
def test_latest(self):
l = list(self.backend.iter_resources([BaseVideo], [u'latest']))
assert len(l)
v = l[0]
self.backend.fillobj(v, ('url',))
self.assertTrue(v.url and v.url.startswith('rtmp://'), 'URL for video "%s" not found: %s' % (v.id, v.url))
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/arte/video.py 0000664 0000000 0000000 00000001664 12117742547 0025242 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 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 .
from weboob.capabilities.video import BaseVideo
__all__ = ['ArteVideo']
class ArteVideo(BaseVideo):
@classmethod
def id2url(cls, _id):
return 'http://videos.arte.tv/fr/videos/%s.html' % _id
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/ 0000775 0000000 0000000 00000000000 12117742547 0024610 5 ustar 00root root 0000000 0000000 woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/__init__.py 0000664 0000000 0000000 00000001442 12117742547 0026722 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2013 Julien Veyssier
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
from .backend import AttilasubBackend
__all__ = ['AttilasubBackend']
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/backend.py 0000664 0000000 0000000 00000003503 12117742547 0026552 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2013 Julien Veyssier
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
from weboob.capabilities.subtitle import ICapSubtitle,LanguageNotSupported
from weboob.tools.backend import BaseBackend
from .browser import AttilasubBrowser
from urllib import quote_plus
__all__ = ['AttilasubBackend']
class AttilasubBackend(BaseBackend, ICapSubtitle):
NAME = 'attilasub'
MAINTAINER = u'Julien Veyssier'
EMAIL = 'julien.veyssier@aiur.fr'
VERSION = '0.f'
DESCRIPTION = '"Attila'' s Website 2.0" french subtitles'
LICENSE = 'AGPLv3+'
LANGUAGE_LIST = ['fr']
BROWSER = AttilasubBrowser
def create_default_browser(self):
return self.create_browser()
def get_subtitle(self, id):
return self.browser.get_subtitle(id)
def get_subtitle_file(self, id):
subtitle = self.browser.get_subtitle(id)
if not subtitle:
return None
return self.browser.openurl(subtitle.url.encode('utf-8')).read()
def iter_subtitles(self, language, pattern):
if language not in self.LANGUAGE_LIST:
raise LanguageNotSupported()
return self.browser.iter_subtitles(language,quote_plus(pattern.encode('utf-8')))
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/browser.py 0000664 0000000 0000000 00000003322 12117742547 0026645 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2013 Julien Veyssier
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
from weboob.tools.browser import BaseBrowser
from .pages import SubtitlesPage, SearchPage
__all__ = ['AttilasubBrowser']
class AttilasubBrowser(BaseBrowser):
DOMAIN = 'davidbillemont3.free.fr'
PROTOCOL = 'http'
ENCODING = 'utf-8'
USER_AGENT = BaseBrowser.USER_AGENTS['wget']
PAGES = {
'http://search.freefind.com/find.html.*': SearchPage,
'http://davidbillemont3.free.fr/.*.htm': SubtitlesPage,
}
def iter_subtitles(self, language, pattern):
self.location('http://search.freefind.com/find.html?id=81131980&_charset_=&bcd=%%F7&scs=1&pageid=r&query=%s&mode=Find%%20pages%%20matching%%20ALL%%20words' % pattern.encode('utf-8'))
assert self.is_on_page(SearchPage)
return self.page.iter_subtitles(language,pattern)
def get_subtitle(self, id):
url_end = id.split('|')[0]
self.location('http://davidbillemont3.free.fr/%s' % url_end)
assert self.is_on_page(SubtitlesPage)
return self.page.get_subtitle(id)
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/favicon.png 0000664 0000000 0000000 00000006406 12117742547 0026751 0 ustar 00root root 0000000 0000000 PNG
IHDR @ @ % sRGB bKGD pHYs tIME 0;! IDAThZrGr='z RV]c×'?+ioa$x\2{0$f(hEu0j39_,5~gfGf}_ \"()23r{+#3j+; $ed-t!(I$Yk"UON&;$%f4tOf/\)#͝?}ζo"HE铇v}ݴ9Hl6^!"_dffnHwtW%!%o߽í;FN_~}xp85O鍣PKRׯGDkd2֖Ax=`Ӭ}5jԵdfFJ"rFXjqz#??S^%unfyR4 fVJ麎$7nefq J+¨SZKZοA$嵩$")]5BJku_YFɾ- ̦WGJL<:(]~'1"M;iqp3/<}ZOqv:puE: ͦl5E"Rw~ݷv$2NzYEKr>g<9LtFJ-3K biōM&wssoDf0
h% $Z0/gNϯ\}gok{+9dqDٽffPKز̌kׯ}?#3}o@C
Upgf^3PPD!E|:ݹ(k8as}2^B7toxms'
V5L L&ޭsV(D@U
L&;7w]#r^&+ЬK(D 0G)0_zQ$* " "jWЖ6Ft@4M{D@ffdzf22W23-6!A
B`r%JC3h|P[Da^)`b)PdH!F:49Z&2
XeD1:\``Ae4F]na+'VA9)&L$BՔeS
ySHi6|>S-IM Ht?/xe!(`%G
%aRHkMX7D$.$R.P̈́FCk6&G7@B$jV/)+d$#$ dORJ@BA`4QF J2XEEhM"h]̎j I
PQܼ#טP$B)hI@r=̭i+P
[)Ns!pPK;"Lܔb ??v&aL$HgL?y>.UiƈL:;=?;=^ͅ㳓㳫,-YP|l'훙ϚRG|{{ݮ$J8w5Y#N'{cgg:͔ h Tkg =K< ~ޝk:Io}oڋLx;@E&#Uo?>>,V2 *P0h"l_Rovn^+/
kν" KBJ41|Z[[7wֵ\&va0@M)A>y6(ϲjIkpÁ@H!82~L0Hd)LDvF#E/\IaV܊yl_a%/ӗ5{M`$5 bN)J4Xty"R.[C+TfC&"9֯m
)) ^I
kG:\0>Z1s@֪>UjE(#rF2+h=ND"org4=HPd3߰d$lC\;ZZ֏mf]Wʤ0D&3 /ffE Jq%Hր!4t]fC)FBW#`oUB:NNk_342Nj@7gziseK"idbOVFe9 ּT/v+=hlB??;=k7kR&6y4fs&gO=^,''?=J`-4K+8b"F}Kz퓧Z $_Bhϩ|cs{i;]=\ TJ5@{|z!֞!/ "f`}hB7IQ뀶x9[ջ8,0IEFFYݼ[k)@2^mݍ $eT*UQVYdž"5"k%{oݾsqht%ǠYPVϕV6AD+d^2ԑDDJ"zY ʬ}jn?;cq>80\ߖ+ZCx`̨,Ç\Mձ*RA֊U J*/*&V_BiiW}EE;YC#֍`9:Ų?',F.ρ熂Ϥ6 b IENDB` woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/pages.py 0000664 0000000 0000000 00000011612 12117742547 0026262 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2013 Julien Veyssier
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
from weboob.capabilities.subtitle import Subtitle
from weboob.capabilities.base import NotAvailable
from weboob.tools.browser import BasePage
__all__ = ['SubtitlesPage','SearchPage']
class SearchPage(BasePage):
def iter_subtitles(self, language, pattern):
fontresult = self.parser.select(self.document.getroot(),'div.search-results font.search-results')
# for each result in freefind, explore the subtitle list page to iter subtitles
for res in fontresult:
a = self.parser.select(res,'a',1)
url = a.attrib.get('href','')
self.browser.location(url)
assert self.browser.is_on_page(SubtitlesPage)
# subtitles page does the job
for subtitle in self.browser.page.iter_subtitles(language, pattern):
yield subtitle
class SubtitlesPage(BasePage):
def get_subtitle(self,id):
href = id.split('|')[1]
# we have to find the 'tr' which contains the link to this address
a = self.parser.select(self.document.getroot(),'a[href="%s"]'%href,1)
line = a.getparent().getparent().getparent().getparent().getparent()
cols = self.parser.select(line,'td')
traduced_title = self.parser.select(cols[0],'font',1).text.lower()
original_title = self.parser.select(cols[1],'font',1).text.lower()
nb_cd = self.parser.select(cols[2],'font',1).text.strip()
nb_cd = int(nb_cd.split()[0])
traduced_title_words = traduced_title.split()
original_title_words = original_title.split()
# this is to trash special spacing chars
traduced_title = " ".join(traduced_title_words)
original_title = " ".join(original_title_words)
name = "%s (%s)"%(original_title,traduced_title)
url = "http://davidbillemont3.free.fr/%s"%href
subtitle = Subtitle(id,name)
subtitle.url = url
subtitle.language = "fre"
subtitle.nb_cd = nb_cd
subtitle.description = NotAvailable.__unicode__()
return subtitle
def iter_subtitles(self,language, pattern):
pattern = pattern.strip().replace('+',' ')
pattern_words = pattern.split()
tab = self.parser.select(self.document.getroot(),'table[bordercolor="#B8C0B2"]')
if len(tab) == 0:
tab = self.parser.select(self.document.getroot(),'table[bordercolordark="#B8C0B2"]')
if len(tab) == 0:
return
# some results of freefind point on useless pages
if tab[0].attrib.get('width','') != '100%':
return
for line in tab[0].getiterator('tr'):
cols = self.parser.select(line,'td')
traduced_title = self.parser.select(cols[0],'font',1).text.lower()
original_title = self.parser.select(cols[1],'font',1).text.lower()
traduced_title_words = traduced_title.split()
original_title_words = original_title.split()
# if the pattern is one word and in the title OR if the
# intersection between pattern and the title is at least 2 words
if (len(pattern_words) == 1 and pattern in traduced_title_words) or\
(len(pattern_words) == 1 and pattern in original_title_words) or\
(len(list(set(pattern_words) & set(traduced_title_words))) > 1) or\
(len(list(set(pattern_words) & set(original_title_words))) > 1):
# this is to trash special spacing chars
traduced_title = " ".join(traduced_title_words)
original_title = " ".join(original_title_words)
nb_cd = self.parser.select(cols[2],'font',1).text.strip()
nb_cd = int(nb_cd.split()[0])
name = "%s (%s)"%(original_title,traduced_title)
href = self.parser.select(cols[3],'a',1).attrib.get('href','')
url = "http://davidbillemont3.free.fr/%s"%href
id = "%s|%s"%(self.browser.geturl().split('/')[-1],href)
subtitle = Subtitle(id,name)
subtitle.url = url
subtitle.language = "fre"
subtitle.nb_cd = nb_cd
subtitle.description = NotAvailable.__unicode__()
yield subtitle
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/attilasub/test.py 0000664 0000000 0000000 00000002435 12117742547 0026145 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2013 Julien Veyssier
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
from weboob.tools.test import BackendTest
import urllib
from random import choice
class AttilasubTest(BackendTest):
BACKEND = 'attilasub'
def test_subtitle(self):
subtitles = list(self.backend.iter_subtitles('fr','spiderman'))
assert (len(subtitles) > 0)
for subtitle in subtitles:
path, qs = urllib.splitquery(subtitle.url)
assert path.endswith('.rar')
# get the file of a random sub
if len(subtitles):
subtitle = choice(subtitles)
self.backend.get_subtitle_file(subtitle.id)
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/ 0000775 0000000 0000000 00000000000 12117742547 0023402 5 ustar 00root root 0000000 0000000 woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/API.txt 0000664 0000000 0000000 00000135444 12117742547 0024567 0 ustar 00root root 0000000 0000000 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 à perdre, je n'ai ni MSN ni Facebook, je considère qu'on apprécie davantage la discussion face à face autour d'un verre que dans les yeux de son écran.\r
\r
Bon et ne venez que si vous avez quelque chose à 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)
Barjavel (La nuit des temps, Le voyageur imprudent, Ravage, \x85)
Boris Vian (J'irai cracher sur vos tombes, L'écume des jours\x85)
Bukowski, Desproges, San Antonio
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étournement \x97 La Classe Américaine
V pour Vendetta, Pulp Fiction, The Truman Show
Eternal Sunshine of the Spotless Mind, Match Point
Idiocracy, The Big Lebowski, La cité de la peur
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
Metallica, Iron Maiden, Accept
Slash's Snakepit, Queen, Deep Purple
Led Zeppelin, Rolling Stones
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ède pas la TV
',
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=,
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 ça n'empêche pas son bidou, ceci dit c'est planqué 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étérinaire m'avait dit après la castration de Futex qu'il fallait faire attention à son poids, du coup ça m'a tellement vexé que j'ai fais attention au point qu'il est sans doute mê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é depuis septembre ! un peu moins de boulot alors j'y traine !!\r\n\r\nEt toi ç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
\r
Et l'humour encore plus, mouhaha\r
\r
PS: Si vous ê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ète - Rendell
Etat limite - Assouline
Marie-Antoinette - Zweig
",
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
Shinning - L'exorciste - Ester - Gothika
How High ! - Requiem for a dream
Remember Me - trainspotting
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éâtre dé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
Red Hot - Ray Charles - BEP - Gorillaz - Birdy nam nam
Citizen Cope - Angus & Julia Stone - Portishead
50cent - Sia - Ben Harper - Busta Rhymes
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
OC
True blood
Envoyé spécial ;) - Arte !
',
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'}}
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/__init__.py 0000664 0000000 0000000 00000001510 12117742547 0025510 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 AuMBrowser
from .backend import AuMBackend
__all__ = ['AuMBrowser', 'AuMBackend']
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/antispam.py 0000664 0000000 0000000 00000011712 12117742547 0025572 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 .
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['who']['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['announce'].startswith('salut! je te donne mon msn'):
return False
if profile['shopping_list'].startswith('cam to cam'):
return False
if profile['shopping_list'].startswith('je suis une femme tres tres belle et je recherche un homme qui aime le sexe'):
return False
if profile['shopping_list'].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'] is None:
return True
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
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/backend.py 0000664 0000000 0000000 00000053277 12117742547 0025361 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 __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, BrowserHTTPNotFound
from weboob.tools.value import Value, ValuesDict, ValueBool, ValueBackendPassword
from weboob.tools.log import getLogger
from weboob.tools.misc import local2utc, to_unicode
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 = u'Romain Bignon'
EMAIL = 'romain@weboob.org'
VERSION = '0.f'
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),
Value('search_query', label='Search query', default=''))
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(),
self.config['search_query'].get())
def report_spam(self, id):
with self.browser:
pass
#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[u'baskets'] = (self.browser.get_baskets, 'You were put into %s\'s basket')
all_events[u'flashs'] = (self.browser.get_flashs, 'You sent a charm to %s')
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
# ---- 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['who']['id'])
continue
t = Thread(int(thread['who']['id']))
t.flags = Thread.IS_DISCUSSION
t.title = u'Discussion with %s' % to_unicode(thread['who']['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['who']['pseudo']
self.storage.set('sluts', int(thread.id), 'status', mails['status'])
self.storage.save()
for mail in mails['results']:
flags = 0
if self.antispam and not self.antispam.check_mail(mail):
self.logger.info('Skipped a spam-mail from %s' % mails['who']['pseudo'])
self.report_spam(thread.id)
break
if parse_dt(mail['date']) > slut['lastmsg']:
flags |= Message.IS_UNREAD
if get_profiles:
if not mail['from'] in contacts:
try:
with self.browser:
contacts[mail['from']] = self.get_contact(mail['from'])
except BrowserHTTPNotFound:
pass
if self.antispam and mail['from'] in contacts and not self.antispam.check_contact(contacts[mail['from']]):
self.logger.info('Skipped a spam-mail-profile from %s' % mails['who']['pseudo'])
self.report_spam(thread.id)
break
if int(mail['from']) == self.browser.my_id:
if mails['remote_status'] == 'new' and msg is None:
flags |= Message.IS_NOT_RECEIVED
else:
flags |= Message.IS_RECEIVED
signature = u''
#if mail.get('src', None):
# signature += u'Sent from my %s\n\n' % mail['src']
if mail['from'] in contacts:
signature += contacts[mail['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=to_unicode(my_name if int(mail['from']) == self.browser.my_id else mails['who']['pseudo']),
receivers=[to_unicode(my_name if int(mail['from']) != self.browser.my_id else mails['who']['pseudo'])],
date=parse_dt(mail['date']),
content=to_unicode(unescape(mail['message'] or '').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):
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['who']['pseudo'])
self.report_spam(thread['member']['id'])
continue
slut = self._get_slut(thread['who']['id'])
if parse_dt(thread['date']) > slut['lastmsg'] or thread['status'] != slut['status']:
t = self.get_thread(thread['who']['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 parse_dt(basket['date']) <= slut['lastmsg']:
continue
contact = self.get_contact(basket['who']['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['who']['id'])
continue
thread = Thread(int(basket['who']['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 int(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', int(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': None}
else:
slut = self.storage.get('sluts', id)
slut['lastmsg'] = slut.get('lastmsg', datetime.datetime(1970,1,1)).replace(tzinfo=tz.tzutc())
slut['status'] = slut.get('status', None)
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):
s = 0
if contact.get('online', False):
s = Contact.STATUS_ONLINE
else:
s = Contact.STATUS_OFFLINE
c = Contact(contact['id'], to_unicode(contact['pseudo']), s)
c.url = self.browser.id2url(contact['id'])
if 'age' in contact:
c.status_msg = u'%s old, %s' % (contact['age'], contact['city'])
if contact['cover'] is not None:
url = contact['cover'] + '/%(type)s'
else:
url = u'http://s.adopteunmec.com/www/img/thumb0.jpg'
c.set_photo(u'image%s' % contact['cover'],
url=url % {'type': 'full'},
thumbnail_url=url % {'type': 'small'})
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['who'])
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('newvisits', 'New visits', self.browser.nb_new_visites()),
)
OBJECTS = {Thread: fill_thread,
Contact: fill_contact,
ContactPhoto: fill_photo
}
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/browser.py 0000664 0000000 0000000 00000027745 12117742547 0025456 0 ustar 00root root 0000000 0000000 # -*- 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 .
from base64 import b64encode
from hashlib import sha256
from datetime import datetime
import math
import re
import urllib
import urllib2
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword, BrowserHTTPNotFound, BrowserUnavailable
from weboob.tools.json import json
from weboob.tools.date import local2utc
from weboob.capabilities.base import UserError
from weboob.capabilities.messages import CantSendMessage
__all__ = ['AuMBrowser']
class AuMException(UserError):
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 = 'www.adopteunmec.com'
APIKEY = 'fb0123456789abcd'
APITOKEN = 'DCh7Se53v8ejS8466dQe63'
APIVERSION = '2.2.5'
USER_AGENT = 'Mozilla/5.0 (Linux; U; Android4.1.1; fr_FR; GT-N7100; Build/JRO03C) com.adopteunmec.androidfr/17'
consts = None
my_sex = 0
my_id = 0
my_name = u''
my_coords = (0,0)
def __init__(self, username, password, search_query, *args, **kwargs):
kwargs['get_home'] = False
BaseBrowser.__init__(self, username, password, *args, **kwargs)
# now we do authentication ourselves
#self.add_password('http://www.adopteunmec.com/api/', self.username, self.password)
self.login()
self.home()
self.search_query = search_query
def id2url(self, id):
return u'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 api0_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('http://api.adopteunmec.com/api.php',
S=self.APIKEY,
C=command,
A=action,
P=parameter,
O='json')
buf = self.openurl(url, data).read()
try:
r = json.loads(buf[buf.find('{'):])
except ValueError:
raise ValueError(buf)
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.api0_request(command, action, parameter, data, nologin=True)
else:
raise BrowserIncorrectPassword(AuMException.ERRORS[code])
else:
raise AuMException(code)
return r
def login(self):
self.api_request('applications/android')
# XXX old API is disabled
#r = self.api0_request('me', 'login', data={'login': self.username,
# 'pass': self.password,
# }, nologin=True)
#self.my_coords = (float(r['result']['me']['lat']), float(r['result']['me']['lng']))
#if not self.search_query:
# self.search_query = 'region=%s' % r['result']['me']['region']
def api_request(self, command, **kwargs):
if 'data' in kwargs:
data = kwargs.pop('data').encode('utf-8', 'replace')
else:
data = None
headers = {}
if not command.startswith('applications'):
today = local2utc(datetime.now()).strftime('%Y-%m-%d')
token = sha256(self.username + self.APITOKEN + today).hexdigest()
headers['Authorization'] = 'Basic %s' % (b64encode('%s:%s' % (self.username, self.password)))
headers['X-Platform'] = 'android'
headers['X-Client-Version'] = self.APIVERSION
headers['X-AUM-Token'] = token
url = self.buildurl(self.absurl('/api/%s' % command), **kwargs)
req = self.request_class(url, data, headers)
buf = self.openurl(req).read()
try:
r = json.loads(buf)
except ValueError:
raise ValueError(buf)
return r
def get_exception(self, e):
if isinstance(e, urllib2.HTTPError) and hasattr(e, 'getcode'):
if e.getcode() in (410,):
return BrowserHTTPNotFound
return BaseBrowser.get_exception(self, e)
def home(self):
r = self.api_request('home/')
self.my_sex = r['user']['sex']
self.my_id = int(r['user']['id'])
self.my_name = r['user']['pseudo']
if self.my_coords == (0,0):
profile = self.get_profile(self.my_id)
if 'lat' in profile and 'lng' in profile:
self.my_coords = [profile['lat'], profile['lng']]
return r
def get_consts(self):
if self.consts is not None:
return self.consts
self.consts = [{}, {}]
for key, sexes in self.api_request('values').iteritems():
for sex, values in sexes.iteritems():
if sex in ('boy', 'both'):
self.consts[0][key] = values
if sex in ('girl', 'both'):
self.consts[1][key] = values
return self.consts
def score(self):
r = self.home()
return int(r['user']['points'])
def get_my_name(self):
return self.my_name
def get_my_id(self):
return self.my_id
def nb_new_mails(self):
r = self.home()
return r['counters']['new_mails']
def nb_new_baskets(self):
r = self.home()
return r['counters']['new_baskets']
def nb_new_visites(self):
r = self.home()
return r['counters']['new_visits']
def nb_available_charms(self):
r = self.home()
return r['subscription']['flashes_stock']
def get_baskets(self):
r = self.api_request('basket', count=30, offset=0)
return r['results']
def get_flashs(self):
r = self.api_request('charms/', count=30, offset=0)
return r['results']
def get_visits(self):
r = self.api_request('visits', count=30, offset=0)
return r['results']
def get_threads_list(self, count=30):
r = self.api_request('threads', count=count, offset=0)
return r['results']
@url2id
def get_thread_mails(self, id, count=30):
r = self.api_request('threads/%s' % id, count=count, offset=0)
return r
@url2id
def post_mail(self, id, content):
content = content.replace('\n', '\r\n')
try:
self.api_request('threads/%s' % id, data=content)
except BrowserHTTPNotFound:
raise CantSendMessage('Unable to send message.')
@url2id
def delete_thread(self, id):
r = self.api_request('message', 'delete', data={'id_user': id})
self.logger.debug('Thread deleted: %r' % r)
@url2id
def send_charm(self, id):
try:
self.api_request('users/%s/charms' % id, data='')
except BrowserHTTPNotFound:
return False
else:
return True
@url2id
def add_basket(self, id):
try:
self.api_request('basket/%s' % id, data='')
except BrowserHTTPNotFound:
return False
else:
return True
def search_profiles(self, **kwargs):
if not self.search_query:
# retrieve query
self.login()
r = self.api_request('users?count=100&offset=0&%s' % self.search_query)
ids = [s['id'] for s in r['results']]
return set(ids)
@url2id
def get_profile(self, id, with_pics=True):
# XXX OLD API IS DISABLED (fucking faggots)
#r = self.api0_request('member', 'view', data={'id': id})
#if not 'result' in r:
# print r
#profile = r['result']['member']
profile = {}
profile.update(self.api_request('users/%s' % id))
try:
doc = self.get_document(self.openurl('http://www.adopteunmec.com/profile/%s' % id))
except BrowserUnavailable:
self.logger.warning('Unable to find profile of %s on website' % id)
else:
profile['popu'] = {}
for tr in doc.xpath('//div[@id="popularity"]//tr'):
cols = tr.findall('td')
if cols[0].text is None:
continue
key = self.parser.tocleanstring(tr.find('th')).strip().lower()
value = int(re.sub(u'[ \xa0x]+', u'', cols[0].text).strip())
profile['popu'][key] = value
for script in doc.xpath('//script'):
text = script.text
if text is None:
continue
m = re.search('memberLat: ([\-\d\.]+),', text, re.IGNORECASE)
if m:
profile['lat'] = float(m.group(1))
m = re.search('memberLng: ([\-\d\.]+),', text, re.IGNORECASE)
if m:
profile['lng'] = float(m.group(1))
# 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
return profile
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/captcha.py 0000664 0000000 0000000 00000015124 12117742547 0025362 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 .
import hashlib
import sys
try:
import Image
except ImportError:
raise ImportError('Please install python-imaging')
class CaptchaError(Exception): pass
class Tile(object):
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(object):
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(object):
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()
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/contact.py 0000664 0000000 0000000 00000030574 12117742547 0025420 0 ustar 00root root 0000000 0000000 # -*- 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 .
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(object):
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 FieldList(FieldBase):
def get_value(self, profile, consts):
return profile[self.key]
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 FieldConst(FieldBase):
def get_value(self, profile, consts):
v = profile[self.key]
if isinstance(v, (basestring,int)):
try:
return consts[self.key][str(v)]
except KeyError:
return ''
elif isinstance(v, (tuple,list)):
labels = []
for i in v:
labels.append(consts[self.key][i])
return labels
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')),
('charms', FieldPopu('charmes')),
('visites', FieldPopu('visites')),
('baskets', FieldPopu('panier')),
('invits', FieldPopu('invits')),
('bonus', FieldPopu('bonus')),
('score', FieldStr('points')),
('ratio', FieldPopuRatio('mails', 'charmes')),
('mailable', FieldBool('can_mail')),
))),
('details', OrderedDict((
('old', FieldStr('age')),
#('old', FieldOld('birthday')),
('birthday', FieldStr('birthday')),
('zipcode', FieldStr('zip')),
('location', FieldStr('city')),
('distance', FieldDist('dist')),
('country', FieldStr('country')),
('phone', FieldStr('phone')),
('eyes', FieldConst('eyes_color')),
('hair_color', FieldConst('hair_color')),
('hair_size', FieldConst('hair_size')),
('height', FieldConst('size')),
('weight', FieldConst('weight')),
('BMI', FieldBMI('size', 'weight')),
('fat', FieldBMI('size', 'weight', fat=True)),
('shape', FieldConst('shape')),
('origins', FieldConst('origins')),
('signs', FieldConst('features')),
('job', FieldStr('job')),
('style', FieldConst('style')),
('food', FieldConst('diet')),
('favorite_food', FieldConst('favourite_food')),
('drink', FieldConst('alcohol')),
('smoke', FieldConst('tobacco')),
))),
('tastes', OrderedDict((
('hobbies', FieldStr('hobbies')),
('music', FieldList('music')),
('cinema', FieldList('cinema')),
('books', FieldList('books')),
('tv', FieldList('tvs')),
))),
('+sex', OrderedDict((
('underwear', FieldConst('underwear')),
('practices', FieldConst('sexgames')),
('favorite', FieldConst('arousing')),
('toys', FieldConst('sextoys')),
))),
('+personality', OrderedDict((
('snap', FieldStr('fall_for')),
('exciting', FieldStr('turned_on_by')),
('hate', FieldStr('cant_stand')),
('vices', FieldStr('vices')),
('assets', FieldStr('assets')),
('fantasies', FieldStr('fantasies')),
('is', FieldConst('character')),
)))
))
def parse_profile(self, profile, consts):
if profile['online']:
self.status = Contact.STATUS_ONLINE
self.status_msg = u'online'
self.status_msg = u'since %s' % profile['last_cnx']
else:
self.status = Contact.STATUS_OFFLINE
self.status_msg = u'last connection %s' % profile['last_cnx']
self.summary = profile.get('announce', '').strip()
if len(profile.get('shopping_list', '')) > 0:
self.summary += u'\n\nLooking for:\n%s' % profile['shopping_list'].strip()
for photo in profile['pics']:
self.set_photo(photo.split('/')[-1],
url=photo + '/full',
thumbnail_url=photo + '/small',
hidden=False)
self.profile = OrderedDict()
if 'sex' in profile:
for section, d in self.TABLE.iteritems():
flags = ProfileNode.SECTION
if section.startswith('_'):
flags |= ProfileNode.HEAD
if (section.startswith('+') and int(profile['sex']) != 1) or \
(section.startswith('-') and int(profile['sex']) != 0):
continue
section = section.lstrip('_+-')
s = ProfileNode(section, section.capitalize(), OrderedDict(), flags=flags)
for key, builder in d.iteritems():
try:
value = builder.get_value(profile, consts[int(profile['sex'])])
except KeyError:
pass
else:
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
woob-05fab92a48047cc78177f37583b6955b0fdc2bca-modules/modules/aum/favicon.png 0000664 0000000 0000000 00000004217 12117742547 0025541 0 ustar 00root root 0000000 0000000 PNG
IHDR @ @ % sRGB pHYs tIME 2 !IDAThip]=!ᒅbFAE.P):J]AjVjGFvZ3.R
U[VK$D,I{O?2%عoy{s"("(Te|D.[)EtV4I?2
+eL'vI?R)FP\0BB.l0AiSN~E0:5KC+O8x>[y}!jpNqx2έ>Y98=eO&n)
oްoFĸ!Aq$~=~BOM_gdZnKa1Eb?{GC!QSeS|7ϩq>*3lL0Ɖx-|O^Ԓ6E[dzV>*=z/zTbxj.ng:4@߫kS:)D]&dؕ!K3IJB/h><B$`+;\M
f!(-G72A=ǹ^䒶F.rOG Uԣc\9]|v'ʹ"|
iJuc>UR}?-(7Ҧ3eWPx!yI ![=n$HUCKe:w%HO?D>(KJ!s7,$~f}7uOC͔.чmp)%`|ZUAdqIO|W($o::hoH%mx9/{^dcʷ'Ħ^ZƓER%
80