示例#1
0
class UpdateSettingsDialog(FormClass, BaseClass):
    updater_branch = Settings.persisted_property(
        'updater/branch', type=str, default_value=UpdateBranch.Prerelease.name)
    updater_downgrade = Settings.persisted_property('updater/downgrade',
                                                    type=bool,
                                                    default_value=False)

    def __init__(self, *args, **kwargs):
        BaseClass.__init__(self, *args, **kwargs)

        self.setModal(True)

    def setup(self):
        self.setupUi(self)

        self.cbChannel.setCurrentIndex(UpdateBranch[self.updater_branch].value)
        self.cbDowngrade.setChecked(self.updater_downgrade)

        self.buttonBox.accepted.connect(self.accepted)
        self.buttonBox.rejected.connect(lambda: self.close())

    def accepted(self):
        branch = UpdateBranch(self.cbChannel.currentIndex())
        self.updater_branch = branch.name
        self.updater_downgrade = self.cbDowngrade.isChecked()
        self.close()
示例#2
0
class UpdateSettings:
    updater_branch = Settings.persisted_property(
        'updater/branch', type=str, default_value=UpdateBranch.Prerelease.name)

    def should_notify(self, releases, force=True):
        self._logger.debug(releases)
        if force:
            return True

        server_releases = [
            release for release in releases if release['branch'] == 'server'
        ]
        stable_releases = [
            release for release in releases if release['branch'] == 'stable'
        ]
        pre_releases = [
            release for release in releases if release['branch'] == 'pre'
        ]
        beta_releases = [
            release for release in releases if release['branch'] == 'beta'
        ]

        have_server = len(server_releases) > 0
        have_stable = len(stable_releases) > 0
        have_pre = len(pre_releases) > 0
        have_beta = len(beta_releases) > 0

        current_version = Version(config.VERSION)
        # null out build because we don't care about it
        current_version.build = ()

        notify_stable = have_stable and self.updater_branch == UpdateBranch.Stable.name \
            and Version(stable_releases[0]['new_version']) > current_version
        notify_pre = have_pre and self.updater_branch == UpdateBranch.Prerelease.name \
            and Version(pre_releases[0]['new_version']) > current_version
        notify_beta = have_beta and self.updater_branch == UpdateBranch.Unstable.name \
            and Version(beta_releases[0]['new_version']) > current_version

        return have_server or notify_stable or notify_pre or notify_beta
示例#3
0
class ChatWidget(FormClass, BaseClass, SimpleIRCClient):
    use_chat = Settings.persisted_property('chat/enabled',
                                           type=bool,
                                           default_value=True)
    '''
    This is the chat lobby module for the FAF client.
    It manages a list of channels and dispatches IRC events (lobby inherits from irclib's client class)
    '''
    def __init__(self, client, *args, **kwargs):
        if not self.use_chat:
            logger.info("Disabling chat")
            return

        logger.debug("Lobby instantiating.")
        BaseClass.__init__(self, *args, **kwargs)
        SimpleIRCClient.__init__(self)

        self.setupUi(self)

        # CAVEAT: These will fail if loaded before theming is loaded
        import json
        chat.OPERATOR_COLORS = json.loads(
            util.readfile("chat/formatters/operator_colors.json"))

        self.client = client
        self.channels = {}

        #avatar downloader
        self.nam = QNetworkAccessManager()
        self.nam.finished.connect(self.finishDownloadAvatar)

        #nickserv stuff
        self.identified = False

        #IRC parameters
        self.ircServer = IRC_SERVER
        self.ircPort = IRC_PORT
        self.crucialChannels = ["#aeolus"]
        self.optionalChannels = []

        #We can't send command until the welcom message is received
        self.welcomed = False

        # Load colors and styles from theme
        self.a_style = util.readfile("chat/formatters/a_style.qss")

        #load UI perform some tweaks
        self.tabBar().setTabButton(0, 1, None)

        #add self to client's window
        self.client.chatTab.layout().addWidget(self)
        self.tabCloseRequested.connect(self.closeChannel)

        #Hook with client's connection and autojoin mechanisms
        self.client.authorized.connect(self.connect)
        self.client.autoJoin.connect(self.autoJoin)
        self.channelsAvailable = []
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.poll)

        # disconnection checks
        self.canDisconnect = False

    @QtCore.pyqtSlot()
    def poll(self):
        self.timer.stop()
        self.once()
        self.timer.start(POLLING_INTERVAL)

    def disconnect(self):
        self.canDisconnect = True
        self.irc_disconnect()
        self.timer.stop()

    @QtCore.pyqtSlot(object)
    def connect(self, player):
        try:
            self.irc_connect(self.ircServer,
                             self.ircPort,
                             player.login,
                             ssl=True,
                             ircname=player.login,
                             username=player.id)
            self.timer.start()

        except:
            logger.debug("Unable to connect to IRC server.")
            self.serverLogArea.appendPlainText(
                "Unable to connect to the chat server, but you should still be able to host and join games."
            )
            logger.error("IRC Exception", exc_info=sys.exc_info())

    def finishDownloadAvatar(self, reply):
        ''' this take care of updating the avatars of players once they are downloaded '''
        img = QtGui.QImage()
        img.loadFromData(reply.readAll())
        pix = util.respix(reply.url().toString())
        if pix:
            pix = QtGui.QIcon(QtGui.QPixmap(img))
        else:
            util.addrespix(reply.url().toString(), QtGui.QPixmap(img))

        for player in util.curDownloadAvatar(reply.url().toString()):
            for channel in self.channels:
                if player in self.channels[channel].chatters:
                    self.channels[channel].chatters[player].avatarItem.setIcon(
                        QtGui.QIcon(util.respix(reply.url().toString())))
                    self.channels[channel].chatters[
                        player].avatarItem.setToolTip(
                            self.channels[channel].chatters[player].avatarTip)

    def closeChannel(self, index):
        '''
        Closes a channel tab.
        '''
        channel = self.widget(index)
        for name in self.channels:
            if self.channels[name] is channel:
                if not self.channels[
                        name].private and self.connection.is_connected(
                        ):  # Channels must be parted (if still connected)
                    self.connection.part([name], "tab closed")
                else:
                    # Queries and disconnected channel windows can just be closed
                    self.removeTab(index)
                    del self.channels[name]

                break

    @QtCore.pyqtSlot(str)
    def announce(self, broadcast):
        '''
        Notifies all crucial channels about the status of the client.
        '''
        logger.debug("BROADCAST:" + broadcast)
        for channel in self.crucialChannels:
            self.sendMsg(channel, broadcast)

    def setTopic(self, chan, topic):
        self.connection.topic(chan, topic)

    def sendMsg(self, target, text):
        if self.connection.is_connected():
            self.connection.privmsg(target, text)
            return True
        else:
            logger.error("IRC connection lost.")
            for channel in self.crucialChannels:
                if channel in self.channels:
                    self.channels[channel].printRaw("Server",
                                                    "IRC is disconnected")
            return False

    def sendAction(self, target, text):
        if self.connection.is_connected():
            self.connection.action(target, text)
            return True
        else:
            logger.error("IRC connection lost.")
            for channel in self.crucialChannels:
                if channel in self.channels:
                    self.channels[channel].printAction("IRC",
                                                       "was disconnected.")
            return False

    def openQuery(self, name, activate=False):
        # Ignore ourselves.
        if name == self.client.login:
            return False

        if name not in self.channels:
            self.channels[name] = Channel(self, name, True)
            self.addTab(self.channels[name], name)

            # Add participants to private channel
            self.channels[name].addChatter(name)
            self.channels[name].addChatter(self.client.login)

        if activate:
            self.setCurrentWidget(self.channels[name])

        self.channels[name].resizing()
        return True

    @QtCore.pyqtSlot(list)
    def autoJoin(self, channels):
        for channel in channels:
            if channel in self.channels:
                continue
            if (self.connection.is_connected()) and self.welcomed:
                #directly join
                self.connection.join(channel)
            else:
                #Note down channels for later.
                self.optionalChannels.append(channel)

    def join(self, channel):
        if channel not in self.channels:
            self.connection.join(channel)

    def log_event(self, e):
        self.serverLogArea.appendPlainText(
            "[%s: %s->%s]" % (e.eventtype(), e.source(), e.target()) +
            "\n".join(e.arguments()))

#SimpleIRCClient Class Dispatcher Attributes follow here.

    def on_welcome(self, c, e):
        self.log_event(e)
        self.welcomed = True

    def nickservIdentify(self):
        if not self.identified:
            self.serverLogArea.appendPlainText("[Identify as : %s]" %
                                               (self.client.login))
            self.connection.privmsg(
                'NickServ', 'identify %s %s' %
                (self.client.login, util.md5text(self.client.password)))

    def on_identified(self):
        if self.connection.get_nickname() != self.client.login:
            self.serverLogArea.appendPlainText(
                "[Retrieving our nickname : %s]" % (self.client.login))
            self.connection.privmsg(
                'NickServ', 'recover %s %s' %
                (self.client.login, util.md5text(self.client.password)))
        #Perform any pending autojoins (client may have emitted autoJoin signals before we talked to the IRC server)
        self.autoJoin(self.optionalChannels)
        self.autoJoin(self.crucialChannels)

    def nickservRegister(self):
        if hasattr(self, '_nickserv_registered'):
            return
        self.connection.privmsg(
            'NickServ', 'register %s %s' %
            (util.md5text(self.client.password),
             '{}@users.faforever.com'.format(self.client.me.login)))
        self._nickserv_registered = True
        self.autoJoin(self.optionalChannels)
        self.autoJoin(self.crucialChannels)

    def on_version(self, c, e):
        self.connection.privmsg(
            e.source(), "Forged Alliance Forever " + util.VERSION_STRING)

    def on_motd(self, c, e):
        self.log_event(e)
        self.nickservIdentify()

    def on_endofmotd(self, c, e):
        self.log_event(e)

    def on_namreply(self, c, e):
        self.log_event(e)
        channel = e.arguments()[1]
        listing = e.arguments()[2].split()

        for user in listing:
            name = user.strip(chat.IRC_ELEVATION)
            id = -1
            if name in self.client.players:
                id = self.client.players[name].id
            self.channels[channel].addChatter(
                name, id, user[0] if user[0] in chat.IRC_ELEVATION else None,
                '')

        logger.debug("Added " + str(len(listing)) + " Chatters")

    def on_whoisuser(self, c, e):
        self.log_event(e)

    def on_join(self, c, e):
        channel = e.target()

        # If we're joining, we need to open the channel for us first.
        if channel not in self.channels:
            self.channels[channel] = Channel(self, channel)
            if (channel.lower() in self.crucialChannels):
                self.insertTab(
                    1, self.channels[channel],
                    channel)  #CAVEAT: This is assumes a server tab exists.
                self.client.localBroadcast.connect(
                    self.channels[channel].printRaw)
                self.channels[channel].printAnnouncement(
                    "Welcome to Forged Alliance Forever!", "red", "+3")
                self.channels[channel].printAnnouncement(
                    "Check out the wiki: http://wiki.faforever.com for help with common issues.",
                    "white", "+1")
                self.channels[channel].printAnnouncement("", "black", "+1")
                self.channels[channel].printAnnouncement("", "black", "+1")

            else:
                self.addTab(self.channels[channel], channel)

            if channel.lower(
            ) in self.crucialChannels:  #Make the crucial channels not closeable, and make the last one the active one
                self.setCurrentWidget(self.channels[channel])
                self.tabBar().setTabButton(self.currentIndex(),
                                           QtGui.QTabBar.RightSide, None)

        name, id, elevation, hostname = parse_irc_source(e.source())
        self.channels[channel].addChatter(name, id, elevation, hostname, True)

        if channel.lower(
        ) in self.crucialChannels and name != self.client.login:
            # TODO: search better solution, that html in nick & channel no rendered
            self.client.notificationSystem.on_event(
                ns.Notifications.USER_ONLINE, {
                    'user': id,
                    'channel': channel
                })
        self.channels[channel].resizing()

    def on_part(self, c, e):
        channel = e.target()
        name = user2name(e.source())
        if name == self.client.login:  #We left ourselves.
            self.removeTab(self.indexOf(self.channels[channel]))
            del self.channels[channel]
        else:  #Someone else left
            self.channels[channel].removeChatter(name, "left.")

    def on_quit(self, c, e):
        name = user2name(e.source())
        for channel in self.channels:
            if (not self.channels[channel].private) or (
                    self.channels[channel].name == user2name(name)):
                self.channels[channel].removeChatter(name, "quit.")

    def on_nick(self, c, e):
        self.log_event(e)

    def on_mode(self, c, e):
        if len(e.arguments()) < 2:
            return
        name = user2name(e.arguments()[1])
        if e.target() in self.channels:
            self.channels[e.target()].elevateChatter(name, e.arguments()[0])

    def on_umode(self, c, e):
        self.log_event(e)

    def on_notice(self, c, e):
        self.log_event(e)

    def on_topic(self, c, e):
        channel = e.target()
        if channel in self.channels:
            self.channels[channel].setAnnounceText(" ".join(e.arguments()))

    def on_currenttopic(self, c, e):
        channel = e.arguments()[0]
        if channel in self.channels:
            self.channels[channel].setAnnounceText(" ".join(e.arguments()[1:]))

    def on_topicinfo(self, c, e):
        self.log_event(e)

    def on_list(self, c, e):
        self.log_event(e)

    def on_bannedfromchan(self, c, e):
        self.log_event(e)

    def on_pubmsg(self, c, e):
        name, id, elevation, hostname = parse_irc_source(e.source())
        target = e.target()

        if target in self.channels and not self.client.players.isFoe(id):
            self.channels[target].printMsg(name, "\n".join(e.arguments()))

    def on_privnotice(self, c, e):
        source = user2name(e.source())
        notice = e.arguments()[0]
        prefix = notice.split(" ")[0]
        target = prefix.strip("[]")

        if source and source.lower() == 'nickserv':
            if notice.find("registered under your account") >= 0 or \
               notice.find("Password accepted") >= 0:
                if not self.identified:
                    self.identified = True
                    self.on_identified()

            elif notice.find("isn't registered") >= 0:
                self.nickservRegister()

            elif notice.find("RELEASE") >= 0:
                self.connection.privmsg(
                    'nickserv', 'release %s %s' %
                    (self.client.login, util.md5text(self.client.password)))

            elif notice.find("hold on") >= 0:
                self.connection.nick(self.client.login)

        message = "\n".join(e.arguments()).lstrip(prefix)
        if target in self.channels:
            self.channels[target].printMsg(source, message)
        elif source == "Global":
            for channel in self.channels:
                self.channels[channel].printAnnouncement(
                    message, "yellow", "+2")
        elif source == "AeonCommander":
            for channel in self.channels:
                self.channels[channel].printMsg(source, message)
        else:
            self.serverLogArea.appendPlainText("%s: %s" % (source, notice))

    def on_disconnect(self, c, e):
        if not self.canDisconnect:
            logger.warn("IRC disconnected - reconnecting.")
            self.identified = False
            self.timer.stop()
            self.connect(self.client.me)

    def on_privmsg(self, c, e):
        name, id, elevation, hostname = parse_irc_source(e.source())

        if self.client.players.isFoe(id):
            return

        # Create a Query if it's not open yet, and post to it if it exists.
        if self.openQuery(name):
            self.channels[name].printMsg(name, "\n".join(e.arguments()))

    def on_action(self, c, e):
        name, id, elevation, hostname = parse_irc_source(
            e.source())  #user2name(e.source())
        target = e.target()

        if self.client.players.isFoe(id):
            return

        # Create a Query if it's not an action intended for a channel
        if target not in self.channels:
            self.openQuery(name)
            self.channels[name].printAction(name, "\n".join(e.arguments()))
        else:
            self.channels[target].printAction(name, "\n".join(e.arguments()))

    def on_default(self, c, e):
        self.serverLogArea.appendPlainText(
            "[%s: %s->%s]" % (e.eventtype(), e.source(), e.target()) +
            "\n".join(e.arguments()))
        if "Nickname is already in use." in "\n".join(e.arguments()):
            self.connection.nick(self.client.login + "_")

    def on_kick(self, c, e):
        pass
示例#4
0
class UpdateDialog(FormClass, BaseClass):
    changelog_url = Settings.persisted_property(
        'updater/changelog_url',
        type=str,
        default_value='https://github.com/FAForever/client/releases/tag')

    def __init__(self, *args, **kwargs):
        BaseClass.__init__(self, *args, **kwargs)

        self._logger.debug("UpdateDialog instantiating")
        self.setModal(True)

    def setup(self, releases):
        self.setupUi(self)

        self.btnStart.clicked.connect(self.startUpdate)
        self.btnAbort.clicked.connect(self.abort)
        self.btnSettings.clicked.connect(self.showSettings)

        self.cbReleases.currentIndexChanged.connect(self.indexChanged)

        self.layout().setSizeConstraint(QLayout.SetFixedSize)

        self.releases = releases
        self.reset_controls()

    def reset_controls(self):
        self.pbDownload.hide()
        self.btnCancel.hide()
        self.btnAbort.setEnabled(True)

        current_version = Version(config.VERSION)

        if any([release['branch'] == 'server' for release in self.releases]):
            self.lblUpdatesFound.setText(
                'Your client version is outdated - you must update to play.')
        else:
            self.lblUpdatesFound.setText('Client releases were found.')

        if len(self.releases) > 0:
            currIdx = 0
            preferIdx = None

            labels = dict([('server', 'Server Version'),
                           ('stable', 'Stable Version'),
                           ('pre', 'Stable Prerelease'), ('beta', 'Unstable')])
            self.cbReleases.blockSignals(True)
            self.cbReleases.clear()
            for currIdx, release in enumerate(self.releases):
                self._logger.debug(release)

                key = release['branch']
                label = labels[key]
                branch_to_key = dict(Stable='stable',
                                     Prerelease='pre',
                                     Unstable='beta')
                prefer = key == branch_to_key[UpdateSettings().updater_branch]

                is_update = ' [New!]' if Version(
                    release['new_version']) > current_version else ''

                self.cbReleases.insertItem(
                    99, '{} {}{}'.format(label, release['new_version'],
                                         is_update), release)

                if prefer and preferIdx is None:
                    preferIdx = currIdx

            if preferIdx is None:
                preferIdx = 0

            self.cbReleases.setCurrentIndex(preferIdx)
            self.indexChanged(preferIdx)
            self.cbReleases.blockSignals(False)

            self.btnStart.setEnabled(True)

    @QtCore.pyqtSlot(int)
    def indexChanged(self, index):
        def _format_changelog(version):
            if version is not None:
                return "<a href=\"{}/{}\">Release Info</a>".format(
                    self.changelog_url, version)
            else:
                return 'Not available'

        release = self.cbReleases.itemData(index)

        self.lblInfo.setText(_format_changelog(release['new_version']))

    def startUpdate(self):
        sender = self.sender()

        release = self.cbReleases.itemData(self.cbReleases.currentIndex())
        url = release['update']

        self.btnStart.setEnabled(False)
        self.btnAbort.setEnabled(False)

        client_updater = ClientUpdater(parent=self,
                                       progress_bar=self.pbDownload,
                                       cancel_btn=self.btnCancel)
        client_updater.finished.connect(self.finishUpdate)
        client_updater.exec_(url)

    def finishUpdate(self):
        self.reset_controls()

    def abort(self):
        self.close()

    def showSettings(self):
        dialog = UpdateSettingsDialog(self)
        dialog.setup()
        dialog.show()
示例#5
0
class UpdateChecker(QObject):
    gh_releases_url = Settings.persisted_property(
        'updater/gh_release_url',
        type=str,
        default_value='https://api.github.com/repos/'
        'FAForever/client/releases?per_page=20')
    updater_downgrade = Settings.persisted_property('updater/downgrade',
                                                    type=bool,
                                                    default_value=False)

    finished = QtCore.pyqtSignal(list)

    def __init__(self, parent, respect_notify=True):
        QObject.__init__(self, parent)
        self._network_manager = client.NetworkManager
        self.respect_notify = respect_notify
        self._releases = None

    def start(self, reset_server=True):
        gh_url = QUrl(self.gh_releases_url)
        self._rep = self._network_manager.get(QNetworkRequest(gh_url))
        self._rep.finished.connect(self._req_done)
        if reset_server:
            self._server_info = None

    def server_update(self, message):
        self._server_info = message
        self._check_updates_complete()

    def server_session(self):
        self._server_info = {}
        self._check_updates_complete()

    def _parse_releases(self, release_info):
        def _parse_release(release_dict):
            client_version = Version(config.VERSION)
            for asset in release_dict['assets']:
                if '.msi' in asset['browser_download_url']:
                    download_url = asset['browser_download_url']
                    tag = release_dict['tag_name']
                    release_version = Version(tag)
                    # We never want to return the current version itself,
                    # but if `updater_downgrade` is set, we do return
                    # older releases.
                    # strange comparison logic is because of semantic_version
                    # so that build info is ignored
                    if self.updater_downgrade:
                        if not (release_version < client_version
                                or client_version < release_version):
                            return None
                    else:
                        if not (release_version > client_version):
                            return None

                    branch = None
                    if release_version.minor % 2 == 1:
                        branch = 'beta'
                    elif release_version.minor % 2 == 0:
                        if release_version.prerelease == ():
                            branch = 'stable'
                        else:
                            branch = 'pre'
                    return dict(branch=branch,
                                update=download_url,
                                new_version=tag)

        try:
            releases = json.loads(release_info.decode('utf-8'))
            if not isinstance(releases, list):
                releases = [releases]
            self._logger.debug('Loaded {} github releases'.format(
                len(releases)))

            return [
                release for release in
                [_parse_release(release) for release in releases]
                if release is not None
            ]
        except:
            self._logger.exception("Error parsing network reply: {}".format(
                repr(release_info)))
            return []

    def _req_done(self):
        if self._rep.error() == QNetworkReply.NoError:
            self._releases = self._parse_releases(bytes(self._rep.readAll()))
        else:
            self._releases = []

        self._check_updates_complete()

    def _check_updates_complete(self):
        if self._server_info is not None and self._releases is not None:
            releases = self._releases
            if self._server_info != {}:
                releases.append(
                    dict(branch='server',
                         update=self._server_info['update'],
                         new_version=self._server_info['new_version']))
            if UpdateSettings().should_notify(releases,
                                              force=not self.respect_notify):
                self.finished.emit(releases)
示例#6
0
class ReplayVaultWidgetHandler(object):
    HOST = "lobby.faforever.com"
    PORT = 11002

    # connect to save/restore persistence settings for checkboxes & search parameters
    automatic = Settings.persisted_property("replay/automatic",
                                            default_value=False,
                                            type=bool)
    spoiler_free = Settings.persisted_property("replay/spoilerFree",
                                               default_value=True,
                                               type=bool)

    def __init__(self, widget, dispatcher, client, gameset, playerset):
        self._w = widget
        self._dispatcher = dispatcher
        self.client = client
        self._gameset = gameset
        self._playerset = playerset

        self.onlineReplays = {}
        self.selectedReplay = None
        self.vault_connection = ReplaysConnection(self._dispatcher, self.HOST,
                                                  self.PORT)
        self.client.lobby_info.replayVault.connect(self.replayVault)
        self.replayDownload = QNetworkAccessManager()
        self.replayDownload.finished.connect(self.finishRequest)

        self.searching = False
        self.searchInfo = "<font color='gold'><b>Searching...</b></font>"

        _w = self._w
        _w.onlineTree.setItemDelegate(ReplayItemDelegate(_w))
        _w.onlineTree.itemDoubleClicked.connect(self.onlineTreeDoubleClicked)
        _w.onlineTree.itemPressed.connect(self.onlineTreeClicked)

        _w.searchButton.pressed.connect(self.searchVault)
        _w.playerName.returnPressed.connect(self.searchVault)
        _w.mapName.returnPressed.connect(self.searchVault)
        _w.automaticCheckbox.stateChanged.connect(self.automaticCheckboxchange)
        _w.spoilerCheckbox.stateChanged.connect(self.spoilerCheckboxchange)
        _w.RefreshResetButton.pressed.connect(self.resetRefreshPressed)

        # restore persistent checkbox settings
        _w.automaticCheckbox.setChecked(self.automatic)
        _w.spoilerCheckbox.setChecked(self.spoiler_free)

    def searchVault(self,
                    minRating=None,
                    mapName=None,
                    playerName=None,
                    modListIndex=None):
        w = self._w
        if minRating is not None:
            w.minRating.setValue(minRating)
        if mapName is not None:
            w.mapName.setText(mapName)
        if playerName is not None:
            w.playerName.setText(playerName)
        if modListIndex is not None:
            w.modList.setCurrentIndex(modListIndex)

        # Map Search helper - the secondary server has a problem with blanks (fix until change to api)
        map_name = w.mapName.text().replace(" ", "*")
        """ search for some replays """
        self._w.searchInfoLabel.setText(self.searchInfo)
        self.searching = True
        self.vault_connection.connect()
        self.vault_connection.send(
            dict(command="search",
                 rating=w.minRating.value(),
                 map=map_name,
                 player=w.playerName.text(),
                 mod=w.modList.currentText()))
        self._w.onlineTree.clear()

    def reloadView(self):
        if not self.searching:  # something else is already in the pipe from SearchVault
            if self.automatic or self.onlineReplays == {}:  # refresh on Tab change or only the first time
                self._w.searchInfoLabel.setText(self.searchInfo)
                self.vault_connection.connect()
                self.vault_connection.send(dict(command="list"))

    def onlineTreeClicked(self, item):
        if QtWidgets.QApplication.mouseButtons() == QtCore.Qt.RightButton:
            if type(item.parent) == ReplaysWidget:  # FIXME - hack
                item.pressed(item)
        else:
            self.selectedReplay = item
            if hasattr(item, "moreInfo"):
                if item.moreInfo is False:
                    self.vault_connection.connect()
                    self.vault_connection.send(
                        dict(command="info_replay", uid=item.uid))
                elif item.spoiled != self._w.spoilerCheckbox.isChecked():
                    self._w.replayInfos.clear()
                    self._w.replayInfos.setHtml(item.replayInfo)
                    item.resize()
                else:
                    self._w.replayInfos.clear()
                    item.generateInfoPlayersHtml()

    def onlineTreeDoubleClicked(self, item):
        if hasattr(item, "duration"):  # it's a game not a date separator
            if "playing" in item.duration:  # live game will not be in vault
                # search result isn't updated automatically - so game status might have changed
                if item.uid in self._gameset.games:  # game still running
                    game = self._gameset.games[item.uid]
                    if not game.launched_at:  # we frown upon those
                        return
                    if game.has_live_replay:  # live game over 5min
                        for name in game.players:  # find a player ...
                            if name in self._playerset:  # still logged in
                                self._startReplay(name)
                                break
                    else:
                        wait_str = time.strftime(
                            '%M Min %S Sec',
                            time.gmtime(game.LIVE_REPLAY_DELAY_SECS -
                                        (time.time() - game.launched_at)))
                        QtWidgets.QMessageBox.information(
                            client.instance, "5 Minute Live Game Delay",
                            "It is too early to join the Game.\n"
                            "You have to wait " + wait_str + " to join.")
                else:  # game ended - ask to start replay
                    if QtWidgets.QMessageBox.question(
                            client.instance, "Live Game ended",
                            "Would you like to watch the replay from the vault?",
                            QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No
                    ) == QtWidgets.QMessageBox.Yes:
                        self.replayDownload.get(
                            QNetworkRequest(QtCore.QUrl(item.url)))

            else:  # start replay
                if hasattr(item, "url"):
                    self.replayDownload.get(
                        QNetworkRequest(QtCore.QUrl(item.url)))

    def _startReplay(self, name):
        if name is None or name not in self._playerset:
            return
        player = self._playerset[name]

        if not player.currentGame:
            return
        replay(player.currentGame.url(player.id))

    def automaticCheckboxchange(self, state):
        self.automatic = state

    def spoilerCheckboxchange(self, state):
        self.spoiler_free = state
        if self.selectedReplay:  # if something is selected in the tree to the left
            if type(self.selectedReplay) == ReplayItem:  # and if it is a game
                self.selectedReplay.generateInfoPlayersHtml(
                )  # then we redo it

    def resetRefreshPressed(
            self):  # reset search parameter and reload recent Replays List
        self._w.searchInfoLabel.setText(self.searchInfo)
        self.vault_connection.connect()
        self.vault_connection.send(dict(command="list"))
        self._w.minRating.setValue(0)
        self._w.mapName.setText("")
        self._w.playerName.setText("")
        self._w.modList.setCurrentIndex(0)  # "All"

    def finishRequest(self, reply):
        if reply.error() != QNetworkReply.NoError:
            QtWidgets.QMessageBox.warning(self._w, "Network Error",
                                          reply.errorString())
        else:
            faf_replay = QtCore.QFile(
                os.path.join(util.CACHE_DIR, "temp.fafreplay"))
            faf_replay.open(QtCore.QIODevice.WriteOnly
                            | QtCore.QIODevice.Truncate)
            faf_replay.write(reply.readAll())
            faf_replay.flush()
            faf_replay.close()
            replay(os.path.join(util.CACHE_DIR, "temp.fafreplay"))

    def replayVault(self, message):
        action = message["action"]
        self._w.searchInfoLabel.clear()
        if action == "list_recents":
            self.onlineReplays = {}
            replays = message["replays"]
            for replay in replays:
                uid = replay["id"]

                if uid not in self.onlineReplays:
                    self.onlineReplays[uid] = ReplayItem(uid, self._w)
                    self.onlineReplays[uid].update(replay, self.client)
                else:
                    self.onlineReplays[uid].update(replay, self.client)

            self.updateOnlineTree()
            self._w.replayInfos.clear()
            self._w.RefreshResetButton.setText("Refresh Recent List")

        elif action == "info_replay":
            uid = message["uid"]
            if uid in self.onlineReplays:
                self.onlineReplays[uid].infoPlayers(message["players"])

        elif action == "search_result":
            self.searching = False
            self.onlineReplays = {}
            replays = message["replays"]
            for replay in replays:
                uid = replay["id"]

                if uid not in self.onlineReplays:
                    self.onlineReplays[uid] = ReplayItem(uid, self._w)
                    self.onlineReplays[uid].update(replay, self.client)
                else:
                    self.onlineReplays[uid].update(replay, self.client)

            self.updateOnlineTree()
            self._w.replayInfos.clear()
            self._w.RefreshResetButton.setText("Reset Search to Recent")

    def updateOnlineTree(self):
        self.selectedReplay = None  # clear because won't be part of the new tree
        self._w.replayInfos.clear()
        self._w.onlineTree.clear()
        buckets = {}
        for uid in self.onlineReplays:
            bucket = buckets.setdefault(self.onlineReplays[uid].startDate, [])
            bucket.append(self.onlineReplays[uid])

        for bucket in buckets.keys():
            bucket_item = QtWidgets.QTreeWidgetItem()
            self._w.onlineTree.addTopLevelItem(bucket_item)

            bucket_item.setIcon(0, util.THEME.icon("replays/bucket.png"))
            bucket_item.setText(0, "<font color='white'>" + bucket + "</font>")
            bucket_item.setText(
                1, "<font color='white'>" + str(len(buckets[bucket])) +
                " replays</font>")

            for replay in buckets[bucket]:
                bucket_item.addChild(replay)
                replay.setFirstColumnSpanned(True)
                replay.setIcon(0, replay.icon)

            bucket_item.setExpanded(True)
示例#7
0
class ChatWidget(FormClass, BaseClass, SimpleIRCClient):

    use_chat = Settings.persisted_property('chat/enabled',
                                           type=bool,
                                           default_value=True)
    irc_port = Settings.persisted_property('chat/port',
                                           type=int,
                                           default_value=6667)
    irc_host = Settings.persisted_property('chat/host',
                                           type=str,
                                           default_value='irc.' +
                                           defaults['host'])
    irc_tls = Settings.persisted_property('chat/tls',
                                          type=bool,
                                          default_value=False)

    auto_join_channels = Settings.persisted_property('chat/auto_join_channels',
                                                     default_value=[])
    """
    This is the chat lobby module for the FAF client.
    It manages a list of channels and dispatches IRC events (lobby inherits from irclib's client class)
    """
    def __init__(self, client, playerset, me, *args, **kwargs):
        if not self.use_chat:
            logger.info("Disabling chat")
            return

        logger.debug("Lobby instantiating.")
        BaseClass.__init__(self, *args, **kwargs)
        SimpleIRCClient.__init__(self)

        self.setupUi(self)

        self.client = client
        self._me = me
        self._chatters = IrcUserset(playerset)
        self.channels = {}

        # avatar downloader
        self.nam = QNetworkAccessManager()
        self.nam.finished.connect(self.finish_download_avatar)

        # nickserv stuff
        self.identified = False

        # IRC parameters
        self.crucialChannels = ["#aeolus"]
        self.optionalChannels = []

        # We can't send command until the welcome message is received
        self.welcomed = False

        # Load colors and styles from theme
        self.a_style = util.THEME.readfile("chat/formatters/a_style.qss")

        # load UI perform some tweaks
        self.tabBar().setTabButton(0, 1, None)

        self.tabCloseRequested.connect(self.close_channel)

        # Hook with client's connection and autojoin mechanisms
        self.client.authorized.connect(self.connect)
        self.client.autoJoin.connect(self.auto_join)
        self.channelsAvailable = []

        self._notifier = None
        self._timer = QTimer()
        self._timer.timeout.connect(self.once)

        # disconnection checks
        self.canDisconnect = False

    def disconnect(self):
        self.canDisconnect = True
        try:
            self.irc_disconnect()
        except ServerConnectionError:
            pass
        if self._notifier:
            self._notifier.activated.disconnect(self.once)
            self._notifier = None

    @QtCore.pyqtSlot(object)
    def connect(self, me):
        try:
            logger.info("Connecting to IRC at: {}:{}. TLS: {}".format(
                self.irc_host, self.irc_port, self.irc_tls))
            self.irc_connect(self.irc_host,
                             self.irc_port,
                             me.login,
                             ssl=self.irc_tls,
                             ircname=me.login,
                             username=me.id)
            self._notifier = QSocketNotifier(
                self.ircobj.connections[0]._get_socket().fileno(),
                QSocketNotifier.Read, self)
            self._notifier.activated.connect(self.once)
            self._timer.start(PONG_INTERVAL)

        except:
            logger.debug("Unable to connect to IRC server.")
            self.serverLogArea.appendPlainText(
                "Unable to connect to the chat server, but you should still be able to host and join games."
            )
            logger.error("IRC Exception", exc_info=sys.exc_info())

    def finish_download_avatar(self, reply):
        """ this take care of updating the avatars of players once they are downloaded """
        img = QtGui.QImage()
        img.loadFromData(reply.readAll())
        url = reply.url().toString()
        if not util.respix(url):
            util.addrespix(url, QtGui.QPixmap(img))

        for chatter in util.curDownloadAvatar(url):
            # FIXME - hack to prevent touching chatter if it was removed
            channel = chatter.channel
            ircuser = chatter.user
            if ircuser in channel.chatters:
                chatter.update_avatar()
        util.delDownloadAvatar(url)

    def add_channel(self, name, channel, index=None):
        self.channels[name] = channel
        if index is None:
            self.addTab(self.channels[name], name)
        else:
            self.insertTab(index, self.channels[name], name)

    def sort_channels(self):
        for channel in self.channels.values():
            channel.sort_chatters()

    def update_channels(self):
        for channel in self.channels.values():
            channel.update_chatters()

    def close_channel(self, index):
        """
        Closes a channel tab.
        """
        channel = self.widget(index)
        for name in self.channels:
            if self.channels[name] is channel:
                if not self.channels[
                        name].private and self.connection.is_connected(
                        ):  # Channels must be parted (if still connected)
                    self.connection.part([name], "tab closed")
                else:
                    # Queries and disconnected channel windows can just be closed
                    self.removeTab(index)
                    del self.channels[name]

                break

    @QtCore.pyqtSlot(str)
    def announce(self, broadcast):
        """
        Notifies all crucial channels about the status of the client.
        """
        logger.debug("BROADCAST:" + broadcast)
        for channel in self.crucialChannels:
            self.send_msg(channel, broadcast)

    def set_topic(self, chan, topic):
        self.connection.topic(chan, topic)

    def send_msg(self, target, text):
        if self.connection.is_connected():
            self.connection.privmsg(target, text)
            return True
        else:
            logger.error("IRC connection lost.")
            for channel in self.crucialChannels:
                if channel in self.channels:
                    self.channels[channel].print_raw("Server",
                                                     "IRC is disconnected")
            return False

    def send_action(self, target, text):
        if self.connection.is_connected():
            self.connection.action(target, text)
            return True
        else:
            logger.error("IRC connection lost.")
            for channel in self.crucialChannels:
                if channel in self.channels:
                    self.channels[channel].print_action(
                        "IRC", "was disconnected.")
            return False

    def open_query(self, chatter, activate=False):
        # Ignore ourselves.
        if chatter.name == self.client.login:
            return False

        if chatter.name not in self.channels:
            priv_chan = Channel(self, chatter.name, self._chatters, self._me,
                                True)
            self.add_channel(chatter.name, priv_chan)

            # Add participants to private channel
            priv_chan.add_chatter(chatter)

            if self.client.me.login is not None:
                my_login = self.client.me.login
                if my_login in self._chatters:
                    priv_chan.add_chatter(self._chatters[my_login])

            if activate:
                self.setCurrentWidget(priv_chan)

        return True

    @QtCore.pyqtSlot(list)
    def auto_join(self, channels):
        for channel in channels:
            if channel in self.channels:
                continue
            if (self.connection.is_connected()) and self.welcomed:
                # directly join
                self.connection.join(channel)
            else:
                # Note down channels for later.
                self.optionalChannels.append(channel)

    def join(self, channel):
        if channel not in self.channels:
            self.connection.join(channel)

    def log_event(self, e):
        self.serverLogArea.appendPlainText(
            "[%s: %s->%s]" % (e.eventtype(), e.source(), e.target()) +
            "\n".join(e.arguments()))

    def should_ignore(self, chatter):
        # Don't ignore mods from any crucial channels
        if any(chatter.is_mod(c) for c in self.crucialChannels):
            return False
        if chatter.player is None:
            return self.client.me.isFoe(name=chatter.name)
        else:
            return self.client.me.isFoe(id_=chatter.player.id)

# SimpleIRCClient Class Dispatcher Attributes follow here.

    def on_welcome(self, c, e):
        self.log_event(e)
        self.welcomed = True

    def nickserv_identify(self):
        if not self.identified:
            self.serverLogArea.appendPlainText("[Identify as : %s]" %
                                               self.client.login)
            self.connection.privmsg(
                'NickServ', 'identify %s %s' %
                (self.client.login, util.md5text(self.client.password)))

    def on_identified(self):
        if self.connection.get_nickname() != self.client.login:
            self.serverLogArea.appendPlainText(
                "[Retrieving our nickname : %s]" % (self.client.login))
            self.connection.privmsg(
                'NickServ', 'recover %s %s' %
                (self.client.login, util.md5text(self.client.password)))
        # Perform any pending autojoins (client may have emitted autoJoin signals before we talked to the IRC server)
        self.auto_join(self.optionalChannels)
        self.auto_join(self.crucialChannels)
        self.auto_join(self.auto_join_channels)
        self._schedule_actions_at_player_available()

    def _schedule_actions_at_player_available(self):
        self._me.playerAvailable.connect(self._at_player_available)
        if self._me.player is not None:
            self._at_player_available()

    def _at_player_available(self):
        self._me.playerAvailable.disconnect(self._at_player_available)
        self._autojoin_newbie_channel()

    def _autojoin_newbie_channel(self):
        if not self.client.useNewbiesChannel:
            return
        game_number_threshold = 50
        if self.client.me.player.number_of_games <= game_number_threshold:
            self.auto_join(["#newbie"])

    def nickserv_register(self):
        if hasattr(self, '_nickserv_registered'):
            return
        self.connection.privmsg(
            'NickServ', 'register %s %s' %
            (util.md5text(self.client.password),
             '{}@users.faforever.com'.format(self.client.me.login)))
        self._nickserv_registered = True
        self.auto_join(self.optionalChannels)
        self.auto_join(self.crucialChannels)

    def on_version(self, c, e):
        self.connection.privmsg(
            e.source(), "Forged Alliance Forever " + util.VERSION_STRING)

    def on_motd(self, c, e):
        self.log_event(e)
        self.nickserv_identify()

    def on_endofmotd(self, c, e):
        self.log_event(e)

    def on_namreply(self, c, e):
        self.log_event(e)
        channel = e.arguments()[1]
        listing = e.arguments()[2].split()

        for user in listing:
            name = user.strip(chat.IRC_ELEVATION)
            elevation = user[0] if user[0] in chat.IRC_ELEVATION else None
            hostname = ''
            self._add_chatter(name, hostname)
            self._add_chatter_channel(self._chatters[name], elevation, channel,
                                      False)

        logger.debug("Added " + str(len(listing)) + " Chatters")

    def _add_chatter(self, name, hostname):
        if name not in self._chatters:
            self._chatters[name] = IrcUser(name, hostname)
        else:
            self._chatters[name].update(hostname=hostname)

    def _remove_chatter(self, name):
        if name not in self._chatters:
            return
        del self._chatters[name]
        # Channels listen to 'chatter removed' signal on their own

    def _add_chatter_channel(self, chatter, elevation, channel, join):
        chatter.set_elevation(channel, elevation)
        self.channels[channel].add_chatter(chatter, join)

    def _remove_chatter_channel(self, chatter, channel, msg):
        chatter.set_elevation(channel, None)
        self.channels[channel].remove_chatter(msg)

    def on_whoisuser(self, c, e):
        self.log_event(e)

    def on_join(self, c, e):
        channel = e.target()

        # If we're joining, we need to open the channel for us first.
        if channel not in self.channels:
            newch = Channel(self, channel, self._chatters, self._me)
            if channel.lower() in self.crucialChannels:
                self.add_channel(
                    channel, newch,
                    1)  # CAVEAT: This is assumes a server tab exists.
                self.client.localBroadcast.connect(newch.print_raw)
                newch.print_announcement("Welcome to Forged Alliance Forever!",
                                         "red", "+3")
                wiki_link = Settings.get("WIKI_URL")
                wiki_msg = "Check out the wiki: {} for help with common issues.".format(
                    wiki_link)
                newch.print_announcement(wiki_msg, "white", "+1")
                newch.print_announcement("", "black", "+1")
                newch.print_announcement("", "black", "+1")
            else:
                self.add_channel(channel, newch)

            if channel.lower(
            ) in self.crucialChannels:  # Make the crucial channels not closeable, and make the last one the active one
                self.setCurrentWidget(self.channels[channel])
                self.tabBar().setTabButton(self.currentIndex(),
                                           QtWidgets.QTabBar.RightSide, None)

        name, _id, elevation, hostname = parse_irc_source(e.source())
        self._add_chatter(name, hostname)
        self._add_chatter_channel(self._chatters[name], elevation, channel,
                                  True)

    def on_part(self, c, e):
        channel = e.target()
        name = user2name(e.source())
        if name not in self._chatters:
            return
        chatter = self._chatters[name]

        if name == self.client.login:  # We left ourselves.
            self.removeTab(self.indexOf(self.channels[channel]))
            del self.channels[channel]
        else:  # Someone else left
            self._remove_chatter_channel(chatter, channel, "left.")

    def on_quit(self, c, e):
        name = user2name(e.source())
        self._remove_chatter(name)

    def on_nick(self, c, e):
        oldnick = user2name(e.source())
        newnick = e.target()
        if oldnick not in self._chatters:
            return

        self._chatters[oldnick].update(name=newnick)
        self.log_event(e)

    def on_mode(self, c, e):
        if e.target() not in self.channels:
            return
        if len(e.arguments()) < 2:
            return
        name = user2name(e.arguments()[1])
        if name not in self._chatters:
            return
        chatter = self._chatters[name]

        self.elevate_chatter(chatter, e.target(), e.arguments()[0])

    def elevate_chatter(self, chatter, channel, modes):
        add = re.compile(".*\+([a-z]+)")
        remove = re.compile(".*\-([a-z]+)")

        addmatch = re.search(add, modes)
        if addmatch:
            modes = addmatch.group(1)
            mode = None
            if "v" in modes:
                mode = "+"
            if "o" in modes:
                mode = "@"
            if "q" in modes:
                mode = "~"
            if mode is not None:
                chatter.set_elevation(channel, mode)

        removematch = re.search(remove, modes)
        if removematch:
            modes = removematch.group(1)
            el = chatter.elevation[channel]
            chatter_mode = {"@": "o", "~": "q", "+": "v"}[el]
            if chatter_mode in modes:
                chatter.set_elevation(channel, None)

    def on_umode(self, c, e):
        self.log_event(e)

    def on_notice(self, c, e):
        self.log_event(e)

    def on_topic(self, c, e):
        channel = e.target()
        if channel in self.channels:
            self.channels[channel].set_announce_text(" ".join(e.arguments()))

    def on_currenttopic(self, c, e):
        channel = e.arguments()[0]
        if channel in self.channels:
            self.channels[channel].set_announce_text(" ".join(
                e.arguments()[1:]))

    def on_topicinfo(self, c, e):
        self.log_event(e)

    def on_list(self, c, e):
        self.log_event(e)

    def on_bannedfromchan(self, c, e):
        self.log_event(e)

    def on_pubmsg(self, c, e):
        name, id, elevation, hostname = parse_irc_source(e.source())
        target = e.target()
        if name not in self._chatters or target not in self.channels:
            return

        if not self.should_ignore(self._chatters[name]):
            self.channels[target].print_msg(name, "\n".join(e.arguments()))

    def on_privnotice(self, c, e):
        source = user2name(e.source())
        notice = e.arguments()[0]
        prefix = notice.split(" ")[0]
        target = prefix.strip("[]")

        if source and source.lower() == 'nickserv':
            if notice.find("registered under your account") >= 0 or \
               notice.find("Password accepted") >= 0:
                if not self.identified:
                    self.identified = True
                    self.on_identified()

            elif notice.find("isn't registered") >= 0:
                self.nickserv_register()

            elif notice.find("RELEASE") >= 0:
                self.connection.privmsg(
                    'nickserv', 'release %s %s' %
                    (self.client.login, util.md5text(self.client.password)))

            elif notice.find("hold on") >= 0:
                self.connection.nick(self.client.login)

        message = "\n".join(e.arguments()).lstrip(prefix)
        if target in self.channels:
            self.channels[target].print_msg(source, message)
        elif source == "Global":
            for channel in self.channels:
                if not channel in self.crucialChannels:
                    continue
                self.channels[channel].print_announcement(
                    message, "yellow", "+2")
        elif source == "AeonCommander":
            for channel in self.channels:
                if not channel in self.crucialChannels:
                    continue
                self.channels[channel].print_msg(source, message)
        else:
            self.serverLogArea.appendPlainText("%s: %s" % (source, notice))

    def on_disconnect(self, c, e):
        if not self.canDisconnect:
            logger.warning("IRC disconnected - reconnecting.")
            self.serverLogArea.appendPlainText(
                "IRC disconnected - reconnecting.")
            self.identified = False
            self._timer.stop()
            self.connect(self.client.me)

    def on_privmsg(self, c, e):
        name, id, elevation, hostname = parse_irc_source(e.source())
        if name not in self._chatters:
            return
        chatter = self._chatters[name]

        if self.should_ignore(chatter):
            return

        # Create a Query if it's not open yet, and post to it if it exists.
        if self.open_query(chatter):
            self.channels[name].print_msg(name, "\n".join(e.arguments()))

    def on_action(self, c, e):
        name, id, elevation, hostname = parse_irc_source(e.source())
        if name not in self._chatters:
            return
        chatter = self._chatters[name]
        target = e.target()

        if self.should_ignore(chatter):
            return

        # Create a Query if it's not an action intended for a channel
        if target not in self.channels:
            self.open_query(chatter)
            self.channels[name].print_action(name, "\n".join(e.arguments()))
        else:
            self.channels[target].print_action(name, "\n".join(e.arguments()))

    def on_nosuchnick(self, c, e):
        self.nickserv_register()

    def on_default(self, c, e):
        self.serverLogArea.appendPlainText(
            "[%s: %s->%s]" % (e.eventtype(), e.source(), e.target()) +
            "\n".join(e.arguments()))
        if "Nickname is already in use." in "\n".join(e.arguments()):
            self.connection.nick(self.client.login + "_")

    def on_kick(self, c, e):
        pass