diff --git a/modules/sprunge/__init__.py b/modules/sprunge/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..70131fe90d425a4d648577ce952f3f139d3c91ca
--- /dev/null
+++ b/modules/sprunge/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2017 Laurent Bachelier
+#
+# 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 .module import SprungeModule
+
+__all__ = ['SprungeModule']
diff --git a/modules/sprunge/browser.py b/modules/sprunge/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..d03a5076815c9a3915473ecdd73f019da71ac66e
--- /dev/null
+++ b/modules/sprunge/browser.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2017 Laurent Bachelier
+#
+# 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.browser.browsers import PagesBrowser
+from weboob.browser.elements import ItemElement, method
+from weboob.browser.filters.standard import BrowserURL, Env, Field
+from weboob.browser.pages import HTMLPage
+from weboob.browser.url import URL
+from weboob.capabilities.base import NotAvailable
+from weboob.capabilities.paste import BasePaste, PasteNotFound
+
+
+class SprungePaste(BasePaste):
+ # all pastes are private
+ public = False
+
+ # TODO perhaps move this logic elsewhere, remove this and id2url from capability
+ # (page_url is required by pastoob)
+ @property
+ def page_url(self):
+ return self.url
+
+
+class PastePage(HTMLPage):
+ @method
+ class fill_paste(ItemElement):
+ klass = SprungePaste
+
+ obj_id = Env('id')
+ obj_title = NotAvailable
+
+ def obj_contents(self):
+ text = self.page.response.text
+ # Sprunge seems to add a newline to our original text
+ if text.endswith(u'\n'):
+ text = text[:-1]
+ return text
+
+ obj_url = BrowserURL('paste', id=Field('id'))
+
+ def validate(self, obj):
+ if obj.contents == u'%s not found.' % obj.id:
+ raise PasteNotFound()
+ return True
+
+
+class SprungeBrowser(PagesBrowser):
+ BASEURL = 'http://sprunge.us/'
+
+ paste = URL(r'(?P\w+)', PastePage)
+ post = URL(r'$')
+
+ @paste.id2url
+ def get_paste(self, url):
+ url = self.absurl(url, base=True)
+ m = self.paste.match(url)
+ if m:
+ return SprungePaste(m.groupdict()['id'])
+
+ def fill_paste(self, paste):
+ """
+ Get as much as information possible from the paste page
+ """
+ return self.paste.stay_or_go(id=paste.id).fill_paste(paste)
+
+ def post_paste(self, paste):
+ url = self.post.open(data={'sprunge': paste.contents}).text.strip()
+ self.location(url)
+ return self.page.fill_paste(paste)
diff --git a/modules/sprunge/favicon.png b/modules/sprunge/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..45a7a136028855f4170a634094f6465fd3e7d1bd
Binary files /dev/null and b/modules/sprunge/favicon.png differ
diff --git a/modules/sprunge/module.py b/modules/sprunge/module.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8788f29abe8dfec9f0e0e5c924ae175e024e6b9
--- /dev/null
+++ b/modules/sprunge/module.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2017 Laurent Bachelier
+#
+# 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 Module
+from weboob.tools.capabilities.paste import BasePasteModule
+
+from .browser import SprungeBrowser, SprungePaste
+
+
+class SprungeModule(Module, BasePasteModule):
+ NAME = 'sprunge'
+ MAINTAINER = u'Laurent Bachelier'
+ EMAIL = 'laurent@bachelier.name'
+ VERSION = '1.4'
+ DESCRIPTION = u'Sprunge text sharing tool'
+ LICENSE = 'AGPLv3+'
+ BROWSER = SprungeBrowser
+
+ EXPIRATIONS = {
+ False: 'f',
+ }
+
+ def new_paste(self, *args, **kwargs):
+ return SprungePaste(*args, **kwargs)
+
+ def can_post(self, contents, title=None, public=None, max_age=None):
+ if public is True:
+ return 0
+ if max_age is not None:
+ if self.get_closest_expiration(max_age) is None:
+ return 0
+ if not title:
+ return 2
+ return 1
+
+ def get_paste(self, _id):
+ return self.browser.get_paste(_id)
+
+ def fill_paste(self, paste, fields):
+ self.browser.fill_paste(paste)
+ return paste
+
+ def post_paste(self, paste, max_age=None):
+ self.browser.post_paste(paste)
+
+ OBJECTS = {SprungePaste: fill_paste}
diff --git a/modules/sprunge/test.py b/modules/sprunge/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..07c0831bb118ab65c2ab99ce1fe33989faaa8abe
--- /dev/null
+++ b/modules/sprunge/test.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2017 Laurent Bachelier
+#
+# 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.base import NotAvailable
+from weboob.capabilities.paste import PasteNotFound
+from weboob.tools.test import BackendTest
+
+
+class SprungeTest(BackendTest):
+ MODULE = 'sprunge'
+
+ def _get_paste(self, _id):
+ # html method
+ p = self.backend.get_paste(_id)
+ self.backend.fillobj(p, ['title'])
+ assert p.title is NotAvailable
+ assert p.page_url.startswith('http://sprunge.us/')
+ assert p.public is False
+
+ def test_post(self):
+ p = self.backend.new_paste(None, contents=u'Weboob Test héhéhé')
+ self.backend.post_paste(p, max_age=False)
+ assert p.id
+ self.backend.fill_paste(p, ['title'])
+ assert p.title is NotAvailable
+ assert p.id in p.page_url
+ assert p.public is False
+
+ # test all get methods from the Paste we just created
+ self._get_paste(p.id)
+
+ # same but from the full URL
+ self._get_paste('http://sprunge.us/%s' % p.id)
+
+ def test_notfound(self):
+ for _id in ('ab',
+ 'http://sprunge.us/ab'):
+ # html method
+ p = self.backend.get_paste(_id)
+ self.assertRaises(PasteNotFound, self.backend.fillobj, p, ['title'])
+
+ # raw method
+ p = self.backend.get_paste(_id)
+ self.assertRaises(PasteNotFound, self.backend.fillobj, p, ['contents'])
+
+ def test_checkurl(self):
+ # call with an URL we can't handle with this backend
+ assert self.backend.get_paste('http://pastebin.com/nJG9ZFG8') is None
+
+ def test_can_post(self):
+ assert 2 == self.backend.can_post(u'hello', public=False)
+ assert 1 == self.backend.can_post(u'hello', public=False, title=u'hello')
+ assert 1 == self.backend.can_post(u'hello', title=u'hello')
+ assert 0 == self.backend.can_post(u'hello', public=True)
+ assert 0 == self.backend.can_post(u'hello', public=False, max_age=3600*24)