#!/usr/bin/env python3 # Slixprint: The XMPP Printer Bot # Copyright (C) 2024 Schimon Zackary # This file is part of Slixmark. # See the file LICENSE for copying permission. from getpass import getpass from argparse import ArgumentParser import cups import logging import magic import pdfkit # from reportlab.pdfgen import canvas import requests import slixmpp import time import xml.sax.saxutils as saxutils class Actions: def determine_mimetype(content): m = magic.Magic(mime=True) mimetype = m.from_buffer(content) return mimetype def download_document(url): response = requests.get(url) content = response.content return content def export_to_pdf(filename): # text = content.decode("utf-8") # pdfkit.from_string(text, filename + '.pdf') pdfkit.from_file(filename, filename + '.pdf') # c = canvas.Canvas("Content.pdf") # # Add content to the canvas according to your requirements here # c.showPage() # c.drawString(100, 750, "Hello World") # c.save() def write_document(content, filename): with open(filename, 'wb') as f: f.write(content) def print_document(filename): try: conn = cups.Connection() default_printer = conn.getDefault() conn.printFile(default_printer, filename, "Slixprint Document Print", {}) except Exception as e: return str(e) def get_printers(): conn = cups.Connection() printers = conn.getPrinters() return printers class Documentation: def about(): return ('Slixprint' '\n' 'Jabber/XMPP Bookmark Manager' '\n\n' 'Slixprint is an XMPP bot that will print documents sent to it.' '\n\n' 'Slixprint is written with slixmppp in Python.' '\n\n' 'https://gitgud.io/sjehuda/slixprint' '\n\n' 'Copyright 2024 Schimon Zackary' '\n\n' 'Made in Switzerland' '\n\n' '🇨🇭️') def commands(): return ("basic usage" "\n" " Send a URL to print." "\n" "printer or " "\n" " Set default printer." "\n" "printers" "\n" " List available printers." "\n") def notice(): return ('Copyright 2024 Schimon Jehudah Zackary' '\n\n' 'Permission is hereby granted, free of charge, to any person ' 'obtaining a copy of this software and associated ' 'documentation files (the “Software”), to deal in the ' 'Software without restriction, including without limitation ' 'the rights to use, copy, modify, merge, publish, distribute, ' 'sublicense, and/or sell copies of the Software, and to ' 'permit persons to whom the Software is furnished to do so, ' 'subject to the following conditions:' '\n\n' 'The above copyright notice and this permission notice shall ' 'be included in all copies or substantial portions of the ' 'Software.' '\n\n' 'THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY ' 'KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE ' 'WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR ' 'PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR ' 'COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ' 'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR ' 'OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ' 'SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.') class Slixprint(slixmpp.ClientXMPP): """ Slixprint - Printer bot for Jabber/XMPP. Slixprint is a printer bot based on slixmpp. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler("message", self.process_message) self.add_event_handler("disco_info", self.discovery) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.adhoc_commands() self.send_presence() await self['xep_0115'].update_caps() vcard = self.plugin['xep_0054'].make_vcard() vcard['FN'] = 'Slixfeed' vcard['NICKNAME'] = 'Slixfeed' vcard['ROLE'] = 'Syndication News Bot' vcard['ORG'] = 'RSS Task Force' vcard['URL'] = 'https://gitgud.io/sjehuda/slixfeed' vcard['NOTE'] = 'Printer bot made for XMPP.' vcard['BDAY'] = '20 March 2024' await self.plugin['xep_0054'].publish_vcard(vcard) self['xep_0030'].add_identity(category='client', itype='printer', name='slixprint', node=None, jid=self.boundjid.full) async def discovery(self, DiscoInfo): jid = DiscoInfo['from'] await self['xep_0115'].update_caps(jid=jid) async def process_message(self, message): """ Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages's type before processing or sending replies. Arguments: message -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ if message['type'] in ('chat', 'normal'): message_text = " ".join(message['body'].split()) if message_text.lower().startswith('http'): url = message_text content = Actions.download_document(url) mimetype = Actions.determine_mimetype(content) extension = mimetype.split('/')[0] jid_bare = message['from'].bare filename = ('{jid}_{timestamp}.{extension}' .format(jid=jid_bare, timestamp=time.time(), extension=extension)) Actions.write_document(content, filename) error = Actions.print_document(filename) if error: logging.error(error) Actions.export_to_pdf(filename) url_upload = await self['xep_0363'].upload_file( filename + '.pdf') message_body = 'Error: {text}'.format(text=error) # message_body = ('Error: {text} {url}' # .format(text=error, url=url_upload)) url_upload = saxutils.escape(url_upload) # html = (f'' # f'{url}') message_file = self.make_message(mto=message['from'], mfrom=self.boundjid.full, mbody=url_upload, # mhtml=html, mtype='chat') message_file['oob']['url'] = url_upload message_file.send() else: message_body = ('Please send a document or a URL.') message.reply(message_body).send() #message.reply("Thanks for sending\n%(body)s" % message).send() def adhoc_commands(self): self['xep_0050'].add_command(node='printers', name='🖨️ Printers', handler=self._handle_printers) self['xep_0050'].add_command(node='help', name='📔️ Help', handler=self._handle_help) self['xep_0050'].add_command(node='about', name='📜️ About Slixprint', handler=self._handle_about) def _handle_printers(self, iq, session): form = self['xep_0004'].make_form('form', 'Add') form['instructions'] = 'Add a new bookmark' printers = Actions.get_printers() options = form.add_field(desc='Select a printer.', ftype='list-single', label='Printers', required=True, var='printer') for printer in printers: options.addOption(printer, printers[printer]["device-uri"]) session['has_next'] = True session['next'] = self._handle_printer session['payload'] = form return session def _handle_cancel(self, payload, session): text_note = 'Operation has been cancelled.' session['notes'] = [['info', text_note]] return session def _handle_printer(self, payload, session): text_note = 'This form is not available yet.' session['notes'] = [['info', text_note]] return session def _handle_help(self, iq, session): text_note = Documentation.commands() session['notes'] = [['info', text_note]] return session def _handle_about(self, iq, session): text_note = Documentation.about() session['notes'] = [['info', text_note]] return session if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser(description=Slixprint.__doc__) # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the bot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = Slixprint(args.jid, args.password) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0050') # Ad-Hoc Commands xmpp.register_plugin('xep_0054') # vcard-temp xmpp.register_plugin('xep_0060') # Publish-Subscribe xmpp.register_plugin('xep_0066') # Out of Band Data xmpp.register_plugin('xep_0115') # Entity Capabilities xmpp.register_plugin('xep_0122') # Data Forms Validation xmpp.register_plugin('xep_0199') # XMPP Ping # xmpp.register_plugin('xep_0363') # HTTP File Upload # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process()