diff --git a/modules/unsee/__init__.py b/modules/unsee/__init__.py new file mode 100644 index 00000000..31ad5ac2 --- /dev/null +++ b/modules/unsee/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2014 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from .backend import UnseeBackend + + +__all__ = ['UnseeBackend'] diff --git a/modules/unsee/backend.py b/modules/unsee/backend.py new file mode 100644 index 00000000..36459cec --- /dev/null +++ b/modules/unsee/backend.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2014 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.backend import BaseBackend +from weboob.capabilities.paste import ICapPaste, BasePaste +from weboob.tools.capabilities.paste import image_mime +import re + +from .browser import UnseeBrowser + + +__all__ = ['UnseeBackend'] + + +class UnPaste(BasePaste): + @classmethod + def id2url(cls, id): + return 'https://unsee.cc/%s' % id + + +class UnseeBackend(BaseBackend, ICapPaste): + NAME = 'unsee' + DESCRIPTION = u'unsee.cc expiring image hosting' + MAINTAINER = u'Vincent A' + EMAIL = 'dev@indigo.re' + LICENSE = 'AGPLv3+' + VERSION = '0.i' + + BROWSER = UnseeBrowser + + def can_post(self, contents, title=None, public=None, max_age=None): + if re.search(r'[^a-zA-Z0-9=+/\s]', contents): + return 0 + elif max_age < 3600: + return 0 + else: + mime = image_mime(contents, ('gif', 'jpeg', 'png')) + return 20 * int(mime is not None) + + def get_paste(self, id): + paste = UnPaste(id) + paste.contents = self.browser.get_image(id).encode('base64') + return paste + + def new_paste(self, *a, **kw): + return UnPaste(*a, **kw) + + def post_paste(self, paste, max_age=None): + d = self.browser.post_image(paste.title, paste.contents.decode('base64'), max_age) + paste.id = d['id'] + return paste + diff --git a/modules/unsee/browser.py b/modules/unsee/browser.py new file mode 100644 index 00000000..468bdd08 --- /dev/null +++ b/modules/unsee/browser.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2014 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.browser import BaseBrowser +import os +from uuid import uuid4 +from urllib2 import Request +from urlparse import urljoin +from weboob.tools.json import json +from weboob.tools.parsers.lxmlparser import LxmlHtmlParser + + +__all__ = ['UnseeBrowser'] + + +def to_bytes(s): + if isinstance(s, unicode): + return s.encode('utf-8') + else: + return s + +class FileField(object): + def __init__(self, filename, contents=None, headers=None): + self.filename = to_bytes(os.path.basename(filename)) + self.headers = headers or {} + if contents is not None: + self.contents = contents + else: + self.contents = open(filename).read() + + +class UnseeBrowser(BaseBrowser): + PROTOCOL = 'https' + DOMAIN = 'unsee.cc' + ENCODING = 'utf-8' + + def _make_boundary(self): + return '==%s==' % uuid4() + + def _make_multipart(self, pairs, boundary): + s = [] + for k, v in pairs: + s.append('--%s' % boundary) + if isinstance(v, FileField): + s.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (k, v.filename)) + for hk, hv in v.headers.items(): + s.append('%s: %s' % (hk, hv)) + v = v.contents + else: + s.append('Content-Disposition: form-data; name="%s"' % k) + s.append('') + s.append(to_bytes(v)) + s.append('--%s--' % boundary) + s.append('') + return '\r\n'.join(s) + + def _multipart(self, url, fields): + b = self._make_boundary() + data = self._make_multipart(fields, b) + headers = {'Content-type': 'multipart/form-data; boundary=%s' % b, 'Content-length': len(data)} + return Request(url, data=self._make_multipart(fields, b), headers=headers) + + def post_image(self, name, contents, max_age): + if max_age >= 86400: + time = 'week' + elif max_age >= 3600: + time = 'day' + else: + time = 'hour' + # time='first' for one-shot view + + params = [('time', time), ('image[]', FileField(name or '-', contents))] + request = self._multipart('https://unsee.cc/upload/', params) + + d = json.loads(self.readurl(request)) + return {'id': d['hash']} + + def get_image(self, id): + doc = self.get_document(self.openurl('https://unsee.cc/%s/' % id)) + images = LxmlHtmlParser.select(doc, '//img/@src[starts-with(., "/image/")]', method='xpath') + url = urljoin('https://unsee.cc', images[0]) + return self.readurl(url) diff --git a/modules/unsee/favicon.png b/modules/unsee/favicon.png new file mode 100644 index 00000000..454f0b01 Binary files /dev/null and b/modules/unsee/favicon.png differ diff --git a/modules/unsee/test.py b/modules/unsee/test.py new file mode 100644 index 00000000..919b48d0 --- /dev/null +++ b/modules/unsee/test.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2014 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.test import BackendTest + + +class UnseeTest(BackendTest): + BACKEND = 'unsee' + + # small gif file + DATA = 'R0lGODlhAQABAIAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==\n' + + def test_unsee(self): + assert self.backend.can_post(self.DATA, max_age=3600) + + post = self.backend.new_paste(None) + post.contents = self.DATA + post = self.backend.post_paste(post, max_age=3600) + assert post.id + + got = self.backend.get_paste(post.id) + assert got + assert got.contents.decode('base64').startswith('GIF89a') + # we can't check the exact data, unsee.cc modifies the images...