# Logbook

# Source code for logbook.notifiers

    """
        logbook.notifiers
        ~~~~~~~~~~~~~~~~~

        System notify handlers for OSX and Linux.

        :copyright: (c) 2010 by Armin Ronacher, Christopher Grebs.
        :license: BSD, see LICENSE for more details.
    """
    import base64
    import os
    import sys
    from http import client as http_client
    from time import time
    from urllib.parse import urlencode

    from logbook.base import ERROR, NOTSET, WARNING
    from logbook.handlers import Handler, LimitingHandlerMixin
    from logbook.helpers import get_application_name

    def create_notification_handler(application_name=None, level=NOTSET, icon=None):
        """Creates a handler perfectly fit the current platform.  On Linux
        systems this creates a :class:`LibNotifyHandler`, on OS X systems it
        will create a :class:`GrowlHandler`.
        """
        if sys.platform == "darwin":
            return GrowlHandler(application_name, level=level, icon=icon)
        return LibNotifyHandler(application_name, level=level, icon=icon)

    class NotificationBaseHandler(Handler, LimitingHandlerMixin):
        """Baseclass for notification handlers."""

        def __init__(
            self,
            application_name=None,
            record_limit=None,
            record_delta=None,
            level=NOTSET,
            filter=None,
            bubble=False,
        ):
            Handler.__init__(self, level, filter, bubble)
            LimitingHandlerMixin.__init__(self, record_limit, record_delta)
            if application_name is None:
                application_name = get_application_name()
            self.application_name = application_name

        def make_title(self, record):
            """Called to get the title from the record."""
            return f"{record.channel}: {record.level_name.title()}"

        def make_text(self, record):
            """Called to get the text of the record."""
            return record.message

    class GrowlHandler(NotificationBaseHandler):
        """A handler that dispatches to Growl.  Requires that either growl-py or
        py-Growl are installed.
        """

        def __init__(
            self,
            application_name=None,
            icon=None,
            host=None,
            password=None,
            record_limit=None,
            record_delta=None,
            level=NOTSET,
            filter=None,
            bubble=False,
        ):
            NotificationBaseHandler.__init__(
                self, application_name, record_limit, record_delta, level, filter, bubble
            )

            # growl is using the deprecated md5 module, but we really don't need
            # to see that deprecation warning
            from warnings import filterwarnings

            filterwarnings(module="Growl", category=DeprecationWarning, action="ignore")

            try:
                import Growl

                self._growl = Growl
            except ImportError:
                raise RuntimeError(
                    "The growl module is not available.  You have "
                    "to install either growl-py or py-Growl to "
                    "use the GrowlHandler."
                )

            if icon is not None:
                if not os.path.isfile(icon):
                    raise OSError("Filename to an icon expected.")
                icon = self._growl.Image.imageFromPath(icon)
            else:
                try:
                    icon = self._growl.Image.imageWithIconForCurrentApplication()
                except TypeError:
                    icon = None

            self._notifier = self._growl.GrowlNotifier(
                applicationName=self.application_name,
                applicationIcon=icon,
                notifications=[
                    "Notset",
                    "Debug",
                    "Info",
                    "Notice",
                    "Warning",
                    "Error",
                    "Critical",
                ],
                hostname=host,
                password=password,
            )
            self._notifier.register()

        def is_sticky(self, record):
            """Returns `True` if the sticky flag should be set for this record.
            The default implementation marks errors and criticals sticky.
            """
            return record.level >= ERROR

        def get_priority(self, record):
            """Returns the priority flag for Growl.  Errors and criticals are
            get highest priority (2), warnings get higher priority (1) and the
            rest gets 0.  Growl allows values between -2 and 2.
            """
            if record.level >= ERROR:
                return 2
            elif record.level == WARNING:
                return 1
            return 0

        def emit(self, record):
            if not self.check_delivery(record)[1]:
                return
            self._notifier.notify(
                record.level_name.title(),
                self.make_title(record),
                self.make_text(record),
                sticky=self.is_sticky(record),
                priority=self.get_priority(record),
            )

    class LibNotifyHandler(NotificationBaseHandler):
        """A handler that dispatches to libnotify.  Requires pynotify installed.
        If `no_init` is set to `True` the initialization of libnotify is skipped.
        """

        def __init__(
            self,
            application_name=None,
            icon=None,
            no_init=False,
            record_limit=None,
            record_delta=None,
            level=NOTSET,
            filter=None,
            bubble=False,
        ):
            NotificationBaseHandler.__init__(
                self, application_name, record_limit, record_delta, level, filter, bubble
            )

            try:
                import pynotify

                self._pynotify = pynotify
            except ImportError:
                raise RuntimeError(
                    "The pynotify library is required for the LibNotifyHandler."
                )

            self.icon = icon
            if not no_init:
                pynotify.init(self.application_name)

        def set_notifier_icon(self, notifier, icon):
            """Used to attach an icon on a notifier object."""
            try:
                from gtk import gdk
            except ImportError:
                # TODO: raise a warning?
                raise RuntimeError("The gtk.gdk module is required to set an icon.")

            if icon is not None:
                if not isinstance(icon, gdk.Pixbuf):
                    icon = gdk.pixbuf_new_from_file(icon)
                notifier.set_icon_from_pixbuf(icon)

        def get_expires(self, record):
            """Returns either EXPIRES_DEFAULT or EXPIRES_NEVER for this record.
            The default implementation marks errors and criticals as EXPIRES_NEVER.
            """
            pn = self._pynotify
            return pn.EXPIRES_NEVER if record.level >= ERROR else pn.EXPIRES_DEFAULT

        def get_urgency(self, record):
            """Returns the urgency flag for pynotify.  Errors and criticals are
            get highest urgency (CRITICAL), warnings get higher priority (NORMAL)
            and the rest gets LOW.
            """
            pn = self._pynotify
            if record.level >= ERROR:
                return pn.URGENCY_CRITICAL
            elif record.level == WARNING:
                return pn.URGENCY_NORMAL
            return pn.URGENCY_LOW

        def emit(self, record):
            if not self.check_delivery(record)[1]:
                return
            notifier = self._pynotify.Notification(
                self.make_title(record), self.make_text(record)
            )
            notifier.set_urgency(self.get_urgency(record))
            notifier.set_timeout(self.get_expires(record))
            self.set_notifier_icon(notifier, self.icon)
            notifier.show()

    class BoxcarHandler(NotificationBaseHandler):
        """Sends notifications to boxcar.io.  Can be forwarded to your iPhone or
        other compatible device.
        """

        api_url = "https://boxcar.io/notifications/"

        def __init__(
            self,
            email,
            password,
            record_limit=None,
            record_delta=None,
            level=NOTSET,
            filter=None,
            bubble=False,
        ):
            NotificationBaseHandler.__init__(
                self, None, record_limit, record_delta, level, filter, bubble
            )
            self.email = email
            self.password = password

        def get_screen_name(self, record):
            """Returns the value of the screen name field."""
            return record.level_name.title()

        def emit(self, record):
            if not self.check_delivery(record)[1]:
                return
            body = urlencode(
                {
                    "notification[from_screen_name]": self.get_screen_name(record).encode(
                        "utf-8"
                    ),
                    "notification[message]": self.make_text(record).encode("utf-8"),
                    "notification[from_remote_service_id]": str(int(time() * 100)),
                }
            )
            con = http_client.HTTPSConnection("boxcar.io")
            con.request(
                "POST",
                "/notifications/",
                headers={
                    "Authorization": "Basic "
                    + base64.b64encode(f"{self.email}:{self.password}".encode()).strip(),
                },
                body=body,
            )
            con.close()

    class NotifoHandler(NotificationBaseHandler):
        """Sends notifications to notifo.com.  Can be forwarded to your Desktop,
        iPhone, or other compatible device.
        """

        def __init__(
            self,
            application_name=None,
            username=None,
            secret=None,
            record_limit=None,
            record_delta=None,
            level=NOTSET,
            filter=None,
            bubble=False,
            hide_level=False,
        ):
            try:
                import notifo
            except ImportError:
                raise RuntimeError(
                    "The notifo module is not available.  You have "
                    "to install notifo to use the NotifoHandler."
                )
            NotificationBaseHandler.__init__(
                self, None, record_limit, record_delta, level, filter, bubble
            )
            self._notifo = notifo
            self.application_name = application_name
            self.username = username
            self.secret = secret
            self.hide_level = hide_level

        def emit(self, record):
            if self.hide_level:
                _level_name = None
            else:
                _level_name = self.level_name

            self._notifo.send_notification(
                self.username,
                self.secret,
                None,
                record.message,
                self.application_name,
                _level_name,
                None,
            )

    class PushoverHandler(NotificationBaseHandler):
        """Sends notifications to pushover.net.  Can be forwarded to your Desktop,
        iPhone, or other compatible device. If `priority` is not one of -2, -1, 0,
        or 1, it is set to 0 automatically.
        """

        def __init__(
            self,
            application_name=None,
            apikey=None,
            userkey=None,
            device=None,
            priority=0,
            sound=None,
            record_limit=None,
            record_delta=None,
            level=NOTSET,
            filter=None,
            bubble=False,
            max_title_len=100,
            max_message_len=512,
        ):
            super().__init__(None, record_limit, record_delta, level, filter, bubble)

            self.application_name = application_name
            self.apikey = apikey
            self.userkey = userkey
            self.device = device
            self.priority = priority
            self.sound = sound

            self.max_title_len = max_title_len
            self.max_message_len = max_message_len

            if self.application_name is None:
                self.title = None
            else:
                self.title = self._crop(self.application_name, self.max_title_len)

            if self.priority not in [-2, -1, 0, 1]:
                self.priority = 0

        def _crop(self, msg, max_len):
            if max_len is not None and max_len > 0 and len(msg) > max_len:
                return f"{msg[: max_len - 3]}..."
            else:
                return msg

        def emit(self, record):
            message = self._crop(record.message, self.max_message_len)

            body_dict = {
                "token": self.apikey,
                "user": self.userkey,
                "message": message,
                "priority": self.priority,
            }

            if self.title is not None:
                body_dict["title"] = self.title
            if self.device is not None:
                body_dict["device"] = self.device
            if self.sound is not None:
                body_dict["sound"] = self.sound

            body = urlencode(body_dict)
            con = http_client.HTTPSConnection("api.pushover.net")
            con.request("POST", "/1/messages.json", body=body)
            con.close()