예제 #1
0
class RichPressence:
    def __init__(self, client):
        self.rpc = Presence(client)  # Initialize the client class
        self.rpc.connect()  # Start the handshake loop

    def set_presence(self, pid, state, detail, large_image=None, starttime=None, limit=5):
        try:
            # while  True:
            if large_image and starttime:
                self.rpc.update(state=state, large_image=large_image, start=starttime, details=detail, pid=pid)  # Set the presence
            elif large_image:
                self.rpc.update(state=state, large_image=large_image, details=detail, pid=pid)  # Set the presence
            elif starttime:
                self.rpc.update(state=state, start=starttime, details=detail, pid=pid)  # Set the presence
            else:
                self.rpc.update(state=state, details=detail, pid=pid)  # Set the presence
                
                # time.sleep(limit)
        except Exception as e:
            print(e)
            self.rpc.close()  # close connection
            exit()

    def clear(self, pid):
        try:
            self.rpc.clear(pid)  # close connection
        except Exception as e:
            print(e)
            pass
예제 #2
0
class Discord:
    def __init__(self, id):
        self.id = id
        self.rpc = Presence(id)
        self.cleared = True
        self.rpc.connect()

    def clear(self):
        if self.cleared == False:
            print("Clearing status")
            self.rpc.clear()

        self.cleared = True

    def setTrack(self, track):
        print("Changing track to: " + track.track + " by " + track.artist + "(" + track.artist.lower().replace(' ', '_') + ")")
        self.cleared = False
        self.rpc.update(
            state="By " + track.artist,
            details=track.track,
            start=int(track.start),
            large_image=track.artist.lower().replace(' ', '_'),
            large_text="Apple Music",
            small_image="logo",
            small_text="github.com/leahlundqvist")
예제 #3
0
class MyClass():
    def __init__(self, client_id):
        self.client_id = client_id
        self.RPC = Presence(self.client_id)

    def connect(self):
        try:
            self.RPC.connect()
            return True
        except Exception as e:
            print(str(e))
            return False

    def statusUpdate(self, state):
        try:
            self.RPC.update(details=state['details'],
                            state=state["state"],
                            start=state["startTimestamp"],
                            large_image=state['largeImageKey'],
                            large_text=state["largeImageText"],
                            small_image=state['smallImageKey'],
                            small_text=state["smallImageText"],
                            buttons=state["buttons"])
        except Exception as e:
            print(str(e))

    def close_presence(self):
        try:
            self.RPC.clear(pid=os.getpid())
        except Exception as e:
            print(str(e))
예제 #4
0
def connect_to_discord():
    '''Connection code for Discord.
    Also clears the current presence in case the app is restarted and Discord
    still assumes the old state.'''
    global p  #Use the global presence variable
    p = Presence(client_id=CLIENT_ID)
    logging.info("Connecting to Discord...")
    p.connect()
    logging.info("Connected.")
    logging.debug("Clearing presence...")
    p.clear()  #Clear the presence
    logging.debug("Presence cleared.")
예제 #5
0
def main():

    VERSION = "1.1.0";

    config = GetConfig();

    mpdc = MPDClient();
    mpdc.timeout = int(config["General"]["MPDtimeout"]);
    mpdc.idletimeout = int(config["General"]["MPDtimeout_idle"]);
    try:
        mpdc.connect(config["General"]["MPDip"], port=int(config["General"]["MPDport"]));
    except ConnectionRefusedError:
        print(f"{TermColors.ERROR}ERROR!!! Either mpd isn't running or you have a mistake in the config. Fix ASAP! Exiting!{TermColors.END}");
        exit(-1);

    RPC = Presence(710956455867580427);
    RPC.connect();

    if (psys() == "Windows"):
        ossys("cls");
    else:
        ossys("clear");

    print(f"{TermColors.WORKS}MPDDRP v.{VERSION} - https://github.com/AKurushimi/mpddrp{TermColors.END}");

    try:
        while True:

            statusout = mpdc.status();
            csout = mpdc.currentsong();

            if (statusout["state"] != "stop"):
                title = csout["title"];
                artist = csout["artist"];
                album = csout["album"];
                timevar = statusout["time"].split(":");
                timenow = str(timedelta(seconds=int(timevar[0])));
                timeall = str(timedelta(seconds=int(timevar[1])));

            if (statusout["state"] == "pause"):
                RPC.update(details=title + " | " + album, state="Paused | " + artist, large_image="mpdlogo", small_image="pause", small_text="Paused");
            elif (statusout["state"] == "stop"):
                RPC.update(details="Stopped | MPD", state="   ", large_image="mpdlogo", small_image="stop", small_text="Stopped");
            elif (statusout["state"] == "play"):
                RPC.update(details=title + " | " + album, state=timenow + "/" + timeall + " | " + artist, large_image="mpdlogo", small_image="play", small_text="Playing");
                sleep(1);

    except (RuntimeError):
        pass;
    except (KeyboardInterrupt, SystemExit):
        RPC.clear();
        RPC.close();
        exit(0);
예제 #6
0
class Discord:
    def __init__(self, id):
        self.id = id
        self.rpc = Presence(id)
        self.cleared = True
        self.rpc.connect()

    def clear(self):
        if self.cleared == False:
            print("Clearing status")
            self.rpc.clear()

        self.cleared = True

    def setTrack(self, track):
        print("Changing track to: " + track.track + " by " + track.artist)
        self.cleared = False
        self.rpc.update(state="By " + track.artist,
                        details=track.track,
                        start=int(track.start),
                        large_image="logo",
                        large_text="Apple Music",
                        small_image="github",
                        small_text="github/memeone/amrpc")
예제 #7
0
class DiscordPresence(object):
    """Provide rich presence integration with Discord for games"""

    def __init__(self):
        self.available = bool(PyPresence)
        self.game_name = ""
        self.runner_name = ""
        self.last_rpc = 0
        self.rpc_interval = 60
        self.presence_connected = False
        self.rpc_client = None
        self.client_id = None
        self.custom_game_name = ''
        self.show_runner = True
        self.custom_runner_name = ''
        self.rpc_enabled = True

    def connect(self):
        """Make sure we are actually connected before trying to send requests"""
        logger.debug("Ensuring connected.")
        if self.presence_connected:
            logger.debug("Already connected!")
        else:
            logger.debug("Creating Presence object.")
            self.rpc_client = PyPresence(self.client_id)
            try:
                logger.debug("Attempting to connect.")
                self.rpc_client.connect()
                self.presence_connected = True
            except (ConnectionError, FileNotFoundError):
                logger.error("Could not connect to Discord")
        return self.presence_connected

    def disconnect(self):
        """Ensure we are definitely disconnected and fix broken event loop from pypresence
        That method is a huge mess of non-deterministic bs and should be nuked from orbit.
        """
        logger.debug("Disconnecting from Discord")
        if self.rpc_client:
            try:
                self.rpc_client.close()
            except Exception as e:
                logger.exception("Unable to close Discord RPC connection: %s", e)
            if self.rpc_client.sock_writer is not None:
                try:
                    logger.debug("Forcefully closing sock writer.")
                    self.rpc_client.sock_writer.close()
                except Exception:
                    logger.exception("Sock writer could not be closed.")
            try:
                logger.debug("Forcefully closing event loop.")
                self.rpc_client.loop.close()
            except Exception:
                logger.debug("Could not close event loop.")
            try:
                logger.debug("Forcefully replacing event loop.")
                self.rpc_client.loop = None
                asyncio.set_event_loop(asyncio.new_event_loop())
            except Exception as e:
                logger.exception("Could not replace event loop: %s", e)
            try:
                logger.debug("Forcefully deleting RPC client.")
                self.rpc_client = None
            except Exception as ex:
                logger.exception(ex)
        self.rpc_client = None
        self.presence_connected = False

    def update_discord_rich_presence(self):
        """Dispatch a request to Discord to update presence"""
        if int(time.time()) - self.rpc_interval < self.last_rpc:
            logger.debug("Not enough time since last RPC")
            return
        if self.rpc_enabled:
            self.last_rpc = int(time.time())
            if not self.connect():
                return
            try:
                state_text = "via %s" % self.runner_name if self.show_runner else "  "
                logger.info("Attempting to update Discord status: %s, %s",
                            self.game_name, state_text)
                self.rpc_client.update(details="Playing %s" % self.game_name, state=state_text)
            except PyPresenceException as ex:
                logger.error("Unable to update Discord: %s", ex)

    def clear_discord_rich_presence(self):
        """Dispatch a request to Discord to clear presence"""
        if self.rpc_enabled:
            if self.connect():
                try:
                    logger.info('Attempting to clear Discord status.')
                    self.rpc_client.clear()
                except PyPresenceException as ex:
                    logger.error("Unable to clear Discord: %s", ex)
                    self.disconnect()
예제 #8
0
def start_mainloop():
    logging.info('Mainloop started')

    count = 0

    rpc = Presence(CLIENT_ID)
    rpc.connect()

    lichess = Lichess(config.get_lichess_username())
    chesscom = Chesscom(config.get_chesscom_username())

    platforms = []

    while states.running:

        updated = False

        lichess.update_username(config.get_lichess_username())
        chesscom.update_username(config.get_chesscom_username())

        if lichess.is_available():
            platforms.append(lichess)

        if chesscom.is_available():
            print('available', chesscom.username)
            platforms.append(chesscom)

        print(platforms)

        if len(platforms) == 0:
            logging.warning('Missing information -> update config')
            rpc.clear()
            utils.open_webpage()

            while config.get_temp_data() == {} or all(
                    config.get_temp_data()[key] is None
                    for key in config.get_temp_data()):
                time.sleep(1)
            else:
                continue

        count += 1
        logging.info(f'Presence updated ({count})')

        for platform in platforms:

            platform.update_data()

            status = platform.get_status()

            if status is OFFLINE:
                continue

            if status is PLAYING:
                platform.display_playing(rpc)
                updated = True
                break

            if status is ONLINE:
                platform.display_online(rpc)
                updated = True
                break

        if not updated:
            rpc.clear()

        platforms.clear()
        time.sleep(15)

    rpc.clear()
    logging.info('Mainloop ended')
예제 #9
0
from pypresence import Presence
import time
import requests

client_id = ""
rpc = Presence(client_id)
rpc.connect()

url = "https://r-a-d.io/api"
rpc.clear()

while True:
    r = requests.get(url=url)
    data = r.json()
    np = data['main']['np']
    dj = data['main']['dj']['djname']
    dj_image = data['main']['dj']['djimage']
    thread = data['main']['thread']
    if dj == "Hanyuu-sama":
        large_image = "hanyuu"
    elif dj == "Claud":
        large_image = "claud"
    elif dj == "apt-get":
        large_image = "apt-get"
    elif dj == "kipukun":
        large_image = "kipukun"
    elif dj == "Suzubrah":
        large_image = "suzubrah"
    elif dj == "exci":
        large_image = "exci"
    elif dj == "Wessie":
예제 #10
0
                       small_text=small_image_text)
     sleep(5)
 elif playback_status == "Stopped":
     if already_stopped == 0:
         RPC_Client.update(state=RPC_DETAILS,
                           details=RPC_STATE,
                           large_image=large_image_key,
                           large_text=large_image_text,
                           small_image=small_image_text,
                           small_text=small_image_text)
         already_stopped = 1
     sleep(5)
     # This handles if discord closes or turns on
     while True:
         try:
             RPC_Client.clear()
         except (ConnectionRefusedError, pyexep.InvalidID):
             logger.error('Connection reset, retrying connection')
             while True:
                 try:
                     RPC_Client.connect()
                 except (FileNotFoundError, ConnectionRefusedError):
                     logger.error(
                         "Could not connect to RPC. Do you have discord running?"
                     )
                     sleep(5)
                 else:
                     break
             sleep(5)
         else:
             break
예제 #11
0
            RPC.update(state="Description: " + des_display,
                       details="" + sub_display,
                       start=int(time.time()),
                       large_text=tol_display,
                       large_image="rmit-l")
        else:
            RPC.update(state="Description: " + des_display,
                       details="" + sub_display,
                       start=int(time.time()),
                       large_image="rmit-l")
        print("Updated Successfully !!!")
        print("")
        command = console(command)

    elif (command == "/clear"):
        print("")
        RPC.clear(pid=os.getpid())
        print("Cleared the status")
        print("")
        command = console(command)

    elif (command == "/quit"):
        print("")
        print("Thank you for using !")
        os._exit(1)

    else:
        print("")
        print("Invalid command!")
        print("")
        command = console(command)
예제 #12
0
            elif (packet.sessionType == 7):
                showQualiPresence("Q3")
            elif (packet.sessionType == 8):
                showQualiPresence("SQ")
            elif (packet.sessionType == 9):
                showQualiPresence("OSQ")
            elif (packet.sessionType == 12):
                showTimeTrialPresence()
            elif (packet.sessionType == 1):
                showPracticePresence("P1")
            elif (packet.sessionType == 2):
                showTimeTrialPresence("P2")
            elif (packet.sessionType == 3):
                showTimeTrialPresence("P3")
            elif (packet.sessionType == 4):
                showTimeTrialPresence("Short Practice")
            else:
                RPC.update(large_image="f12020", details="Idling")

    except socket.timeout:
        RPC.update(large_image="f12020", details="Idling")
        while not "F1_2020_dx12.exe" in (p.name() for p in psutil.process_iter()):
            RPC.clear(os.getpid())
            continue






예제 #13
0
class DiscordPresence(object):
    """This class provides rich presence integration
       with Discord for games.
    """

    def __init__(self):
        self.available = bool(PyPresence)
        self.game_name = ""
        self.runner_name = ""
        self.last_rpc = 0
        self.rpc_interval = 60
        self.presence_connected = False
        self.rpc_client = None
        self.client_id = None
        self.custom_game_name = ''
        self.show_runner = True
        self.custom_runner_name = ''
        self.rpc_enabled = True

    def load_from_config(self, config):
        """Loads
        """

    def ensure_discord_connected(self):
        """Make sure we are actually connected before trying to send requests"""
        logger.debug("Ensuring connected.")
        if self.presence_connected:
            logger.debug("Already connected!")
        else:
            logger.debug("Creating Presence object.")
            self.rpc_client = PyPresence(self.client_id)
            try:
                logger.debug("Attempting to connect.")
                self.rpc_client.connect()
                self.presence_connected = True
            except Exception as ex:
                logger.exception("Unable to reach Discord.  Skipping update: %s", ex)
                self.ensure_discord_disconnected()
        return self.presence_connected

    def ensure_discord_disconnected(self):
        """Ensure we are definitely disconnected and fix broken event loop from pypresence"""
        logger.debug("Ensuring disconnected.")
        if self.rpc_client is not None:
            try:
                self.rpc_client.close()
            except Exception as e:
                logger.exception("Unable to close Discord RPC connection: %s", e)
            if self.rpc_client.sock_writer is not None:
                try:
                    logger.debug("Forcefully closing sock writer.")
                    self.rpc_client.sock_writer.close()
                except Exception:
                    logger.exception("Sock writer could not be closed.")
            try:
                logger.debug("Forcefully closing event loop.")
                self.rpc_client.loop.close()
            except Exception:
                logger.debug("Could not close event loop.")
            try:
                logger.debug("Forcefully replacing event loop.")
                self.rpc_client.loop = None
                asyncio.set_event_loop(asyncio.new_event_loop())
            except Exception as e:
                logger.exception("Could not replace event loop: %s", e)
            try:
                logger.debug("Forcefully deleting RPC client.")
                self.rpc_client = None
            except Exception as ex:
                logger.exception(ex)
        self.rpc_client = None
        self.presence_connected = False

    def update_discord_rich_presence(self):
        """Dispatch a request to Discord to update presence"""
        if int(time.time()) - self.rpc_interval < self.last_rpc:
            logger.debug("Not enough time since last RPC")
            return
        if self.rpc_enabled:
            self.last_rpc = int(time.time())
            connected = self.ensure_discord_connected()
            if not connected:
                return
            try:
                state_text = "via %s" % self.runner_name if self.show_runner else "  "
                logger.info("Attempting to update Discord status: %s, %s",
                            self.game_name, state_text)
                self.rpc_client.update(details="Playing %s" % self.game_name, state=state_text)
            except PyPresenceException as ex:
                logger.error("Unable to update Discord: %s", ex)

    def clear_discord_rich_presence(self):
        """Dispatch a request to Discord to clear presence"""
        if self.rpc_enabled:
            connected = self.ensure_discord_connected()
            if connected:
                try:
                    logger.info('Attempting to clear Discord status.')
                    self.rpc_client.clear()
                except PyPresenceException as e:
                    logger.error("Unable to clear Discord: %s", e)
                    self.ensure_discord_disconnected()
예제 #14
0
class DiscordStatusMessage(EventPlugin):
    PLUGIN_ID = _("Discord status message")
    PLUGIN_NAME = _("Discord Status Message")
    PLUGIN_DESC = _("Change your Discord status message according to what "
                    "you're currently listening to.")
    VERSION = VERSION

    def __init__(self):
        self.song = None
        self.discordrp = None

    def update_discordrp(self, details, state=None):
        if not self.discordrp:
            try:
                self.discordrp = Presence(QL_DISCORD_RP_ID, pipe=0)
                self.discordrp.connect()
            except (DiscordNotFound, ConnectionRefusedError):
                self.discordrp = None

        if self.discordrp:
            try:
                self.discordrp.update(details=details,
                                      state=state,
                                      large_image=QL_LARGE_IMAGE)
            except InvalidID:
                # XXX Discord was closed?
                self.discordrp = None

    def handle_play(self):
        if self.song:
            details = Pattern(discord_status_config.rp_line1) % self.song
            state = Pattern(discord_status_config.rp_line2) % self.song

            # The details and state fields must be atleast 2 characters.
            if len(details) < 2:
                details = None

            if len(state) < 2:
                state = None

            self.update_discordrp(details, state)

    def handle_paused(self):
        self.update_discordrp(details=_("Paused"))

    def handle_unpaused(self):
        if not self.song:
            self.song = app.player.song
        self.handle_play()

    def plugin_on_song_started(self, song):
        self.song = song
        if not app.player.paused:
            self.handle_play()

    def plugin_on_paused(self):
        self.handle_paused()

    def plugin_on_unpaused(self):
        self.handle_unpaused()

    def enabled(self):
        if app.player.paused:
            self.handle_paused()
        else:
            self.handle_unpaused()

    def disabled(self):
        if self.discordrp:
            self.discordrp.clear()
            self.discordrp.close()
            self.discordrp = None
            self.song = None

    def PluginPreferences(self, parent):
        vb = Gtk.VBox(spacing=6)

        def rp_line1_changed(entry):
            discord_status_config.rp_line1 = entry.get_text()
            if not app.player.paused:
                self.handle_play()

        def rp_line2_changed(entry):
            discord_status_config.rp_line2 = entry.get_text()
            if not app.player.paused:
                self.handle_play()

        status_line1_box = Gtk.HBox(spacing=6)
        status_line1_box.set_border_width(3)

        status_line1 = Gtk.Entry()
        status_line1.set_text(discord_status_config.rp_line1)
        status_line1.connect('changed', rp_line1_changed)

        status_line1_box.pack_start(Gtk.Label(label=_("Status Line #1")),
                                    False, True, 0)
        status_line1_box.pack_start(status_line1, True, True, 0)

        status_line2_box = Gtk.HBox(spacing=3)
        status_line2_box.set_border_width(3)

        status_line2 = Gtk.Entry()
        status_line2.set_text(discord_status_config.rp_line2)
        status_line2.connect('changed', rp_line2_changed)

        status_line2_box.pack_start(Gtk.Label(label=_('Status Line #2')),
                                    False, True, 0)
        status_line2_box.pack_start(status_line2, True, True, 0)

        vb.pack_start(status_line1_box, True, True, 0)
        vb.pack_start(status_line2_box, True, True, 0)

        return vb
예제 #15
0
class PoeRPC:
    def __init__(self, loop, account_name, cookies, logger):
        self.rpc = Presence(CLIENT_ID, pipe=0, loop=loop)
        self.cookies = cookies
        self.log_path = None
        self.on = True
        self.loop = loop
        self.logger = logger
        self.last_location = None
        self.last_latest_message = ""
        self.last_event = LogEvents.LOGOUT
        self.afk = False
        self.dnd = False
        self.afk_message = ""
        self.dnd_message = ""
        self.account_name = account_name
        self.current_rpc = {}
        self.locations = {}
        self.quit = False
        self.logger.debug("Loading areas")
        with open('areas.json') as f:
            areas = json.load(f)
        self.logger.debug("Loaded areas")
        self.locations.update(areas)
        self.logger.debug("Loading maps")
        with open('maps.json') as f:
            self.maps = json.load(f)
        self.logger.debug("Loaded maps")

        self.logger.debug("Loading icon refs")
        with open('available_icons.json') as f:
            self.icons = json.load(f)
        self.logger.debug("Loaded icon refs")

        self.logger.debug("Loading xp ref")
        with open('experience.json') as f:
            self.xp = json.load(f)
        self.logger.debug("Loaded xp ref")

    def do_quit(self):
        self.quit = True

    async def check_poe(self):
        """Tries to check if poe is running every 15sec, clears RPC if not.
        Waits for poe to start again, calls init again with restart as True"""
        while 1:
            if self.quit:
                break
            poe = get_path()
            if not poe:
                if self.on:
                    self.logger.debug(
                        "PoE no longer open, setting on to false")
                    self.on = False
                    self.rpc.clear()
            else:
                if not self.on:
                    self.logger.debug(
                        f"Found launched poe at {poe} setting on to True and restarting init"
                    )
                    self.on = True
                    self.loop.create_task(self.init(restart=True))
            await asyncio.sleep(15)

    def update_rpc(self, field, value):
        """Basically just neater way to indicate updating of each field for rpc dict"""
        self.current_rpc[field] = value

    def submit_update(self):
        """rpc.update takes in everything as a kwarg, using splat operator the current_rpc
        dict is turned into kwargs and passed to update"""
        self.logger.debug(
            f"Submitting rpc update with content: {self.current_rpc}")
        self.rpc.update(**self.current_rpc)

    async def fetch_char(self):
        """Calls to get-characters at poe API, interprets response and updates rpc"""
        async with self.ses.get(
                f'https://www.pathofexile.com/character-window/get-characters?accountName={self.account_name}'
        ) as resp:
            js = await resp.json()
        for char in js:
            # js is still a valid json on error, if you try to iterate over the items, compared to a valid request
            # it is an str and not a dict, thus this works.
            if char is str():
                self.logger.info(
                    "Your character tab is set to be hidden or your profile is private\nchange private to true in config.json"
                    "and set the value for sessid as your POESESSID\nAlternatively you can also make your character tab or profile public."
                )
                exit()
            if 'lastActive' in char.keys():
                break
        asc = char['class']
        level = char['level']
        name = char['name']
        xp = char['experience']
        # Calculate percent to next level
        # XP json contains only the total xp, so it must be subtracted from the xp of next level
        # and then calculated with difference of current xp
        max_cur_xp = int(self.xp[str(level)].replace(',', ''))
        needed_xp = int(self.xp[str(min(level + 1, 100))].replace(',', ''))
        diff1 = needed_xp - xp
        diff2 = needed_xp - max_cur_xp
        perc = round(((diff2 - diff1) / diff2) * 100)
        lg_text = f"{name} | Level {level} {asc}\n{perc}% to Level {'-' if level==100 else level+1}"
        self.update_rpc('large_image', asc.lower())
        self.update_rpc('large_text', lg_text)
        self.update_rpc('state', f"in {char['league']} league")

    @staticmethod
    def fix_names(name):
        """Icon names on discord RPC follow a pretty standard pattern, no special chars, commas, apostrophes, spaces or unicode chars.
        Nothing other than maelstrom has a unicode char, so a chain of replaces just works as well."""
        return name.replace(',',
                            '').replace("'",
                                        "").replace('ö',
                                                    'o').replace(' ',
                                                                 '_').lower()

    async def fetch_area_data(self, name):
        """Runs Area name through a multitude of filters to conclude which area the player is in, 
        then finds the appropriate icon and then update rpc dict"""
        loc = None
        if 'hideout' in name.lower():
            loc = {'name': name}
            img = 'hideout'
        if not loc:
            for map in self.maps:
                if name in map['name']:
                    loc = map
                    fixed_name = self.fix_names(name)
                    if fixed_name in self.icons:
                        img = fixed_name
                    elif int(map['tier']) < 6:
                        img = 'white'
                    elif 6 <= int(map['tier']) <= 10:
                        img = 'yellow'
                    elif int(map['tier']) > 10:
                        img = 'red'
                    elif name.lower() in self.locations['guardians']:
                        if 'Phoenix' in name:
                            img = 'phoenix'
                        elif 'Minotaur' in name:
                            img = 'minotaur'
                        elif 'Hydra' in name:
                            img = 'hydra'
                        else:
                            img = 'chimera'
                    elif name in "Vaal Temple":
                        img = 'vaal_temple'
                    break
        if not loc:
            for town in self.locations['towns']:
                if name in town['name']:
                    loc = town
                    img = 'town'
                    break
        if not loc:
            for part in self.locations['lab_rooms']:
                if part in name:
                    loc = {'name': "The Lord's Labyrinth"}
                    img = 'lab'
                    break

        if not loc:
            if "Azurite Mine" in name:
                loc = {'name': "Azurite Mine"}
                img = 'mine'

        if not loc:
            if "Menagerie" in name or name in self.locations['bestiary_bosses']:
                loc = {'name': name}
                img = 'menagerie'

        if not loc:
            if "absence of value" in name.lower():
                loc = {'name': name}
                img = 'elder'

        if not loc:
            for guardian in self.locations['elder_guardians']:
                if name in self.locations['elder_guardians'][guardian]:
                    loc = {'name': f"{name} - {guardian}"}
                    img = guardian.split()[1].lower()
                    break

        if not loc:
            loc = {'name': name}
            img = 'waypoint'

        small_text = loc['name']
        timestamp = round(time.time())
        self.update_rpc('small_text', small_text)
        self.update_rpc('small_image', img)
        self.update_rpc('start', timestamp)
        if 'tier' in loc.keys():
            self.update_rpc('details',
                            f"{loc['name']} | Tier: {loc['tier']} | ")
        elif 'details' in self.current_rpc:
            if 'Tier' in self.current_rpc['details']:
                del self.current_rpc['details']

    async def handle_log_event(self, log):
        """On the event of a log update, handle_log_event is called.
        The log is passed to it and it iters through messages to decide what methods to call for which event"""
        messages = reversed(log.split('\n'))
        event = None
        ping = None
        for ind, message in enumerate(messages):
            if message == self.last_latest_message:
                self.logger.debug(
                    f"Reached last message from previous update: {message}")
                break
            elif "You have entered" in message:
                loc = message.split("You have entered ")[1].replace('.', '')
                self.logger.info(f"Entered {loc}")
                if self.last_location != loc and loc != "Arena":
                    event = LogEvents.AREA
                    self.last_location = loc
                else:
                    return
                break
            elif "Async connecting" in message or "Abnormal disconnect" in message:
                self.logger.info("On character selection")
                self.last_location = None
                event = LogEvents.LOGOUT
                break
            elif "Connect time" in message:
                ping = message.split("was ")[1]
                self.logger.info(f"Ping to instance was: {ping}")
            elif "AFK mode is now" in message:
                if message.split("AFK mode is now O")[1][0] == "N":
                    self.afk = True
                    self.afk_message = message.split('Autoreply "')[1][:-1]
                    self.logger.info(f"AFK: {self.afk_message}")
                else:
                    self.afk = False
                    self.afk_message = ""
                    self.logger.info("AFK: Turned Off")
            elif "DND mode is now" in message:
                if message.split("DND mode is now O")[1][0] == "N":
                    self.dnd = True
                    self.dnd_message = message.split('Autoreply "')[1][:-1]
                    self.logger.info(f"DND: {self.afk_message}")
                else:
                    self.dnd = False
                    self.dnd_message = ""
                    self.logger.info("DND: Turned Off")
        self.last_latest_message = log.split('\n')[-1] or log.split('\n')[-2]
        if event != LogEvents.LOGOUT:
            await self.fetch_char()
        if event == LogEvents.AREA:
            await self.fetch_area_data(loc)
        elif event == LogEvents.LOGOUT:
            self.current_rpc = {}
            self.update_rpc('large_image', 'login_screen')
            self.update_rpc('state', 'On Character Selection')

        def update_dnd():
            self.update_rpc('details', f"DND: {self.dnd_message}")

        def update_afk():
            self.update_rpc('details', f"AFK: {self.afk_message}")

        if not self.afk and "AFK" in self.current_rpc.get('details', '') \
                or not self.dnd and "DND" in self.current_rpc.get('details', ''):
            self.current_rpc.pop('details')

        if self.dnd and self.afk and "AFK" not in self.current_rpc.get(
                'details', ''):
            update_afk()
        elif self.afk and "AFK" not in self.current_rpc.get('details', ''):
            update_afk()
        elif self.dnd and "DND" not in self.current_rpc.get('details', ''):
            update_dnd()

        if ping:
            state = self.current_rpc.get('state', '')
            if not 'Ping' in state:
                self.update_rpc(
                    'state', f'{state}{" | " if state else ""}Ping: {ping}')
            else:
                current_ping = state.split('Ping: ')[1]
                state = state.replace(current_ping, ping)
                self.update_rpc('state', state)
        self.submit_update()
        self.last_event = event

    async def monitor_log(self):
        """Monitors if log file has changed by checking last message,
        passes log to handler if yes, tries again in 5 seconds"""
        self.logger.info("Log monitor has started")
        while 1:
            if self.quit:
                break
            if not self.on:
                self.logger.info("Log monitor now sleeping")
                await self.ses.close()
                break
            with open(self.log_path, encoding='utf-8') as f:
                log = f.read()
            # log = log.encode('utf-8')
            new_last = log.split('\n')[-1] or log.split('\n')[-2]
            if self.last_latest_message != new_last:
                self.logger.debug(f"Log update observed: {new_last}")
                await self.handle_log_event(log)
            await asyncio.sleep(5)

    @staticmethod
    async def get_poe():
        """When initializing, keeps trying to find a launched Path Of Exile before initializing the monitors"""
        while 1:
            poe = get_path()
            if not poe:
                await asyncio.sleep(10)
            else:
                return poe

    async def init(self, restart=False):
        """Standard method for initialization called by launcher, sets up the aiohttp session
        and starts monitor loops on confirmation from get_poe"""
        try:
            await self.rpc.connect()
        except:
            self.logger.info(
                "Discord not open, waiting for discord to launch...")
            while 1:
                if self.quit:
                    break
                try:
                    await self.rpc.connect()
                    self.logger.info("Discord launched")
                    break
                except:
                    pass
                await asyncio.sleep(5)

        self.logger.info("Waiting for path of exile to launch...")
        poe = await self.get_poe()
        self.log_path = f"{poe}/logs/Client.txt"
        self.logger.info(f"Found path of exile log at {self.log_path}")
        self.ses = aiohttp.ClientSession(cookies=self.cookies)
        if not restart:
            self.loop.create_task(self.check_poe())
        self.loop.create_task(self.monitor_log())
예제 #16
0
class Presence(object):
    """This class provides rich presence integration
       with Discord for games.
    """

    def __init__(self):
        if PyPresence is not None:
            logger.debug('Discord Rich Presence not available due to lack of pypresence')
            self.rich_presence_available = True
        else:
            logger.debug('Discord Rich Presence enabled')
            self.rich_presence_available = False
        self.last_rpc = 0
        self.rpc_interval = 60
        self.presence_connected = False
        self.rpc_client = None
        self.client_id = None
        self.custom_game_name = ''
        self.show_runner = True
        self.custom_runner_name = ''
        self.rpc_enabled = True

    def available(self):
        """Confirm that we can run Rich Presence functions"""
        return self.rich_presence_available

    def ensure_discord_connected(self):
        """Make sure we are actually connected before trying to send requests"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        logger.debug("Ensuring connected.")
        if self.presence_connected:
            logger.debug("Already connected!")
        else:
            logger.debug("Creating Presence object.")
            self.rpc_client = PyPresence(self.client_id)
            try:
                logger.debug("Attempting to connect.")
                self.rpc_client.connect()
                self.presence_connected = True
            except Exception as e:
                logger.error("Unable to reach Discord.  Skipping update: %s", e)
                self.ensure_discord_disconnected()
        return self.presence_connected

    def ensure_discord_disconnected(self):
        """Ensure we are definitely disconnected and fix broken event loop from pypresence"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        logger.debug("Ensuring disconnected.")
        if self.rpc_client is not None:
            try:
                self.rpc_client.close()
            except Exception as e:
                logger.error("Unable to close Discord RPC connection: %s", e)
            if self.rpc_client.sock_writer is not None:
                try:
                    logger.debug("Forcefully closing sock writer.")
                    self.rpc_client.sock_writer.close()
                except Exception:
                    logger.debug("Sock writer could not be closed.")
            try:
                logger.debug("Forcefully closing event loop.")
                self.rpc_client.loop.close()
            except Exception:
                logger.debug("Could not close event loop.")
            try:
                logger.debug("Forcefully replacing event loop.")
                self.rpc_client.loop = None
                asyncio.set_event_loop(asyncio.new_event_loop())
            except Exception as e:
                logger.debug("Could not replace event loop: %s", e)
            try:
                logger.debug("Forcefully deleting RPC client.")
                del self.rpc_client
            except Exception:
                pass
        self.rpc_client = None
        self.presence_connected = False

    def update_discord_rich_presence(self):
        """Dispatch a request to Discord to update presence"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        if int(time.time()) - self.rpc_interval < self.last_rpc:
            logger.debug("Not enough time since last RPC")
            return
        if self.rpc_enabled:
            logger.debug("RPC is enabled")
            self.last_rpc = int(time.time())
            connected = self.ensure_discord_connected()
            if not connected:
                return
            try:
                state_text = "via {}".format(self.runner_name) if self.show_runner else "  "
                logger.info("Attempting to update Discord status: %s, %s", self.game_name, state_text)
                self.rpc_client.update(details="Playing {}".format(self.game_name), state=state_text)
            except PyPresenceException as e:
                logger.error("Unable to update Discord: %s", e)
        else:
            logger.debug("RPC disabled")

    def clear_discord_rich_presence(self):
        """Dispatch a request to Discord to clear presence"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        if self.rpc_enabled:
            connected = self.ensure_discord_connected()
            if connected:
                try:
                    logger.info('Attempting to clear Discord status.')
                    self.rpc_client.clear()
                except PyPresenceException as e:
                    logger.error("Unable to clear Discord: %s", e)
                    self.ensure_discord_disconnected()
예제 #17
0
def main():
    logger.info("Checking for updates...")
    os.system("git pull")
    logger.info(
        "If updates were done, restart this script by using CTRL-C to terminate it, and re run it."
    )

    # Make connection to Discord
    try:
        RPC = Presence(client_id)  # Initialize the Presence class
        RPC.connect()  # Start the handshake loop
    except pypresence.exceptions.InvalidPipe:
        logger.error(
            "Could not connect to the discord pipe. Please ensure it's running."
        )
        exit(1)
    except FileNotFoundError:
        logger.error(
            "Could not connect to the discord pipe. Please ensure it's running."
        )
        exit(1)

    # Load our current config

    config = nso_functions.get_config_file()

    logger.info("Check discord!")

    # get friend code from config, and add config option if does not exist
    try:
        friend_code = config['friend_code']
    except KeyError:
        config['friend_code'] = 'Unset'
        config_f = open("config.txt", "w")
        config_f.write(json.dumps(
            config,
            sort_keys=True,
            indent=4,
        ))
        config_f.close()
        friend_code = config['friend_code']

    while True:  # The presence will stay on as long as the program is running

        for i in range(0, 4):
            minutes_since, last_match = get_minutes_since()
            # int is here so we don't have funky floating point madness
            seconds_since = int(minutes_since * 60)
            hours_since = int(minutes_since / 60)
            if minutes_since >= 60:
                details = "Last match: {} hour(s) ago".format(hours_since)
            elif minutes_since > 1:
                details = "Last match: {} minute(s) ago".format(
                    math.floor(minutes_since))
            else:
                details = "Last match: {} second(s) ago".format(seconds_since)
            # job_result is only present in salmon run JSON
            if last_match.get('job_result') is not None:
                gamemode_key = "salmon_run"
                if last_match['job_result']['is_clear']:
                    outcome = "winning"
                else:
                    outcome = "losing"
                large_text = "Last match was Salmon Run, {} with {} eggs".format(
                    outcome, last_match['job_score'])
                if i == 0:
                    state = "Grade: {}".format(
                        (last_match["grade"])["long_name"])
                elif i == 1:
                    state = "Friend code: {}".format(friend_code)
                elif i == 2:
                    state = "Difficulty: {}".format(
                        str(last_match["danger_rate"]))
                elif i == 3:
                    state = "Played on {}".format(
                        last_match['schedule']['stage']['name'])
            else:
                large_text = "Last match was {}, {} on {}".format(
                    last_match["game_mode"]["name"],
                    last_match["rule"]["name"], last_match["stage"]["name"])
                gamemode_key = last_match["rule"]["key"]
                if i == 0:
                    state = "Friend code: {}".format(friend_code)
                elif i == 1:
                    state = "K/D: {}/{}".format(
                        last_match["player_result"]["kill_count"],
                        last_match["player_result"]["death_count"])
                elif i == 2:
                    details = last_match["my_team_result"]["name"]
                    try:
                        state = "{}% vs {}%".format(
                            last_match["my_team_percentage"],
                            last_match["other_team_percentage"])
                    except KeyError:
                        try:
                            state = "{} vs {}".format(
                                last_match["my_team_count"],
                                last_match["other_team_count"])
                        except KeyError:
                            state = "Gamemode not yet supported"

                elif i == 3:
                    state = "{}p".format(
                        last_match["player_result"]["game_paint_point"])
                    if show_weapon:
                        details = "{}".format(last_match["player_result"]
                                              ["player"]["weapon"]["name"])
                    else:
                        pass
            if minutes_since < timeout_minutes:
                RPC.update(details=details,
                           state=state,
                           large_image=gamemode_key,
                           small_image="default",
                           large_text=large_text)
            else:
                RPC.clear()
                logger.debug("RPC cleared, not in game long enough")
            time.sleep(time_interval)
def main():
    logger.info("Checking for updates...")
    os.system("git pull")
    logger.info(
        "If updates were done, restart this script by using CTRL-C to terminate it, and re run it."
    )

    # Make connection to Discord
    try:
        RPC = Presence(client_id)  # Initialize the Presence class
        RPC.connect()  # Start the handshake loop
    except pypresence.exceptions.InvalidPipe:
        logger.error(
            "Could not connect to the discord pipe. Please ensure it's running."
        )
        exit(1)
    except FileNotFoundError:
        logger.error(
            "Could not connect to the discord pipe. Please ensure it's running."
        )
        exit(1)

    # Load our current config
    config = nso_functions.get_config_file()
    logger.info("Check discord!")

    # Get friend code from config, and add config option if does not exist
    try:
        friend_code = config['friend_code']
    except KeyError:
        config['friend_code'] = 'Unset'
        config_f = open("config.txt", "w")
        config_f.write(json.dumps(
            config,
            sort_keys=True,
            indent=4,
        ))
        config_f.close()
        friend_code = config['friend_code']

    while True:  # The presence will stay on as long as the program is running

        for i in range(0, 5):
            minutes_since, last_match = get_minutes_since()

            # Calculating the secs/hours/days since Last Match/Run
            seconds_since = int(minutes_since * 60)
            hours_since = int(minutes_since / 60)
            days_since = int(minutes_since / 1440)

            # When Previous Match was Salmon Run
            # job_result is only present in salmon run JSON
            if last_match.get('job_result') is not None:

                # Sets Gamemode Key in order to change the Picture
                gamemode_key = "salmon_run"

                # Decides if last Run is shown in days, hours, minutes or seconds
                # In Days
                if minutes_since >= 1440:
                    details = "Last Run: {} day{} ago".format(
                        days_since, plural_logic(days_since))

                # In Hours
                elif minutes_since >= 60:
                    details = "Last Run: {} h{} ago".format(
                        hours_since, plural_logic(hours_since))

                # In Minutes
                elif minutes_since > 1:
                    details = "Last Run: {} min{} ago".format(
                        math.floor(minutes_since),
                        plural_logic(math.floor(minutes_since)))

                # In Seconds
                else:
                    details = "Last Run: {} sec{} ago".format(
                        seconds_since, plural_logic(seconds_since))

                # Deciding the Result
                if last_match['job_result']['is_clear']:
                    outcome = "WON"
                else:
                    outcome = "LOST"

                ### Checks how many waves were played on last Run
                # If all 3 Waves were played
                if last_match["wave_details"][2]:
                    goldEgg = last_match["wave_details"][0]["golden_ikura_num"] + \
                        last_match["wave_details"][1]["golden_ikura_num"] + \
                        last_match["wave_details"][2]["golden_ikura_num"]
                    powEgg = last_match["wave_details"][0]["ikura_num"] + \
                        last_match["wave_details"][1]["ikura_num"] + \
                        last_match["wave_details"][2]["ikura_num"]

                # If only 2 Waves were played
                elif not last_match["wave_details"][2] and last_match[
                        "wave_details"][1]:
                    goldEgg = last_match["wave_details"][0][
                        "golden_ikura_num"] + last_match["wave_details"][1][
                            "golden_ikura_num"]
                    powEgg = last_match["wave_details"][0][
                        "ikura_num"] + last_match["wave_details"][1][
                            "ikura_num"]

                # If only 1 Wave was played
                else:
                    goldEgg = last_match["wave_details"][0]["golden_ikura_num"]
                    powEgg = last_match["wave_details"][0]["ikura_num"]

                # When hovering on the Picture
                large_text = "Last match was Salmon Run on {}".format(
                    last_match['schedule']['stage']['name'])

                # IGN and Salmon Run Rank
                if i == 0:
                    details = "IGN: {}".format(last_match["my_result"]["name"])
                    state = "{} {}".format((last_match["grade"])["long_name"],
                                           last_match["grade_point"])

                # Friend code
                elif i == 1:
                    if not friend_code:
                        state = "FC: Not Given"
                    else:
                        state = "FC: {}".format(friend_code)

                # Hazard Level
                elif i == 2:
                    state = "Hazard Level: {}".format(
                        str(last_match["danger_rate"]) + "%")

                # Result and Total Collected Golden Eggs / Power Eggs
                elif i == 3:
                    details = "GoldEgg/PowEgg ({})".format(outcome)

                    state = "{} / {}".format(goldEgg, powEgg)

                # Save / Death Ratio
                elif i == 4:
                    state = "Save/Death Ratio: {}/{}".format(
                        last_match["my_result"]["help_count"],
                        last_match["my_result"]["dead_count"])

                if minutes_since < timeout_minutes:
                    RPC.update(details=details,
                               state=state,
                               large_image=gamemode_key,
                               small_image="default",
                               large_text=large_text)
                else:
                    RPC.clear()
                    logger.debug("RPC cleared, not in game long enough")
                time.sleep(time_interval)

            # When Previous Match was Turf, Ranked, League or Private
            else:

                # Decides if last Match is shown in days, hours, minutes or seconds
                # In Days
                if minutes_since >= 1440:
                    details = "Last Match: {} day{} ago".format(
                        days_since, plural_logic(days_since))

                # In Hours
                elif minutes_since >= 60:
                    details = "Last Match: {} h{} ago".format(
                        hours_since, plural_logic(hours_since))

                # In Minutes
                elif minutes_since > 1:
                    details = "Last Match: {} min{} ago".format(
                        math.floor(minutes_since),
                        plural_logic(math.floor(minutes_since)))

                # In Seconds
                else:
                    details = "Last Match: {} sec{} ago".format(
                        seconds_since, plural_logic(seconds_since))

                # When hovering on the Picture
                large_text = "Last match was {}, {} on {}".format(
                    last_match["game_mode"]["name"],
                    last_match["rule"]["name"], last_match["stage"]["name"])

                # Gets Gamemode Key in order to change the Picture
                gamemode_key = last_match["rule"]["key"]

                # Gets Lobby Key
                lobby_key = last_match["game_mode"]["key"]

                # IGN and Level (+ Rank)
                if i == 0:
                    details = "IGN: {}".format(
                        last_match["player_result"]["player"]["nickname"])

                    # Checks if player has a Level Star
                    # If player has no Level Star (yet XP)
                    if not last_match["star_rank"]:

                        # If last match was in a Regular Lobby (Turf War) or Private Lobby
                        if lobby_key == "regular" or lobby_key == "private":
                            state = "Level: {}".format(
                                last_match["player_result"]["player"]
                                ["player_rank"], )

                        # If last match was in a Ranked Solo Lobby
                        elif lobby_key == "gachi":

                            # If last match was Splat Zones
                            if gamemode_key == "splat_zones":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}/R(SZ): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}/R(SZ): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"], )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}/R(SZ): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}/R(SZ): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"])

                            # If last match was Tower Control
                            elif gamemode_key == "tower_control":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}/R(TC): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}/R(TC): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"], )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}/R(TC): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}/R(TC): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"])

                            # If last match was Rainmaker
                            elif gamemode_key == "rainmaker":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}/R(RM): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}/R(RM): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"], )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}/R(RM): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}/R(RM): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"])

                            # If last match was Clam Blitz
                            else:

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}/R(CB): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}/R(CB): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"], )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}/R(CB): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}/R(CB): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["udemae"]["name"])

                        # If last match was in a League Pair/Team Lobby
                        elif lobby_key == "league_pair" or lobby_key == "league_team":

                            # Checks if Player has League Power
                            # If Player has no League Power (yet XP)
                            if not last_match["league_point"]:
                                state = "Lvl: {}/Power: TBD".format(
                                    last_match["player_result"]["player"]
                                    ["player_rank"])

                            # If Player has League Power
                            else:
                                state = "Lvl: {}/Power: {}".format(
                                    last_match["player_result"]["player"]
                                    ["player_rank"],
                                    last_match["league_point"])

                    # If player has a Level Star
                    else:

                        # If last match was in a Regular Lobby (Turf War) or Private Lobby
                        if lobby_key == "regular" or lobby_key == "private":
                            state = "Level: {}☆{}".format(
                                last_match["player_result"]["player"]
                                ["player_rank"],
                                last_match["player_result"]["player"]
                                ["star_rank"],
                            )

                        # If last match was in a Ranked Solo Lobby
                        elif lobby_key == "gachi":

                            # If last match was Splat Zones
                            if gamemode_key == "splat_zones":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}☆{}/R(SZ): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}☆{}/R(SZ): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                        )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}☆{}/R(SZ): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}☆{}/R(SZ): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"])

                            # If last match was Tower Control
                            elif gamemode_key == "tower_control":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}☆{}/R(TC): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}☆{}/R(TC): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                        )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}☆{}/R(TC): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}☆{}/R(TC): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"])

                            # If last match was Rainmaker
                            elif gamemode_key == "rainmaker":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}☆{}/R(RM): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}☆{}/R(RM): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                        )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}☆{}/R(RM): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}☆{}/R(RM): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"])

                            # If last match was Clam Blitz
                            elif gamemode_key == "clam_blitz":

                                # If player has S+ Rank
                                if last_match["udemae"]["name"] == "S+":
                                    state = "Lvl: {}☆{}/R(CZ): {}{}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"],
                                        last_match["udemae"]["s_plus_number"])

                                # If player has X Rank
                                elif last_match["udemae"]["name"] == "X":

                                    # Checks if Player has any X Power
                                    # If Player has no X Power (yet XP)
                                    if not last_match["x_power"]:
                                        state = "Lvl: {}☆{}/R(CZ): X(TBD)".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                        )

                                    # If Player has X Power
                                    else:
                                        state = "Lvl: {}☆{}/R(CZ): X({})".format(
                                            last_match["player_result"]
                                            ["player"]["player_rank"],
                                            last_match["player_result"]
                                            ["player"]["star_rank"],
                                            last_match["x_power"])

                                # If player has other Ranks
                                else:
                                    state = "Lvl: {}☆{}/R(CZ): {}".format(
                                        last_match["player_result"]["player"]
                                        ["player_rank"],
                                        last_match["player_result"]["player"]
                                        ["star_rank"],
                                        last_match["udemae"]["name"])

                        # If last match was in a League Pair/Team Lobby
                        elif lobby_key == "league_pair" or lobby_key == "league_team":

                            # Checks if Player has League Power
                            # If Player has no League Power (yet XP)
                            if not last_match["league_point"]:
                                state = "Lvl: {}☆{}/Power: TBD".format(
                                    last_match["player_result"]["player"]
                                    ["player_rank"],
                                    last_match["player_result"]["player"]
                                    ["star_rank"],
                                )

                            # If Player has League Power
                            else:
                                state = "Lvl: {}☆{}/Power: {}".format(
                                    last_match["player_result"]["player"]
                                    ["player_rank"],
                                    last_match["player_result"]["player"]
                                    ["star_rank"], last_match["league_point"])

                # Friend Code
                elif i == 1:
                    if not friend_code:
                        state = "FC: Not Given"
                    else:
                        state = "FC: {}".format(friend_code)

                # Kill (Assist) / Death Ratio
                elif i == 2:
                    state = "K(A)/D: {}({})/{}".format(
                        last_match["player_result"]["kill_count"],
                        last_match["player_result"]["assist_count"],
                        last_match["player_result"]["death_count"])

                # Result and Percentages
                elif i == 3:
                    details = last_match["my_team_result"]["name"]
                    try:
                        state = "{}% vs {}%".format(
                            last_match["my_team_percentage"],
                            last_match["other_team_percentage"])
                    except KeyError:
                        try:
                            state = "{} vs {}".format(
                                last_match["my_team_count"],
                                last_match["other_team_count"])
                        except KeyError:
                            state = "Gamemode not yet supported"

                # Used Weapon and Total Points
                elif i == 4:
                    state = "{}p".format(
                        last_match["player_result"]["game_paint_point"])

                    if show_weapon:
                        details = "{}".format(last_match["player_result"]
                                              ["player"]["weapon"]["name"])
                    else:
                        pass

                if minutes_since < timeout_minutes:
                    RPC.update(details=details,
                               state=state,
                               large_image=gamemode_key,
                               small_image="default",
                               large_text=large_text)
                else:
                    RPC.clear()
                    logger.debug("RPC cleared, not in game long enough")
                time.sleep(time_interval)
예제 #19
0
파일: ghrp.py 프로젝트: elevenchars/ghrp
class ghrp():
    def __init__(self,
                 discord_client_id: str,
                 github_username: str,
                 github_client_id: str = None,
                 github_client_secret: str = None):
        """Init method for the GHRP class.

        Arguments:
            discord_client_id {str} -- Discord Client ID from the developer
            portal.
            github_username {str} -- GitHub username to monitor

        Keyword Arguments:
            github_client_id {str} -- GitHub OAuth application client id 
            (default: {None})
            github_client_secret {str} -- GitHub OAuth application client
            secret (default: {None})
        """
        self.dclient = discord_client_id
        self.gusername = github_username
        self.gclientid = github_client_id
        self.gclientsecret = github_client_secret
        self.show_status = False

        self.payload = None
        self.headers = {}
        self.events_url = "https://api.github.com/users/{}/events".format(
            config["github_username"])
        self.session = requests.Session()

        self.timestamp = 0

        # if github client id and secret are not provided we need to make sure
        # we don't go over the ratelimit.
        if self.gclientid and self.gclientsecret:
            self.interval = 30
            payload = {
                "client_id": config["github_client_id"],
                "client_secret": config["github_client_secret"]
            }
        else:
            self.interval = 60

        self.rpc = Presence(self.dclient)
        self.rpc.connect()

    def get_newest_push(self, events: dict) -> dict:
        """Helper method for getting the most recent commit.
        Returns the most recent event object that includes a commit
        (not new projects)

        Returns:
            dict -- Most recent event containing a commit.
        """
        for event in events:
            if event["type"] == "PushEvent":
                return event
        return None

    def update(self) -> None:
        """Method to update the rich presence instance.
        Every 30 or 60 seconds (see __init__) it queries the GitHub events API.
        If there is new info, it checks if it is a commit. If it is, it
        parses it and updates the rich presence. After 1 hour, it clears the
        rich presence.
        """
        events_rq = self.session.get(self.events_url,
                                     headers=self.headers,
                                     params=self.payload)
        if events_rq.status_code != 304:
            self.show_status = True
            self.headers["If-None-Match"] = events_rq.headers["ETag"]
            events = json.loads(events_rq.text)
            latest = self.get_newest_push(events)
            repo_name = latest["repo"]["name"].split("/")[1]
            commit_message = latest["payload"]["commits"][0]["message"].split(
                "\n")[0]
            self.timestamp = calendar.timegm(
                time.strptime(latest["created_at"], "%Y-%m-%dT%H:%M:%SZ"))

            self.rpc.update(details=repo_name,
                            state=commit_message,
                            large_image="github")
        else:
            if (time.time() - self.timestamp) > 60 * 60 and self.show_status:
                self.rpc.clear()
                self.show_status = False
예제 #20
0
class rich_presence:
    def __init__(self):
        super().__init__()

        self.vbox = virtualbox.VirtualBox()
        self.config = configparser.ConfigParser()
        self.config.read("config.ini")
        self.assets = json.load(open("assets.json", "r"))
        self.previous_formatdict = None

        client_id = self.config["Rich Presence"]["client_id"]
        self.RPC = Presence(client_id)
        self.RPC.connect()
        self.start_time = time.time()

        while True:
            if ("VirtualBox.exe" in (p.name() for p in psutil.process_iter())
                    or "VirtualBoxVM.exe" in (p.name()
                                              for p in psutil.process_iter())
                ) and (sys.platform.startswith("win32")):
                pvars = self.presence_gen()
                # print("-------------------------\npresence_dict\n-------------------------")
                # pprint.pprint(pvars)
                self.RPC.update(
                    large_image=pvars["large_image"],
                    large_text=pvars["large_text"],
                    small_image=pvars["small_image"],
                    small_text=pvars["small_text"],
                    details=pvars["details"],
                    state=pvars["state"],
                    start=pvars["start"],
                )
                pprint.pprint(pvars)
                print("--------------------")
            elif sys.platform.startswith("win32"):
                print("VirtualBox is not running")
                self.RPC.clear()
            else:
                pvars = self.presence_gen()
                # print("-------------------------\npresence_dict\n-------------------------")
                # pprint.pprint(pvars)
                self.RPC.update(
                    large_image=pvars["large_image"],
                    large_text=pvars["large_text"],
                    small_image=pvars["small_image"],
                    small_text=pvars["small_text"],
                    details=pvars["details"],
                    state=pvars["state"],
                    start=pvars["start"],
                )
                pprint.pprint(pvars)
                print("--------------------")
            time.sleep(15)

    def vbox_to_dict(self):

        # Generate metadata for each machine
        self.machines = self.vbox.machines
        self.machine_names = [m.name for m in self.machines]
        self.machine_states = [m.state for m in self.machines]
        self.machine_operating_systems = [m.os_type_id for m in self.machines]

        # Create dictionary to store info
        self.vd_machine_dict = {}

        for i in range(len(self.machines)):

            # Extract OS version and architecture info
            self.vd_operating_system, self.vd_architecture = self.os_to_arch(
                self.machine_operating_systems[i])

            self.vd_name = self.machine_names[i]  # Get name of VM
            self.vd_machine_dict[self.vd_name] = {}  # Initialize dictionaries
            self.vd_machine_dict[self.vd_name][
                "os version"] = self.vd_operating_system  # Add OS version
            self.vd_machine_dict[self.vd_name][
                "architecture"] = self.vd_architecture  # Add architecture
            self.vd_machine_dict[self.vd_name]["state"] = str(
                self.machine_states[i])  # Add state

            # Run through assets and find the correct OS
            for key in list(self.assets["operating systems"].keys()):
                if self.vd_operating_system in list(
                        self.assets["operating systems"][key]
                    ["versions"].keys()):
                    self.vd_machine_dict[
                        self.vd_name]["operating system"] = key

        # print("-------------------------\nvbox_to_dict\n-------------------------")
        # pprint.pprint(self.vd_machine_dict)
        return self.vd_machine_dict

    def os_to_arch(self, oa_input):
        if "_" in oa_input:
            self.oa_operating_system, self.oa_architecture = oa_input.split(
                "_", 1)
        else:
            self.oa_operating_system = oa_input
            self.oa_architecture = "32"
        return self.oa_operating_system, self.oa_architecture

    def vdict_to_formatdict(self, vf_input):
        self.vf_active_vm = None
        self.format_dict = {}
        self.format_dict["active_vm"] = False
        for vf_key in list(vf_input.keys()):
            if vf_input[vf_key]["state"] == "FirstOnline":
                self.vf_active_vm = vf_key
        if self.vf_active_vm:
            self.format_dict["active_vm"] = True
            self.format_dict["os_hf"] = vf_input[
                self.vf_active_vm]["operating system"]
            self.format_dict["version_hf"] = self.assets["operating systems"][
                vf_input[self.vf_active_vm]["operating system"]]["versions"][
                    vf_input[self.vf_active_vm]["os version"]]["name"]
            self.format_dict["version_image"] = self.assets[
                "operating systems"][vf_input[
                    self.vf_active_vm]["operating system"]]["versions"][
                        vf_input[self.vf_active_vm]["os version"]]["image"]
            self.format_dict["architecture"] = vf_input[
                self.vf_active_vm]["architecture"]
            self.format_dict["architecture_image"] = vf_input[
                self.vf_active_vm]["architecture"]
        self.format_dict["icon"] = "icon"
        # print("-------------------------\nformat_dict\n-------------------------")
        # pprint.pprint(self.format_dict)
        return self.format_dict

    def presence_gen(self):
        self.pg_formatdict = self.vdict_to_formatdict(self.vbox_to_dict())
        if self.pg_formatdict != self.previous_formatdict:
            self.pg_presencedict = {}
            self.pg_presencedict["start"] = time.time()
        else:
            self.pg_pdtime = self.pg_presencedict["start"]
            self.pg_presencedict = {"start": self.pg_pdtime}

        if self.pg_formatdict["active_vm"] == True:
            for pg_field in list(self.config["In VM"].keys()):
                self.pg_presencedict[pg_field] = self.config["In VM"][
                    pg_field].format(**self.pg_formatdict)

        else:
            for pg_field in list(self.config["In Menu"].keys()):
                self.pg_presencedict[pg_field] = self.config["In Menu"][
                    pg_field].format(**self.pg_formatdict)
            self.pg_presencedict["start"] = None
        for pg_field in list(self.pg_presencedict.keys()):
            if (self.pg_presencedict[pg_field] == ""
                    or self.pg_presencedict[pg_field] == []):
                self.pg_presencedict[pg_field] = None
        self.previous_formatdict = self.pg_formatdict
        return self.pg_presencedict
예제 #21
0
    def do():
        try:  # 애러나면 로그는 해야죠
            while True:
                # 죽을때까지 실행 검증 루프
                a = True
                while a:  # 실행 없으면 15초 쉬었다 루프 다시. --> 30초로 변경.
                    try:
                        pinfo = get_process_info()
                        if not pinfo[0]:
                            log("DEBUG", "Process NF", datetime)
                            log("DEBUG", "Retry in 30s...", datetime)
                            time.sleep(30)
                            continue
                        window_title = pinfo[3]
                        a = False  # 루프 종료
                    except IndexError as e:
                        log("ERROR", "caught error.. Retry in 10s : %s" % (e),
                            datetime)
                        time.sleep(10)

                RPC = Presence(pinfo[len(pinfo) - 1]['appid'])

                try:
                    RPC.connect()
                except Exception as e:
                    log("ERROR", "Discord Connection Failed", datetime)
                    log("DEBUG", "ERRINFO :: %s" % (e), datetime)
                    plyer.notification.notify(
                        title='Adobe Discord RPC',
                        #message='디스코드와 연결하지 못하였습니다.\n디스코드가 켜져 있는지 다시 한번 확인 해 주세요.\n30초 후 연결을 다시 시도합니다.',
                        message=lang['DiscordFailed'],
                        app_name='Adobe Discord RPC',
                        app_icon='icon_alpha.ico')
                    goout(datetime)
                log("INFO", "Discord Connect Success", datetime)

                dt = pandas.to_datetime(datetime.datetime.now())
                # -1은 미사용
                # 그 이상은 splitindex 용도로 사용함
                if pinfo[len(pinfo) - 1]['getver'] == -1:
                    version = ''
                else:
                    path = pinfo[5].split(pinfo[len(pinfo) -
                                                1]['publicName'])[1]
                    path = path.split('\\')[pinfo[len(pinfo) - 1]['getver']]
                    version = path.replace(" ", '')

                filename = window_title.split(
                    pinfo[len(pinfo) - 1]['splitBy'])[pinfo[len(pinfo) -
                                                            1]['splitIndex']]
                lmt = pinfo[len(pinfo) - 1][langcode]['largeText'].replace(
                    '%Ver%', version)
                lmt = lmt.replace('%Filename%', filename)
                smt = pinfo[len(pinfo) - 1][langcode]['smallText'].replace(
                    '%Filename%', filename)
                smt = smt.replace('%Ver%', version)
                b = True

                plyer.notification.notify(
                    title='Adobe Discord RPC',
                    #message='성공적으로 디스코드와 연결했습니다.\n%s를 플레이 하게 됩니다.' % (pinfo[len(pinfo)-1]['publicName']),
                    message='%s\n%s %s' %
                    (lang['Connected01'], pinfo[len(pinfo) - 1]['publicName'],
                     lang['Connected02']),
                    app_name='Adobe Discord RPC',
                    app_icon='icon_alpha.ico')

                while b:
                    # 20-04-25 코드 구조 대량으로 변경되어서 응답없음 여부 사용 안함
                    """if not isresponding(pinfo['processName'].replace('.exe', ''), datetime):
                        # 응답없음은 10초 간격으로 체크해서 응답없음 풀리면 c = False 로 만듬
                        c = True
                        while c:
                            try:
                                rtn = RPC.update(
                                    large_image='lg', large_text='프로그램 : %s' % (pinfo['publicName']),
                                    small_image='sm_temp', small_text="파일명 : %s" %(filename),
                                    details="응답없음", state="응답없음",
                                    start=int(time.mktime(dt.timetuple()))
                                )
                            except Exception as e:
                                log("ERROR", "pypresence 갱신 실패... 10초 대기 : %s" % (e), datetime)
                                plyer.notification.notify(
                                    title='Adobe Discord RPC',
                                    message='RPC 갱신에 실패하였습니다.\n10초 후 다시 시도합니다.',
                                    app_name='Adobe Discord RPC',
                                    app_icon='icon_alpha.ico'
                                )
                                b = False
                                time.sleep(10)
                            else:
                                log("DEBUG", "pypresence 리턴 : %s" % (rtn), datetime)
                                time.sleep(10)
    
                                if not isresponding(pinfo['processName'].replace('.exe', ''), datetime):
                                    pass
                                else:
                                    # 정상실행 경우
                                    # RPC.clear(pid=os.getpid())
                                    c = False
                                    pass
                    else:"""
                    try:
                        rtn = RPC.update(large_image='lg',
                                         large_text='Program : %s' %
                                         (pinfo[len(pinfo) - 1]['publicName']),
                                         small_image='sm_temp',
                                         small_text="Adobe Discord RPC",
                                         details=lmt,
                                         state=smt,
                                         start=int(time.mktime(
                                             dt.timetuple())))
                    except Exception as e:
                        log("ERROR", "pypresence 갱신 실패... 30초 대기 : %s" % (e),
                            datetime)
                        plyer.notification.notify(
                            title='Adobe Discord RPC',
                            #message='RPC 갱신에 실패하였습니다.\n30초 후 다시 시도합니다.',
                            message=lang['UpdateFailed'],
                            app_name='Adobe Discord RPC',
                            app_icon='icon_alpha.ico')
                        b = False
                        time.sleep(30)
                    else:
                        log("DEBUG", "pypresence Return : %s" % (rtn),
                            datetime)
                        time.sleep(30)
                        #time.sleep(5) # 테스트용인데 왜 주석 안하고 배포한거야

                        dur = True
                        window_title = get_window_title(pinfo[2])
                        filename = window_title.split(
                            pinfo[len(pinfo) -
                                  1]['splitBy'])[pinfo[len(pinfo) -
                                                       1]['splitIndex']]
                        lmt = pinfo[len(pinfo) -
                                    1][langcode]['largeText'].replace(
                                        '%Ver%', version)
                        lmt = lmt.replace('%Filename%', filename)
                        smt = pinfo[len(pinfo) -
                                    1][langcode]['smallText'].replace(
                                        '%Filename%', filename)
                        smt = smt.replace('%Ver%', version)
                        dur = False
                        pass

        except KeyboardInterrupt:
            log("DEBUG", "KeyboardInterrupted by console", datetime)
            goout()
        except Exception as e:
            if dur:
                # PID 변경 OR 프로그램 종료. 같은 PID 금방 다시 할당할 일 없음.
                log("DEBUG", "PID 변동 OR 프로세스 종료. : %s -> %s" % (pinfo, e),
                    datetime)
                RPC.clear(pid=os.getpid())
                b = False
                do()
                pass
            else:
                log("ERROR", "Undefined : %s" % (e), datetime)
                goout()
예제 #22
0
def main():
    consoleargs = parser.parse_args()

    switch_ip = consoleargs.ip
    client_id = consoleargs.client_id

    if not checkIP(switch_ip):
        print('Invalid IP')
        exit()

    rpc = Presence(str(client_id))
    try:
        rpc.connect()
        rpc.clear()
    except:
        print('Unable to start RPC!')

    switch_server_address = (switch_ip, TCP_PORT)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        sock.connect(switch_server_address)
        print('Successfully connected to %s' % switch_ip + ':' + str(TCP_PORT))
    except:
        print('Error connection to %s refused' % switch_ip + ':' + str(TCP_PORT))
        exit()

    lastProgramName = ''
    startTimer = 0

    while True:
        data = None
        try:
            data = sock.recv(628)
        except:
            print('Could not connect to Server! Retrying...')
            startTimer = 0
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                sock.connect(switch_server_address)
                print('Successfully reconnected to %s' %
                      repr(switch_server_address))
            except:
                print('Error reconnection to %s refused' %
                      repr(switch_server_address))
                exit()
        title = Title(data)
        if title.magic == PACKETMAGIC:
            if lastProgramName != title.name:
                startTimer = int(time.time())
            if consoleargs.ignore_home_screen and title.name == 'Home Menu':
                rpc.clear()
            else:
                smallimagetext = ''
                largeimagekey = ''
                details = ''
                largeimagetext = title.name
                if int(title.pid) != PACKETMAGIC:
                    smallimagetext = 'SwitchPresence-Rewritten'
                    if title.name not in switchOverrides:
                        largeimagekey = iconFromPid(title.pid)
                        details = 'Playing ' + str(title.name)
                    else:
                        orinfo = switchOverrides[title.name]
                        largeimagekey = orinfo['CustomKey'] or iconFromPid(title.pid)
                        details = orinfo['CustomPrefix'] or 'Playing'
                        details += ' ' + title.name
                else:
                    smallimagetext = 'QuestPresence'
                    if title.name not in questOverrides:
                        largeimagekey = title.name.lower().replace(' ', '')
                        details = 'Playing ' + title.name
                    else:
                        orinfo = questOverrides[title.name]
                        largeimagekey = orinfo['CustomKey'] or title.name.lower().replace(
                            ' ', '')
                        details = orinfo['CustomPrefix'] or 'Playing'
                        details += ' ' + title.name
                if not title.name:
                    title.name = ''
                lastProgramName = title.name
                rpc.update(details=details, start=startTimer, large_image=largeimagekey,
                        large_text=largeimagetext, small_text=smallimagetext)
            time.sleep(1)
        else:
            time.sleep(1)
            rpc.clear()
            rpc.close()
            sock.close()
            exit()
예제 #23
0
            print('The episode title is:', data['episode']['title'])
            print('The show is:', data['show']['title'])
            print('The IMDB ID is:', data['show']['ids']['imdb'])
            newdetails = data['show']['title']
            newstate = data['episode']['title']
            #            newstate='https://www.imdb.com/title/' + data['show']['ids']['imdb']
            media = 'tv'
        elif (data['type'] == 'movie'):
            print('The name of the movie is:', data['movie']['title'])
            print('The IMDB ID is:', data['movie']['ids']['imdb'])
            newdetails = data['movie']['title']
            newstate = 'https://www.imdb.com/title/' + data['movie']['ids'][
                'imdb']
            media = 'movie'
        else:
            print('Ya f****d bud')
        starttime = data['started_at']
        starttime = dp.parse(starttime)
        starttime = starttime.astimezone(est)
        starttime = starttime.timestamp()
        endtime = data['expires_at']
        endtime = dp.parse(endtime)
        endtime = endtime.astimezone(est)
        endtime = endtime.timestamp()
        print(starttime, endtime)
        updateRPC(newstate, newdetails, starttime, endtime, media)
    except:
        print('Nothing is being played')
        RPC.clear()
    time.sleep(15)
예제 #24
0
class HistoryViewModel(QtCore.QObject):
    # Constants
    __INITIAL_SCROBBLE_HISTORY_COUNT = int(
        os.environ.get('INITIAL_HISTORY_ITEMS',
                       30))  # 30 is the default but can be configured
    __MEDIA_PLAYER_POLLING_INTERVAL = 100 if os.environ.get('MOCK') else 1000
    __CURRENT_SCROBBLE_INDEX = -1
    __NO_SELECTION_INDEX = -2
    __DISCORD_RPC_MAX_RETRIES = 5

    # Qt Property changed signals
    is_enabled_changed = QtCore.Signal()
    current_scrobble_data_changed = QtCore.Signal()
    scrobble_percentage_changed = QtCore.Signal()
    is_player_paused_changed = QtCore.Signal()
    is_spotify_plugin_available_changed = QtCore.Signal()
    is_using_mock_player_changed = QtCore.Signal()
    is_discord_rich_presence_enabled_changed = QtCore.Signal()
    selected_scrobble_changed = QtCore.Signal()
    selected_scrobble_index_changed = QtCore.Signal()
    is_loading_changed = QtCore.Signal()
    media_player_name_changed = QtCore.Signal()

    # Scrobble history list model signals
    pre_append_scrobble = QtCore.Signal()
    post_append_scrobble = QtCore.Signal()
    scrobble_album_image_changed = QtCore.Signal(int)
    scrobble_lastfm_is_loved_changed = QtCore.Signal(int)
    begin_refresh_history = QtCore.Signal()
    end_refresh_history = QtCore.Signal()

    # Signals handled from QML
    preloadProfileAndFriends = QtCore.Signal()

    def __init__(self) -> None:
        QtCore.QObject.__init__(self)

        # TODO: Figure out which things should be in reset_state
        self.__application_reference: ApplicationViewModel = None
        self.__is_enabled: bool = False

        self.__media_player: MediaPlayerPlugin = None
        self.__is_spotify_plugin_available = False  # This is set to true if Spotify is installed

        # Set up Discord presence
        self.__is_discord_rpc_enabled = False
        self.__is_discord_rpc_connected = False
        self.__discord_rpc = Presence('799678908819439646')

        # Settings
        # TODO: Move these properties to an App view model
        self.__is_submission_enabled: bool = None
        self.is_player_paused = False
        self.__was_last_player_event_paused: bool = False

        if os.environ.get('MOCK'):
            self.__media_player = MockPlayerPlugin()
            self.__connect_media_player_signals()
        else:
            # Initialize media player plugins
            self.__spotify_plugin = SpotifyPlugin()
            self.__music_app_plugin = MusicAppPlugin()

            use_spotify = False
            spotify_app = SBApplication.applicationWithBundleIdentifier_(
                'com.spotify.client')

            # Use Music app plugin by default since every Mac has it (this will be changed when the user preference is loaded from the database)
            # TODO: Make it possible for no media player to be set during onboarding so we don't need to have this temporary value?
            self.switchToMediaPlugin('musicApp',
                                     should_update_in_database=False)

        self.media_player_name_changed.emit()

        # Start polling interval to check for new media player position
        self.__timer = QtCore.QTimer(self)
        self.__timer.timeout.connect(self.__fetch_new_media_player_position)

        self.reset_state()

    def reset_state(self) -> None:
        # Store Scrobble objects that have been submitted
        self.scrobble_history: List[Scrobble] = []

        # Keep track of whether the history view is loading data
        self.__is_loading = False

        # Hold a Scrobble object for currently playing track (will later be submitted)
        self.__current_scrobble: Scrobble = None

        # Hold the index of the selected scrobble in the sidebar
        self.__selected_scrobble_index: int = None

        # Hold the Scrobble object at the __selected_scrobble_index
        # This can either be a copy of the current scrobble or one in the history
        self.selected_scrobble: Scrobble = None

        # Keep track of whether the current scrobble has hit the threshold for submission
        self.__current_scrobble_percentage: float = None
        self.__should_submit_current_scrobble = False

        # Keep track of furthest position reached in a song
        self.__furthest_player_position_reached: float = None

        # Keep track of how many poll ticks have passed since the playback position changed
        self.__cached_playback_position: float = None
        self.__ticks_since_position_change: int = None

        # Store the current track's crop values since they aren't part of the scrobble object
        self.__current_track_crop: TrackCrop = None

        if (self.__is_enabled):
            self.__is_spotify_plugin_available = False

            # Load preferences from database
            media_player_preference = db_helper.get_preference('media_player')
            is_rich_presence_enabled = db_helper.get_preference(
                'rich_presence_enabled')

            # Check if Spotify is installed; show option to switch media player in status bar menu if it's installed
            spotify_app = SBApplication.applicationWithBundleIdentifier_(
                'com.spotify.client')

            if spotify_app:
                self.__is_spotify_plugin_available = True
                self.is_spotify_plugin_available_changed.emit()

            if media_player_preference == 'spotify':
                # If Spotify is not installed, reset media player preference back to Music.app
                # TODO: Use better method to figure out if Spotify is installed without logged error
                if self.__is_spotify_plugin_available:
                    self.switchToMediaPlugin('spotify',
                                             should_update_in_database=False)
                else:
                    logging.info(
                        'Spotify not detected on device; switching back to Music.app as media player'
                    )
                    db_helper.set_preference('media_player', 'musicApp')
                    media_player_preference = 'musicApp'

            if media_player_preference == 'musicApp':
                self.switchToMediaPlugin('musicApp',
                                         should_update_in_database=False)

            self.media_player_name_changed.emit()

            # Try connecting to Discord rich presence if enabled
            self.__is_discord_rpc_enabled = db_helper.get_preference(
                'rich_presence_enabled')
            self.is_discord_rich_presence_enabled_changed.emit()

            if self.__is_discord_rpc_enabled and helpers.is_discord_open():
                self.__connect_discord_rpc_if_open()

    # --- Qt Property Getters and Setters ---

    def set_is_discord_rich_presence_enabled(self, is_enabled):
        self.__is_discord_rpc_enabled = is_enabled

        if is_enabled:
            if self.__connect_discord_rpc_if_open():
                # If there is a track playing when rich presence is enabled, update the rich presence immediately
                # This is nested because we want to the __connect_discord_rpc_if_open logic to run regardless
                if self.__current_scrobble:
                    self.__update_discord_rpc(
                        self.__current_scrobble.track_title,
                        self.__current_scrobble.artist_name,
                        self.__current_scrobble.album_title,
                        self.__cached_playback_position)
        else:
            if self.__is_discord_rpc_connected:
                self.__is_discord_rpc_connected = False

                if helpers.is_discord_open():
                    self.__discord_rpc.close()

        self.is_discord_rich_presence_enabled_changed.emit()
        db_helper.set_preference('rich_presence_enabled', is_enabled)

    def set_application_reference(self,
                                  new_reference: ApplicationViewModel) -> None:
        if not new_reference:
            return

        self.__application_reference = new_reference
        self.__application_reference.is_logged_in_changed.connect(
            lambda: self.set_is_enabled(self.__application_reference.
                                        is_logged_in))

    def get_selected_scrobble_index(self):
        '''Make the private selected scrobble index variable available to the UI'''

        if self.__selected_scrobble_index is None:
            # NO_SELECTION_INDEX represents no selection because Qt doesn't understand Python's None value
            return HistoryViewModel.__NO_SELECTION_INDEX

        return self.__selected_scrobble_index

    def set_selected_scrobble_index(self, new_index: int) -> None:
        if not self.__is_enabled:
            return

        # Prevent setting an illegal index with keyboard shortcuts
        if (
                # Prevent navigating before current scrobble
                new_index < HistoryViewModel.__CURRENT_SCROBBLE_INDEX or
            (
                # Prevent navigating to current scrobble when there isn't one
                new_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX
                and self.__current_scrobble is None)

                # Prevent navigating past scrobble history
                or new_index == len(self.scrobble_history)):
            return

        self.__selected_scrobble_index = new_index
        self.selected_scrobble_index_changed.emit()

        if new_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            self.selected_scrobble = self.__current_scrobble
        else:
            self.selected_scrobble = self.scrobble_history[new_index]

        # Update details view
        self.selected_scrobble_changed.emit()

    def set_is_enabled(self, is_enabled: bool) -> None:
        self.__is_enabled = is_enabled
        self.is_enabled_changed.emit()

        if is_enabled:
            # Reset view model
            self.reset_state()
            self.__timer.start(self.__MEDIA_PLAYER_POLLING_INTERVAL)

            # Check for network connection on open
            self.__application_reference.update_is_offline()

            if not self.__application_reference.is_offline:
                self.reloadHistory()

                # Don't preload profile and friends in mock mode since it sends off tons of requests
                if not os.environ.get('MOCK'):
                    self.preloadProfileAndFriends.emit()
        else:
            self.begin_refresh_history.emit()
            self.reset_state()
            self.end_refresh_history.emit()
            self.selected_scrobble_index_changed.emit()
            self.selected_scrobble_changed.emit(
            )  # This causes details pane to stop showing a scrobble
            self.current_scrobble_data_changed.emit()
            self.__timer.stop()

        self.is_loading_changed.emit()

    # --- Slots ---

    @QtCore.Slot()
    def reloadHistory(self):
        '''Reload recent scrobbles from Last.fm'''

        if (self.__INITIAL_SCROBBLE_HISTORY_COUNT == 0

                # Prevent reloading during onboarding
                or not self.__is_enabled

                # Prevent reloading while the view is already loading
                or self.__is_loading):
            return

        # Update loading indicator
        self.__is_loading = True
        self.is_loading_changed.emit()

        # Reset scrobble history list
        self.begin_refresh_history.emit()
        self.scrobble_history = []
        self.end_refresh_history.emit()

        # Fetch and load recent scrobbles
        fetch_recent_scrobbles_task = FetchRecentScrobbles(
            lastfm=self.__application_reference.lastfm,
            count=self.__INITIAL_SCROBBLE_HISTORY_COUNT)
        fetch_recent_scrobbles_task.finished.connect(
            self.__handle_recent_scrobbles_fetched)
        QtCore.QThreadPool.globalInstance().start(fetch_recent_scrobbles_task)

    @QtCore.Slot(int)
    def toggleLastfmIsLoved(self, scrobble_index: int) -> None:

        if not self.__is_enabled:
            return

        scrobble = None

        if scrobble_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            scrobble = self.__current_scrobble
        else:
            scrobble = self.scrobble_history[scrobble_index]

        new_value = not scrobble.lastfm_track.is_loved
        scrobble.lastfm_track.is_loved = new_value

        # Update any matching scrobbles in history
        for history_scrobble in self.scrobble_history:
            if history_scrobble == scrobble:
                history_scrobble.lastfm_track.is_loved = new_value

        # Update UI to reflect changes
        self.__emit_scrobble_ui_update_signals(scrobble)

        # Submit new value to Last.fm
        QtCore.QThreadPool.globalInstance().start(
            UpdateTrackLoveOnLastfm(lastfm=self.__application_reference.lastfm,
                                    scrobble=scrobble,
                                    value=new_value))

    @QtCore.Slot(str)
    def switchToMediaPlugin(self,
                            media_plugin_name: str,
                            should_update_in_database: bool = True) -> None:
        # Fake stopped event to un-load the current scrobble
        self.__handle_media_player_stopped()

        # Reset scrobble percentage
        self.__current_scrobble_percentage = 0

        # Reset whether the last event was a pause
        self.__was_last_player_event_paused = False

        # Disconnect event signals
        if self.__media_player:
            self.__media_player.stopped.disconnect(
                self.__handle_media_player_stopped)
            self.__media_player.playing.disconnect(
                self.__handle_media_player_playing)
            self.__media_player.paused.disconnect(
                self.__handle_media_player_paused)

        if media_plugin_name == 'spotify':
            self.__media_player = self.__spotify_plugin
        elif media_plugin_name == 'musicApp':
            self.__media_player = self.__music_app_plugin

        if should_update_in_database:
            db_helper.set_preference('media_player', media_plugin_name)

        self.__set_is_scrobble_submission_enabled(
            self.__media_player.IS_SUBMISSION_ENABLED)
        logging.info(
            f'Switched media player to {self.__media_player.MEDIA_PLAYER_NAME}'
        )

        self.__connect_media_player_signals()
        self.__media_player.request_initial_state()

        # Update 'Listening on X' text in history view for current scrobble
        self.media_player_name_changed.emit()

    def __connect_media_player_signals(self):
        # Reconnect event signals
        self.__media_player.stopped.connect(self.__handle_media_player_stopped)
        self.__media_player.playing.connect(self.__handle_media_player_playing)
        self.__media_player.paused.connect(self.__handle_media_player_paused)
        self.__media_player.showNotification.connect(
            lambda title, content: self.__application_reference.
            showNotification.emit(title, content))

    @QtCore.Slot(str)
    def mock_event(self, event_name):
        '''Allow mock player events to be triggered from QML'''

        self.__media_player.mock_event(event_name)

    # --- Private Methods ---

    def __connect_discord_rpc_if_open(self) -> None:
        '''Check if Discord is open; if open, connect with a new client ID if not already connected. If closed, disconnect'''

        if helpers.is_discord_open():
            if not self.__is_discord_rpc_connected:
                self.__discord_rpc.connect()
                self.__is_discord_rpc_connected = True
                logging.info(f'Discord RPC connected')
        else:
            if self.__is_discord_rpc_connected:
                self.__is_discord_rpc_connected = False
                self.__discord_rpc.close()
                logging.info(f'Discord RPC disconnected')

        return self.__is_discord_rpc_connected

    def __run_discord_rpc_request(self, request):
        '''Runs "request" lambda if Discord is open. If InvalidID occurs due to Discord generating a new client ID on relaunch, retry the request'''

        # Don't run any request if Discord isn't open
        if self.__connect_discord_rpc_if_open():
            did_request_succeed = False
            number_of_retries = 0

            while not did_request_succeed and number_of_retries < self.__DISCORD_RPC_MAX_RETRIES:
                try:
                    request(self)
                    did_request_succeed = True
                except InvalidID:
                    self.__is_discord_rpc_connected = False

                    if self.__connect_discord_rpc_if_open():
                        logging.info(
                            f'Discord RPC reconnected to new client (at retry #{number_of_retries})'
                        )

                    number_of_retries += 1

            if number_of_retries == self.__DISCORD_RPC_MAX_RETRIES:
                logging.warning('Max retries exceeded on Discord RPC request')

    def __update_discord_rpc(self, track_title: str, artist_name: str,
                             album_title: str, player_position: float):
        '''Update Discord RPC with new track details'''

        self.__discord_rpc.update(
            details=track_title,
            state=artist_name,
            large_image='music-logo',
            large_text='Playing on Music',
            small_image='lastredux-logo',
            small_text='Scrobbling on LastRedux',
            start=(datetime.now() - timedelta(seconds=player_position)).
            timestamp(
            )  # Don't include track start to accurately reflect timestamp in uncropped track
        )

    def __handle_recent_scrobbles_fetched(
            self, recent_scrobbles: LastfmList[LastfmScrobble]):
        # Tell the history list model that we are going to change the data it relies on
        self.begin_refresh_history.emit()

        # User might not have any scrobbles (new account)
        if recent_scrobbles:
            # Convert scrobbles from history into scrobble objects
            for i, recent_scrobble in enumerate(recent_scrobbles.items):
                self.scrobble_history.append(
                    Scrobble.from_lastfm_scrobble(recent_scrobble))

                self.__load_external_scrobble_data(self.scrobble_history[i])

        self.end_refresh_history.emit()

        self.__is_loading = False
        self.is_loading_changed.emit()

    def __load_external_scrobble_data(self, scrobble: Scrobble) -> None:
        if self.__application_reference.is_offline:
            return

        load_lastfm_track_info = LoadLastfmTrackInfo(
            self.__application_reference.lastfm, scrobble)
        load_lastfm_track_info.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_lastfm_track_info)

        load_lastfm_artist_info = LoadLastfmArtistInfo(
            self.__application_reference.lastfm, scrobble)
        load_lastfm_artist_info.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_lastfm_artist_info)

        load_lastfm_album_info = LoadLastfmAlbumInfo(
            self.__application_reference.lastfm, scrobble)
        load_lastfm_album_info.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_lastfm_album_info)

        load_track_images = LoadTrackImages(
            self.__application_reference.lastfm,
            self.__application_reference.art_provider, scrobble)
        load_track_images.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_track_images)

    def __handle_piece_of_external_scrobble_data_loaded(
            self, scrobble: Scrobble):
        if not self.__is_enabled:
            return

        # TODO: Find a better way to do this that's less hacky
        if not getattr(scrobble, 'TEMP_things_loaded', False):
            scrobble.TEMP_things_loaded = 0

        scrobble.TEMP_things_loaded += 1

        if scrobble.TEMP_things_loaded == 4:
            scrobble.is_loading = False

        self.__emit_scrobble_ui_update_signals(scrobble)

    def __emit_scrobble_ui_update_signals(self, scrobble: Scrobble) -> None:
        if not self.__is_enabled:
            return

        # Update details view if needed (all external scrobble data)
        if scrobble == self.selected_scrobble:
            self.selected_scrobble_changed.emit()

        # Update current scrobble view if needed (album art, is_loved)
        if scrobble == self.__current_scrobble:
            self.current_scrobble_data_changed.emit()

        # Update loved status and album art for all applicable history items if they match
        for i, history_scrobble in enumerate(self.scrobble_history):
            if scrobble == history_scrobble:
                self.scrobble_album_image_changed.emit(i)
                self.scrobble_lastfm_is_loved_changed.emit(i)

    def __submit_scrobble(self, scrobble: Scrobble) -> None:
        '''Add a scrobble object to the history array and submit it to Last.fm'''

        if not self.__is_enabled:
            return

        # Tell scrobble history list model that a change will be made
        self.pre_append_scrobble.emit()

        # Prepend the new scrobble to the scrobble_history array in the view model
        self.scrobble_history.insert(0, scrobble)

        # Tell scrobble history list model that a change was made in the view model
        # The list model will call the data function in the background to get the new data
        self.post_append_scrobble.emit()

        if not self.__selected_scrobble_index is None:
            # Shift down the selected scrobble index if new scrobble has been added to the top
            # This is because if the user has a scrobble in the history selected and a new scrobble is
            # submitted, it will display the wrong data if the index isn't updated

            # Change __selected_scrobble_index instead of calling set___selected_scrobble_index because the
            # selected scrobble shouldn't be redundantly set to itself and still emit selected_scrobble_changed
            if self.__selected_scrobble_index > HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
                # Shift down the selected scrobble index by 1
                self.__selected_scrobble_index += 1

                # Tell the UI that the selected index changed, so it can update the selection highlight
                self.selected_scrobble_index_changed.emit()

        # Submit scrobble to Last.fm
        if self.__is_submission_enabled:
            # Use end time to submit to play more nicely with apps like Marvis
            # TODO: Make this a setting
            self.__current_scrobble.timestamp = datetime.now()

            submit_scrobble_task = SubmitScrobble(
                lastfm=self.__application_reference.lastfm,
                scrobble=self.__current_scrobble)
            QtCore.QThreadPool.globalInstance().start(submit_scrobble_task)

        # Update playcounts for scrobbles (including the one just added to history)
        for history_scrobble in self.scrobble_history:
            if history_scrobble.lastfm_track and scrobble.lastfm_track:
                if history_scrobble.lastfm_track == scrobble.lastfm_track:
                    history_scrobble.lastfm_track.plays += 1

            if history_scrobble.lastfm_artist and scrobble.lastfm_artist:
                if history_scrobble.lastfm_artist == scrobble.lastfm_artist:
                    history_scrobble.lastfm_artist.plays += 1

        # Reset flag so new scrobble can later be submitted
        self.__should_submit_current_scrobble = False

    def __update_current_scrobble(
            self, media_player_state: MediaPlayerState) -> None:
        '''Replace the current scrobble, update cached track start/finish, update the UI'''

        if not self.__is_enabled:
            return

        # Initialize a new Scrobble object with the updated media player state
        self.__current_scrobble = Scrobble(
            artist_name=media_player_state.artist_name,
            track_title=media_player_state.track_title,
            album_title=media_player_state.album_title,
            album_artist_name=media_player_state.album_artist_name,
            timestamp=
            None  # TODO: Make this configurable, it should default to datetime.now() to match official app
        )

        # Update UI content in current scrobble sidebar item
        self.current_scrobble_data_changed.emit()

        # Reset player position to temporary value until a new value can be recieved from the media player
        self.__furthest_player_position_reached = 0

        # Refresh selected_scrobble with new __current_scrobble object if the current scrobble is selected
        if self.__selected_scrobble_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            self.selected_scrobble = self.__current_scrobble

            # Update details pane view
            self.selected_scrobble_changed.emit()
        elif self.__selected_scrobble_index is None:
            self.__selected_scrobble_index = HistoryViewModel.__CURRENT_SCROBBLE_INDEX
            self.selected_scrobble = self.__current_scrobble

            # Update the current scrobble highlight and song details pane views
            self.selected_scrobble_index_changed.emit()

            # Update details pane view
            self.selected_scrobble_changed.emit()

        # Update cached media player track playback data
        self.__current_track_crop = media_player_state.track_crop

        # Load Last.fm data and album art
        self.__load_external_scrobble_data(self.__current_scrobble)

        # Reset scrobble meter
        self.__current_scrobble_percentage = 0
        self.scrobble_percentage_changed.emit()

    def __fetch_new_media_player_position(self) -> None:
        '''Fetch the current player position timestamp from the selected media player'''

        if (not self.__is_enabled

                # Skip fetching if there isn't a track playing
                or not self.__current_scrobble):
            return

        fetch_player_position_task = FetchPlayerPosition(self.__media_player)
        fetch_player_position_task.finished.connect(
            self.__handle_player_position_fetched)
        QtCore.QThreadPool.globalInstance().start(fetch_player_position_task)

    def __handle_player_position_fetched(self, player_position: float) -> None:
        '''Update furthest player position reached if needed based on player position data'''

        if not self.__is_enabled:
            return

        # Only update the furthest reached position if it's further than the last recorded furthest position
        # This is because if the user scrubs backward in the track, the scrobble progress bar will stop
        # moving until they reach the previous furthest point reached in the track
        # TODO: Add support for different scrobble submission styles such as counting seconds of playback
        if player_position >= self.__furthest_player_position_reached:
            self.__furthest_player_position_reached = player_position

        # Count how many ticks have passed since the playback position changed (used for Discord RPC)
        if player_position == self.__cached_playback_position:
            self.__ticks_since_position_change += 1

            # Clear discord status if paused for more than 60 seconds
            if self.__ticks_since_position_change == 60 and self.__is_discord_rpc_enabled:
                self.__run_discord_rpc_request(
                    lambda self: self.__discord_rpc.clear())
        else:
            self.__ticks_since_position_change = 0

        self.__cached_playback_position = player_position
        self.__current_scrobble_percentage = self.__determine_current_scrobble_percentage(
        )
        self.scrobble_percentage_changed.emit()

    def __determine_current_scrobble_percentage(self) -> int:
        '''Determine the percentage of the track that has played'''

        if not self.__is_enabled or not self.__current_scrobble:
            return 0

        # Skip calculations if the track has already reached the scrobble threshold
        if self.__should_submit_current_scrobble:
            return 1

        # Compensate for custom track start and end times
        # TODO: Only do this if the media player is the mac Music app
        relative_position = self.__furthest_player_position_reached - self.__current_track_crop.start
        relative_track_length = self.__current_track_crop.finish - self.__current_track_crop.start
        min_scrobble_length = relative_track_length * 0.75  # TODO: Grab the percentage from settings

        # Prevent scenarios where the relative position is negative
        relative_position = max(0, relative_position)
        scrobble_percentage = relative_position / min_scrobble_length

        # Prevent scenarios where the relative player position is greater than the relative track length
        # (don't let the percentage by greater than 1)
        scrobble_percentage = min(scrobble_percentage, 1)

        # Submit current scrobble if the progress towards the scrobble threshold is 100%
        if scrobble_percentage == 1:
            self.__should_submit_current_scrobble = True

        return scrobble_percentage

    def __set_is_scrobble_submission_enabled(self, value: bool) -> None:
        # Ignore any changes to is_submission_enabled if it's disabled globally
        if os.environ.get('DISABLE_SUBMISSION'):
            self.__is_submission_enabled = False
        else:
            self.__is_submission_enabled = value

        logging.info(
            f'Scrobble submission is set to {self.__is_submission_enabled}')

    def __handle_media_player_stopped(self) -> None:
        '''Handle media player stop event (no track is loaded)'''

        if (not self.__is_enabled

                # Don't do anything this case if there was nothing playing previously
                or not self.__current_scrobble):
            return

        if self.__is_discord_rpc_enabled:
            self.__run_discord_rpc_request(
                lambda self: self.__discord_rpc.clear())

        # Submit if the music player stops as well, not just when a new track starts
        if self.__should_submit_current_scrobble:
            self.__submit_scrobble(self.__current_scrobble)

        # Reset current scrobble
        self.__current_scrobble = None

        # Update the UI in current scrobble sidebar item
        self.current_scrobble_data_changed.emit()

        # If the current scrobble is selected, deselect it
        if self.__selected_scrobble_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            self.__selected_scrobble_index = None
            self.selected_scrobble = None

            # Update the current scrobble highlight and song details pane views
            self.selected_scrobble_index_changed.emit()
            self.selected_scrobble_changed.emit()

    def __handle_media_player_playing(
            self, new_media_player_state: MediaPlayerState) -> None:
        '''Handle media player play event'''

        if not self.__is_enabled:
            return

        # Update now playing on Last.fm regardless of whether it's a new play
        if self.__is_submission_enabled:
            QtCore.QThreadPool.globalInstance().start(
                UpdateNowPlaying(
                    lastfm=self.__application_reference.lastfm,
                    artist_name=new_media_player_state.artist_name,
                    track_title=new_media_player_state.track_title,
                    album_title=new_media_player_state.album_title,
                    album_artist_name=new_media_player_state.album_artist_name,
                    duration=new_media_player_state.track_crop.finish -
                    new_media_player_state.track_crop.start))

        # Update Discord rich presence regardless of whether it's a new play
        if self.__is_discord_rpc_enabled:
            self.__run_discord_rpc_request(
                lambda self: self.__update_discord_rpc(
                    new_media_player_state.track_title, new_media_player_state.
                    artist_name, new_media_player_state.album_title,
                    new_media_player_state.position))

        # Update playback indicator
        self.is_player_paused = False
        self.is_player_paused_changed.emit()

        # If we just resumed from paused state, we don't need to continue with checking for new tracks
        # if self.__was_last_player_event_paused:
        #   self.__was_last_player_event_paused = False
        #   logging.debug(f'Ignoring resume event for {new_media_player_state.track_title}')
        #   return

        # Check if the track has changed or not
        is_same_track = None

        if not self.__current_scrobble:
            # This is the first track
            is_same_track = False
        else:
            is_same_track = (self.__current_scrobble.track_title
                             == new_media_player_state.track_title
                             and self.__current_scrobble.artist_name
                             == new_media_player_state.artist_name
                             and self.__current_scrobble.album_title
                             == new_media_player_state.album_title)

        # Submit the current scrobble if it hit the scrobbling threshold
        if not is_same_track:

            # # Make sure that the current track has finished playing if it's being looped
            # if is_same_track and not self.__was_last_player_event_paused:
            #   track_duration = self.__current_track_crop.finish - self.__current_track_crop.start
            #   margin_of_error = self.__MEDIA_PLAYER_POLLING_INTERVAL + 1

            #   if (track_duration - self.__furthest_player_position_reached) > margin_of_error:
            #     # Track didn't finish playing
            #     return

            # Submit current scrobble to Last.fm
            if self.__should_submit_current_scrobble:
                self.__submit_scrobble(self.__current_scrobble)

        # Load new track data into current scrobble
            self.__update_current_scrobble(new_media_player_state)

    def __handle_media_player_paused(self) -> None:
        '''Handle media player pause event'''

        # Update playback indicator
        self.is_player_paused = True
        self.is_player_paused_changed.emit()
        self.__was_last_player_event_paused = True

    # --- Qt Properties ---

    applicationReference = QtCore.Property(
        type=ApplicationViewModel,
        fget=lambda self: self.__application_reference,
        fset=set_application_reference)

    isEnabled = QtCore.Property(type=bool,
                                fget=lambda self: self.__is_enabled,
                                fset=set_is_enabled,
                                notify=is_enabled_changed)

    currentScrobble = QtCore.Property(
        type='QVariant',
        fget=lambda self: asdict(self.__current_scrobble)
        if self.__current_scrobble else None,
        notify=current_scrobble_data_changed)

    scrobblePercentage = QtCore.Property(
        type=float,
        fget=lambda self: self.__current_scrobble_percentage,
        notify=scrobble_percentage_changed)

    isSpotifyPluginAvailable = QtCore.Property(
        type=bool,
        fget=lambda self: self.__is_spotify_plugin_available,
        notify=is_spotify_plugin_available_changed)

    isUsingMockPlayer = QtCore.Property(
        type=bool,
        fget=lambda self: isinstance(self.__media_player, MockPlayerPlugin),
        notify=is_using_mock_player_changed)

    isDiscordRichPresenceEnabled = QtCore.Property(
        type=bool,
        fget=lambda self: self.__is_discord_rpc_enabled,
        fset=set_is_discord_rich_presence_enabled,
        notify=is_discord_rich_presence_enabled_changed)

    selectedScrobbleIndex = QtCore.Property(
        type=int,
        fget=get_selected_scrobble_index,
        fset=set_selected_scrobble_index,
        notify=selected_scrobble_index_changed)

    isLoading = QtCore.Property(type=bool,
                                fget=lambda self: self.__is_loading,
                                notify=is_loading_changed)

    mediaPlayerName = QtCore.Property(
        type=str,
        fget=lambda self: self.__media_player.MEDIA_PLAYER_NAME,
        notify=media_player_name_changed)