From 7950c435bbc106a5bac9bba64a4624552c17093b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Cuevas?= Date: Sat, 3 Dec 2022 15:14:54 +0100 Subject: [PATCH] Used proper OOP for the main module, as it grows Fixed a bug with non titled RSS items like the ones found in Mastodon network. --- smallprint.py | 723 ++++++++++++++++++++++++++------------------------ 1 file changed, 374 insertions(+), 349 deletions(-) diff --git a/smallprint.py b/smallprint.py index 8612b2b..cdcb2c9 100644 --- a/smallprint.py +++ b/smallprint.py @@ -29,393 +29,417 @@ logger = logging.getLogger(__name__) config = {} -genqr = False +class SmallPrint: + def __init__(self, gen_qr=False): + """ + Initializes the printer and returns a printer object to + generate the print + """ + self.gen_qr = gen_qr + self.config = load_config() + if not self.config: + print(f"Config file created, check {CONFIG_PATH}") -def initialize(): - """ - Initializes the printer and returns a printer object to - generate the print - """ - global config - config = load_config() - if not config: - print(f"Config file created, check {CONFIG_PATH}") - - if os.path.exists(config.get("PRINTER_FILE")): - return File(config.get("PRINTER_FILE")) - - printer_id = config.get("PRINTER_USB_ID") - - if not printer_id: - logger.error("Please configure your printer") - - prid1, prid2 = printer_id.split(":") - - printer_interface = config.get("PRINTER_INTERFACE") or 0 - printer_endpoint = config.get("PRINTER_ENDPOINT") or 0x01 - - printer = Usb(prid1, prid2, printer_interface, printer_endpoint) - printer.charcode(code="WEST_EUROPE") - return printer - - -def reset_defaults(printer): - """ - Reset the printer to the defaults - """ - printer.set(align='left', font='a', width=1, text_type="normal", - height=1, density=9, invert=False, smooth=False, flip=False) - - -def print_weather(printer, city=None): - reset_defaults(printer) - appkey = config.get("OWM") - if not appkey: - logger.error("Open Weather key not set!") - return - - if not city: - city = config.get("CITY") - - if not city: - logger.error("No city set") - - params = {"q": city, "APPID": appkey, "units": "metric"} - - weatherdata = requests.get('http://api.openweathermap.org/data/2.5/weather', params=params) - - if "weather" in weatherdata.json(): - weather = weatherdata.json() - today = datetime.datetime.now() - current_day = today.strftime("%a, %d %b, %Y") - - printer.set(align="center", - font="a", - text_type="b") - printer.text(f"{current_day}\n\n{city}\n") - - reset_defaults(printer) - printer.set(align="center", font="b") - - description = weather['weather'][0]['description'] - printer.text(f"{description}\n\n") - icon_code = weather['weather'][0]['icon'] - - dir_path = os.path.dirname(os.path.realpath(__file__)) # Get this file's current directory - - if not os.path.exists(os.path.join(dir_path, "icons/weather")): - dir_path = "/usr/share/smallprint/icons/weather" + if os.path.exists(self.config.get("PRINTER_FILE")): + self.printer = File(self.config.get("PRINTER_FILE")) else: - dir_path = os.path.join(dir_path, "icons/weather") + printer_id = self.config.get("PRINTER_USB_ID") - if not os.path.exists(os.path.join(dir_path, f"{icon_code}.png")): - icon_path = os.path.join(dir_path, "any.png") + if not printer_id: + logger.error("Please configure your printer") + + prid1, prid2 = printer_id.split(":") + + printer_interface = config.get("PRINTER_INTERFACE") or 0 + printer_endpoint = config.get("PRINTER_ENDPOINT") or 0x01 + + printer = Usb(prid1, prid2, printer_interface, printer_endpoint) + printer.charcode(code="WEST_EUROPE") + self.printer = printer + + + def reset_defaults(self): + """ + Reset the printer to the defaults + """ + self.printer.set(align='left', font='a', width=1, text_type="normal", + height=1, density=9, invert=False, smooth=False, flip=False) + + + def print_weather(self, city=None): + """ + Gets a weather report and sends it to the printer + with a nice icon and data, on the city specified + or the default in the config + """ + # Get our printer + printer = self.printer + + # Get it on the right shape for printing + self.reset_defaults() + + # Get OpenWeatherMap key + appkey = self.config.get("OWM") + if not appkey: + logger.error("Open Weather key not set!") + return + + if not city: + city = self.config.get("CITY") + + if not city: + logger.error("No city set") + return + + params = {"q": city, "APPID": appkey, "units": "metric"} + + weatherdata = requests.get('http://api.openweathermap.org/data/2.5/weather', params=params) + + if "weather" in weatherdata.json(): + weather = weatherdata.json() + today = datetime.datetime.now() + current_day = today.strftime("%a, %d %b, %Y") + + printer.set(align="center", + font="a", + text_type="b") + printer.text(f"{current_day}\n\n{city}\n") + + self.reset_defaults() + printer.set(align="center", font="b") + + description = weather['weather'][0]['description'] + printer.text(f"{description}\n\n") + icon_code = weather['weather'][0]['icon'] + + dir_path = os.path.dirname(os.path.realpath(__file__)) # Get this file's current directory + + if not os.path.exists(os.path.join(dir_path, "icons/weather")): + dir_path = "/usr/share/smallprint/icons/weather" + + else: + dir_path = os.path.join(dir_path, "icons/weather") + + if not os.path.exists(os.path.join(dir_path, f"{icon_code}.png")): + icon_path = os.path.join(dir_path, "any.png") + + else: + icon_path = os.path.join(dir_path, f"{icon_code}.png") + + # TODO: The image impl should be an option + printer.image(icon_path, + impl="bitImageColumn") + printer.text("\n") + # TODO: Print a nice icon based on the codes here: https://openweathermap.org/weather-conditions + temperature = weather['main']['temp'] + humidity = weather['main']['humidity'] + wind = weather['wind']['speed'] + + printer.text(f"Temperature: {temperature}C\n") + printer.text(f"Humidity: {humidity}%\n") + printer.text(f"Wind: {wind}km\\h\n") else: - icon_path = os.path.join(dir_path, f"{icon_code}.png") + logger.error("No weather info available") - # TODO: The image impl should be an option - printer.image(icon_path, - impl="bitImageColumn") - printer.text("\n") - # TODO: Print a nice icon based on the codes here: https://openweathermap.org/weather-conditions - temperature = weather['main']['temp'] - humidity = weather['main']['humidity'] - wind = weather['wind']['speed'] + def print_gemini(self, link): + """ + Given a Gemini link, it prints the file to the printer + and tries to do some interpretation if it is gemtext + """ + printer = self.printer + response = ignition.request(link) - printer.text(f"Temperature: {temperature}C\n") - printer.text(f"Humidity: {humidity}%\n") - printer.text(f"Wind: {wind}km\\h\n") + if not response.success(): + logger.error(f"Received error {response.status}") + return - else: - logger.error("No weather info available") - - -def print_gemini(printer, link): - """ - Given a Gemini link, it prints the file to the printer - and tries to do some interpretation if it is gemtext - """ - response = ignition.request(link) - - if not response.success(): - logger.error(f"Received error {response.status}") - return - - reset_defaults(printer) - if response.meta == "text/gemini": - for raw_line in str(response.data()).split("\n"): - line = raw_line.rstrip() - if len(line) == 0: - printer.text("\n") - continue - - if line.startswith("# "): - printer.set(align="left", - text_type="BU", - width=2, height=2, - smooth=True) - printer.text(line[2:]) - - elif line.startswith("## "): - printer.set(align="left", - text_type="BU", - width=1, height=1, - smooth=True) - printer.text(line[3:]) - - elif line.startswith("### "): - printer.set(align="left", - font="a", - text_type="U", - width=1, height=1, - smooth=True) - printer.text(line[4:]) - - elif line.startswith("#### "): - printer.set(align="left", - font="b", - text_type="U", - width=1, height=1, - smooth=True) - printer.text(line[5:]) - - elif line.startswith("=>"): - reset_defaults(printer) - if generate_qr: - printer.set(align="left", - font="b") - printer.text(line[3:]) - reset_defaults(printer) - lnk = _process_link(link, line[3:]) + self.reset_defaults() + if response.meta == "text/gemini": + for raw_line in str(response.data()).split("\n"): + line = raw_line.rstrip() + if len(line) == 0: printer.text("\n") - printer.qr(lnk, size=6) + continue + + if line.startswith("# "): + printer.set(align="left", + text_type="BU", + width=2, height=2, + smooth=True) + printer.text(line[2:]) + + elif line.startswith("## "): + printer.set(align="left", + text_type="BU", + width=1, height=1, + smooth=True) + printer.text(line[3:]) + + elif line.startswith("### "): + printer.set(align="left", + font="a", + text_type="U", + width=1, height=1, + smooth=True) + printer.text(line[4:]) + + elif line.startswith("#### "): + printer.set(align="left", + font="b", + text_type="U", + width=1, height=1, + smooth=True) + printer.text(line[5:]) + + elif line.startswith("=>"): + self.reset_defaults() + if generate_qr: + printer.set(align="left", + font="b") + printer.text(line[3:]) + self.reset_defaults() + lnk = self._process_link(link, line[3:]) + printer.text("\n") + printer.qr(lnk, size=6) + + else: + printer.set(align="left", + font="b", + text_type="U") + printer.text(line) + printer.text("\n") else: printer.set(align="left", - font="b", - text_type="U") + font="b") printer.text(line) - printer.text("\n") - else: - printer.set(align="left", - font="b") - printer.text(line) + printer.text("\n") - printer.text("\n") + else: + printer.text(response.data()) - else: - printer.text(response.data()) + def _process_link(self, orig_link, link_text): + """ + Extracts the link from the text, and completes it if + necessary + """ + uri = link_text.split()[0] + if "://" in link_text: + return uri -def _process_link(orig_link, link_text): - """ - Extracts the link from the text, and completes it if - necessary - """ - uri = link_text.split()[0] + if link_text.startswith("/"): + return orig_link + link_text - if "://" in link_text: - return uri + else: + return orig_link + "/" + link_text - if link_text.startswith("/"): - return orig_link + link_text + def print_rss(self, link, news=3): + """ + Given an RSS link and a printer, prints the news from it + """ + printer = self.printer + self.reset_defaults() + feed = feedparser.parse(link) + title = feed["channel"].get("title", link)[:16] - else: - return orig_link + "/" + link_text + printer.set(align="center", + text_type="BU", + width=2, height=2, + smooth=True) + printer.text(f"{title}\n\n") + self.reset_defaults() + items = feed["items"][:news] + for item in items: + self._print_rss_item(item) -def print_rss(printer, link, news=3): - """ - Given an RSS link and a printer, prints the news from it - """ - reset_defaults(printer) - feed = feedparser.parse(link) - title = feed["channel"].get("title", link)[:16] + def _print_rss_item(self, item): + """ + Process an item from an RSS feed and print it out + """ + printer = self.printer - printer.set(align="center", - text_type="BU", - width=2, height=2, - smooth=True) + title = item.get("title", "") + date = item["published"] + text = clear_html(item["summary"]) + if len(text) > 255: + # Limit the text output in certain "summaries" + text = text[:252] + "...\n" + link = item["link"] - printer.text(f"{title}\n\n") - reset_defaults(printer) - items = feed["items"][:news] - for item in items: - _print_rss_item(item) - - -def _print_rss_item(item): - title = item["title"] - date = item["published"] - text = clear_html(item["summary"]) - if len(text) > 255: - # Limit the text output in certain "summaries" - text = text[:252] + "...\n" - link = item["link"] - - printer.set(align="center", - text_type="BU", - smooth=True) - printer.text(f"{title}\n") - reset_defaults(printer) - printer.set(align="left", - font="b") - printer.text(f"{date}\n\n") - printer.text(text) - printer.text("\n") - - if generate_qr: - printer.qr(link, size=6) + printer.set(align="center", + text_type="BU", + smooth=True) + printer.text(f"{title}\n") + self.reset_defaults() + printer.set(align="left", + font="b") + printer.text(f"{date}\n\n") + printer.text(text) printer.text("\n") + if self.gen_qr: + printer.qr(link, size=6) + printer.text("\n") -def print_text(printer, text): - """ - Prints a text in the smallest form possible - """ - reset_defaults(printer) - # Set the font to a small one and align to - # the left - printer.set(align="left", - text_type="NORMAL", - font="b", - smooth=True) - printer.text("\n") - if text.strip() == "-": - # Load stdin data - for line in sys.stdin: + def print_text(self, text): + """ + Prints a text in the smallest form possible + """ + printer = self.printer + + self.reset_defaults() + # Set the font to a small one and align to + # the left + printer.set(align="left", + text_type="NORMAL", + font="b", + smooth=True) + printer.text("\n") + if text.strip() == "-": + # Load stdin data + for line in sys.stdin: + printer.text(line) + + printer.text(text) + + def print_empty_lines(self, lines=2): + """ + Generates several \n depending on lines to create + empty spaces in the ticket printed + """ + linebreaks = "\n" * lines + self.printer.text(linebreaks) + + def print_file(self, file): + """ + Prints a file + """ + printer = self.printer + self.reset_defaults() + # Set the font to a small one and align to + # the left + printer.set(align="left", + text_type="NORMAL", + font="b", + smooth=True) + + printer.text("\n") + + for line in file: printer.text(line) - printer.text(text) + file.close() + def print_image(self, image): + """ + Prints an image + """ + printer = self.printer + # Load the image to adjust it + im = Image.open(image) -def print_file(printer, file): - """ - Prints a file - """ - reset_defaults(printer) - # Set the font to a small one and align to - # the left - printer.set(align="left", - text_type="NORMAL", - font="b", - smooth=True) + ratio = float(im.size[0]) / float(im.size[1]) - printer.text("\n") + if im.size[0] > im.size[1]: + # The image needs to be rotated + width = math.floor(384 * ratio) + im = im.resize((width, 384)) + im = im.transpose(Image.ROTATE_90) - for line in file: - printer.text(line) + else: + height = math.floor(384.0 / ratio) + im = im.resize((384, height)) - file.close() + im.save("temp.png") + printer.hw("INIT") + printer.image("temp.png") + os.remove("temp.png") + def print_calendar(self): + """ + Connects to a webdav calendar and retrieves + the events for today + """ + printer = self.printer + c = Client(self.config) + events = c.get_todays_events() -def print_image(printer, image): - """ - Prints an image - """ - # Load the image to adjust it - im = Image.open(image) - - ratio = float(im.size[0]) / float(im.size[1]) - - if im.size[0] > im.size[1]: - # The image needs to be rotated - width = math.floor(384 * ratio) - im = im.resize((width, 384)) - im = im.transpose(Image.ROTATE_90) - - else: - height = math.floor(384.0 / ratio) - im = im.resize((384, height)) - - im.save("temp.png") - printer.hw("INIT") - printer.image("temp.png") - os.remove("temp.png") - - -def print_calendar(printer): - """ - Connects to a webdav calendar and retrieves - the events for today - """ - c = Client(config) - events = c.get_todays_events() - - printer.set(align="center", - text_type="BU", - smooth=True) - printer.text("Today:\n") - - printer.set(align="left", - text_type="NORMAL", - font="b", - smooth=True) - - if len(events) > 0: - for event in events: - hour = event[1].hour - minutes = event[1].minute - if minutes < 10: - printer.text(f"{hour}:0{minutes}\n") - - else: - printer.text(f"{hour}:{minutes}\n") - - printer.text(f"{event[0]}\n--\n") - - else: printer.set(align="center", + text_type="BU", + smooth=True) + printer.text("Today:\n") + + printer.set(align="left", text_type="NORMAL", font="b", smooth=True) - printer.text("No events today\n") + if len(events) > 0: + for event in events: + hour = event[1].hour + minutes = event[1].minute + if minutes < 10: + printer.text(f"{hour}:0{minutes}\n") -def printing_script(printer): - """ - Gets the configuration and parses its script - """ - script = config.get("SCRIPT") + else: + printer.text(f"{hour}:{minutes}\n") - if not script: - logger.error("No script in config file") - return + printer.text(f"{event[0]}\n--\n") - for command in script: - key = list(command)[0] - value = command[key] - if key == 'WEATHER': - print_weather(printer, city=value) + else: + printer.set(align="center", + text_type="NORMAL", + font="b", + smooth=True) + printer.text("No events today\n") - if key == 'RSS': - print_rss(printer, value) + def printing_script(self): + """ + Gets the configuration and parses its script + """ + printer = self.printer + script = self.config.get("SCRIPT") - if key == 'GEMINI': - print_gemini(printer, value) + if not script: + logger.error("No script in config file") + return - if key == 'FILE': - print_file(printer, value) + for command in script: + key = list(command)[0] + value = command[key] + if key == 'WEATHER': + self.print_weather(city=value) - if key == 'IMAGE': - print_image(printer, value) + if key == 'RSS': + self.print_rss(value) - if key == 'TEXT': - print_text(printer, value) - printer.text('\n') + if key == 'GEMINI': + self.print_gemini(value) - if key == 'CALENDAR': - print_calendar(printer) + if key == 'FILE': + self.print_file(value) - printer.set(align="center", - text_type="NORMAL", - font="b", - smooth=True) - printer.text("-----\n") + if key == 'IMAGE': + self.print_image(value) + + if key == 'TEXT': + self.print_text(value) + printer.text('\n') + + if key == 'CALENDAR': + self.print_calendar() + + printer.set(align="center", + text_type="NORMAL", + font="b", + smooth=True) + printer.text("-----\n") def clear_html(text): @@ -443,51 +467,52 @@ def create_parser(): if __name__ == "__main__": - printer = initialize() parser = create_parser() args = parser.parse_args() generate_qr = args.genqr + printer = SmallPrint(generate_qr) + ops_called = 0 if args.text: ops_called += 1 - print_text(printer, args.text[0]) - printer.text("\n\n") + printer.print_text(args.text[0]) + printer.print_empty_lines(2) if args.file: ops_called += 1 - print_file(printer, args.file) - printer.text("\n\n") + printer.print_file(args.file) + printer.print_empty_lines(2) if args.weather: ops_called += 1 - print_weather(printer, args.weather) - printer.text("\n\n") + printer.print_weather(args.weather) + printer.print_empty_lines(2) if args.gemini: ops_called += 1 - print_gemini(printer, args.gemini) - printer.text("\n\n") + printer.print_gemini(args.gemini) + printer.print_empty_lines(2) if args.news: ops_called += 1 - print_rss(printer, args.news) + printer.print_rss(args.news, gen_qr=generate_qr) if args.image: ops_called += 1 - print_image(printer, args.image[0]) - printer.text("\n\n") + printer.print_image(args.image[0]) + printer.print_empty_lines(2) if args.calendar: ops_called += 1 - print_calendar(printer) - printer.text("\n\n") + printer.print_calendar(printer) + printer.print_empty_lines(2) # In case we get called (almost) empty if ops_called == 0: # Try to parse a config script - printing_script(printer) - printer.text("\n\n") + printer.printing_script() + printer.print_empty_lines(2)