Commit 06a13409 authored by Alexander's avatar Alexander
Browse files

[stickers] Implement XEP-0449

parent 32d8eef0
Pipeline #6867 failed with stage
from .plugin import StickersPlugin
#
# Copyright (C) 2020 Alexander "PapaTutuWawa" <papatutuwawa AT polynom.me>
# This file is part of the Stickers plugin for Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>..check_file_before_transfer(sticker)
#
from collections import namedtuple
Sticker = namedtuple('Sticker', ['type', 'hashes', 'size', 'dimension', 'desc', 'suggests', 'filename', 'url', 'pixbuf'])
StickerPack = namedtuple('StickerPack', ['id_', 'hash', 'name', 'summary', 'stickers'])
Hash = namedtuple('Hash', ['algo', 'value'])
def parse_hashes(hashes):
'''
Turn an array of {'algo': ..., 'value': ...} hash descriptions into
Hash objects
'''
return [Hash(h['algo'], h['value']) for h in hashes]
def dump_hashes(hashes):
'''
Turn an array of Hash objects into their JSON representation
'''
return [{
'algo': h.algo,
'value': h.value
} for h in hashes]
ANIMATED_MIME_TYPES = ['image/gif']
STICKERS_NAMESPACE = 'urn:xmpp:stickers:0'
SFS_NAMESPACE = 'urn:xmpp:sfs:0'
FME_NAMESPACE = 'urn:xmpp:file:metadata:0'
URL_DATA_NAMESPACE = 'http://jabber.org/protocol/url-data'
#
# Copyright (C) 2020 Alexander "PapaTutuWawa" <papatutuwawa AT polynom.me>
# This file is part of the Stickers plugin for Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
#
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GObject
from gajim.common.i18n import _
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.plugins.helpers import get_builder
from gajim.gtk.dialogs import ConfirmationDialog
from gajim.gtk.dialogs import DialogButton
class StickerPackObject(GObject.GObject):
def __init__(self, name, summary, amount, id_):
self._name = name
self._summary = summary
self._amount = amount
self._id = id_
GObject.GObject.__init__(self)
@property
def name(self):
return self._name
@property
def summary(self):
return self._summary
@property
def amount(self):
return self._amount
@property
def id_(self):
return self._id
class StickersConfigDialog(GajimPluginConfigDialog):
def init(self):
path = self.plugin.local_file_path('gtk/config.ui')
self._ui = get_builder(path)
box = self.get_content_area()
box.pack_start(self._ui.stickers_config_dialog, True, True, 0)
self._ui.connect_signals(self)
self._list_model = Gio.ListStore()
self._ui.get_object('STICKER_WIDTH').set_range(0, 400)
self._ui.get_object('STICKER_WIDTH').set_increments(1, -1)
self._ui.get_object('sticker_packs_list').bind_model(self._list_model, self._create_sticker_pack_row)
def set_wrapper(setting):
return lambda widget: self._on_setting_changed(widget, setting)
for setting in ('DOWNLOAD_NEW', 'DOWNLOAD_NEW_SIGNIN', 'UPLOAD_NEW_SIGNIN', 'SHOW_ANIMATED_STICKERS'):
self._ui.get_object(setting).connect('toggled', set_wrapper(setting))
self._ui.get_object('STICKER_WIDTH').connect('value-changed', set_wrapper('STICKER_WIDTH'))
def on_sticker_pack_added(self, pack):
'''
Called when a new sticker pack has been added.
'''
self._list_model.append(StickerPackObject(pack.name,
pack.summary,
len(pack.stickers),
pack.id_))
def on_sticker_pack_removed(self, pack):
'''
Called when a sticker pack has been removed.
'''
# Find the correct index in the ListStore
index = -1
for index_ in range(self._list_model.get_n_items()):
if self._list_model.get_item(index_).id_ == pack.id_:
index = index_
break
if index != -1:
self._list_model.remove(index)
def on_run(self):
# Update all config settings
for setting in ('DOWNLOAD_NEW', 'DOWNLOAD_NEW_SIGNIN', 'UPLOAD_NEW_SIGNIN', 'SHOW_ANIMATED_STICKERS'):
self._ui.get_object(setting).set_active(self.plugin.config[setting])
self._ui.get_object('STICKER_WIDTH').set_value(self.plugin.config['STICKER_WIDTH'])
def _on_setting_changed(self, widget, data):
if isinstance(widget, Gtk.CheckButton):
value = widget.get_active()
elif isinstance(widget, Gtk.SpinButton):
value = int(widget.get_value())
self.plugin.config[data] = value
def _on_delete_button_clicked(self, id_):
def delete_sticker_pack():
self.plugin.delete_sticker_pack(id_)
ConfirmationDialog(
_('Delete Sticker Pack'),
_('Are you sure you want to delete the sticker pack?'),
_('This will delete the sticker pack and its files from your computer.'),
[DialogButton.make('Cancel'),
DialogButton.make('Remove',
callback=delete_sticker_pack)]).show()
def _create_sticker_pack_row(self, pack):
item = get_builder(self.plugin.local_file_path('gtk/config_stickers_listitem.ui'))
item.get_object('header').set_markup(f'<b>{pack.name}</b>')
item.get_object('summary').set_text(pack.summary)
item.get_object('amount').set_text(f'{pack.amount} stickers')
item.get_object('delete_button').connect('clicked', lambda x: self._on_delete_button_clicked(pack.id_))
return item.get_object('sticker_pack_list_item')
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkNotebook" id="stickers_config_dialog">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkListBox" id="sticker_packs_list">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
</object>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Sticker Packs</property>
</object>
<packing>
<property name="tab-fill">False</property>
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">12</property>
<property name="left-padding">12</property>
<child>
<!-- n-columns=3 n-rows=5 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkCheckButton" id="DOWNLOAD_NEW">
<property name="label" translatable="yes">Download new sticker packs</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">When receiving a message containing a sticker from an unknown sticker pack, Gajim will try to download it</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="DOWNLOAD_NEW_SIGNIN">
<property name="label" translatable="yes">Download new stickers on login</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">Download locally unknown sticker packs that are published to your account</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="UPLOAD_NEW_SIGNIN">
<property name="label" translatable="yes">Publish new stickers on login</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">Upload and publish all locally available sticker packs that are not published on your account</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">12</property>
<property name="label" translatable="yes">Sticker display width</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="STICKER_WIDTH">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="input-purpose">number</property>
<property name="numeric">True</property>
<property name="value">150</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="SHOW_ANIMATED_STICKERS">
<property name="label" translatable="yes">Show animated stickers</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">Instead of showing animated stickers, just show the first frame</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Settings</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab-fill">False</property>
</packing>
</child>
</object>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid" id="sticker_pack_list_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<!-- n-columns=1 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkLabel" id="header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="summary">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="amount">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="delete_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="margin-end">12</property>
<property name="always-show-image">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stock">gtk-delete</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid" id="sticker_pack_row">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<child>
<object class="GtkLabel" id="pack_name">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFlowBox" id="stickers">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="max-children-per-line">3</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
</interface>
[info]
name: Stickers
short_name: stickers
version: 1.1.0
description: Send stickers. Note that stickers are currently always sent unencrypted. Requires python-pillow.
authors = Alexander "PapaTutuWawa" <papatutuwawa@polynom.me>
homepage = https://git.polynom.me/PapaTutuWawa/gajim-plugins
min_gajim_version: 1.2.91
max_gajim_version: 1.3.90
#
# Copyright (C) 2020 Alexander "PapaTutuWawa" <papatutuwawa AT polynom.me>
# This file is part of the Stickers plugin for Gajim.
#
# Gajim is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim 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 General Public License for more details.
#
# You should have received a co'y of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from nbxmpp.errors import PubSubStanzaError
from nbxmpp.namespaces import Namespace
from nbxmpp.simplexml import Node
from gajim.common import app
from gajim.common.nec import NetworkEvent
from gajim.common.modules.base import BaseModule
from gajim.common.const import FTState
from stickers.utils import find_one
from stickers.utils import sticker_path
from stickers.utils import body
from stickers.common import Sticker
from stickers.common import StickerPack
from stickers.common import Hash
from stickers.common import STICKERS_NAMESPACE
from stickers.common import SFS_NAMESPACE
from stickers.common import FME_NAMESPACE
from stickers.common import URL_DATA_NAMESPACE
log = logging.getLogger('gajim.p.stickers.module')
name = 'Stickers'
zeroconf = False
def parse_hash_node(node):
'''
Parse a <hash> urn:xmpp:hashes:2 node.
Returns a Hash object
'''
return Hash(algo=node.getAttr('algo'), value=node.getPayload()[0])
def parse_sticker_pack(item):
'''
Turn a PubSub item into a sticker pack object
'''
pack =</