Commit d7e58ec1 authored by Alexander's avatar Alexander

[stickers] Implement the manual download of sticker packs

parent 06a13409
Pipeline #6870 failed with stage
...@@ -43,3 +43,4 @@ STICKERS_NAMESPACE = 'urn:xmpp:stickers:0' ...@@ -43,3 +43,4 @@ STICKERS_NAMESPACE = 'urn:xmpp:stickers:0'
SFS_NAMESPACE = 'urn:xmpp:sfs:0' SFS_NAMESPACE = 'urn:xmpp:sfs:0'
FME_NAMESPACE = 'urn:xmpp:file:metadata:0' FME_NAMESPACE = 'urn:xmpp:file:metadata:0'
URL_DATA_NAMESPACE = 'http://jabber.org/protocol/url-data' URL_DATA_NAMESPACE = 'http://jabber.org/protocol/url-data'
PUBSUB_MAX_FEATURE = 'http://jabber.org/protocol/pubsub#config-node-max'
...@@ -22,8 +22,8 @@ from gi.repository import GObject ...@@ -22,8 +22,8 @@ from gi.repository import GObject
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.plugins.gui import GajimPluginConfigDialog from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.plugins.helpers import get_builder from gajim.plugins.helpers import get_builder
from gajim.gtk.dialogs import ConfirmationDialog from gajim.gui.dialogs import ConfirmationDialog
from gajim.gtk.dialogs import DialogButton from gajim.gui.dialogs import DialogButton
class StickerPackObject(GObject.GObject): class StickerPackObject(GObject.GObject):
def __init__(self, name, summary, amount, id_): def __init__(self, name, summary, amount, id_):
......
[info] [info]
name: Stickers name: Stickers
short_name: stickers short_name: stickers
version: 1.1.0 version: 1.2.0
description: Send stickers. Note that stickers are currently always sent unencrypted. Requires python-pillow. description: Send stickers. Note that stickers are currently always sent unencrypted. Requires python-pillow.
authors = Alexander "PapaTutuWawa" <papatutuwawa@polynom.me> authors = Alexander "PapaTutuWawa" <papatutuwawa@polynom.me>
homepage = https://git.polynom.me/PapaTutuWawa/gajim-plugins homepage = https://dev.gajim.org/gajim/gajim-plugins
min_gajim_version: 1.2.91 min_gajim_version: 1.2.91
max_gajim_version: 1.3.90 max_gajim_version: 1.3.90
...@@ -35,6 +35,7 @@ from stickers.common import STICKERS_NAMESPACE ...@@ -35,6 +35,7 @@ from stickers.common import STICKERS_NAMESPACE
from stickers.common import SFS_NAMESPACE from stickers.common import SFS_NAMESPACE
from stickers.common import FME_NAMESPACE from stickers.common import FME_NAMESPACE
from stickers.common import URL_DATA_NAMESPACE from stickers.common import URL_DATA_NAMESPACE
from stickers.common import PUBSUB_MAX_FEATURE
log = logging.getLogger('gajim.p.stickers.module') log = logging.getLogger('gajim.p.stickers.module')
...@@ -96,6 +97,7 @@ class StickersModule(BaseModule): ...@@ -96,6 +97,7 @@ class StickersModule(BaseModule):
self._plugin = find_one(lambda x: x.short_name == 'stickers', app.plugin_manager.plugins) self._plugin = find_one(lambda x: x.short_name == 'stickers', app.plugin_manager.plugins)
self._pack_upload_state = {} # Sticker pack ID -> (filename -> uploaded?) self._pack_upload_state = {} # Sticker pack ID -> (filename -> uploaded?)
self._account_pubsub_max = {} # Account name -> Max PubSub items
@property @property
def _sticker_packs(self): def _sticker_packs(self):
...@@ -257,8 +259,7 @@ class StickersModule(BaseModule): ...@@ -257,8 +259,7 @@ class StickersModule(BaseModule):
node_options = { node_options = {
'pubsub#persist_items': 'true', 'pubsub#persist_items': 'true',
# TODO: Spec says 'max' could be used but my server does not suport this 'pubsub#max_items': self._account_pubsub_max[self._client.account],
'pubsub#max_items': '10',
'pubsub#access_model': 'presence' 'pubsub#access_model': 'presence'
} }
...@@ -290,6 +291,23 @@ class StickersModule(BaseModule): ...@@ -290,6 +291,23 @@ class StickersModule(BaseModule):
uri = transfer.get_transformed_uri() uri = transfer.get_transformed_uri()
self._on_sticker_uploaded(transfer.sticker, transfer.pack_id, uri) self._on_sticker_uploaded(transfer.sticker, transfer.pack_id, uri)
def _discover_pubsub_max(self):
'''
Find the maximum amount of persistent items in a PubSub node out.
'''
features = self._client.get_module('Discovery').server_info.features
if PUBSUB_MAX_FEATURE in features:
log.debug('%s supports pubsub#max_items=max', self._client.account)
self._account_pubsub_max[self._client.account] = 'max'
else:
# NOTE: We could do some fancy things like discover the default
# PubSub configuration and find out what 'max' would default
# to, but that is more complicated.
# As such, we just default to a reasonable guess of how many
# sticker packs a user might use.
log.debug('%s does not support pubsub#max_items=max', self._client.account)
self._account_pubsub_max[self._client.account] = '50'
def publish_pack(self, sticker_pack, upload=True): def publish_pack(self, sticker_pack, upload=True):
''' '''
Publish a sticker pack on PubSub Publish a sticker pack on PubSub
...@@ -297,6 +315,10 @@ class StickersModule(BaseModule): ...@@ -297,6 +315,10 @@ class StickersModule(BaseModule):
sticker_pack: The sticker pack object that should be published sticker_pack: The sticker pack object that should be published
upload: Should the stickers be first uploaded using HTTP Upload upload: Should the stickers be first uploaded using HTTP Upload
''' '''
# Check if we have a PubSub max for the account
if self._client.account not in self._account_pubsub_max:
self._discover_pubsub_max()
if not upload: if not upload:
self._publish_pack(sticker_pack) self._publish_pack(sticker_pack)
return return
......
...@@ -43,6 +43,8 @@ from gajim.groupchat_control import GroupchatControl ...@@ -43,6 +43,8 @@ from gajim.groupchat_control import GroupchatControl
from gajim.common import app from gajim.common import app
from gajim.common import ged from gajim.common import ged
from gajim.common.structs import OutgoingMessage from gajim.common.structs import OutgoingMessage
from gajim.gui.dialogs import ConfirmationDialog
from gajim.gui.dialogs import DialogButton
from gajim.plugins import GajimPlugin from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import get_builder from gajim.plugins.helpers import get_builder
...@@ -62,6 +64,7 @@ from stickers.utils import dict_append_or_set ...@@ -62,6 +64,7 @@ from stickers.utils import dict_append_or_set
from stickers.utils import hash_function_from_algo from stickers.utils import hash_function_from_algo
from stickers.utils import body from stickers.utils import body
from stickers.utils import detect_path_escape from stickers.utils import detect_path_escape
from stickers.utils import print_widget
from stickers.common import Sticker from stickers.common import Sticker
from stickers.common import Hash from stickers.common import Hash
from stickers.common import StickerPack from stickers.common import StickerPack
...@@ -344,11 +347,10 @@ class StickersPlugin(GajimPlugin): ...@@ -344,11 +347,10 @@ class StickersPlugin(GajimPlugin):
for mention in self.sticker_mentions[pack.id_]: for mention in self.sticker_mentions[pack.id_]:
sticker = find_one(lambda x: x.desc == mention.desc, pack.stickers) sticker = find_one(lambda x: x.desc == mention.desc, pack.stickers)
buf = mention.start.get_buffer() buf = mention.start.get_buffer()
self._update_textview(buf, self._print_sticker(buf.get_iter_at_mark(mention.start),
buf.get_iter_at_mark(mention.start), mention.tv,
mention.tv, sticker,
sticker, mention.end)
mention.end)
del self.sticker_mentions[pack.id_] del self.sticker_mentions[pack.id_]
def delete_sticker_pack(self, id_): def delete_sticker_pack(self, id_):
...@@ -440,7 +442,12 @@ class StickersPlugin(GajimPlugin): ...@@ -440,7 +442,12 @@ class StickersPlugin(GajimPlugin):
# Download the stickers # Download the stickers
# NOTE: Maybe some stickers do not have a URL, so we don't even bother with them # NOTE: Maybe some stickers do not have a URL, so we don't even bother with them
downloadable_stickers = filter(lambda x: x.url, event.pack.stickers) downloadable_stickers = list(filter(lambda x: x.url, event.pack.stickers))
if not downloadable_stickers:
log.error('No downloadable stickers found for %s', event.pack.id_)
self.sticker_requests.remove(event.pack.id_)
return
log.debug('Downloading stickers of stickerpack %s', event.pack.id_) log.debug('Downloading stickers of stickerpack %s', event.pack.id_)
for i, sticker in enumerate(downloadable_stickers): for i, sticker in enumerate(downloadable_stickers):
msg = Soup.Message.new('GET', sticker.url) msg = Soup.Message.new('GET', sticker.url)
...@@ -493,20 +500,22 @@ class StickersPlugin(GajimPlugin): ...@@ -493,20 +500,22 @@ class StickersPlugin(GajimPlugin):
fs = event.stanza.getTag('file-sharing') fs = event.stanza.getTag('file-sharing')
url = fs.getTag('sources').getTag('url-data').getAttr('target') url = fs.getTag('sources').getTag('url-data').getAttr('target')
desc = body(fs.getTag('file'), 'desc') desc = body(fs.getTag('file'), 'desc')
jid = event.stanza.getAttr('from').bare
event.additional_data['sticker'] = { event.additional_data['sticker'] = {
'pack_id': pack_id, 'pack_id': pack_id,
'url': url, 'url': url,
'desc': desc, 'desc': desc,
'account': event.account,
'from': jid,
} }
# Download the sticker pack if we don't have it # Download the sticker pack if we don't have it
if (not self.__has_sticker_pack(pack_id) and if (not self.__has_sticker_pack(pack_id) and
self._should_download_sticker_pack()): self._should_download_sticker_pack()):
jid = event.stanza.getAttr('from')
self.sticker_requests.append(pack_id) self.sticker_requests.append(pack_id)
app.connections[event.account].get_module('Stickers').request_sticker_pack(pack_id, app.connections[event.account].get_module('Stickers').request_sticker_pack(pack_id,
jid.bare) jid)
def _sticker_width(self): def _sticker_width(self):
''' '''
...@@ -532,17 +541,66 @@ class StickersPlugin(GajimPlugin): ...@@ -532,17 +541,66 @@ class StickersPlugin(GajimPlugin):
end_mark = buffer_.create_mark(None, iter_, True) end_mark = buffer_.create_mark(None, iter_, True)
return start_mark, end_mark return start_mark, end_mark
def _update_textview(self, buf, iter_, tv, sticker, end=None): def _on_manual_sticker_download(self, pack_id, jid, account):
def download_sticker_pack():
log.debug('Manually requested download of sticker pack %s from %s',
pack_id,
jid)
self.sticker_requests.append(pack_id)
app.connections[account].get_module('Stickers').request_sticker_pack(pack_id,
jid)
if pack_id in self.sticker_requests:
return
ConfirmationDialog(
_('Download Sticker Pack'),
_('Are you sure you want to do download this sticker pack?'),
_('This will create files on your computer and publish it on your account.'),
[DialogButton.make('Cancel'),
DialogButton.make('Accept',
callback=download_sticker_pack)]).show()
def _print_non_download_mention(self, iter_, tv, pack_id, jid, account, text):
'''
Print a button to allow manual downloading of a sticker pack. Returns
the start and end marks of the button.
iter_: The TextView's iterator
tv: The TextView
pack_id: The ID of the sticker pack
jid: The bare JID of the sender
account: The name of the account that received the message
text: The text of the message
'''
button = Gtk.Button.new_with_label(text)
button.connect('clicked', lambda x: self._on_manual_sticker_download(pack_id, jid, account))
button.set_tooltip_text('Download this sticker pack')
start_mark, end_mark = print_widget(button, iter_, tv, True)
if tv.autoscroll:
tv.scroll_to_end()
return start_mark, end_mark
def _print_sticker(self, iter_, tv, sticker, end=None):
'''
Print a sticker.
iter_: The TextView's iterator
tv: The TextView
sticker: The Sticker object
end: If end is a TextMark, remove everything from iter_ to the end mark.
'''
img = image_from_pixbuf(sticker.pixbuf) img = image_from_pixbuf(sticker.pixbuf)
img.set_tooltip_text(sticker.desc) img.set_tooltip_text(sticker.desc)
anchor = buf.create_child_anchor(iter_)
anchor.plaintext = ''
img.show_all() img.show_all()
tv.tv.add_child_at_anchor(img, anchor)
tv.plugin_modified = True print_widget(img, iter_, tv, False)
if end: if end:
buf = tv.tv.get_buffer()
buf.delete(iter_, buf.get_iter_at_mark(end)) buf.delete(iter_, buf.get_iter_at_mark(end))
if tv.autoscroll: if tv.autoscroll:
...@@ -555,10 +613,20 @@ class StickersPlugin(GajimPlugin): ...@@ -555,10 +613,20 @@ class StickersPlugin(GajimPlugin):
# Find the correct sticker pack # Find the correct sticker pack
pack_id = additional_data['sticker']['pack_id'] pack_id = additional_data['sticker']['pack_id']
if not self.__has_sticker_pack(pack_id): if not self.__has_sticker_pack(pack_id):
# We have requested it during _on_message_received if not self._should_download_sticker_pack():
start, end = self._print_text(tv.tv.get_buffer(), from_ = additional_data['sticker']['from']
iter_, account = additional_data['sticker']['account']
text) start, end = self._print_non_download_mention(iter_,
tv,
pack_id,
from_,
account,
text)
else:
# We have requested it during _on_message_received
start, end = self._print_text(tv.get_buffer(),
iter_,
text)
dict_append_or_set(self.sticker_mentions, dict_append_or_set(self.sticker_mentions,
pack_id, pack_id,
StickerMention(start=start, StickerMention(start=start,
...@@ -580,10 +648,9 @@ class StickersPlugin(GajimPlugin): ...@@ -580,10 +648,9 @@ class StickersPlugin(GajimPlugin):
log.warning('Sticker "%s" of "%s" not found!', text, pack_id) log.warning('Sticker "%s" of "%s" not found!', text, pack_id)
return return
self._update_textview(tv.tv.get_buffer(), self._print_sticker(iter_,
iter_, tv,
tv, sticker)
sticker)
def connect_with_chat_control(self, control): def connect_with_chat_control(self, control):
# NOTE: Based on our gui_extension_points we *should* # NOTE: Based on our gui_extension_points we *should*
......
import sys
import os
import json
import hashlib
import mimetypes
from PIL import Image
config_path = sys.argv[1]
with open(config_path, 'r') as f:
config = json.loads(f.read())
pack_path = os.path.dirname(config_path)
unit_sep = str(0x1f)
record_sep = str(0x1e)
group_sep = str(0x1d)
file_sep = str(0x1c)
# TODO: Handle multiple languages
meta_string = f'{config["name"]}{unit_sep}{config["summary"]}{record_sep}{file_sep}'
tmp = ''
for sticker in config['stickers']:
tmp += sticker['desc'] + record_sep
sticker_file = os.path.join(pack_path, sticker['filename'])
with open(sticker_file, 'rb') as f:
sha256_hash = hashlib.sha256(f.read()).hexdigest()
sticker['hashes'] = [{'algo': 'sha-256', 'value': sha256_hash}]
tmp += 'sha-256' + sha256_hash + unit_sep + record_sep
tmp += group_sep
image = Image.open(sticker_file)
sticker['dimension'] = f'{image.width}x{image.height}'
sticker['size'] = os.path.getsize(sticker_file)
sticker['type'] = mimetypes.MimeTypes().guess_type(sticker_file)[0]
tmp += file_sep
result = meta_string + tmp
config['hash'] = {'algo': 'sha-256', 'value': hashlib.sha256(result.encode()).hexdigest()}
config['id'] = config['hash']['value'][:24]
with open(config_path, 'w') as f:
f.write(json.dumps(config, indent=4))
print('Sticker pack configuration updated!')
print(f'New sticker pack ID: {config["id"]}')
...@@ -82,3 +82,33 @@ def detect_path_escape(spath, full_path): ...@@ -82,3 +82,33 @@ def detect_path_escape(spath, full_path):
''' '''
real_path = os.path.realpath(full_path) real_path = os.path.realpath(full_path)
return not real_path.startswith(spath) return not real_path.startswith(spath)
def print_widget(widget, iter_, tv, create_marks=False):
'''
"Print" a widget into the TextView
widget: The Gtk.Widget to print
buf: The TextView's buffer
iter_: The iterator
tv: The actual TextView
create_marks: Whether Gtk.TextMarks should be created before and after
the widget insertion. If True, the start and end mark are
returned. If False, None, None is returned.
'''
buf = tv.tv.get_buffer()
if create_marks:
start_mark = buf.create_mark(None, iter_, True)
else:
start_mark = None
anchor = buf.create_child_anchor(iter_)
anchor.plaintext = ''
tv.tv.add_child_at_anchor(widget, anchor)
tv.plugin_modified = True
if create_marks:
end_mark = buf.create_mark(None, iter_, True)
else:
end_mark = None
return start_mark, end_mark
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment