#!/usr/bin/env python3 from getpass import getpass from os.path import dirname, expanduser from requests.auth import AuthBase from requests.exceptions import RequestException from sys import exit, stderr import gi import json import os import platform import requests gi.require_version('Gtk', '3.0') from gi.repository import Gtk class Api: class DeviceAuth(AuthBase): def __init__(self, token): self.token = token def __call__(self, r): r.headers['Authorization'] = f'Device {self.token}' return r def __init__(self, config): self.config = config def __call__(self, method, endpoint, **kwargs): data = {'params' if method == 'GET' else 'json': kwargs} r = requests.request(method, self.config['url'] + endpoint % self.config, auth=Api.DeviceAuth(self.config['api_token']), **data) try: r.raise_for_status() except RequestException as e: print(e, file=stderr) print(r.text, file=stderr) raise e if r.headers['content-type'] == 'application/json': return r.json() return r.text class Window(Gtk.Window): def __init__(self, config): super().__init__(title='Mark as paid') self.config = config self.api = Api(config) self.entry = Gtk.Entry() self.entry.set_visibility(False) self.entry.set_width_chars(32) self.entry.connect('activate', self.on_entry_activate) self.add(self.entry) def info(self, primary_text, secondary_text): dialog = Gtk.MessageDialog( transient_for=self, flags=0, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, text=primary_text, ) dialog.format_secondary_text(secondary_text) dialog.run() dialog.destroy() def on_entry_activate(self, widget): results = self.api('GET', '/api/v1/organizers/%(organizer)s/checkinrpc/search/', ignore_status='true', list=self.config['list'], search=widget.get_text()) widget.set_text('') if results['count'] == 0: self.info('Keine Ergebnisse', 'Bitte präzisiere deine Eingabe.') return if results['count'] > 1: self.info('Mehrere Ergebnisse', 'Bitte präzisiere deine Eingabe.') return order = self.api('GET', f"/api/v1/organizers/%(organizer)s/events/%(event)s/orders/{results['results'][0]['order']}/") if order['status'] == 'p': self.info('Bereits bezahlt', 'Die Bestellung ist bereits bezahlt.') return if order['status'] == 'e': self.info('Abgelaufen', 'Die Bestellung ist abgelaufen.') return if order['status'] == 'c': self.info('Storniert', 'Die Bestellung wurde storniert.') return dialog = Gtk.MessageDialog( transient_for=self, flags=0, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO, text=f"Gesamtbetrag: {order['total']}", ) dialog.format_secondary_text('Wurde der gesamte Betrag in bar entgegengenommen?') response = dialog.run() dialog.destroy() if response == Gtk.ResponseType.YES: try: self.api('POST', f"/api/v1/organizers/%(organizer)s/events/%(event)s/orders/{results['results'][0]['order']}/mark_paid/") self.info('Zahlung bestätigt', 'Die Zahlung wurde bestätigt, die Tickets sind nun gültig.') except RequestException: dialog = Gtk.MessageDialog( transient_for=self, flags=0, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text='Fehler', ) dialog.format_secondary_text('Ein Fehler ist aufgetreten.') dialog.run() dialog.destroy() if __name__ == '__main__': config_path = expanduser('~/.config/pretix-markaspaid/config.json') try: with open(config_path) as f: config = json.loads(f.read()) except FileNotFoundError: handshake = json.loads(getpass('Please scan initialization qr code... ')) if handshake['handshake_version'] != 1: print(f"Unknown handshake version {handshake['handshake_version']}", file=stderr) exit(1) r = requests.post(f"{handshake['url']}/api/v1/device/initialize", json={ 'hardware_brand': platform.system(), 'hardware_model': platform.release(), 'software_brand': 'mark-as-paid', 'software_version': '0.1.0', 'token': handshake['token'], }) try: r.raise_for_status() except RequestException as e: print(e, file=stderr) print(r.text, file=stderr) exit(1) r = r.json() config = { 'api_token': r['api_token'], 'device_id': r['device_id'], 'event': input('Event: '), 'list': int(input('Check-in list: ')), 'organizer': r['organizer'], 'unique_serial': r['unique_serial'], 'url': handshake['url'], } os.makedirs(dirname(config_path)) with open(config_path, 'w') as f: f.write(json.dumps(config)) window = Window(config) window.connect('destroy', Gtk.main_quit) window.show_all() Gtk.main()