diff --git a/plinth/modules/diaspora/manifest.py b/plinth/modules/diaspora/manifest.py index 0ec1578c8..1afd28ed2 100644 --- a/plinth/modules/diaspora/manifest.py +++ b/plinth/modules/diaspora/manifest.py @@ -31,11 +31,9 @@ clients = [{ 'platforms': [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, - 'os_version': '>4.2.0', 'store_name': Store.F_DROID.value, 'url': 'https://f-droid.org/repository/browse/?fdid=com' '.github.dfa.diaspora_android ', - 'fully_qualified_name': 'com.github.dfa.diaspora_android' }] }, { 'name': diff --git a/plinth/modules/ejabberd/manifest.py b/plinth/modules/ejabberd/manifest.py index c35fa1767..b80eca90a 100644 --- a/plinth/modules/ejabberd/manifest.py +++ b/plinth/modules/ejabberd/manifest.py @@ -19,17 +19,20 @@ from django.utils.translation import ugettext_lazy as _ from plinth.modules.jsxc import manifest as jsxc_manifest from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import play_store_url -clients = [{ +yaxim_package_id = 'org.yaxim.androidclient' +bruno_package_id = 'org.yaxim.bruno' +conversations_package_id = 'eu.siacs.conversations' + +_clients = [{ 'name': _('yaxim'), 'platforms': [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'fully_qualified_name': 'org.yaxim.androidclient', - 'url': 'https://play.google.com/store/apps/details?id=org' - '.yaxim.androidclient ' + 'url': play_store_url(yaxim_package_id), }] }, { 'name': @@ -41,9 +44,7 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'fully_qualified_name': 'org.yaxim.bruno', - 'url': 'https://play.google.com/store/apps/details?id' - '=org.yaxim.bruno ' + 'url': play_store_url(bruno_package_id) }] }, { 'name': @@ -70,49 +71,30 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'url': 'https://play.google.com/store/apps/details?id' - '=eu.siacs.conversations ', - 'fully_qualified_name': 'eu.siacs.conversations' + 'url': play_store_url(conversations_package_id) }] }, { 'name': _('Dino'), 'platforms': [{ 'type': 'download', - 'os': 'Debian', - 'url': 'https://download.opensuse.org/repositories/network' - ':/messaging:/xmpp:/dino/Debian_9.0/amd64/dino_0.0' - '~git178.9d8e1e8_amd64.deb', + 'os': Desktop_OS.GNU_LINUX, + 'url': 'https://github.com/dino/dino/wiki/Distribution-Packages', }] }, { 'name': _('Gajim'), 'platforms': [{ - 'type': 'apt', + 'type': 'package', 'os': 'Debian', 'package_name': 'gajim' }, { 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'url': 'https://gajim.org/downloads/0.16/gajim-0.16.8-2.exe' + 'url': 'https://gajim.org/downloads.php' }] -}, { - 'name': - _('OneTeam'), - 'platforms': [{ - 'type': 'download', - 'os': Desktop_OS.WINDOWS.value, - 'url': 'https://download.process-one.net/oneteam/release' - '-installers/OneTeam.msi' - }, { - 'type': 'download', - 'os': Desktop_OS.MAC_OS.value, - 'url': 'https://download.process-one.net/oneteam/release' - '-installers/OneTeam.dmg ' - }, { - 'type': 'download', - 'os': 'Linux', - 'url': 'https://download.process-one.net/oneteam/release' - '-installers/oneteam.tar.bz2 ' - }] -}].append(jsxc_manifest.clients) +}] + +_clients.extend(jsxc_manifest.clients) + +clients = _clients diff --git a/plinth/modules/infinoted/manifest.py b/plinth/modules/infinoted/manifest.py index 72f6db676..46b1d3502 100644 --- a/plinth/modules/infinoted/manifest.py +++ b/plinth/modules/infinoted/manifest.py @@ -36,7 +36,7 @@ clients = [{ 'os': Desktop_OS.WINDOWS.value, 'url': 'http://releases.0x539.de/gobby/gobby-stable.exe' }, { - 'type': 'apt', + 'type': 'package', 'os': 'Debian', 'package_name': 'gobby' }] diff --git a/plinth/modules/jsxc/manifest.py b/plinth/modules/jsxc/manifest.py index 313db43d0..4c83196e8 100644 --- a/plinth/modules/jsxc/manifest.py +++ b/plinth/modules/jsxc/manifest.py @@ -17,15 +17,15 @@ from django.utils.translation import ugettext_lazy as _ -clients = [ - { - 'name': _('JSXC'), - 'platforms': [ - { - 'type': 'apt', - 'os': 'Debian', - 'package_name': 'libjs-jsxc' - } - ] - } -] +clients = [{ + 'name': + _('JSXC'), + 'platforms': [{ + 'type': 'package', + 'os': 'Debian', + 'package_name': 'libjs-jsxc' + }, { + 'type': 'web', + 'url': '/jsxc' + }] +}] diff --git a/plinth/modules/matrixsynapse/manifest.py b/plinth/modules/matrixsynapse/manifest.py index 0a981ce0e..9023f192e 100644 --- a/plinth/modules/matrixsynapse/manifest.py +++ b/plinth/modules/matrixsynapse/manifest.py @@ -18,6 +18,10 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import f_droid_url, play_store_url + +android_package_id = 'im.vector.alpha' +riot_desktop_download_url = 'https://riot.im/desktop.html' clients = [{ 'name': @@ -27,17 +31,13 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'fully_qualified_name': 'im.vector.alpha', - 'url': 'https://play.google.com/store/apps/details?id=im' - '.vector.alpha ' + 'url': play_store_url(android_package_id) }, { 'type': 'store', 'os': Mobile_OS.ANDROID.value, - 'os_version': '>=6.0', 'store_name': Store.F_DROID.value, - 'fully_qualified_name': 'im.vector.alpha', - 'url': 'https://f-droid.org/packages/im.vector.alpha/' + 'url': f_droid_url(android_package_id) }, { 'type': 'web', @@ -46,18 +46,17 @@ clients = [{ { 'type': 'download', 'os': Desktop_OS.GNU_LINUX.value, - 'url': 'https://riot.im/desktop.html' + 'url': riot_desktop_download_url, }, { 'type': 'download', 'os': Desktop_OS.MAC_OS.value, - 'url': 'https://riot.im/desktop.html' + 'url': riot_desktop_download_url, }, { 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'os_version': '>=7', - 'url': 'https://riot.im/desktop.html' + 'url': riot_desktop_download_url, }, ] }] diff --git a/plinth/modules/minetest/manifest.py b/plinth/modules/minetest/manifest.py index db556ba63..90332ff7d 100644 --- a/plinth/modules/minetest/manifest.py +++ b/plinth/modules/minetest/manifest.py @@ -25,22 +25,18 @@ clients = [{ 'platforms': [{ 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'arch': 'amd64', - 'os_version': 'XP, Vista, >=7', 'url': 'https://github.com/minetest/minetest/releases' '/download/0.4.16/minetest-0.4.16-win64.zip ' }, { 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'fully_qualified_name': 'net.minetest.minetest', 'url': 'https://play.google.com/store/apps/details?id=net' '.minetest.minetest ' }, { 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.F_DROID.value, - 'fully_qualified_name': 'net.minetest.minetest', 'url': 'https://f-droid.org/packages/net.minetest.minetest/ ' }, { 'type': 'package', diff --git a/plinth/modules/mumble/manifest.py b/plinth/modules/mumble/manifest.py index f9aaf7b30..c3b950bdb 100644 --- a/plinth/modules/mumble/manifest.py +++ b/plinth/modules/mumble/manifest.py @@ -18,6 +18,9 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import f_droid_url, play_store_url + +plumble_package_id = 'com.morlunk.mumbleclient.free' clients = [{ 'name': @@ -25,14 +28,11 @@ clients = [{ 'platforms': [{ 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'arch': 'amd64', - 'url': 'https://dl.mumble.info/mumble-1.3.0~2569~gd196a4b' - '~snapshot.winx64.msi ' + 'url': 'https://wiki.mumble.info/wiki/Main_Page' }, { 'type': 'download', 'os': Desktop_OS.MAC_OS.value, - 'url': 'https://github.com/mumble-voip/mumble/releases' - '/download/1.2.19/Mumble-1.2.19.dmg ' + 'url': 'https://wiki.mumble.info/wiki/Main_Page' }, { 'type': 'package', 'format': 'deb', @@ -40,7 +40,6 @@ clients = [{ }, { 'type': 'store', 'os': Mobile_OS.IOS.value, - 'os_version': '>=8.0', 'store_name': Store.APP_STORE.value, 'url': 'https://itunes.apple.com/us/app/mumble/id443472808' }] @@ -51,16 +50,12 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'url': 'https://play.google.com/store/apps/details?id=com' - '.morlunk.mumbleclient.free ', - 'fully_qualified_name': 'com.morlunk.mumbleclient' + 'url': play_store_url(plumble_package_id) }, { 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.F_DROID.value, - 'url': 'https://play.google.com/store/apps/details?id=com' - '.morlunk.mumbleclient.free ', - 'fully_qualified_name': 'com.morlunk.mumbleclient' + 'url': f_droid_url(plumble_package_id) }] }, { 'name': @@ -68,7 +63,6 @@ clients = [{ 'platforms': [{ 'type': 'store', 'os': Mobile_OS.IOS.value, - 'os_version': '>=7.0', 'store_name': Store.APP_STORE.value, 'url': 'https://itunes.apple.com/dk/app/mumblefy/id858752232' }] diff --git a/plinth/modules/quassel/manifest.py b/plinth/modules/quassel/manifest.py index f886cd440..f763f28ff 100644 --- a/plinth/modules/quassel/manifest.py +++ b/plinth/modules/quassel/manifest.py @@ -18,6 +18,10 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import play_store_url + +quasseldroid_package_id = 'com.iskrembilen.quasseldroid', +quassel_download_url = 'http://quassel-irc.org/downloads' clients = [{ 'name': @@ -25,12 +29,11 @@ clients = [{ 'platforms': [{ 'type': 'download', 'os': Desktop_OS.MAC_OS.value, - 'url': 'http://quassel-irc.org/pub/QuasselClient_MacOSX' - '-x86_64_0.12.4.dmg' + 'url': quassel_download_url, }, { - 'type': 'apt', + 'type': 'package', 'os': 'Debian', - 'package_name': 'quassel-client' + 'package_name': 'quassel-client', }] }, { 'name': @@ -39,8 +42,6 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'url': 'https://play.google.com/store/apps/details?id=com' - '.iskrembilen.quasseldroid', - 'fully_qualified_name': 'com.iskrembilen.quasseldroid' + 'url': play_store_url(quasseldroid_package_id), }] }] diff --git a/plinth/modules/radicale/manifest.py b/plinth/modules/radicale/manifest.py index 3addbf01b..ec44426f9 100644 --- a/plinth/modules/radicale/manifest.py +++ b/plinth/modules/radicale/manifest.py @@ -18,6 +18,9 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Mobile_OS, Store +from plinth.utils import play_store_url + +davdroid_package_id = 'at.bitfire.davdroid' clients = [{ 'name': @@ -31,15 +34,13 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'url': 'https://play.google.com/store/apps/details?id=at' - '.bitfire.davdroid', - 'fully_qualified_name': 'at.bitfire.davdroid' + 'url': play_store_url(davdroid_package_id), }] }, { 'name': _('GNOME Calendar'), 'platforms': [{ - 'type': 'apt', + 'type': 'package', 'os': 'Debian', 'package-name': 'gnome-calendar' }] @@ -57,7 +58,7 @@ clients = [{ 'Clicking on the search button will list the existing ' 'calendars and address books.'), 'platforms': [{ - 'type': 'apt', + 'type': 'package', 'os': 'Debian', 'package-name': 'gnome-calendar' }] diff --git a/plinth/modules/repro/manifest.py b/plinth/modules/repro/manifest.py index b3b036368..9e3ed6160 100644 --- a/plinth/modules/repro/manifest.py +++ b/plinth/modules/repro/manifest.py @@ -18,6 +18,12 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import play_store_url + +jitsi_package_id = 'org.jitsi.meet' +csipsimple_package_id = 'com.csipsimple' + +jitsi_download_url = 'https://download.jitsi.org/jitsi/' clients = [{ 'name': @@ -35,9 +41,7 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'fully_qualified_name': 'org.jitsi.meet', - 'url': 'https://play.google.com/store/apps/details?id=org' - '.jitsi.meet ' + 'url': play_store_url(jitsi_package_id) }, { 'type': 'store', 'os': Mobile_OS.IOS.value, @@ -46,7 +50,7 @@ clients = [{ }, { 'type': 'download', 'os': Desktop_OS.GNU_LINUX.value, - 'url': 'https://download.jitsi.org/jitsi/debian/' + 'url': jitsi_download_url }, { 'type': 'package', 'format': 'deb', @@ -54,13 +58,11 @@ clients = [{ }, { 'type': 'download', 'os': Desktop_OS.MAC_OS.value, - 'url': 'https://download.jitsi.org/jitsi/macosx/jitsi-latest' - '.dmg ' + 'url': jitsi_download_url }, { 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'url': 'https://download.jitsi.org/jitsi/windows/jitsi-latest' - '-x86.exe ' + 'url': jitsi_download_url }] }, { 'name': @@ -69,8 +71,6 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'fully_qualified_name': 'com.csipsimple', - 'url': 'https://play.google.com/store/apps/details?id=com' - '.csipsimple ' + 'url': play_store_url(csipsimple_package_id) }] }] diff --git a/plinth/modules/syncthing/manifest.py b/plinth/modules/syncthing/manifest.py index 3ec6de5fc..230417da0 100644 --- a/plinth/modules/syncthing/manifest.py +++ b/plinth/modules/syncthing/manifest.py @@ -18,13 +18,10 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import f_droid_url, play_store_url -metadata = { - 'syncthing': { - 'version': '0.14.39', - 'android-package-id': 'com.nutomic.syncthingandroid', - }, -} +syncthing_package_id = 'com.nutomic.syncthingandroid' +syncthing_download_url = 'https://syncthing.net/' clients = [{ 'name': @@ -40,48 +37,25 @@ clients = [{ }, { 'type': 'download', 'os': Desktop_OS.GNU_LINUX.value, - 'arch': 'amd64', - 'url': 'https://github.com/syncthing/syncthing/releases/' - 'download/v{0}/syncthing-linux-amd64-v{0}.tar.gz' - .format(metadata['syncthing']['version']), + 'url': syncthing_download_url, }, { 'type': 'download', 'os': Desktop_OS.MAC_OS.value, - 'arch': 'amd64', - 'url': 'https://github.com/syncthing/syncthing/releases/' - 'download/v{0}/syncthing-macosx-amd64-v{0}.tar.gz' - .format(metadata['syncthing']['version']), + 'url': syncthing_download_url, }, { 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'arch': 'amd64', - 'url': 'https://github.com/syncthing/syncthing/releases/' - 'download/v{0}/syncthing-windows-amd64-v{0}.zip' - .format(metadata['syncthing']['version']), + 'url': syncthing_download_url, }, { - 'type': - 'store', - 'os': - Mobile_OS.ANDROID.value, - 'store_name': - Store.GOOGLE_PLAY.value, - 'fully_qualified_name': - metadata['syncthing']['android-package-id'], - 'url': - 'https://play.google.com/store/apps/details?id={}' - .format(metadata['syncthing']['android-package-id']) + 'type': 'store', + 'os': Mobile_OS.ANDROID.value, + 'store_name': Store.GOOGLE_PLAY.value, + 'url': play_store_url(syncthing_package_id) }, { - 'type': - 'store', - 'os': - Mobile_OS.ANDROID.value, - 'store_name': - Store.F_DROID.value, - 'fully_qualified_name': - metadata['syncthing']['android-package-id'], - 'url': - 'https://f-droid.org/packages/{}' - .format(metadata['syncthing']['android-package-id']) + 'type': 'store', + 'os': Mobile_OS.ANDROID.value, + 'store_name': Store.F_DROID.value, + 'url': f_droid_url(syncthing_package_id) }, { 'type': 'web', 'url': '/syncthing' diff --git a/plinth/modules/tor/manifest.py b/plinth/modules/tor/manifest.py index 2216dc72c..fff134bf2 100644 --- a/plinth/modules/tor/manifest.py +++ b/plinth/modules/tor/manifest.py @@ -18,8 +18,10 @@ from django.utils.translation import ugettext_lazy as _ from plinth.templatetags.plinth_extras import Desktop_OS, Mobile_OS, Store +from plinth.utils import f_droid_url, play_store_url -version = '7.0.6' +orbot_package_id = 'org.torproject.android' +tor_browser_download_url = 'https://www.torproject.org/download/download-easy.html' clients = [{ 'name': @@ -27,20 +29,15 @@ clients = [{ 'platforms': [{ 'type': 'download', 'os': Desktop_OS.WINDOWS.value, - 'os_version': 'Windows XP, Vista, >=7', - 'url': 'https://www.torproject.org/dist/torbrowser/{v}' - '/torbrowser-install-{v}_en-US.exe '.format(v=version) + 'url': tor_browser_download_url, }, { 'type': 'download', 'os': Desktop_OS.GNU_LINUX.value, - 'url': 'https://www.torproject.org/dist/torbrowser/{v}/tor' - '-browser-linux64-{v} _en-US.tar.xz'.format(v=version) + 'url': tor_browser_download_url, }, { 'type': 'download', 'os': Desktop_OS.MAC_OS.value, - 'arch': 'amd64', - 'url': 'https://www.torproject.org/dist/torbrowser/{v}' - '/TorBrowser-{v}-osx64_en-US.dmg'.format(v=version) + 'url': tor_browser_download_url, }] }, { 'name': @@ -49,15 +46,11 @@ clients = [{ 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.GOOGLE_PLAY.value, - 'url': 'https://play.google.com/store/apps/details?id=org' - '.torproject.android', - 'fully_qualified_name': 'org.torproject.android' + 'url': play_store_url(orbot_package_id) }, { 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.F_DROID.value, - 'url': 'https://play.google.com/store/apps/details?id=org' - '.torproject.android', - 'fully_qualified_name': 'org.torproject.android' + 'url': f_droid_url(orbot_package_id) }] }] diff --git a/plinth/modules/ttrss/manifest.py b/plinth/modules/ttrss/manifest.py index 92cbbab5b..d4ee2cb6b 100644 --- a/plinth/modules/ttrss/manifest.py +++ b/plinth/modules/ttrss/manifest.py @@ -28,13 +28,11 @@ clients = [{ 'store_name': Store.GOOGLE_PLAY.value, 'url': 'https://play.google.com/store/apps/details?id=org' '.ttrssreader', - 'fully_qualified_name': 'org.ttrssreader' }, { 'type': 'store', 'os': Mobile_OS.ANDROID.value, 'store_name': Store.F_DROID.value, 'url': 'https://f-droid.org/packages/org.ttrssreader/', - 'fully_qualified_name': 'org.ttrssreader' }, { 'type': 'web', 'url': '/tt-rss' diff --git a/plinth/templatetags/plinth_extras.py b/plinth/templatetags/plinth_extras.py index 0b13671fb..efe4541cf 100644 --- a/plinth/templatetags/plinth_extras.py +++ b/plinth/templatetags/plinth_extras.py @@ -40,7 +40,7 @@ class Store(Enum): GOOGLE_PLAY = 'google-play' -def string_values(enum): +def enum_values(enum): return [x.value for x in list(enum)] @@ -94,14 +94,14 @@ def __check(clients, cond): def has_desktop_clients(clients): """Filter to find out whether an application has desktop clients""" return __check(clients, - lambda x: x.get('os', '') in string_values(Desktop_OS)) + lambda x: x.get('os', '') in enum_values(Desktop_OS)) @register.filter(name='has_mobile_clients') def has_mobile_clients(clients): """Filter to find out whether an application has mobile clients""" return __check(clients, - lambda x: x.get('os', '') in string_values(Mobile_OS)) + lambda x: x.get('os', '') in enum_values(Mobile_OS)) @register.filter(name='has_web_clients') @@ -112,12 +112,10 @@ def has_web_clients(clients): @register.filter(name='of_type') def of_type(clients, typ): - """Filter clients of a particular type""" - if typ == 'mobile': - return list(filter(has_mobile_clients, clients)) - elif typ == 'desktop': - return list(filter(has_desktop_clients, clients)) - elif typ == 'web': - return list(filter(has_web_clients, clients)) - else: - return clients + """Filter and get clients of a particular type""" + filters = { + 'mobile': has_mobile_clients, + 'desktop': has_desktop_clients, + 'web': has_web_clients + } + return list(filter(filters.get(typ, lambda x: x), clients)) diff --git a/plinth/utils.py b/plinth/utils.py index e0a6856db..20ccfe6ac 100644 --- a/plinth/utils.py +++ b/plinth/utils.py @@ -14,15 +14,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ Miscellaneous utility methods. """ import importlib import os -import ruamel.yaml + from django.utils.functional import lazy + +import ruamel.yaml from plinth.modules import names @@ -83,12 +84,14 @@ def get_domain_names(): class YAMLFile(object): """A context management class for updating YAML files""" + def __init__(self, yaml_file, post_exit=None): """Return a context object for the YAML file. Parameters: yaml_file - the YAML file to update. - post_exit - a function that will be called after updating the YAML file. + post_exit - a function that will be called after + updating the YAML file. """ self.yaml_file = yaml_file self.post_exit = post_exit @@ -112,3 +115,12 @@ class YAMLFile(object): def is_file_empty(self): return os.stat(self.yaml_file).st_size == 0 + + +def play_store_url(package_id): + return 'https://play.google.com/store/apps/details?id={}'.format( + package_id) + + +def f_droid_url(package_id): + return 'https://f-droid.org/packages/{}'.format(package_id)