### # Copyright (c) 2019, cottongin # Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import pendulum import requests, json from roman_numerals import convert_to_numeral from supybot import utils, plugins, ircutils, callbacks from supybot.commands import * try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('NFL') except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x BASE_URL = "https://feeds.nfl.com/feeds-rs{}.json" def getValidDateFmt(irc, msg, args, state): date = args[0] valid = ['yesterday', 'tomorrow'] check = None try: if date.lower() in valid: if date.lower() == 'yesterday': check = pendulum.yesterday().format('MM/DD/YYYY') else: check = pendulum.tomorrow().format('MM/DD/YYYY') else: check = pendulum.parse(date, strict=False).format('MM/DD/YYYY') except: pass if not check: state.errorInvalid(_('date format'), str(date)) else: state.args.append(check) del args[0] addConverter('validDate', getValidDateFmt) class NFL(callbacks.Plugin): """Fetches scores and game information from NFL.com""" threaded = True def __init__(self, irc): super().__init__(irc) self.GOOG = irc.getCallback('Google') @wrap([getopts({"week": "positiveInt", "season": "positiveInt", "type": ("literal", ("hof","pre", "reg", "post", "pro", "sb")), "inp": "", "pro": "", "date": "validDate"}), optional("somethingWithoutSpaces")]) def nfl(self, irc, msg, args, options, team): """(--week <#> | --type
 | --inp | --date ) ()
        Fetches scores.
        """
        options = dict(options)
        inp = options.get('inp')
        seasonType = options.get('type')
        date = options.get('date') or pendulum.now().format('MM/DD/YYYY')
        week = options.get('week')
        season = options.get('season')
        gameIds = []
        network = None

        date = dict(zip(['month', 'day', 'year'], date.split('/')))
        if 1 <= int(date['month']) <= 6:
            url = BASE_URL.format(f"/schedules/{int(date['year'])-1}")
        else:
            url = BASE_URL.format(f"/schedules/{date['year']}")
        data = requests.get(url).json()
        
        if not week:
            url = BASE_URL.format('/currentWeek')
            week = requests.get(url).json()['week']
        if not season:
            url = BASE_URL.format('/currentWeek')
            season = requests.get(url).json()['seasonId']
        if not seasonType:
            url = BASE_URL.format('/currentWeek')
            tmp = requests.get(url).json()['seasonType']
            if tmp == "PRO":
                if not options.get('pro'):
                    tmp = "POST"
                    week = 22 if not week or week == 4 or week == 21 else week
            seasonType = tmp

        if options.get('date'):
            found = False
            for game in data['gameSchedules']:
                if game['gameDate'] == f"{'/'.join(date.values())}":
                    if team:
                        teams = [game['visitorTeamAbbr'], game['homeTeamAbbr']]
                        if team.upper() in teams:
                            gameIds.append(game['gameId'])
                            network = ' :: {}'.format(game['networkChannel'])
                            week = str(game['week'])
                            season = game['season']
                            seasonType = game['seasonType']
                            found = True
                            break
                    else:
                        gameIds.append(game['gameId'])
                        network = ' :: {}'.format(game['networkChannel'])
                        week = str(game['week'])
                        season = game['season']
                        seasonType = game['seasonType']
                        found = True
            if not found:
                date = '/'.join(date.values())
                irc.reply('Error: No games found on {}'.format(
                    f"{date if not team else date + ' for ' + team.upper()}"))
                return

        if seasonType.upper() in ['POST']:
            if int(week) <= 5: week += 17
        url = BASE_URL.format('/scores/{}/{}/{}'.format(
            season, seasonType.upper(), week
        ))
        try:
            scores = requests.get(url).json()['gameScores']
        except json.decoder.JSONDecodeError:
            irc.error('invalid input', Raise=True)
        except Exception as e:
            print(e)
            irc.error('something went wrong parsing data', Raise=True)

        new_scores = []
        if gameIds or team:
            for game in scores:
                if gameIds and not team:
                    if game['gameSchedule']['gameId'] in gameIds:
                        new_scores.append(game)
                if team:
                    teams = [game['gameSchedule']['visitorTeamAbbr'], game['gameSchedule']['homeTeamAbbr']]
                    if team.upper() in teams:
                        new_scores.append(game)
                        break
        else:
            new_scores = scores
                    
        week = int(week)
        if week >= 18: week -= 17
        if seasonType in ['PRE']:
            if week != 0:
                prefix = self._bold("Preseason Week {}:".format(week))
            else:
                prefix = self._bold("Hall of Fame Game:")
        elif seasonType in ['REG']:
            prefix = self._bold("Week {}:".format(week))
        else:
            prefix = self._bold("{}:")
            if new_scores[0]['gameSchedule']['gameType'] == "WC":
                prefix = prefix.format("Wildcard Weekend")
            elif new_scores[0]['gameSchedule']['gameType'] == "DIV":
                prefix = prefix.format("Divisional Round")
            elif new_scores[0]['gameSchedule']['gameType'] == "CON":
                prefix = prefix.format("Conference Finals")
            elif new_scores[0]['gameSchedule']['gameType'] == "PRO":
                prefix = prefix.format("Pro Bowl")
            elif new_scores[0]['gameSchedule']['gameType'] == "SB":
                prefix = prefix.format(f"Super Bowl {convert_to_numeral(int(season)-1965)}")
            else:
                prefix = prefix.format("Week {}".format(week))
        
        games = []
        print(new_scores)
        for game in new_scores:
            if len(new_scores) == 1:
                long_ = True
                home = "homeDisplayName"
                away = "visitorDisplayName"
                time_format = "dddd, M/D, h:mm A zz"
                sep = " :: "
                if not network:
                    for s in data['gameSchedules']:
                        if game['gameSchedule']['gameId'] == s['gameId']:
                            network = ' :: {}'.format(s['networkChannel'])
                            break
            else:
                long_ = False
                home = "homeTeamAbbr"
                away = "visitorTeamAbbr"
                time_format = "ddd h:mm A zz"
                sep = " "
                network = ''
            score = game['score']
            info = game['gameSchedule']
            time = f"{pendulum.from_timestamp(info['isoTime']/1000).in_tz('US/Eastern').format(time_format)}"
            if not score:
                string = (f"{info[away]} @ {info[home]}{sep}"
                          f"{time}{network}")
                if info['gameType'] == "SB":
                    string += f" :: {info['site']['siteFullname']}{' ({})'.format(info['site']['roofType'].title()) if info['site']['roofType'] else ''}, {info['site']['siteCity']}, {info['site']['siteState']}"
                games.append(string)
            else:
                if "FINAL" in score['phase']:
                    phase = score['phase']
                    if "OVERTIME" in phase:
                        phase = "Final/Overtime" if long_ else "F/OT"
                    else:
                        phase = "Final" if long_ else "F"
                    phase = self._color(phase, 'red')
                    h_score = score['homeTeamScore']['pointTotal']
                    v_score = score['visitorTeamScore']['pointTotal']
                    if v_score > h_score:
                        v_str = self._bold(f"{info[away]} {v_score}")
                        h_str = f"{info[home]} {h_score}"
                    elif h_score > v_score:
                        v_str = f"{info[away]} {v_score}"
                        h_str = self._bold(f"{info[home]} {h_score}")
                    else:
                        v_str = f"{info[away]} {v_score}"
                        h_str = f"{info[home]} {h_score}"
                    string = (f"{v_str} @ {h_str}{sep}{phase}")
                    if info['gameType'] == "SB":
                        string += f" :: {info['site']['siteFullname']}{' ({})'.format(info['site']['roofType'].title()) if info['site']['roofType'] else ''}, {info['site']['siteCity']}, {info['site']['siteState']}"
                    games.append(string)
                elif "PRE" in score['phase']:
                    string = (f"{info[away]} @ {info[home]}{sep}"
                              f"{time}{network}")
                    if info['gameType'] == "SB":
                        string += f" :: {info['site']['siteFullname']}{' ({})'.format(info['site']['roofType'].title()) if info['site']['roofType'] else ''}, {info['site']['siteCity']}, {info['site']['siteState']}"
                    games.append(string)
                elif "HALFTIME" in score['phase']:
                    phase = "Halftime" if long_ else "HT"
                    phase = self._color(phase, 'orange')
                    h_score = score['homeTeamScore']['pointTotal']
                    v_score = score['visitorTeamScore']['pointTotal']
                    if v_score > h_score:
                        v_str = self._bold(f"{info[away]} {v_score}")
                        h_str = f"{info[home]} {h_score}"
                    elif h_score > v_score:
                        v_str = f"{info[away]} {v_score}"
                        h_str = self._bold(f"{info[home]} {h_score}")
                    else:
                        v_str = f"{info[away]} {v_score}"
                        h_str = f"{info[home]} {h_score}"
                    string = (f"{v_str} @ {h_str}{sep}{phase}")
                    games.append(string)
                else:
                    phase = score['phaseDescription'] if long_ else score['phase']
                    phase = self._color(phase, 'green')
                    time = self._color(score['time'], 'green')
                    h_score = score['homeTeamScore']['pointTotal']
                    v_score = score['visitorTeamScore']['pointTotal']
                    if v_score > h_score:
                        v_str = self._bold(f"{info[away]} {v_score}")
                        h_str = f"{info[home]} {h_score}"
                    elif h_score > v_score:
                        v_str = f"{info[away]} {v_score}"
                        h_str = self._bold(f"{info[home]} {h_score}")
                    else:
                        v_str = f"{info[away]} {v_score}"
                        h_str = f"{info[home]} {h_score}"
                    string = (f"{v_str} @ {h_str}{sep}{time} {phase}")
                    status = None
                    try:
                        pos_team = score.get('possessionTeamAbbr')
                        at = score['yardline']
                        down = "{} and {}".format(score['down'], score['yardsToGo'])
                        status = " :: {}".format(down)
                        last_play = None
                        if pos_team:
                            status += " :: {} has the ball at {}".format(pos_team, at)
                        if len(new_scores) == 1:
                            gameId = info['gameId']
                            url = BASE_URL.format('/playbyplay/{}/latest'.format(gameId))
                            try:
                                last_play = requests.get(url).json()
                                last_play = last_play['plays'][-1]['playDescription']
                            except:
                                pass
                        if last_play:
                            status += " :: {}".format(last_play)
                    except:
                        pass
                    if status:
                        string += status
                    games.append(string)

        irc.reply(f"{prefix} {' | '.join(games)}")
        


    @wrap([ "text"])
    def nflgame(self, irc, msg, args, player):
        """
        Fetches current/previous game stats for given player.
        """
        player_id = None
        try:
            try:
                burl = "site:nfl.com {} stats".format(player.lower())
                search = self.GOOG.search('{0}'.format(burl),'#reddit-nfl',{'smallsearch': True})
                search = self.GOOG.decode(search)
                if search:
                    url = search[0]['url']
                    print(url)
                    player_id = url.split('/')[-2]
                    player_id = player_id.replace('-', ' ')
                    print(player_id)
                
            except:
                self.log.exception("ERROR :: NFL :: failed to get link for {0}".format(burl))
                pass
        except Exception as e:
            self.log.info("ERROR :: NFL :: {0}".format(e))
            pass
        
        if not player_id:
            irc.reply('ERROR: Could not find a player id for {}'.format(player))
            return
        
        endpoint = '/playerGameStats/{}'.format(player_id)
        data = requests.get(BASE_URL.format(endpoint)).json()
        game_stats = data['playerGameStats']
        player_info = data['teamPlayer']

        if not game_stats:
            irc.reply("I couln't find any current or previous game stats for {}".format(player_info['displayName']))
            return
        
        recent = game_stats[-1]
        
        name = (f"{self._bold(self._color(player_info['displayName'], 'red'))}"
                f" (#{player_info['jerseyNumber']} {player_info['position']})"
                f" [{player_info['yearsOfExperience']}yrs exp]"
                f" :: {player_info['teamFullName']}")
        
        game_time = recent['gameSchedule']['isoTime'] / 1000
        info = (f"{recent['gameSchedule']['visitorTeamAbbr']} "
                f"{recent['score']['visitorTeamScore']['pointTotal']} @ "
                f"{recent['gameSchedule']['homeTeamAbbr']} "
                f"{recent['score']['homeTeamScore']['pointTotal']} - "
                f"{pendulum.from_timestamp(game_time).in_tz('US/Eastern').format('ddd MM/DD h:mm A zz')}")

        if player_info['positionGroup'] == 'QB':
            #passing, rush, fumble
            tmp = recent['playerPassingStat']
            stats = [(f"{self._ul('Passing')}: {self._bold('Comp')} {tmp.get('passingCompletions', '-')} "
                      f"{self._bold('Att')} {tmp.get('passingAttempts', '-')} "
                      f"{self._bold('Pct')} {tmp.get('passingCompletionPercentage', '-')} "
                      f"{self._bold('Yds')} {tmp.get('passingYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('passingYardsPerAttempts', '-')} "
                      f"{self._bold('TD')} {tmp.get('passingTouchdowns', '-')} "
                      f"{self._bold('Int')} {tmp.get('passingInterceptions', '-')} "
                      f"{self._bold('Sck')} {tmp.get('passingSacked', '-')} "
                      f"{self._bold('SckY')} {tmp.get('passingSackedYardsLost', '-')} "
                      f"{self._bold('Rate')} {tmp.get('passingRating', '-')}")]
            tmp = recent['playerRushingStat']
            line2 = []
            line2.append(
                     (f"{self._ul('Rushing')}: {self._bold('Att')} {tmp.get('rushingAttempts', '-')} "
                      f"{self._bold('Yds')} {tmp.get('rushingYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('rushingYardsPerAttempt', '-')} "
                      f"{self._bold('TD')} {tmp.get('rushingTouchdowns', '-')}"))
            tmp = recent['playerFumbleStat']
            line2.append(
                     (f"{self._ul('Fumbles')}: {self._bold('Fum')} {tmp.get('fumbles', '-')} "
                      f"{self._bold('Lst')} {tmp.get('fumblesLost', '-')}"))
            stats.append(' :: '.join(line2))
        elif player_info['positionGroup'] == 'RB':
            #rush, recev, fumble
            line1 = []
            line2 = []
            stats = []
            tmp = recent['playerRushingStat']
            line1 = [(f"{self._ul('Rushing')}: {self._bold('Att')} {tmp.get('rushingAttempts', '-')} "
                      f"{self._bold('Att')} {tmp.get('rushingYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('rushingYardsPerAttempt', '-')} "
                      f"{self._bold('Lng')} {tmp.get('rushingLong', '-')} "
                      f"{self._bold('TD')} {tmp.get('rushingTouchdowns', '-')}")] if tmp else []
            tmp = recent['playerReceivingStat']
            if tmp: line1.append(
                     (f"{self._ul('Receiving')}: {self._bold('Rec')} {tmp.get('receivingReceptions', '-')} "
                      f"{self._bold('Yds')} {tmp.get('receivingYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('receivingYardsPerReception', '-')} "
                      f"{self._bold('Lng')} {tmp.get('receivingLong', '-')} "
                      f"{self._bold('TD')} {tmp.get('receivingTouchdowns', '-')}"))
            tmp = recent['playerFumbleStat']
            line2.append(
                     (f"{self._ul('Fumbles')}: {self._bold('Fum')} {tmp.get('fumbles', '-')} "
                      f"{self._bold('Lst')} {tmp.get('fumblesLost', '-')}"))
            if len(line1) == 1 and len(line2) == 1:
                stats.append('{} :: {}'.format(line1[0], line2[0]))
            else:
                if line1: stats.append(' :: '.join(line1))
                if line2: stats.append(' :: '.join(line2))
        elif player_info['positionGroup'] in ['WR', 'TE']:
            #recv, rush, fumble
            line1 = []
            line2 = []
            stats = []
            tmp = recent['playerReceivingStat']
            line1 = [(f"{self._ul('Receiving')}: {self._bold('Rec')} {tmp.get('receivingReceptions', '-')} "
                      f"{self._bold('Yds')} {tmp.get('receivingYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('receivingYardsPerReception', '-')} "
                      f"{self._bold('Lng')} {tmp.get('receivingLong', '-')} "
                      f"{self._bold('TD')} {tmp.get('receivingTouchdowns', '-')}")] if tmp else []
            tmp = recent['playerRushingStat']
            if tmp: line1.append(
                     (f"{self._ul('Rushing')}: {self._bold('Att')} {tmp.get('rushingAttempts', '-')} "
                      f"{self._bold('Att')} {tmp.get('rushingYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('rushingYardsPerAttempt', '-')} "
                      f"{self._bold('Lng')} {tmp.get('rushingLong', '-')} "
                      f"{self._bold('TD')} {tmp.get('rushingTouchdowns', '-')}"))
            tmp = recent['playerFumbleStat']
            line2.append(
                     (f"{self._ul('Fumbles')}: {self._bold('Fum')} {tmp.get('fumbles', '-')} "
                      f"{self._bold('Lst')} {tmp.get('fumblesLost', '-')}"))
            if len(line1) == 1 and len(line2) == 1:
                stats.append('{} :: {}'.format(line1[0], line2[0]))
            else:
                if line1: stats.append(' :: '.join(line1))
                if line2: stats.append(' :: '.join(line2))
        elif player_info['position'] == 'K':
            #overall fg, pats, koffs
            line1 = []
            line2 = []
            stats = []
            tmp = recent['playerKickingStat']
            line1 = [(f"{self._ul('Field Goals')}: {self._bold('FG Att')} {tmp.get('kickingFgAttempts', '-')} "
                      f"{self._bold('FGM')} {tmp.get('kickingFgMade', '-')} "
                      f"{self._bold('Pct')} {tmp.get('kickingFgPercentage', '-')} "
                      f"{self._bold('Lng')} {tmp.get('kickingFgLong', '-')} "
                      f"{self._bold('Blk')} {tmp.get('kickingFgBlocked', '-')}")] if tmp else []
            if tmp: line1.append(
                     (f"{self._ul('PATs')}: {self._bold('XP Att')} {tmp.get('kickingXkAttempts', '-')} "
                      f"{self._bold('XPM')} {tmp.get('kickingXkMade', '-')} "
                      f"{self._bold('Pct')} {tmp.get('kickingXkPercentage', '-')} "
                      f"{self._bold('Blk')} {tmp.get('kickingXkBlocked', '-')} "))
            line2.append(
                     (f"{self._ul('Kickoffs')}: {self._bold('KO')} {tmp.get('kickoffs', '-')} "
                      f"{self._bold('Avg')} {tmp.get('kickoffAverageYards', '-')} "
                      f"{self._bold('TB')} {tmp.get('kickoffTouchbacks', '-')} "
                      f"{self._bold('Ret')} {tmp.get('kickoffReturns', '-')} "
                      f"{self._bold('Avg')} {tmp.get('kickoffReturnsAverageYards', '-  ')}"))
            if len(line1) == 1 and len(line2) == 1:
                stats.append('{} :: {}'.format(line1[0], line2[0]))
            else:
                if line1: stats.append(' :: '.join(line1))
                if line2: stats.append(' :: '.join(line2))
        elif player_info['positionGroup'] in ['LB', 'DB', 'DL']:
            #defense
            line1 = []
            line2 = []
            stats = []
            tmp = recent['playerDefensiveStat']
            line1 = [(f"{self._ul('Tackles')}: {self._bold('Comb')} {tmp.get('defensiveCombineTackles', '-')} "
                      f"{self._bold('Total')} {tmp.get('defensiveTotalTackles', '-')} "
                      f"{self._bold('Ast')} {tmp.get('defensiveAssist', '-')} "
                      f"{self._bold('Sck')} {tmp.get('defensiveSacks', '-')} "
                      f"{self._bold('SFTY')} {tmp.get('defensiveSafeties', '-')}")] if tmp else []
            if tmp: line1.append(
                     (f"{self._ul('Interceptions')}: {self._bold('PDef')} {tmp.get('defensivePassesDefensed', '-')} "
                      f"{self._bold('Int')} {tmp.get('defensiveInterceptions', '-')} "
                      f"{self._bold('Yds')} {tmp.get('defensiveInterceptionYards', '-')} "
                      f"{self._bold('Avg')} {tmp.get('defensiveInterceptionsAvgyds', '-')} "
                      f"{self._bold('Lng')} {tmp.get('defensiveInterceptionsLong', '-')} "
                      f"{self._bold('TDs')} {tmp.get('defensiveInterceptionsTds', '-')} "))
            line2.append(
                     (f"{self._ul('Fumbles')}: {self._bold('FF')} {tmp.get('kickoffs', '-')} "))
            if len(line1) == 1 and len(line2) == 1:
                stats.append('{} :: {}'.format(line1[0], line2[0]))
            else:
                if line1: stats.append(' :: '.join(line1))
                if line2: stats.append(' :: '.join(line2))
        elif player_info['position'] == 'P':
            line1 = []
            stats = []
            tmp = recent['playerPuntingStat']
            line1 = [(f"{self._ul('Punting')}: {self._bold('Punts')} {tmp.get('puntingPunts', '-')} "
                      f"{self._bold('Yds')} {tmp.get('puntingYards', '-')} "
                      f"{self._bold('Net Yds')} {tmp.get('puntingNetYardage', '-')} "
                      f"{self._bold('Lng')} {tmp.get('puntingLong', '-')} "
                      f"{self._bold('Avg')} {tmp.get('puntingAverageYards', '-')} "
                      f"{self._bold('Net Avg')} {tmp.get('puntingNetAverage', '-')} "
                      f"{self._bold('Blk')} {tmp.get('puntingBlocked', '-')} "
                      f"{self._bold('OOB')} {tmp.get('puntingOutOfBounds', '-')} "
                      f"{self._bold('Dn')} {tmp.get('puntingDowned', '-')} "
                      f"{self._bold('In 20')} {tmp.get('puntingPuntsInside20', '-')} "
                      f"{self._bold('TB')} {tmp.get('puntingTouchbacks', '-')} "
                      f"{self._bold('FC')} {tmp.get('puntingPuntsFairCaught', '-')} "
                      f"{self._bold('Ret')} {tmp.get('puntingNumberReturned', '-')} "
                      f"{self._bold('RetY')} {tmp.get('puntingReturnYards', '-')} "
                      f"{self._bold('TD')} {tmp.get('puntingReturnTouchdowns', '-')}")] if tmp else []
            stats.append(' :: '.join(line1))
        else:
            stats = ["No stats found"]
        
        strings = [f"{name} :: {info}"]
        
        for string in strings:
            irc.reply(string)
        for stat in stats:
            irc.reply(stat)

    def _color(self, string, color):
        return ircutils.mircColor(string, color)

    def _bold(self, string):
        return ircutils.bold(string)

    def _ul(self, string):
        return ircutils.underline(string)        
        

Class = NFL


# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: