slixprint.py
This commit is contained in:
commit
64b8cc167c
1 changed files with 322 additions and 0 deletions
322
slixprint.py
Normal file
322
slixprint.py
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
#!/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 <name> or <number>"
|
||||||
|
"\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'<body xmlns="http://www.w3.org/1999/xhtml">'
|
||||||
|
# f'<a href="{url}">{url}</a></body>')
|
||||||
|
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()
|
Loading…
Reference in a new issue