From 07e59a1188a7d6302c033191a5ed7b489e437e50 Mon Sep 17 00:00:00 2001 From: <> Date: Sat, 30 Mar 2024 19:42:30 +0000 Subject: [PATCH] Update documentation --- .buildinfo | 4 + .nojekyll | 0 _modules/cogs/commands/analyze.html | 498 +++++ _modules/cogs/commands/earnings.html | 501 +++++ _modules/cogs/commands/help.html | 547 +++++ _modules/cogs/commands/portfolio.html | 621 ++++++ _modules/cogs/commands/sentiment.html | 614 ++++++ _modules/cogs/commands/stock.html | 725 +++++++ _modules/cogs/listeners/on_member_join.html | 437 ++++ .../cogs/listeners/on_raw_reaction_add.html | 606 ++++++ _modules/cogs/loops/assets.html | 795 ++++++++ _modules/cogs/loops/earnings_overview.html | 550 +++++ _modules/cogs/loops/events.html | 744 +++++++ _modules/cogs/loops/funding.html | 536 +++++ _modules/cogs/loops/gainers.html | 567 ++++++ _modules/cogs/loops/ideas.html | 756 +++++++ _modules/cogs/loops/index.html | 727 +++++++ _modules/cogs/loops/liquidations.html | 610 ++++++ _modules/cogs/loops/losers.html | 474 +++++ _modules/cogs/loops/new_listings.html | 602 ++++++ _modules/cogs/loops/nfts.html | 909 +++++++++ _modules/cogs/loops/option_alert.html | 711 +++++++ _modules/cogs/loops/options.html | 615 ++++++ _modules/cogs/loops/overview.html | 641 ++++++ _modules/cogs/loops/reddit.html | 623 ++++++ _modules/cogs/loops/stock_halts.html | 581 ++++++ _modules/cogs/loops/stocktwits.html | 556 +++++ _modules/cogs/loops/timeline.html | 809 ++++++++ _modules/cogs/loops/trades.html | 538 +++++ _modules/cogs/loops/trending.html | 609 ++++++ _modules/cogs/loops/yield.html | 604 ++++++ _modules/discord/ext/tasks.html | 1202 +++++++++++ _modules/index.html | 453 +++++ _modules/main.html | 537 +++++ _modules/util/afterhours.html | 459 +++++ _modules/util/cg_data.html | 797 ++++++++ _modules/util/confirm_stock.html | 466 +++++ _modules/util/db.html | 752 +++++++ _modules/util/disc_util.html | 604 ++++++ _modules/util/earnings_scraper.html | 491 +++++ _modules/util/exchange_data.html | 585 ++++++ _modules/util/formatting.html | 655 ++++++ _modules/util/get_tweet.html | 468 +++++ _modules/util/parse_tweet.html | 648 ++++++ _modules/util/sentiment_analyis.html | 518 +++++ _modules/util/ticker_classifier.html | 626 ++++++ _modules/util/trades_msg.html | 620 ++++++ _modules/util/tv_data.html | 817 ++++++++ _modules/util/tweet_embed.html | 818 ++++++++ _modules/util/vars.html | 587 ++++++ _modules/util/yf_data.html | 529 +++++ _sources/cogs.commands.rst.txt | 61 + _sources/cogs.listeners.rst.txt | 29 + _sources/cogs.loops.rst.txt | 181 ++ _sources/cogs.rst.txt | 20 + _sources/index.rst.txt | 20 + _sources/main.rst.txt | 7 + _sources/modules.rst.txt | 13 + _sources/util.rst.txt | 157 ++ _static/basic.css | 925 +++++++++ _static/doctools.js | 156 ++ _static/documentation_options.js | 13 + _static/file.png | Bin 0 -> 286 bytes _static/fintwit-nobg.ico | Bin 0 -> 125222 bytes _static/fintwit.png | Bin 0 -> 254738 bytes _static/graphviz.css | 19 + _static/language_data.js | 199 ++ _static/minus.png | Bin 0 -> 90 bytes _static/plus.png | Bin 0 -> 90 bytes _static/pygments.css | 152 ++ _static/scripts/bootstrap.js | 3 + _static/scripts/bootstrap.js.LICENSE.txt | 5 + _static/scripts/bootstrap.js.map | 1 + _static/scripts/pydata-sphinx-theme.js | 2 + _static/scripts/pydata-sphinx-theme.js.map | 1 + _static/searchtools.js | 574 ++++++ _static/sphinx_highlight.js | 154 ++ _static/styles/bootstrap.css | 6 + _static/styles/bootstrap.css.map | 1 + _static/styles/pydata-sphinx-theme.css | 2 + _static/styles/pydata-sphinx-theme.css.map | 1 + _static/styles/theme.css | 2 + _static/vendor/fontawesome/6.1.2/LICENSE.txt | 165 ++ .../vendor/fontawesome/6.1.2/css/all.min.css | 5 + .../6.1.2/webfonts/fa-brands-400.ttf | Bin 0 -> 181264 bytes .../6.1.2/webfonts/fa-brands-400.woff2 | Bin 0 -> 105112 bytes .../6.1.2/webfonts/fa-regular-400.ttf | Bin 0 -> 60236 bytes .../6.1.2/webfonts/fa-regular-400.woff2 | Bin 0 -> 24028 bytes .../6.1.2/webfonts/fa-solid-900.ttf | Bin 0 -> 389948 bytes .../6.1.2/webfonts/fa-solid-900.woff2 | Bin 0 -> 154840 bytes .../6.1.2/webfonts/fa-v4compatibility.ttf | Bin 0 -> 10084 bytes .../6.1.2/webfonts/fa-v4compatibility.woff2 | Bin 0 -> 4776 bytes _static/vendor/fontawesome/6.5.1/LICENSE.txt | 165 ++ .../vendor/fontawesome/6.5.1/css/all.min.css | 5 + .../vendor/fontawesome/6.5.1/js/all.min.js | 2 + .../6.5.1/js/all.min.js.LICENSE.txt | 5 + .../6.5.1/webfonts/fa-brands-400.ttf | Bin 0 -> 207972 bytes .../6.5.1/webfonts/fa-brands-400.woff2 | Bin 0 -> 117372 bytes .../6.5.1/webfonts/fa-regular-400.ttf | Bin 0 -> 68004 bytes .../6.5.1/webfonts/fa-regular-400.woff2 | Bin 0 -> 25452 bytes .../6.5.1/webfonts/fa-solid-900.ttf | Bin 0 -> 419720 bytes .../6.5.1/webfonts/fa-solid-900.woff2 | Bin 0 -> 156496 bytes .../6.5.1/webfonts/fa-v4compatibility.ttf | Bin 0 -> 10832 bytes .../6.5.1/webfonts/fa-v4compatibility.woff2 | Bin 0 -> 4792 bytes _static/webpack-macros.html | 31 + cogs.commands.html | 604 ++++++ cogs.html | 737 +++++++ cogs.listeners.html | 608 ++++++ cogs.loops.html | 1601 +++++++++++++++ genindex.html | 1374 +++++++++++++ index.html | 449 +++++ main.html | 512 +++++ modules.html | 914 +++++++++ objects.inv | Bin 0 -> 2227 bytes py-modindex.html | 648 ++++++ search.html | 413 ++++ searchindex.js | 1 + util.html | 1786 +++++++++++++++++ 118 files changed, 43471 insertions(+) create mode 100644 .buildinfo create mode 100644 .nojekyll create mode 100644 _modules/cogs/commands/analyze.html create mode 100644 _modules/cogs/commands/earnings.html create mode 100644 _modules/cogs/commands/help.html create mode 100644 _modules/cogs/commands/portfolio.html create mode 100644 _modules/cogs/commands/sentiment.html create mode 100644 _modules/cogs/commands/stock.html create mode 100644 _modules/cogs/listeners/on_member_join.html create mode 100644 _modules/cogs/listeners/on_raw_reaction_add.html create mode 100644 _modules/cogs/loops/assets.html create mode 100644 _modules/cogs/loops/earnings_overview.html create mode 100644 _modules/cogs/loops/events.html create mode 100644 _modules/cogs/loops/funding.html create mode 100644 _modules/cogs/loops/gainers.html create mode 100644 _modules/cogs/loops/ideas.html create mode 100644 _modules/cogs/loops/index.html create mode 100644 _modules/cogs/loops/liquidations.html create mode 100644 _modules/cogs/loops/losers.html create mode 100644 _modules/cogs/loops/new_listings.html create mode 100644 _modules/cogs/loops/nfts.html create mode 100644 _modules/cogs/loops/option_alert.html create mode 100644 _modules/cogs/loops/options.html create mode 100644 _modules/cogs/loops/overview.html create mode 100644 _modules/cogs/loops/reddit.html create mode 100644 _modules/cogs/loops/stock_halts.html create mode 100644 _modules/cogs/loops/stocktwits.html create mode 100644 _modules/cogs/loops/timeline.html create mode 100644 _modules/cogs/loops/trades.html create mode 100644 _modules/cogs/loops/trending.html create mode 100644 _modules/cogs/loops/yield.html create mode 100644 _modules/discord/ext/tasks.html create mode 100644 _modules/index.html create mode 100644 _modules/main.html create mode 100644 _modules/util/afterhours.html create mode 100644 _modules/util/cg_data.html create mode 100644 _modules/util/confirm_stock.html create mode 100644 _modules/util/db.html create mode 100644 _modules/util/disc_util.html create mode 100644 _modules/util/earnings_scraper.html create mode 100644 _modules/util/exchange_data.html create mode 100644 _modules/util/formatting.html create mode 100644 _modules/util/get_tweet.html create mode 100644 _modules/util/parse_tweet.html create mode 100644 _modules/util/sentiment_analyis.html create mode 100644 _modules/util/ticker_classifier.html create mode 100644 _modules/util/trades_msg.html create mode 100644 _modules/util/tv_data.html create mode 100644 _modules/util/tweet_embed.html create mode 100644 _modules/util/vars.html create mode 100644 _modules/util/yf_data.html create mode 100644 _sources/cogs.commands.rst.txt create mode 100644 _sources/cogs.listeners.rst.txt create mode 100644 _sources/cogs.loops.rst.txt create mode 100644 _sources/cogs.rst.txt create mode 100644 _sources/index.rst.txt create mode 100644 _sources/main.rst.txt create mode 100644 _sources/modules.rst.txt create mode 100644 _sources/util.rst.txt create mode 100644 _static/basic.css create mode 100644 _static/doctools.js create mode 100644 _static/documentation_options.js create mode 100644 _static/file.png create mode 100644 _static/fintwit-nobg.ico create mode 100644 _static/fintwit.png create mode 100644 _static/graphviz.css create mode 100644 _static/language_data.js create mode 100644 _static/minus.png create mode 100644 _static/plus.png create mode 100644 _static/pygments.css create mode 100644 _static/scripts/bootstrap.js create mode 100644 _static/scripts/bootstrap.js.LICENSE.txt create mode 100644 _static/scripts/bootstrap.js.map create mode 100644 _static/scripts/pydata-sphinx-theme.js create mode 100644 _static/scripts/pydata-sphinx-theme.js.map create mode 100644 _static/searchtools.js create mode 100644 _static/sphinx_highlight.js create mode 100644 _static/styles/bootstrap.css create mode 100644 _static/styles/bootstrap.css.map create mode 100644 _static/styles/pydata-sphinx-theme.css create mode 100644 _static/styles/pydata-sphinx-theme.css.map create mode 100644 _static/styles/theme.css create mode 100644 _static/vendor/fontawesome/6.1.2/LICENSE.txt create mode 100644 _static/vendor/fontawesome/6.1.2/css/all.min.css create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-brands-400.ttf create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-brands-400.woff2 create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-regular-400.ttf create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-regular-400.woff2 create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-solid-900.ttf create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-solid-900.woff2 create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-v4compatibility.ttf create mode 100644 _static/vendor/fontawesome/6.1.2/webfonts/fa-v4compatibility.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/LICENSE.txt create mode 100644 _static/vendor/fontawesome/6.5.1/css/all.min.css create mode 100644 _static/vendor/fontawesome/6.5.1/js/all.min.js create mode 100644 _static/vendor/fontawesome/6.5.1/js/all.min.js.LICENSE.txt create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-brands-400.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-brands-400.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-regular-400.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-regular-400.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-solid-900.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-solid-900.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-v4compatibility.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-v4compatibility.woff2 create mode 100644 _static/webpack-macros.html create mode 100644 cogs.commands.html create mode 100644 cogs.html create mode 100644 cogs.listeners.html create mode 100644 cogs.loops.html create mode 100644 genindex.html create mode 100644 index.html create mode 100644 main.html create mode 100644 modules.html create mode 100644 objects.inv create mode 100644 py-modindex.html create mode 100644 search.html create mode 100644 searchindex.js create mode 100644 util.html diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..1b292b5b --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: f4c6a53cb60d3ac97ff4cb03e08c0f39 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_modules/cogs/commands/analyze.html b/_modules/cogs/commands/analyze.html new file mode 100644 index 00000000..04afb22c --- /dev/null +++ b/_modules/cogs/commands/analyze.html @@ -0,0 +1,498 @@ + + + + + + + + + + cogs.commands.analyze — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.commands.analyze

+## > Imports
+# > Standard Library
+import datetime
+
+# > 3rd Party Dependencies
+from bs4 import BeautifulSoup
+
+# > Discord imports
+import discord
+from discord.ext import commands
+from discord.commands import Option
+
+# > Local dependencies
+from util.vars import get_json_data
+
+
+
+[docs] +class Analyze(commands.Cog): + """ + This class is used to handle the analyze command. + You can enable / disable this command in the config, under ["COMMANDS"]["ANALYZE"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + @commands.slash_command( + description="Request the current analysis for a stock ticker." + ) + async def analyze( + self, + ctx: commands.Context, + stock: Option(str, description="Stock ticker, e.g. AAPL.", required=True), + ) -> None: + """ + The analyze command is used to get the current analyst ratings for a stock ticker from benzinga.com. + + Parameters + ---------- + ctx : commands.Context + Discord context object. + stock : Option, optional + The ticker of a stock, e.g. AAPL + """ + + await ctx.response.defer(ephemeral=True) + + req = await get_json_data( + f"https://www.benzinga.com/quote/{stock}/analyst-ratings", text=True + ) + + soup = BeautifulSoup(req, "html.parser") + + tables = soup.find_all("tbody") + table = tables[1] + + headers = ["Buy", "Overweight", "Hold", "Underweight", "Sell"] + + data = [] + for row in table.find_all("td"): + data.append(row.text) + + e = discord.Embed( + title=f"{stock.upper()} Analysist Rating Summary ", + url=f"https://www.benzinga.com/quote/{stock}/analyst-ratings", + description="", + color=0x1F7FC1, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.set_footer( + text="\u200b", + icon_url="https://www.benzinga.com/next-assets/images/apple-touch-icon.png", + ) + + for i in range(len(headers)): + e.add_field(name=headers[i], value=data[i], inline=True) + + await ctx.respond(embed=e)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Analyze(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/commands/earnings.html b/_modules/cogs/commands/earnings.html new file mode 100644 index 00000000..5e437b82 --- /dev/null +++ b/_modules/cogs/commands/earnings.html @@ -0,0 +1,501 @@ + + + + + + + + + + cogs.commands.earnings — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.commands.earnings

+##> Imports
+# > 3rd Party Dependencies
+from discord.ext import commands
+from discord.commands import Option
+
+# Local dependencies
+from util.earnings_scraper import YahooEarningsCalendar
+from util.confirm_stock import confirm_stock
+
+
+
+[docs] +class Earnings(commands.Cog): + """ + This class is used to handle the earnings command. + You can enable / disable this command in the config, under ["COMMANDS"]["EARNINGS"]. + """ + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.slash_command( + name="earnings", description="Gets next earnings date for a given stock." + ) + async def earnings( + self, + ctx: commands.Context, + stock: Option(str, description="The requested stock.", required=True), + ): + """ + Gets next earnings date for a given stock. + For instance `/earnings AAPL` will return the next earnings date for Apple. + + Parameters + ---------- + ctx : commands.Context + Necessary Discord context object. + stock : str + The stock ticker to get the earnings date for. + + Raises + ------ + commands.UserInputError + If the provided stock ticker is not valid. + """ + + if input: + # Check if this stock exists + if not await confirm_stock(self.bot, ctx, stock): + return + + next_earnings = YahooEarningsCalendar().get_next_earnings_date(stock) + msg = ( + f"The next earnings date for {stock.upper()} is <t:{next_earnings}:R>." + ) + await ctx.respond(msg) + else: + raise commands.UserInputError() + +
+[docs] + @earnings.error + async def earnings_error(self, ctx: commands.context.Context, error: Exception): + """ + Catches the errors when using the `!earnings` command. + + Parameters + ---------- + ctx : commands.Context + Necessary Discord context object. + error : Exception + The exception that was raised when using the `!earnings` command. + """ + print(error) + if isinstance(error, commands.UserInputError): + await ctx.send( + f"{ctx.author.mention} You must specify a stock to request the next earnings of!" + ) + else: + await ctx.send( + f"{ctx.author.mention} An error has occurred. Please try again later." + )
+
+ + + +def setup(bot: commands.Bot): + bot.add_cog(Earnings(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/commands/help.html b/_modules/cogs/commands/help.html new file mode 100644 index 00000000..205c8fc6 --- /dev/null +++ b/_modules/cogs/commands/help.html @@ -0,0 +1,547 @@ + + + + + + + + + + cogs.commands.help — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.commands.help

+##> Imports
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.commands import Option
+
+from util.disc_util import get_guild
+
+
+
+[docs] +class Help(commands.Cog): + """ + Custom help command. + """ + + def __init__(self, bot): + self.bot = bot + self.cmd_dict = {} + self.guild = None + + @commands.slash_command(description="Receive information about a command.") + async def help( + self, + ctx: commands.Context, + command: Option(str, description="Command to get help for.", required=False), + ): + """ + Receive information about a command or channel + Usage: `/help <command>` + List all commands available to you. If you want more information about a specific command, simply type that command after `/help`. + + + Parameters + ---------- + ctx : commands.Context + The context of the command. + command : Option, optional + A specific command, by default it will show all commands, required=False) + """ + await ctx.response.defer(ephemeral=True) + + if self.cmd_dict == {}: + self.get_cmd_dict() + + if not self.guild: + self.guild = get_guild(self.bot) + + # List all commands + if not command: + e = discord.Embed( + title="Available commands", + color=self.guild.self_role.color, + description="Use `/help <command>` to get more information about a command!", + ) + + cmd_mentions = [] + cmd_descs = [] + + for v in list(self.cmd_dict.values()): + cmd_mentions.append(v[0]) + cmd_descs.append(v[1]) + + e.add_field(name="Commands", value="\n".join(cmd_mentions), inline=True) + e.add_field(name="Description", value="\n".join(cmd_descs), inline=True) + + await ctx.respond(embed=e) + else: + command = command.lower() + + if command in self.cmd_dict.keys(): + e = discord.Embed( + title=f"The {command} command", + color=self.guild.self_role.color, + description="", + ) + + options = [] + for option in self.cmd_dict[command][2]: + options.append(f"**{option.name}**: {option.description}") + + e.add_field(name="Command", value=self.cmd_dict[command][0]) + e.add_field(name="Description", value=self.cmd_dict[command][1]) + e.add_field(name="Parameters", value="\n".join(options)) + await ctx.respond(embed=e) + else: + await ctx.respond(f"The {command} command was not found.") + +
+[docs] + def get_cmd_dict(self): + self.cmd_dict = {} + + # Iterate through all commands + for _, cog in self.bot.cogs.items(): + commands = cog.get_commands() + # https://docs.pycord.dev/en/stable/api.html?highlight=slashcommand#discord.SlashCommand + for command in commands: + # https://docs.pycord.dev/en/stable/api.html?highlight=slashcommand#slashcommandgroup + if type(command) == discord.SlashCommandGroup: + for subcommand in command.walk_commands(): + self.cmd_dict[f"{command.name} {subcommand.name}"] = [ + subcommand.mention, + subcommand.description, + subcommand.options, + ] + + elif type(command) == discord.SlashCommand: + self.cmd_dict[command.name] = [ + command.mention, + command.description, + command.options, + ]
+
+ + + # @help.error + # async def help_error(self, ctx, error): + # if isinstance(error, commands.UserInputError): + # await ctx.send( + # f"Too many arguments given. Correct usage of this command: `!help [command]`." + # ) + # elif isinstance(error, commands.CommandNotFound): + # e = discord.Embed( + # title="Help", + # color=0x00FFFF, + # description="This command could not be found... Try `!help` to list all available commands.", + # ) + # await ctx.send(embed=e) + # else: + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Help(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/commands/portfolio.html b/_modules/cogs/commands/portfolio.html new file mode 100644 index 00000000..7553d6f2 --- /dev/null +++ b/_modules/cogs/commands/portfolio.html @@ -0,0 +1,621 @@ + + + + + + + + + + cogs.commands.portfolio — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.commands.portfolio

+# > 3rd Party Dependencies
+import pandas as pd
+import ccxt
+
+# > Discord dependencies
+from discord.ext import commands
+from discord.commands import SlashCommandGroup, Option
+
+# Local dependencies
+import util.vars
+from util.db import update_db
+from cogs.loops.trades import Trades
+from cogs.loops.assets import Assets
+
+
+
+[docs] +class Portfolio(commands.Cog): + """ + This class is used to handle the portfolio command. + You can enable / disable this command in the config, under ["COMMANDS"]["PORTFOLIO"]. + """ + + # Create a slash command group + portfolios = SlashCommandGroup("portfolio", description="Manage your portfolio.") + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + +
+[docs] + def update_portfolio_db(self, new_db): + # Set the new portfolio so other functions can access it + util.vars.portfolio_db = new_db + + # Write to SQL database + update_db(new_db, "portfolio")
+ + + @commands.dm_only() + @portfolios.command(name="add", description="Add a portfolio to the database.") + async def add( + self, + ctx: commands.Context, + input: Option( + str, + description="Provide the follow information: <exchange> <key> <secret> (<passphrase>).", + required=True, + ), + ) -> None: + """ + Adds your portfolio to the database. + + Usage: + `/portfolio add <exchange> <key> <secret> (<passphrase>)` to add your portfolio to the database. + + Parameters + ---------- + ctx : commands.context.Context + The context of the command, for instance the user who used it. + input : tuple + The information specified after `!portfolio`. + """ + + # Split the input using the spaces + input = input.split(" ") + + if len(input) < 3 or len(input) > 4: + raise commands.UserInputError() + + exchange = input[0] + key = input[1] + secret = input[2] + passphrase = None + + # Check if the exchange is supported + if exchange.lower() not in ["binance", "kucoin"]: + raise commands.BadArgument() + + # Set the passphrase if necessary + if exchange.lower() == "kucoin": + if len(input) == 4: + passphrase = input[3] + else: + raise commands.BadArgument() + + if exchange.lower() == "binance": + if len(input) != 3: + raise commands.BadArgument() + + exchange = ccxt.binance({"apiKey": key, "secret": secret}) + + new_data = pd.DataFrame( + { + "id": ctx.author.id, + "user": ctx.author.name, + "exchange": exchange.lower(), + "key": key, + "secret": secret, + "passphrase": passphrase, + }, + index=[0], + ) + + # Before adding the portfolio, check if it already exists + + # Check if the API keys are valid + print(exchange.fetch_status()) + + # Update the databse + util.vars.portfolio_db = pd.concat( + [util.vars.portfolio_db, new_data], ignore_index=True + ) + update_db(util.vars.portfolio_db, "portfolio") + + await ctx.respond( + "Succesfully added your portfolio to the database!\nPlease ensure that you set the API for read-only access." + ) + + # Init Exchanges to start websockets + Trades(self.bot, new_data) + # Post the assets + Assets(self.bot, new_data) + + @portfolios.command( + name="remove", description="Remove a portfolio to the database." + ) + async def remove( + self, + ctx: commands.Context, + exchange: Option( + str, + description="The name of the exchange that you want to remove, if left empty all will be removed.", + required=True, + ), + ) -> None: + """ + `/portfolio remove (<exchange>)` if exchange is not specified, all your portfolio(s) will be removed. + """ + + old_db = util.vars.portfolio_db + if exchange: + rows = old_db.index[ + (old_db["id"] == ctx.author.id) & (old_db["exchange"] == exchange) + ].tolist() + else: + rows = old_db.index[old_db["id"] == ctx.author.id].tolist() + + # Update database + self.update_portfolio_db(old_db.drop(rows)) + + await ctx.respond("Succesfully removed your portfolio from the database!") + + # Maybe unsubscribe from websockets + + @commands.dm_only() + @portfolios.command( + name="show", description="Show the portfolio(s) in the database." + ) + async def show( + self, + ctx: commands.Context, + ) -> None: + """ + `/portfolio show` to show your portfolio(s) in our database. + """ + + db = util.vars.portfolio_db + rows = db.loc[db["id"] == ctx.author.id] + if not rows.empty: + for _, row in rows.iterrows(): + await ctx.respond( + f"Exchange: {row['exchange']} \nKey: {row['key']} \nSecret: {row['secret']}" + ) + else: + await ctx.respond("Your portfolio could not be found") + +
+[docs] + @add.error + async def add_error(self, ctx: commands.Context, error: Exception) -> None: + # print(traceback.format_exc()) + if isinstance(error, commands.BadArgument): + await ctx.respond( + f"The exchange you specified is currently not supported! \nSupported exchanges: Kucoin, Binance" + ) + elif isinstance(error, commands.UserInputError): + await ctx.respond( + f"If using `/portfolio add`, you must specify an exchange, key, secret, and optionally a passphrase!" + ) + elif isinstance(error, commands.PrivateMessageOnly): + await ctx.respond( + "Please only use the `/portfolio` command in private messages for security reasons." + ) + else: + print(error) + await ctx.respond(f"An error has occurred. Please try again later.")
+ + +
+[docs] + @remove.error + async def remove_error(self, ctx: commands.Context, error: Exception) -> None: + # print(traceback.format_exc()) + await ctx.respond(f"An error has occurred. Please try again later.")
+ + +
+[docs] + @show.error + async def show_error(self, ctx: commands.Context, error: Exception) -> None: + # print(traceback.format_exc()) + if isinstance(error, commands.PrivateMessageOnly): + await ctx.respond( + "Please only use the `/portfolio` command in private messages for security reasons." + ) + else: + await ctx.respond(f"An error has occurred. Please try again later.")
+
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Portfolio(bot)) +
+ +
+ + + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/commands/sentiment.html b/_modules/cogs/commands/sentiment.html new file mode 100644 index 00000000..0f9992e3 --- /dev/null +++ b/_modules/cogs/commands/sentiment.html @@ -0,0 +1,614 @@ + + + + + + + + + + cogs.commands.sentiment — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.commands.sentiment

+## > Imports
+# > Standard Library
+import datetime
+
+# > 3rd Party Dependencies
+import pandas as pd
+import nltk
+from nltk.sentiment.vader import SentimentIntensityAnalyzer
+
+# > Discord imports
+import discord
+from discord.ext import commands
+from discord.commands import Option
+
+# > Local dependencies
+from util.vars import get_json_data
+from util.confirm_stock import confirm_stock
+
+
+
+[docs] +class Sentiment(commands.Cog): + """ + This class is used to handle the sentiment command. + You can enable / disable this command in the config, under ["COMMANDS"]["SENTIMENT"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + @commands.slash_command( + description="Request the current sentiment for a stock ticker." + ) + async def sentiment( + self, + ctx: commands.Context, + stock: Option(str, description="Stock ticker, i.e. AAPL.", required=True), + ) -> None: + """ + This method is used to handle the sentiment command. + Usage: `!sentiment <stock ticker>` for instance `!sentiment AAPL`. + + Parameters + ---------- + ctx : commands.context.Context + The context of the command. + stock : str + The stock ticker, i.e. AAPL. Specified at the end of the command. + + Returns + ------- + None + """ + + await ctx.response.defer(ephemeral=True) + + # Check if this stock exists + if not await confirm_stock(self.bot, ctx, stock): + return + + news_df = await self.get_news(stock) + + # Get the total mean + total_mean = news_df["Sentiment"].mean() + + # The mean of the most recent 50 articles + fifty_mean = news_df["Sentiment"].head(50).mean() + + # The mean of the most recent 10 articles + ten_mean = news_df["Sentiment"].head(10).mean() + + e = discord.Embed( + title=f"Sentiment of Latest {stock.upper()} News", + url=f"https://finviz.com/quote.ashx?t={stock}", + description="", + color=0xFFFFFF, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + e.set_footer( + text="\u200b", + icon_url="https://pbs.twimg.com/profile_images/554978836488540160/rqhRqbgQ_400x400.png", + ) + + # Start by showing the means + e.add_field(name="Last 10 Mean", value=f"{ten_mean:.2f}", inline=True) + e.add_field(name="Last 50 Mean", value=f"{fifty_mean:.2f}", inline=True) + e.add_field(name="Total Mean", value=f"{total_mean:.2f}", inline=True) + + # Display the last 10 articles + last_5 = news_df.head(5) + + dates = last_5["Date"].dt.strftime("%d/%m/%y").tolist() + headlines = last_5["Headline"].astype(str).tolist() + sentiments = last_5["Sentiment"].astype(str).tolist() + + for i in range(5): + e.add_field( + name="Date" if i == 0 else "\u200b", value=dates[i], inline=True + ) + e.add_field( + name="Headline" if i == 0 else "\u200b", + value=headlines[i], + inline=True, + ) + e.add_field( + name="Sentiment" if i == 0 else "\u200b", + value=sentiments[i], + inline=True, + ) + + await ctx.respond(embed=e) + +
+[docs] + @sentiment.error + async def sentiment_error(self, ctx: commands.Context, error: Exception) -> None: + """ + Catches the errors when using the `!sentiment` command. + + Parameters + ---------- + ctx : commands.Context + The context of the command. + error : Exception + The exception that was raised when using the `!sentiment` command. + """ + print(error) + await ctx.respond(f"An error has occurred. Please try again later.")
+ + +
+[docs] + async def get_news(self, ticker: str) -> pd.DataFrame: + """ + Get the latest news for a given stock ticker. + + Parameters + ---------- + ticker : str + The stock ticker to get the news for. + + Returns + ------- + pd.DataFrame + The latest news for a given stock ticker. + """ + + html = await get_json_data( + url=f"https://finviz.com/quote.ashx?t={ticker}", + headers={ + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" + }, + text=True, + ) + + print(html) + + # Get everything part of id='news-table' + html = html[html.find('id="news-table"') :] + html = html[: html.find("</table>")] + + # Split headlines by <tr> until </tr> + headlines = html.split("<tr>")[1:] + + text_only = [] + last_date = "" + dates = [] + sentiment = [] + + for headline in headlines: + date = headline[ + headline.find('style="white-space:nowrap">') + + len('style="white-space:nowrap">') : headline.find("&nbsp;") + ] + + if date.startswith('ht">'): + date = last_date + " " + date[len('ht">') :] + else: + last_date = date.split()[0] + + # Month-date-year hour:minute AM/pm + # For instance May-23-22 11:31PM + dates.append(datetime.datetime.strptime(date, "%b-%d-%y %I:%M%p")) + + text = headline[ + headline.find('class="tab-link-news">') + + len('class="tab-link-news">') : headline.find("</a>") + ].replace("&amp;", "&") + + url = headline[ + headline.find("href=") + + len("href=") + + 1 : headline.find('" target="_blank"') + ] + + text_only.append(f"[{text}]({url})") + + try: + analyzer = SentimentIntensityAnalyzer() + sentiment.append(analyzer.polarity_scores(text)["compound"]) + except LookupError: + # Download the NLTK packages + nltk.download("vader_lexicon") + + # Try again + analyzer = SentimentIntensityAnalyzer() + sentiment.append(analyzer.polarity_scores(text)["compound"]) + + return pd.DataFrame( + {"Date": dates, "Headline": text_only, "Sentiment": sentiment} + )
+
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Sentiment(bot)) +
+ +
+ + + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/commands/stock.html b/_modules/cogs/commands/stock.html new file mode 100644 index 00000000..d0cd0746 --- /dev/null +++ b/_modules/cogs/commands/stock.html @@ -0,0 +1,725 @@ + + + + + + + + + + cogs.commands.stock — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.commands.stock

+##> Imports
+import datetime
+
+# > 3rd Party Dependencies
+import pandas as pd
+import yfinance as yf
+
+# Discord imports
+import discord
+from discord.ext import commands
+from discord.commands import SlashCommandGroup, Option
+
+# Local dependencies
+import util.vars
+from util.vars import config
+from util.db import update_db, merge_and_update
+from util.disc_util import get_channel
+from util.confirm_stock import confirm_stock
+from util.trades_msg import trades_msg
+
+
+
+[docs] +class Stock(commands.Cog): + """ + This class handles the `/stock` command. + You can enable / disable this command in the config, under ["COMMANDS"]["STOCK"]. + """ + + # Create a slash command group + stocks = SlashCommandGroup("stock", description="Add stocks to your portfolio.") + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + +
+[docs] + def update_assets_db(self, new_db): + """ + Updates the assets database. + + Parameters + ---------- + new_db : pandas.DataFrame + The new database to be written to the assets database. + + Returns + ------- + None + """ + + # Set the new portfolio so other functions can access it + util.vars.assets_db = new_db + + # Write to SQL database + update_db(new_db, "assets")
+ + + @stocks.command(name="add", description="Add a stock to your portfolio.") + async def add( + self, + ctx: commands.Context, + ticker: Option( + str, description="The ticker of the stock e.g., AAPL", required=True + ), + buying_price: Option( + str, + description="The price of the stock when you bought it, e.g., 106.40", + required=True, + ), + amount: Option( + str, + description="The amount of stocks that you own at this price, e.g., 2", + required=True, + ), + ) -> None: + """ + Add stocks to your portfolio. + Usage: + `/stock add <ticker> <buying price> <amount>` to add a stock to your portfolio + + + Parameters + ---------- + ctx : commands.context.Context + The context of the command, such as the user who used it. + input : tuple + The keywords used following the `!stock` command. + + Returns + ------- + None + """ + + await ctx.response.defer(ephemeral=True) + + # Make sure that the user is aware of this stock's existence + if not await confirm_stock(self.bot, ctx, ticker): + return + + try: + amount = float(amount) + buying_price = float(buying_price) + except Exception: + await ctx.respond("Please provide a valid buying price and/or amount.") + return + + try: + price = yf.Ticker(ticker).info["regularMarketPrice"] + except Exception: + price = 0 + + # Add ticker to database + new_data = pd.DataFrame( + [ + { + "asset": ticker.upper(), + "buying_price": buying_price, + "owned": amount, + "exchange": "stock", + "id": ctx.author.id, + "user": ctx.author.name, + } + ] + ) + + old_db = util.vars.assets_db + + # Check if the user has this asset already + owned_in_db = old_db.loc[ + (old_db["id"] == ctx.author.id) & (old_db["asset"] == ticker.upper()) + ] + + # If the user does not yet own this stock + if owned_in_db.empty: + util.vars.assets_db = merge_and_update(old_db, new_data, "assets") + else: + # Increase the amount if everything is the same + same_price = old_db.loc[ + (old_db["id"] == ctx.author.id) + & (old_db["asset"] == ticker.upper()) + & (old_db["buying_price"] == buying_price) + ] + + if not same_price.empty: + old_db.loc[ + (old_db["id"] == ctx.author.id) + & (old_db["asset"] == ticker.upper()), + "owned", + ] += amount + + else: + # Get the old buying price and average it with the new one + old_buying_price = owned_in_db["buying_price"].values[0] + old_amount_owned = owned_in_db["owned"].values[0] + + new_buying_price = ( + old_buying_price * old_amount_owned + buying_price * amount + ) / (old_amount_owned + amount) + + # Update the buying price and amount owned + old_db.loc[ + (old_db["id"] == ctx.author.id) + & (old_db["asset"] == ticker.upper()), + "buying_price", + ] = new_buying_price + + # Update the amount owned + old_db.loc[ + (old_db["id"] == ctx.author.id) + & (old_db["asset"] == ticker.upper()), + "owned", + ] += amount + + self.update_assets_db(old_db) + await ctx.respond("Succesfully added your stock to the database!") + + channel = get_channel(self.bot, config["LOOPS"]["TRADES"]["CHANNEL"]) + + # Send message in trades channel + await trades_msg( + "stocks", + channel, + ctx.author, + ticker, + "buy", + "market", + buying_price, + amount, + round(price * amount, 2), + None, + ) + + @stocks.command( + name="remove", description="Remove a specific stock from your portfolio." + ) + async def remove( + self, + ctx: commands.Context, + ticker: Option( + str, description="The ticker of the stock e.g., AAPL", required=True + ), + amount: Option( + str, + description="The amount of stocks that you want to delete, e.g., 2", + required=False, + ), + ) -> None: + """ + Usage: + `!stock remove <ticker> (<amount>)` to remove a stock from your portfolio + """ + await ctx.response.defer(ephemeral=True) + + old_db = util.vars.assets_db + + if not amount: + row = old_db.index[ + (old_db["id"] == ctx.author.id) & (old_db["asset"] == ticker) + ] + + # Update database + if not row.empty: + amount = old_db.loc[row, "owned"].values[0] + self.update_assets_db(old_db.drop(index=row)) + await ctx.respond( + f"Succesfully removed all {ticker.upper()} from your owned stocks!" + ) + else: + await ctx.respond("You do not own this stock!") + return + + else: + try: + amount = float(amount) + except Exception: + await ctx.respond("Please provide a valid amount.") + return + + row = old_db.loc[ + (old_db["id"] == ctx.author.id) & (old_db["asset"] == ticker) + ] + + # Update database + if not row.empty: + # Check the amount owned + owned_now = row["owned"].tolist()[0] + # if it is equal to or greater than the amount to remove, remove all + if float(amount) >= owned_now: + self.update_assets_db(old_db.drop(index=row.index)) + await ctx.respond( + f"Succesfully removed all {ticker.upper()} from your owned stocks!" + ) + else: + old_db.loc[ + (old_db["id"] == ctx.author.id) + & (old_db["asset"] == ticker.upper()), + "owned", + ] -= float(amount) + self.update_assets_db(old_db) + await ctx.respond( + f"Succesfully removed {amount} {ticker.upper()} from your owned stocks!" + ) + else: + await ctx.respond("You do not own this stock!") + return + + try: + price = yf.Ticker(ticker).info["regularMarketPrice"] + except Exception: + price = 0 + + buying_price = row["buying_price"].tolist()[0] + + channel = get_channel(self.bot, config["LOOPS"]["TRADES"]["CHANNEL"]) + + # Send message in trades channel + await trades_msg( + "stocks", + channel, + ctx.author, + ticker, + "sold", + "market", + price, + amount, + round(price * amount, 2), + buying_price, + ) + + @stocks.command(name="show", description="Show the stocks in your portfolio.") + async def show(self, ctx: commands.Context) -> None: + """ + Usage: + `!stock show` to show the stocks in your portfolio + """ + await ctx.response.defer(ephemeral=True) + db = util.vars.assets_db + rows = db.loc[(db["id"] == ctx.author.id) & (db["exchange"] == "stock")] + if not rows.empty: + for _, row in rows.iterrows(): + # Maybe send this an embed + await ctx.respond( + f"Stock: {row['asset'].upper()} \nAmount: {row['owned']}" + ) + else: + await ctx.respond("You do not have any stocks")
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Stock(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/listeners/on_member_join.html b/_modules/cogs/listeners/on_member_join.html new file mode 100644 index 00000000..65197de0 --- /dev/null +++ b/_modules/cogs/listeners/on_member_join.html @@ -0,0 +1,437 @@ + + + + + + + + + + cogs.listeners.on_member_join — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.listeners.on_member_join

+##> Imports
+# > Discord dependencies
+from discord.ext import commands
+
+
+[docs] +class On_member_join(commands.Cog): + def __init__(self, bot): + self.bot = bot + +
+[docs] + @commands.Cog.listener() + async def on_member_join(self, member) -> None: + """ Sends a private message to the member when they join the server """ + + await member.send("""Welcome to the server! You can use `/help` to get a list of all commands available to you. +For more information about a specific command, use `/help <command>`. +Be sure to add your portfolio API read-only keys to your profile using `/portfolio`.""")
+
+ + +def setup(bot): + bot.add_cog(On_member_join(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/listeners/on_raw_reaction_add.html b/_modules/cogs/listeners/on_raw_reaction_add.html new file mode 100644 index 00000000..f0cbf5c0 --- /dev/null +++ b/_modules/cogs/listeners/on_raw_reaction_add.html @@ -0,0 +1,606 @@ + + + + + + + + + + cogs.listeners.on_raw_reaction_add — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.listeners.on_raw_reaction_add

+##> Imports
+# > Standard libraries
+from csv import writer
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+
+# > Local dependencies
+from util.disc_util import get_channel, get_webhook
+from util.vars import config
+
+
+
+[docs] +class On_raw_reaction_add(commands.Cog): + """ + This class is used to handle the on_raw_reaction_add event. + You can enable / disable this command in the config, under ["LISTENERS"]["ON_RAW_REACTION_ADD"]. + """ + + def __init__(self, bot): + self.bot = bot + self.channel = get_channel( + self.bot, config["LISTENERS"]["ON_RAW_REACTION_ADD"]["CHANNEL"] + ) + +
+[docs] + @commands.Cog.listener() + async def on_raw_reaction_add( + self, reaction: discord.RawReactionActionEvent + ) -> None: + """ + This function is called when a reaction is added to a message. + + Parameters + ---------- + reaction : discord.RawReactionActionEvent + The information about the reaction that was added. + + Returns + ------- + None + """ + + # Ignore private messages + if reaction.guild_id is None: + return + + try: + # Load necessary variables + channel = self.bot.get_channel(reaction.channel_id) + try: + message = discord.utils.get( + await channel.history(limit=100).flatten(), id=reaction.message_id + ) + except Exception as e: + print(f"Error getting channel.history for {channel}. Error:", e) + return + + if reaction.user_id != self.bot.user.id: + if ( + str(reaction.emoji) == "🐻" + or str(reaction.emoji) == "🐂" + or str(reaction.emoji) == "🦆" + ): + await self.classify_reaction(reaction, message) + elif str(reaction.emoji) == "💸": + await self.highlight(message, reaction.member) + elif str(reaction.emoji) == "❤️": + await self.send_dm(message, reaction.member) + + except commands.CommandError as e: + print(e)
+ + +
+[docs] + async def classify_reaction( + self, reaction: discord.RawReactionActionEvent, message: discord.Message + ) -> None: + """ + This function gets called if a reaction was used for classifying a tweet. + + Parameters + ---------- + reaction : discord.RawReactionActionEvent + The information about the reaction that was added. + message : discord.Message + The message that the reaction was added to. + + Returns + ------- + None + """ + + with open("data/sentiment_data.csv", "a", newline="") as file: + writer_object = writer(file) + if str(reaction.emoji) == "🐻": + writer_object.writerow( + [message.embeds[0].description.replace("\n", " "), -1] + ) + elif str(reaction.emoji) == "🐂": + writer_object.writerow( + [message.embeds[0].description.replace("\n", " "), 1] + ) + elif str(reaction.emoji) == "🦆": + writer_object.writerow( + [message.embeds[0].description.replace("\n", " "), 0] + )
+ + +
+[docs] + async def highlight(self, message: discord.Message, user: discord.User) -> None: + """ + This function gets called if a reaction was used for highlighting a tweet. + + Parameters + ---------- + message : discord.Message + The tweet that should be posted in the highlight channel. + user : discord.User + The user that added this reaction to the tweet. + + Returns + ------- + None + """ + + # Get the old embed + e = message.embeds[0] + + # Get the Discord name of the user + e.set_footer( + text=f"{e.footer.text} | Highlighted by {str(user).split('#')[0]}", + icon_url=e.footer.icon_url, + ) + + if len(message.embeds) > 1: + image_e = [e] + [ + discord.Embed(url=em.url).set_image(url=em.image.url) + for em in message.embeds[1:] + ] + + webhook = await get_webhook(self.channel) + + # Wait so we can use this message as reference + await webhook.send( + embeds=image_e, + username="FinTwit", + wait=True, + avatar_url=self.bot.user.avatar.url, + ) + + else: + await self.channel.send(embed=e)
+ + +
+[docs] + async def send_dm(self, message: discord.Message, user: discord.User) -> None: + """ + This function gets called if a reaction was used for sending a tweet via DM. + + Parameters + ---------- + message : discord.Message + The tweet that should be send to the DM of the user. + user : discord.User + The user that added this reaction to the tweet. + + Returns + ------- + None + """ + + # Check if the message has an embed + if message.embeds == []: + return + + # Get the old embed + e = message.embeds[0] + + # Send the embed to the user + await user.send(embed=e)
+
+ + + +def setup(bot): + bot.add_cog(On_raw_reaction_add(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/assets.html b/_modules/cogs/loops/assets.html new file mode 100644 index 00000000..3ea4bf07 --- /dev/null +++ b/_modules/cogs/loops/assets.html @@ -0,0 +1,795 @@ + + + + + + + + + + cogs.loops.assets — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.assets

+## > Imports
+# > Standard libraries
+from __future__ import annotations
+import asyncio
+import datetime
+
+# > 3rd Party Dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+import pandas as pd
+import numpy as np
+
+# > Local dependencies
+import util.vars
+from util.yf_data import get_stock_info
+from util.cg_data import get_coin_info
+from util.db import update_db
+from util.disc_util import get_channel, get_user
+from util.vars import config
+from util.disc_util import get_guild
+from util.formatting import format_embed_length, format_change
+from util.exchange_data import get_data
+
+
+
+[docs] +class Assets(commands.Cog): + """ + The class is responsible for posting the assets of Discord users. + You can enabled / disable it in config under ["LOOPS"]["ASSETS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.assets.start() + +
+[docs] + async def usd_value(self, asset: str, exchange: str) -> tuple[float, float]: + """ + Get the USD value of an asset, based on the exchange. + + Parameters + ---------- + asset : str + The ticker of the asset, i.e. 'BTC'. + exchange : str + The exchange the asset is on, currently only 'binance' and 'kucoin' are supported. + + Returns + ------- + float + The worth of this asset in USD. + """ + + usd_val = change = None + + if exchange.lower() != "stock": + _, _, _, usd_val, change, _ = await get_coin_info(asset) + else: + _, _, _, usd_val, change, _ = await get_stock_info( + asset, do_format_change=False + ) + if isinstance(usd_val, list): + usd_val = usd_val[0] + if isinstance(change, list): + change = change[0] + + return usd_val, change
+ + + @loop(hours=1) + async def assets(self) -> None: + """ + Only do this function at startup and if a new portfolio has been added. + Checks the account balances of accounts saved in portfolio db, then updates the assets db. + + Parameters + ---------- + portfolio_db : pd.DataFrame + The portfolio db or the db for a new user. + + Returns + ------- + None + """ + assets_db_columns = { + "asset": str, + "buying_price": float, + "owned": float, + "exchange": str, + "id": np.int64, + "user": str, + "worth": float, + "price": float, + "change": float, + } + + if util.vars.portfolio_db.empty: + print("No portfolios in the database.") + return + + # Drop all crypto assets, so we can update them + if not util.vars.assets_db.empty: + crypto_rows = util.vars.assets_db.index[ + util.vars.assets_db["exchange"] != "stock" + ].tolist() + assets_db = util.vars.assets_db.drop(index=crypto_rows) + else: + # Create a new database + assets_db = pd.DataFrame(columns=list(assets_db_columns.keys())) + + # Get the assets of each user + for _, row in util.vars.portfolio_db.iterrows(): + # Add this data to the assets db + exch_data = await get_data(row) + assets_db = pd.concat([assets_db, exch_data], ignore_index=True) + + # Ensure that the db knows the right types + assets_db = assets_db.astype(assets_db_columns) + + # Update the assets db + update_db(assets_db, "assets") + util.vars.assets_db = assets_db + + # Post the assets + await self.post_assets() + +
+[docs] + async def update_prices_and_changes(self, new_df: pd.DataFrame) -> pd.DataFrame: + """ + Updates the prices and changes of the stock assets in the DataFrame. + """ + # Filter DataFrame to only include rows where exchange is "stock" + stock_df = new_df[new_df["exchange"] == "stock"] + + # Asynchronously get price and change for each asset + async def get_price_change(row): + price, change = await self.usd_value(row["asset"], row["exchange"]) + return { + "price": 0 if price is None else round(price, 2), + "change": 0 if change is None else change, + "worth": ( + 0 + if price in [None, np.nan] + else round(price * float(row["owned"]), 2) + ), + } + + # Using asyncio.gather to run all async operations concurrently + results = await asyncio.gather( + *(get_price_change(row) for _, row in stock_df.iterrows()) + ) + + # Update the DataFrame with the results + for i, (index, row) in enumerate(stock_df.iterrows()): + new_df.at[index, "price"] = results[i]["price"] + new_df.at[index, "change"] = results[i]["change"] + new_df.at[index, "worth"] = results[i]["worth"] + + return new_df
+ + +
+[docs] + async def format_exchange( + self, + exchange_df: pd.DataFrame, + exchange: str, + e: discord.Embed, + ) -> discord.Embed: + """ + Formats the embed used for updating user's assets. + + Parameters + ---------- + exchange_df : pd.DataFrame + The dataframe of assets owned by a user. + exchange : str + The exchange the assets are on, currently only 'binance' and 'kucoin' are supported. + e : discord.Embed + The embed to be formatted. + old_worth : str + The worth of the user's assets before the update. + old_assets : str + The assets of the user before the update. + + Returns + ------- + discord.Embed + The new embed. + """ + + # Necessary to prevent panda warnings + new_df = exchange_df.copy() + + # Add stock data to the DataFrame + stock_df = util.vars.assets_db[util.vars.assets_db["exchange"] == "stock"] + if not stock_df.empty: + new_df = await self.update_prices_and_changes(new_df) + + # Remove everything after % in change + #new_df["change"] = new_df["change"].str.split("%").str[0] + + # Set the types (again) + new_df = new_df.astype( + { + "asset": str, + "buying_price": float, + "owned": float, + "exchange": str, + "id": np.int64, + "user": str, + "worth": float, + "price": float, + "change": float, # Make sure this is not the formatted change + } + ) + + # Format the price change + new_df["change"] = new_df["change"].apply(lambda x: format_change(x)) + + # Format price and change + new_df["price_change"] = ( + "$" + + new_df["price"].astype(str) + + " (" + + new_df["change"].astype(str) + + ")" + ) + + # Fill NaN values of worth + new_df["worth"] = new_df["worth"].fillna(0) + + # Set buying price to float + new_df["buying_price"] = new_df["buying_price"].astype(float) + + # Add worth_change column + new_df["worth_change"] = "?" + + # Calculate the worth_change percentage only where buying_price is not 0 + mask = new_df["buying_price"] != 0 + new_df.loc[mask, "worth_change"] = round( + ((new_df["price"] - new_df["buying_price"]) / new_df["buying_price"] * 100), + 2, + ) + + # Apply format_change to worth_change + new_df.loc[mask, "worth_change"] = new_df.loc[mask, "worth_change"].apply( + lambda x: format_change(x) + ) + + # Sort by usd value + new_df = new_df.sort_values(by=["worth"], ascending=False) + + new_df["worth"] = ( + "$" + + new_df["worth"].astype(str) + + " (" + + new_df["worth_change"].astype(str) + + ")" + ) + + # Create the list of string values + assets = "\n".join(new_df["asset"].to_list()) + prices = "\n".join(new_df["price_change"].to_list()) + worth = "\n".join(new_df["worth"].to_list()) + + # Ensure that the length is not bigger than allowed + assets, prices, worth = format_embed_length([assets, prices, worth]) + + exchange_title = exchange + if exchange.lower() in util.vars.custom_emojis.keys(): + exchange_title = f"{exchange} {util.vars.custom_emojis[exchange.lower()]}" + + # These are the new fields added to the embed + e.add_field(name=exchange_title, value=assets, inline=True) + e.add_field(name="Price", value=prices, inline=True) + e.add_field(name="Worth", value=worth, inline=True) + + return e
+ + +
+[docs] + async def post_assets(self) -> None: + """ + Posts the assets of the users that added their portfolio. + + Returns + ------- + None + """ + + # Use the user name as channel + for id in util.vars.assets_db["id"].unique(): + # Get the assets of this user + user_assets = util.vars.assets_db.loc[util.vars.assets_db["id"] == id] + + # Only post if there are assets + if not user_assets.empty: + # Get the Discord objects + channel = await self.get_user_channel(user_assets["user"].values[0]) + disc_user = await self.get_user(user_assets) + + e = discord.Embed( + title="", + description="", + color=0x1DA1F2, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + if disc_user: + e.set_author( + name=disc_user.name + "'s Assets", + icon_url=disc_user.display_avatar.url, + ) + + # Finally, format the embed before posting it + for exchange in ["Binance", "KuCoin", "Stock"]: + exchange_df = user_assets.loc[ + user_assets["exchange"] == exchange.lower() + ] + + if not exchange_df.empty: + e = await self.format_exchange(exchange_df, exchange, e) + + await channel.purge(limit=1) + await channel.send(embed=e)
+ + +
+[docs] + async def get_user_channel(self, name: str) -> discord.TextChannel: + """ + Based on the username returns the user specific channel. + + Parameters + ---------- + name : str + The name of the Discord user. + + Returns + ------- + discord.TextChannel + The user specific channel. + """ + channel_name = config["LOOPS"]["ASSETS"]["CHANNEL_PREFIX"] + name.lower() + + # If this channel does not exist make it + channel = get_channel(self.bot, channel_name) + if channel is None: + guild = get_guild(self.bot) + channel = await guild.create_text_channel( + channel_name, category=config["CATEGORIES"]["USERS"] + ) + print(f"Created channel {channel_name}") + + return channel
+ + +
+[docs] + async def get_user(self, assets): + id = assets["id"].values[0] + disc_user = self.bot.get_user(id) + + if disc_user is None: + try: + disc_user = await get_user(self.bot, id) + except Exception as e: + print(f"Could not get user with id: {id}.\n{assets} \nError:", e) + + return disc_user
+
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Assets(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/earnings_overview.html b/_modules/cogs/loops/earnings_overview.html new file mode 100644 index 00000000..ca8d16e1 --- /dev/null +++ b/_modules/cogs/loops/earnings_overview.html @@ -0,0 +1,550 @@ + + + + + + + + + + cogs.loops.earnings_overview — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.earnings_overview

+import datetime
+from lib2to3.pgen2.pgen import DFAState
+
+# > 3rd party dependencies
+import pandas as pd
+from yahoo_fin.stock_info import get_earnings_in_date_range
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+import util.vars
+from util.vars import config, data_sources
+from util.disc_util import get_channel, get_tagged_users
+
+
+
+[docs] +class Earnings_Overview(commands.Cog): + """ + This class is responsible for sending weekly overview of upcoming earnings. + You can enable / disable this command in the config, under ["LOOPS"]["EARNINGS_OVERVIEW"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.channel = get_channel( + self.bot, config["LOOPS"]["EARNINGS_OVERVIEW"]["CHANNEL"] + ) + + self.earnings.start() + +
+[docs] + def earnings_embed(self, df: pd.DataFrame, date: str) -> tuple[str, discord.Embed]: + # Create lists of the important info + tickers = "\n".join(df["ticker"].to_list()) + + time_type = "\n".join(df["startdatetimetype"].to_list()) + + epsestimate = "\n".join(df["epsestimate"].replace("nan", "N/A").to_list()) + + # Make an embed with these tickers and their earnings date + estimation + e = discord.Embed( + title=f"Earnings for {date}", + url=f"https://finance.yahoo.com/calendar/earnings?day={date}", + description="", + color=data_sources["yahoo"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field(name="Stock", value=tickers, inline=True) + e.add_field(name="Time", value=time_type, inline=True) + e.add_field(name="Estimate", value=epsestimate, inline=True) + + e.set_footer( + text="\u200b", + icon_url=data_sources["yahoo"]["icon"], + ) + + tags = get_tagged_users(df["ticker"].to_list()) + + return tags, e
+ + + @loop(hours=1) + async def earnings(self) -> None: + """ + Checks every hour if today is a friday and if the market is closed. + If that is the case a overview will be posted with the upcoming earnings. + + Returns + ---------- + None + """ + + # Send this message every friday at 23:00 UTC + if datetime.datetime.today().weekday() == 4: + if datetime.datetime.utcnow().hour == 23: + earnings = get_earnings_in_date_range( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=7), + ) + earnings_df = pd.DataFrame(earnings) + + # Filter on unique tickers in the nasdaq list + earnings_df = earnings_df[ + earnings_df["ticker"].isin(util.vars.nasdaq_tickers) + ] + + earnings_df = earnings_df.drop_duplicates(subset="ticker") + + # Split dataframe based on date + earnings_df["date"] = pd.to_datetime( + earnings_df["startdatetime"] + ).dt.date + + dates = earnings_df["date"].unique() + + for date in dates: + date_df = earnings_df.loc[earnings_df["date"] == date] + + # Necessary for using inplace operations below + date_df_copy = date_df.copy() + + # Format the dataframe + date_df_copy.sort_values(by="ticker", inplace=True) + + # AMC after market close (After-hours) + # BMO before market open (Pre-market) + # TNS Time not supplied (Unknown) + date_df_copy["startdatetimetype"].replace( + { + "AMC": "After-hours", + "BMO": "Pre-market", + "TNS": "Unknown", + "TAS": "Unknown", + }, + inplace=True, + ) + + date_df_copy = date_df_copy.astype({"epsestimate": str}) + + split = 50 + while not date_df_copy.iloc[split - 50 : split].empty: + tags, e = self.earnings_embed( + date_df_copy.iloc[split - 50 : split], date + ) + await self.channel.send(content=tags, embed=e) + split += split
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Earnings_Overview(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/events.html b/_modules/cogs/loops/events.html new file mode 100644 index 00000000..24812183 --- /dev/null +++ b/_modules/cogs/loops/events.html @@ -0,0 +1,744 @@ + + + + + + + + + + cogs.loops.events — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.events

+import pytz
+import datetime
+from lxml.html import fromstring
+from io import StringIO
+
+# 3rd party imports
+import pandas as pd
+from bs4 import BeautifulSoup
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, post_json_data, data_sources, get_json_data
+from util.disc_util import get_channel
+
+
+
+[docs] +class Events(commands.Cog): + """ + This class is responsible for sending weekly overview of upcoming events. + You can enable / disable this command in the config, under ["LOOPS"]["EVENTS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["EVENTS"]["FOREX"]["ENABLED"]: + self.forex_channel = get_channel( + self.bot, + config["LOOPS"]["EVENTS"]["CHANNEL"], + config["CATEGORIES"]["FOREX"], + ) + self.post_events.start() + + if config["LOOPS"]["EVENTS"]["CRYPTO"]["ENABLED"]: + self.crypto_channel = get_channel( + self.bot, + config["LOOPS"]["EVENTS"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + ) + self.post_crypto_events.start() + +
+[docs] + async def get_events(self): + """ + Gets the economic calendar from Investing.com for the next week. + The data contains the most important information for the USA and EU. + + Forked from: https://github.com/alvarobartt/investpy/blob/master/investpy/news.py + """ + + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", + "X-Requested-With": "XMLHttpRequest", + } + + data = { + "country[]": [72, 5], # USA and EU + "importance[]": 3, # Highest importance, 3 stars + "timeZone": 8, + "timeFilter": "timeRemain", + "currentTab": "nextWeek", + "submitFilters": 1, + "limit_from": 0, + } + + url = "https://www.investing.com/economic-calendar/Service/getCalendarFilteredData" + + req = await post_json_data(url, headers=headers, data=data) + root = fromstring(req["data"]) + table = root.xpath(".//tr") + + results = [] + + for reversed_row in table[::-1]: + id_ = reversed_row.get("id") + if id_ is not None: + id_ = id_.replace("eventRowId_", "") + + for row in table: + id_ = row.get("id") + if id_ == None: + curr_timescope = int(row.xpath("td")[0].get("id").replace("theDay", "")) + curr_date = datetime.datetime.fromtimestamp( + curr_timescope, tz=pytz.timezone("GMT") + ).strftime("%d/%m/%Y") + else: + id_ = id_.replace("eventRowId_", "") + + time = zone = currency = event = actual = forecast = previous = None + + if row.get("id").__contains__("eventRowId_"): + for value in row.xpath("td"): + if value.get("class").__contains__("first left"): + time = value.text_content() + elif value.get("class").__contains__("flagCur"): + zone = value.xpath("span")[0].get("title").lower() + currency = value.text_content().strip() + elif value.get("class") == "left event": + event = value.text_content().strip() + elif value.get("id") == "eventActual_" + id_: + actual = value.text_content().strip() + elif value.get("id") == "eventForecast_" + id_: + forecast = value.text_content().strip() + elif value.get("id") == "eventPrevious_" + id_: + previous = value.text_content().strip() + + results.append( + { + "id": id_, + "date": curr_date, + "time": time, + "zone": zone, + "currency": None if currency == "" else currency, + "event": event, + "actual": None if actual == "" else actual, + "forecast": None if forecast == "" else forecast, + "previous": None if previous == "" else previous, + } + ) + + return pd.DataFrame(results)
+ + + @loop(hours=1) + async def post_events(self): + """ + Checks every hour if today is a friday and if the market is closed. + If that is the case a overview will be posted with the upcoming earnings. + + Returns + ---------- + None + """ + + # Send this message every friday at 23:00 UTC + if datetime.datetime.today().weekday() == 4: + if datetime.datetime.utcnow().hour == 23: + df = await self.get_events() + + # If time == "All Day" convert it to 00:00 + df["time"] = df["time"].str.replace("All Day", "00:00") + + # Create datetime + df["datetime"] = pd.to_datetime( + df["date"] + " " + df["time"], + format="%d/%m/%Y %H:%M", + ) + + # Convert datetime to unix timestamp + df["timestamp"] = df["datetime"].astype("int64") // 10**9 + + # Convert timestamp to Discord timestamp using mode F + df["timestamp"] = df["timestamp"].apply(lambda x: f"<t:{int(x)}:d>") + + # Replace zone names + # TODO: fix future warning + df["zone"].replace( + {"euro zone": "🇪🇺", "united states": "🇺🇸"}, + inplace=True, + ) + + time = "\n".join(df["timestamp"]) + + # Do this if both forecast and previous are not NaN + if ( + not df["forecast"].isnull().all() + and not df["previous"].isnull().all() + ): + df["forecast|previous"] = df["forecast"] + " | " + df["previous"] + for_prev = "\n".join(df["forecast|previous"].astype(str)) + for_prev_title = "Forecast | Previous" + + else: + for_prev_title = "Previous" + for_prev = "\n".join(df["previous"].astype(str)) + + df["info"] = df["zone"] + " " + df["event"] + info = "\n".join(df["info"]) + + # Make an embed with these tickers and their earnings date + estimation + e = discord.Embed( + title=f"Events Upcoming Week", + url=f"https://www.investing.com/economic-calendar/", + description="", + color=data_sources["investing"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field(name="Date", value=time, inline=True) + e.add_field(name="Event", value=info, inline=True) + e.add_field(name=for_prev_title, value=for_prev, inline=True) + + e.set_footer( + text="\u200b", + icon_url=data_sources["investing"]["icon"], + ) + + await self.forex_channel.send(embed=e) + +
+[docs] + async def get_crypto_calendar(self) -> pd.DataFrame: + """ + Gets the economic calendar from CryptoCraft.com for the next week. + + Returns + ------- + pd.DataFrame + The formatted DataFrame containing the economic calendar. + """ + html = await get_json_data( + url="https://www.cryptocraft.com/calendar", + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4240.193 Safari/537.36" + }, + text=True, + ) + + soup = BeautifulSoup(html, "html.parser") + + # Get the first table + table = soup.find("table") + + impact_emoji = { + "yel": "🟨", + "ora": "🟧", + "red": "🟥", + } + + impacts = [] + for row in table.find_all("tr")[2:]: # Skip the header row + # Get the impact value from the span class including "impact" + impact = row.find("span", class_=lambda s: s and "impact" in s) + if impact: + impact = impact.get("class", [])[-1][-3:] + impacts.append(impact_emoji[impact]) + + # Convert the table to a string and read it into a DataFrame + df = pd.read_html(StringIO(str(table)))[0] + + # Drop the first row + df = df.iloc[1:] + + # Drop rows where the first and second column values are the same + df = df[df.iloc[:, 0] != df.iloc[:, 1]] + + # Convert MultiIndex columns to regular columns + df.columns = ["_".join(col).strip() for col in df.columns.values] + + # Rename second column to time and fifth column to event + df.rename( + columns={ + df.columns[0]: "date", + df.columns[1]: "time", + df.columns[4]: "event", + df.columns[6]: "actual", + df.columns[7]: "forecast", + df.columns[8]: "previous", + }, + inplace=True, + ) + + # Drop third and fourth column + df.drop(df.columns[[2, 3, 5, 9]], axis=1, inplace=True) + + # Remove rows where event is NaN + df = df[df["event"].notna()] + + # Reset index + df.reset_index(drop=True, inplace=True) + + # Add impact column + df["impact"] = impacts + + # Use ffill() for forward fill + df["time"] = df["time"].ffill() + + # Add the current year to the date string and convert to datetime + df["datetime"] = pd.to_datetime( + df["date"] + " " + str(datetime.datetime.now().year) + " " + df["time"], + format="%a %b %d %Y %I:%M%p", + errors="coerce", + ) + + return df
+ + + @loop(hours=24) + async def post_crypto_events(self): + df = await self.get_crypto_calendar() + + # Make an embed with these tickers and their earnings date + estimation + e = discord.Embed( + title=f"Upcoming Crypto Events", + url=f"https://www.cryptocraft.com/calendar", + description="", + color=data_sources["cryptocraft"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + # Convert datetime to unix timestamp + df["timestamp"] = df["datetime"].astype("int64") // 10**9 + + # Convert timestamp to Discord timestamp using mode F + df["timestamp"] = df["timestamp"].apply(lambda x: f"<t:{int(x)}:d>") + + date = "\n".join(df["timestamp"]) + event = "\n".join(df["event"]) + impact = "\n".join(df["impact"]) + + e.add_field(name="Date", value=date, inline=True) + e.add_field(name="Event", value=event, inline=True) + e.add_field(name="Impact", value=impact, inline=True) + + e.set_footer( + text="\u200b", + icon_url=data_sources["cryptocraft"]["icon"], + ) + + await self.crypto_channel.send(embed=e)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Events(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/funding.html b/_modules/cogs/loops/funding.html new file mode 100644 index 00000000..83922371 --- /dev/null +++ b/_modules/cogs/loops/funding.html @@ -0,0 +1,536 @@ + + + + + + + + + + cogs.loops.funding — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.funding

+import datetime
+
+# > 3rd party dependencies
+import pandas as pd
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data, data_sources
+from util.disc_util import get_channel
+
+
+
+[docs] +class Funding(commands.Cog): + """ + This class is used to handle the funding loop. + This can be enabled / disabled in the config, under ["LOOPS"]["FUNDING"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.channel = get_channel(self.bot, config["LOOPS"]["FUNDING"]["CHANNEL"]) + + self.funding.start() + + @loop(hours=4) + async def funding(self) -> None: + """ + This function gets the data from the funding API and posts it in the funding channel. + + Returns + ------- + None + """ + + # Get the JSON data from the Binance API + binance_data = await get_json_data( + "https://fapi.binance.com/fapi/v1/premiumIndex" + ) + + # If the call did not work + if not binance_data: + print("Could not get funding data...") + return + + # Cast to dataframe + df = pd.DataFrame(binance_data) + + # Keep only the USDT pairs + df = df[df["symbol"].str.contains("USDT")] + + # Remove USDT from the symbol + df["symbol"] = df["symbol"].str.replace("USDT", "") + + # Set it to numeric + df["lastFundingRate"] = df["lastFundingRate"].apply(pd.to_numeric) + + # Sort on lastFundingRate, lowest to highest + sorted = df.sort_values(by="lastFundingRate", ascending=True) + + # Multiply by 100 to get the funding rate in percent + sorted["lastFundingRate"] = sorted["lastFundingRate"] * 100 + + # Round to 4 decimal places + sorted["lastFundingRate"] = sorted["lastFundingRate"].round(4) + + # Convert them back to string + sorted = sorted.astype(str) + + # Add percentage to it + sorted["lastFundingRate"] = sorted["lastFundingRate"] + "%" + + # Post the top 15 lowest + lowest = sorted.head(15) + + e = discord.Embed( + title=f"Binance Top 15 Lowest Funding Rates", + url="", + description="", + color=data_sources["binance"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + # Get time to next funding, unix is in milliseconds + nextFundingTime = int(lowest["nextFundingTime"].tolist()[0]) // 1000 + nextFundingTime = datetime.datetime.fromtimestamp(nextFundingTime) + + # Get difference + timeToNextFunding = nextFundingTime - datetime.datetime.now() + + # Set datetime and icon + e.set_footer( + text=f"Next funding in {str(timeToNextFunding).split('.')[0]}", + icon_url=data_sources["binance"]["icon"], + ) + + lowest_tickers = "\n".join(lowest["symbol"].tolist()) + lowest_rates = "\n".join(lowest["lastFundingRate"].tolist()) + + e.add_field( + name="Coin", + value=lowest_tickers, + inline=True, + ) + + e.add_field( + name="Funding Rate", + value=lowest_rates, + inline=True, + ) + + # Post the embed in the channel + await self.channel.purge(limit=1) + await self.channel.send(embed=e)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Funding(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/gainers.html b/_modules/cogs/loops/gainers.html new file mode 100644 index 00000000..0f2bdad2 --- /dev/null +++ b/_modules/cogs/loops/gainers.html @@ -0,0 +1,567 @@ + + + + + + + + + + cogs.loops.gainers — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.gainers

+import traceback
+
+# > 3rd party dependencies
+import yahoo_fin.stock_info as si
+import pandas as pd
+
+# > Discord dependencies
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data
+from util.disc_util import get_channel
+from util.afterhours import afterHours
+from util.formatting import format_embed
+
+
+
+[docs] +class Gainers(commands.Cog): + """ + This class contains the cog for posting the top crypto and stocks gainers. + It can be enabled / disabled in the config under ["LOOPS"]["GAINERS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["GAINERS"]["STOCKS"]["ENABLED"]: + self.stocks_channel = get_channel( + self.bot, + config["LOOPS"]["GAINERS"]["CHANNEL"], + config["CATEGORIES"]["STOCKS"], + ) + self.stocks.start() + + if config["LOOPS"]["GAINERS"]["CRYPTO"]["ENABLED"]: + self.crypto_gainers_channel = get_channel( + self.bot, + config["LOOPS"]["GAINERS"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + ) + + if config["LOOPS"]["LOSERS"]["CRYPTO"]["ENABLED"]: + self.crypto_losers_channel = get_channel( + self.bot, + config["LOOPS"]["LOSERS"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + ) + + if ( + config["LOOPS"]["GAINERS"]["CRYPTO"]["ENABLED"] + or config["LOOPS"]["LOSERS"]["CRYPTO"]["ENABLED"] + ): + self.crypto.start() + + @loop(hours=1) + async def crypto(self) -> None: + """ + This function will check the gainers and losers on Binance, using USDT as the base currency. + To prevent too many calls the losers are also done in this section. + + Returns + ------- + None + """ + + binance_data = await get_json_data("https://api.binance.com/api/v3/ticker/24hr") + + # If the call did not work + if not binance_data: + return + + # Cast to dataframe + df = pd.DataFrame(binance_data) + + # Keep only the USDT pairs + df = df[df["symbol"].str.contains("USDT")] + + # Remove USDT from the symbol + df["symbol"] = df["symbol"].str.replace("USDT", "") + + df[["priceChangePercent", "weightedAvgPrice", "volume"]] = df[ + ["priceChangePercent", "weightedAvgPrice", "volume"] + ].apply(pd.to_numeric) + + # Sort on priceChangePercent + sorted = df.sort_values(by="priceChangePercent", ascending=False) + + sorted.rename( + columns={ + "symbol": "Symbol", + "priceChangePercent": "% Change", + "weightedAvgPrice": "Price", + "volume": "Volume", + }, + inplace=True, + ) + + # Add website to symbol + sorted["Symbol"] = ( + "[" + + sorted["Symbol"] + + "](https://www.binance.com/en/price/" + + sorted["Symbol"] + + ")" + ) + + # Post the top 10 highest + gainers = sorted.head(10) + + # Post the top 10 lowest + losers = sorted.tail(10) + losers = losers.iloc[::-1] + + # Format the embed + e_gainers = await format_embed(gainers, "Gainers", "binance") + e_losers = await format_embed(losers, "Losers", "binance") + + # Post the embed in the channel + if config["LOOPS"]["GAINERS"]["CRYPTO"]["ENABLED"]: + await self.crypto_gainers_channel.purge(limit=1) + await self.crypto_gainers_channel.send(embed=e_gainers) + + if config["LOOPS"]["LOSERS"]["CRYPTO"]["ENABLED"]: + await self.crypto_losers_channel.purge(limit=1) + await self.crypto_losers_channel.send(embed=e_losers) + + @loop(hours=1) + async def stocks(self) -> None: + """ + This function uses the yahoo_fin.stock_info module to get the gainers for todays stocks. + + Returns + ------- + None + """ + + # Dont send if the market is closed + if afterHours(): + return + + try: + e = await format_embed(si.get_day_gainers().head(10), "Gainers", "yahoo") + await self.stocks_channel.purge(limit=1) + await self.stocks_channel.send(embed=e) + except Exception as e: + print("Error posting stocks gainers: ", e)
+ + #print(traceback.format_exc()) + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Gainers(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/ideas.html b/_modules/cogs/loops/ideas.html new file mode 100644 index 00000000..89e0aee7 --- /dev/null +++ b/_modules/cogs/loops/ideas.html @@ -0,0 +1,756 @@ + + + + + + + + + + cogs.loops.ideas — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.ideas

+## > Imports
+
+# > Standard library
+import datetime
+
+# > 3rd party dependencies
+import json
+import pandas as pd
+from bs4 import BeautifulSoup
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# > Local dependencies
+import util.vars
+from util.vars import get_json_data, config, data_sources
+from util.disc_util import get_channel, get_tagged_users
+from util.db import update_db
+
+
+
+[docs] +async def scraper(type: str) -> pd.DataFrame: + """ + Extract the front page of trading ideas on TradingView. + Written by: https://github.com/mnwato/tradingview-scraper. + + Parameters + ---------- + type : string + Specify the type of trading ideas to scrape, either "stocks" or "crypto". + + Returns + ------- + pd.DataFrame + A dataframe with the ideas of the specified symbol. + """ + + if type == "crypto": + url = "https://www.tradingview.com/markets/cryptocurrencies/ideas/" + elif type == "forex": + url = "https://www.tradingview.com/markets/currencies/ideas/" + else: + url = "https://www.tradingview.com/ideas/stocks/" + + # The information will be saved in these lists + titleList = [] + descriptionList = [] + labelList = [] + timeFrameList = [] + symbolList = [] + timestampList = [] + commentsList = [] + imageUrlList = [] + likesList = [] + urlList = [] + + # Fetch the page as text + response = await get_json_data(url, text=True) + + # The response is a HTML page + soup = BeautifulSoup(response, "html.parser") + + # Find all divs with the following class + content = soup.find( + "div", + class_="tv-card-container__ideas tv-card-container__ideas--with-padding js-balance-content", + ) + + # Save the Timestamps + for time_upd in content.find_all("span", class_="tv-card-stats__time js-time-upd"): + timestampList.append( + datetime.datetime.fromtimestamp( + float(time_upd["data-timestamp"]), tz=datetime.timezone.utc + ) + ) + + for img_row in content.find_all("div", class_="tv-widget-idea__cover-wrap"): + # Get the image url + imageUrlList.append(img_row.find("img")["data-src"]) + + # Save their social info in the list + for social_row in content.find_all( + "div", class_="tv-social-row tv-widget-idea__social-row" + ): + social_info = json.loads(social_row["data-model"]) + + commentsList.append(social_info["commentsCount"]) + likesList.append(social_info["agreesCount"]) + urlList.append(f"https://www.tradingview.com{social_info['publishedUrl']}") + + # Save the titles in the list + for title_row in content.find_all("div", class_="tv-widget-idea__title-row"): + titleList.append(title_row.a.get_text()) + + for description_row in content.find_all( + "p", + class_="tv-widget-idea__description-row tv-widget-idea__description-row--clamped js-widget-idea__popup", + ): + descriptionList.append(description_row.get_text()) + + # Get the Labels, timeFrame and Symbol + for info_row in content.find_all("div", class_="tv-widget-idea__info-row"): + if "type-long" in str(info_row): + label = "Long" + elif "type-short" in str(info_row): + label = "Short" + else: + label = "Neutral" + + labelList.append(label) + + symbol_info = info_row.find("div", class_="tv-widget-idea__symbol-info") + + if symbol_info: + if symbol_info.a: + symbolList.append(symbol_info.a.text) + elif symbol_info.span: + symbolList.append(symbol_info.span.text) + else: + symbolList.append(None) + else: + symbolList.append(None) + + timeFrameList.append( + info_row.find_all("span", class_="tv-widget-idea__timeframe")[1].text + ) + + data = { + "Timestamp": timestampList, + "Title": titleList, + "Description": descriptionList, + "Symbol": symbolList, + "Timeframe": timeFrameList, + "Label": labelList, + "Url": urlList, + "ImageURL": imageUrlList, + "Likes": likesList, + "Comments": commentsList, + } + + return pd.DataFrame(data)
+ + + +
+[docs] +class TradingView_Ideas(commands.Cog): + """ + This class contains the cog for posting the latest Trading View ideas. + It can be enabled / disabled in the config under ["LOOPS"]["TV_IDEAS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["IDEAS"]["CRYPTO"]["ENABLED"]: + self.crypto_channel = get_channel( + self.bot, + config["LOOPS"]["IDEAS"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + ) + + self.crypto_ideas.start() + + if config["LOOPS"]["IDEAS"]["STOCKS"]["ENABLED"]: + self.stocks_channel = get_channel( + self.bot, + config["LOOPS"]["IDEAS"]["CHANNEL"], + config["CATEGORIES"]["STOCKS"], + ) + + self.stock_ideas.start() + + if config["LOOPS"]["IDEAS"]["FOREX"]["ENABLED"]: + self.forex_channel = get_channel( + self.bot, + config["LOOPS"]["IDEAS"]["CHANNEL"], + config["CATEGORIES"]["FOREX"], + ) + + self.forex_ideas.start() + +
+[docs] + def add_id_to_db(self, id: str) -> None: + """ + Adds the given id to the database. + """ + + util.vars.ideas_ids = pd.concat( + [ + util.vars.ideas_ids, + pd.DataFrame( + [ + { + "id": id, + "timestamp": datetime.datetime.now(), + } + ] + ), + ], + ignore_index=True, + )
+ + +
+[docs] + async def send_embed(self, df: pd.DataFrame, type: str) -> None: + """ + Creates an embed based on the given DataFrame and type. + Then sends this embed in the designated channel. + + Parameters + ---------- + df : pd.DataFrame + The dataframe with the ideas. + type : str + The type of ideas, either "stocks" or "crypto". + + Returns + ------- + None + """ + + # Get the database + if not util.vars.ideas_ids.empty: + # Set the types + util.vars.ideas_ids = util.vars.ideas_ids.astype( + { + "id": str, + "timestamp": "datetime64[ns]", + } + ) + + # Only keep ids that are less than 72 hours old + util.vars.ideas_ids = util.vars.ideas_ids[ + util.vars.ideas_ids["timestamp"] + > datetime.datetime.now() - datetime.timedelta(hours=72) + ] + + counter = 1 + for _, row in df.iterrows(): + if not util.vars.ideas_ids.empty: + if row["Url"] in util.vars.ideas_ids["id"].tolist(): + counter += 1 + continue + + self.add_id_to_db(row["Url"]) + + if row["Label"] == "Long": + color = 0x3CC474 + elif row["Label"] == "Short": + color = 0xE40414 + else: + color = 0x808080 + + e = discord.Embed( + title=row["Title"], + url=row["Url"], + description=row["Description"], + color=color, + timestamp=row["Timestamp"], + ) + + e.set_image(url=row["ImageURL"]) + + e.add_field( + name="Symbol", + value=row["Symbol"] if row["Symbol"] is not None else "None", + inline=True, + ) + e.add_field(name="Timeframe", value=row["Timeframe"], inline=True) + e.add_field(name="Prediction", value=row["Label"], inline=True) + + e.set_footer( + text=f"👍 {row['Likes']} | 💬 {row['Comments']}", + icon_url=data_sources["tradingview"]["icon"], + ) + + if type == "stocks": + channel = self.stocks_channel + elif type == "crypto": + channel = self.crypto_channel + elif type == "forex": + channel = self.forex_channel + + await channel.send(content=get_tagged_users([row["Symbol"]]), embed=e) + + counter += 1 + + # Only show the top 10 ideas + if counter == 11: + break + # Write to db + update_db(util.vars.ideas_ids, "ideas_ids")
+ + + @loop(hours=24) + async def crypto_ideas(self) -> None: + """ + This function posts the crypto Trading View ideas. + + Returns + ------- + None + """ + + df = await scraper("crypto") + await self.send_embed(df, "crypto") + + @loop(hours=24) + async def stock_ideas(self) -> None: + """ + This function posts the stocks Trading View ideas. + + Returns + ------- + None + """ + + df = await scraper("stocks") + await self.send_embed(df, "stocks") + + @loop(hours=24) + async def forex_ideas(self) -> None: + """ + This function posts the forex Trading View ideas. + + Returns + ------- + None + """ + + df = await scraper("currencies") + await self.send_embed(df, "forex")
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(TradingView_Ideas(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/index.html b/_modules/cogs/loops/index.html new file mode 100644 index 00000000..16705c40 --- /dev/null +++ b/_modules/cogs/loops/index.html @@ -0,0 +1,727 @@ + + + + + + + + + + cogs.loops.index — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.index

+# Standard libraries
+from __future__ import annotations
+import datetime
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data, data_sources
+from util.tv_data import tv
+from util.disc_util import get_channel
+from util.afterhours import afterHours
+from util.formatting import human_format
+from util.tv_symbols import crypto_indices, stock_indices, forex_indices
+
+
+
+[docs] +class Index(commands.Cog): + """ + This class contains the cog for posting the crypto and stocks indices. + It can be enabled / disabled in the config under ["LOOPS"]["INDEX"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["INDEX"]["CRYPTO"]["ENABLED"]: + self.crypto_channel = get_channel( + self.bot, + config["LOOPS"]["INDEX"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + ) + + self.crypto_indices = [sym.split(":")[1] for sym in crypto_indices] + self.crypto.start() + + if config["LOOPS"]["INDEX"]["STOCKS"]["ENABLED"]: + self.stocks_channel = get_channel( + self.bot, + config["LOOPS"]["INDEX"]["CHANNEL"], + config["CATEGORIES"]["STOCKS"], + ) + self.stock_indices = [sym.split(":")[1] for sym in stock_indices] + self.stocks.start() + + if config["LOOPS"]["INDEX"]["FOREX"]["ENABLED"]: + self.forex_channel = get_channel( + self.bot, + config["LOOPS"]["INDEX"]["CHANNEL"], + config["CATEGORIES"]["FOREX"], + ) + self.forex_indices = [sym.split(":")[1] for sym in forex_indices] + self.forex.start() + +
+[docs] + async def get_feargread(self) -> tuple[int, str] | None: + """ + Gets the last 2 Fear and Greed indices from the API. + + Returns + ------- + int + Today's Fear and Greed index. + str + The percentual change compared to yesterday's Fear and Greed index. + """ + + response = await get_json_data("https://api.alternative.me/fng/?limit=2") + + if "data" in response.keys(): + today = int(response["data"][0]["value"]) + yesterday = int(response["data"][1]["value"]) + + change = round((today - yesterday) / yesterday * 100, 2) + change = f"+{change}% 📈" if change > 0 else f"{change}% 📉" + + return today, change
+ + + @loop(hours=1) + async def crypto(self) -> None: + """ + This function will get the current prices of crypto indices on TradingView and the Fear and Greed index. + It will then post the prices in the configured channel. + + Returns + ------- + None + """ + e = discord.Embed( + title=f"Crypto Indices", + description="", + color=data_sources["tradingview"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + ticker = [] + prices = [] + changes = [] + + for index in self.crypto_indices: + price, change, _, exchange, _ = await tv.get_tv_data(index, "crypto") + if price == 0: + print(index) + continue + change = round(change, 2) + change = f"+{change}% 📈" if change > 0 else f"{change}% 📉" + + if index == "TOTAL" or index == "TOTAL2" or index == "TOTAL3": + price = f"{human_format(price)}" + else: + price = f"{round(price, 2)}%" + + ticker.append( + f"[{index}](https://www.tradingview.com/symbols/{exchange}-{index}/)" + ) + prices.append(price) + changes.append(change) + + succes = await self.get_feargread() + + if succes is not None: + value, change = succes + + ticker.append( + f"[Fear&Greed](https://alternative.me/crypto/fear-and-greed-index/)" + ) + prices.append(str(value)) + changes.append(change) + + ticker = "\n".join(ticker) + prices = "\n".join(prices) + changes = "\n".join(changes) + + e.add_field( + name="Index", + value=ticker, + inline=True, + ) + + e.add_field( + name="Value", + value=prices, + inline=True, + ) + + e.add_field( + name="% Change", + value=changes, + inline=True, + ) + + e.set_footer( + text="\u200b", + icon_url=data_sources["tradingview"]["icon"], + ) + + await self.crypto_channel.purge(limit=1) + await self.crypto_channel.send(embed=e) + + @loop(hours=1) + async def stocks(self) -> None: + """ + Posts the stock indices in the configured channel, only posts if the market is open. + + Returns + ------- + None + """ + + # Dont send if the market is closed + if afterHours(): + return + + e = discord.Embed( + title=f"Stock Indices", + description="", + color=data_sources["tradingview"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + ticker = [] + prices = [] + changes = [] + + for index in self.stock_indices: + price, change, _, exchange, _ = await tv.get_tv_data(index, "stock") + if price == 0: + continue + change = round(change, 2) + change = f"+{change}% 📈" if change > 0 else f"{change}% 📉" + + if index in ["SPY", "NDX"]: + price = f"${round(price, 2)}" + # elif index == "USD10Y": + # price = f"{round(price, 2)}%" + else: + price = f"{round(price, 2)}" + + ticker.append( + f"[{index}](https://www.tradingview.com/symbols/{exchange}-{index}/)" + ) + prices.append(price) + changes.append(change) + + ticker = "\n".join(ticker) + prices = "\n".join(prices) + changes = "\n".join(changes) + + e.add_field( + name="Index", + value=ticker, + inline=True, + ) + + e.add_field( + name="Value", + value=prices, + inline=True, + ) + e.add_field( + name="% Change", + value=changes, + inline=True, + ) + + e.set_footer( + text="\u200b", + icon_url=data_sources["tradingview"]["icon"], + ) + + await self.stocks_channel.purge(limit=1) + await self.stocks_channel.send(embed=e) + + @loop(hours=1) + async def forex(self) -> None: + """ + Posts the forex indices in the configured channel, only posts if the market is open. + + Returns + ------- + None + """ + + # Dont send if the market is closed + if afterHours(): + return + + e = discord.Embed( + title=f"Forex Indices", + description="", + color=data_sources["tradingview"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + ticker = [] + prices = [] + changes = [] + + for index in self.forex_indices: + price, change, _, exchange, _ = await tv.get_tv_data(index, "forex") + if price == 0: + continue + change = round(change, 2) + change = f"+{change}% 📈" if change > 0 else f"{change}% 📉" + + price = f"{round(price, 2)}" + + ticker.append( + f"[{index}](https://www.tradingview.com/symbols/{exchange}-{index}/)" + ) + prices.append(price) + changes.append(change) + + if ticker == [] or prices == [] or changes == []: + return + + ticker = "\n".join(ticker) + prices = "\n".join(prices) + changes = "\n".join(changes) + + e.add_field( + name="Index", + value=ticker, + inline=True, + ) + + e.add_field( + name="Value", + value=prices, + inline=True, + ) + e.add_field( + name="% Change", + value=changes, + inline=True, + ) + + e.set_footer( + text="\u200b", + icon_url=data_sources["tradingview"]["icon"], + ) + + await self.forex_channel.purge(limit=1) + await self.forex_channel.send(embed=e)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Index(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/liquidations.html b/_modules/cogs/loops/liquidations.html new file mode 100644 index 00000000..7cf23dfc --- /dev/null +++ b/_modules/cogs/loops/liquidations.html @@ -0,0 +1,610 @@ + + + + + + + + + + cogs.loops.liquidations — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.liquidations

+import datetime
+import os
+
+# > 3rd party dependencies
+import pandas as pd
+from matplotlib import pyplot as plt
+from matplotlib import ticker
+import matplotlib.dates as mdates
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import get_json_data, data_sources
+from util.formatting import human_format
+from util.vars import config
+from util.disc_util import get_channel
+
+
+
+[docs] +class Liquidations(commands.Cog): + """ + This class contains the cog for posting the Liquidations chart. + It can be enabled / disabled in the config under ["LOOPS"]["LIQUIDATIONS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["LIQUIDATIONS"]["ENABLED"]: + self.channel = get_channel( + self.bot, config["LOOPS"]["LIQUIDATIONS"]["CHANNEL"] + ) + # Disabled for now + self.post_liquidations.start() + +
+[docs] + async def get_df(self) -> pd.DataFrame: + data = await get_json_data( + "https://open-api.coinglass.com/public/v2/liquidation_history?time_type=all&symbol=all", + headers={ + "accept": "application/json", + "coinglassSecret": os.getenv("COINGLASS_API_KEY"), + }, + ) + + if "data" not in data: + print("Could not get liquidation data from coinglass") + return pd.DataFrame() + + df = pd.DataFrame(data["data"]) + + df.rename( + {"buyVolUsd": "Shorts", "sellVolUsd": "Longs", "createTime": "time"}, + axis=1, + inplace=True, + ) + + # Set correct column names + df["date"] = pd.to_datetime(df["time"], unit="ms") + + # Set date as index + df = df.set_index("date") + return df
+ + + @loop(hours=24) + async def post_liquidations(self): + """ + Copy chart like https://www.coinglass.com/LiquidationData + + Codes based on: + https://github.com/OpenBB-finance/OpenBBTerminal/blob/main/openbb_terminal/cryptocurrency/due_diligence/coinglass_view.py + """ + + # Process dataframe + df = await self.get_df() + + if df is None or df.empty: + return + + df_price = df[["price"]].copy() + df_without_price = df.drop("price", axis=1) + df_without_price["Shorts"] = df_without_price["Shorts"] * -1 + + plt.style.use("dark_background") + + # This plot has 2 axes + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%d %b")) + plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=5)) + + ax1.bar( + df_without_price.index, + df_without_price["Shorts"], + label="Shorts", + color="#d9024b", + ) + + ax1.bar( + df_without_price.index, + df_without_price["Longs"], + label="Longs", + color="#45bf87", + ) + + ax1.get_yaxis().set_major_formatter( + ticker.FuncFormatter(lambda x, _: f"${human_format(x, absolute=True)}") + ) + + ax1.set_title("Total Liquidations") + + # Set price axis + ax2.plot(df_price.index, df_price, color="#edba35", label="BTC Price") + ax2.set_xlim([df_price.index[0], df_price.index[-1]]) + ax2.set_ylim( + bottom=df_price.min().values * 0.95, top=df_price.max().values * 1.05 + ) + ax2.get_yaxis().set_major_formatter(lambda x, _: f"${human_format(x)}") + + # Add combined legend + lines, labels = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + ax2.legend( + lines + lines2, + labels + labels2, + loc="upper center", + fontsize="x-small", + ncol=3, + ) + + # Add gridlines + plt.grid(axis="y", color="grey", linestyle="-.", linewidth=0.5, alpha=0.5) + + # Remove spines + ax1.spines["top"].set_visible(False) + ax1.spines["bottom"].set_visible(False) + ax1.spines["right"].set_visible(False) + ax1.spines["left"].set_visible(False) + ax1.tick_params(left=False, bottom=False, right=False) + + ax2.spines["top"].set_visible(False) + ax2.spines["bottom"].set_visible(False) + ax2.spines["right"].set_visible(False) + ax2.spines["left"].set_visible(False) + ax2.tick_params(left=False, bottom=False, right=False) + + # Fixes first and last bar not showing + ax1.set_xlim( + left=df_without_price.index[0] - datetime.timedelta(days=1), + right=df_without_price.index[-1] + datetime.timedelta(days=1), + ) + ax2.set_xlim( + left=df_without_price.index[0] - datetime.timedelta(days=1), + right=df_without_price.index[-1] + datetime.timedelta(days=1), + ) + + # Set correct size + fig.set_size_inches(15, 6) + + # Convert to plot to a temporary image + file_name = "liquidations.png" + file_path = os.path.join("temp", file_name) + plt.savefig(file_path, bbox_inches="tight", dpi=300) + plt.cla() + plt.close() + + e = discord.Embed( + title="Total Liquidations", + description="", + color=data_sources["coinglass"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + url="https://www.coinglass.com/LiquidationData", + ) + file = discord.File(file_path, filename=file_name) + e.set_image(url=f"attachment://{file_name}") + e.set_footer( + text="\u200b", + icon_url=data_sources["coinglass"]["icon"], + ) + + await self.channel.purge(limit=1) + await self.channel.send(file=file, embed=e) + + # Delete yield.png + os.remove(file_path)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Liquidations(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/losers.html b/_modules/cogs/loops/losers.html new file mode 100644 index 00000000..13fb1832 --- /dev/null +++ b/_modules/cogs/loops/losers.html @@ -0,0 +1,474 @@ + + + + + + + + + + cogs.loops.losers — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.losers

+# Standard libraries
+import traceback
+
+# > 3rd party dependencies
+import yahoo_fin.stock_info as si
+
+# > Discord dependencies
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config
+from util.disc_util import get_channel
+from util.afterhours import afterHours
+from util.formatting import format_embed
+
+
+
+[docs] +class Losers(commands.Cog): + """ + This class contains the cog for posting the top crypto and stocks losers. + It can be enabled / disabled in the config under ["LOOPS"]["LOSERS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["LOSERS"]["STOCKS"]["ENABLED"]: + self.channel = get_channel( + self.bot, + config["LOOPS"]["LOSERS"]["CHANNEL"], + config["CATEGORIES"]["STOCKS"], + ) + self.losers.start() + + @loop(hours=2) + async def losers(self) -> None: + """ + If the market is open, this function posts the top 50 losers for todays stocks. + + Returns + ------- + None + """ + + # Dont send if the market is closed + if afterHours(): + return + + try: + e = await format_embed(si.get_day_losers().head(10), "Losers", "yahoo") + await self.channel.send(embed=e) + except Exception as e: + print("Error getting or posting stock losers, error:", e) + print(traceback.format_exc())
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Losers(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/new_listings.html b/_modules/cogs/loops/new_listings.html new file mode 100644 index 00000000..4c9229cc --- /dev/null +++ b/_modules/cogs/loops/new_listings.html @@ -0,0 +1,602 @@ + + + + + + + + + + cogs.loops.new_listings — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.new_listings

+import asyncio
+import datetime
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data, data_sources
+from util.disc_util import get_channel
+
+
+
+[docs] +class Exchange_Listings: + """ + This class contains the cog for posting the new Binance listings + It can be enabled / disabled in the config under ["LOOPS"]["NEW_LISTINGS"]. + """ + + def __init__(self, bot: commands.Bot, exchange: str) -> None: + self.bot = bot + self.exchange = exchange + self.old_symbols = [] + self.channel = get_channel(self.bot, config["LOOPS"]["NEW_LISTINGS"]["CHANNEL"]) + + asyncio.create_task(self.set_old_symbols()) + self.new_listings.start() + +
+[docs] + async def get_symbols(self) -> list: + """ + Gets the symbols currently listed on the exchange. + + Returns + ------- + list + The symbols currently listed on the exchange + """ + + if self.exchange == "binance": + url = "https://api.binance.com/api/v3/exchangeInfo" + key1 = "symbols" + key2 = "symbol" + elif self.exchange == "kucoin": + url = "https://api.kucoin.com/api/v1/symbols" + key1 = "data" + key2 = "symbol" + elif self.exchange == "coinbase": + url = "https://api.exchange.coinbase.com/currencies" + key2 = "id" + + # Check if there have been new listings + response = await get_json_data(url) + + # Get the symbols + if self.exchange == "coinbase": + return [x[key2] for x in response] + + return [x[key2] for x in response[key1]]
+ + +
+[docs] + def create_embed(self, ticker: str) -> discord.Embed: + """ + Creates a styled embed for the newly listed ticker. + + Parameters + ---------- + ticker : str + The ticker that was newly listed. + + Returns + ------- + discord.embeds.Embed + The styled embed for the newly listed ticker. + """ + + if self.exchange == "binance": + color = data_sources["binance"]["color"] + icon_url = data_sources["binance"]["icon"] + url = f"https://www.{self.exchange}.com/en/trade/{ticker}" + elif self.exchange == "kucoin": + color = data_sources["kucoin"]["color"] + icon_url = data_sources["kucoin"]["icon"] + url = f"https://www.{self.exchange}.com/trade/{ticker}" + else: # Coinbase + color = data_sources["coinbase"]["color"] + icon_url = data_sources["coinbase"]["icon"] + url = f"https://www.pro.{self.exchange}.com/trade/{ticker}" + + e = discord.Embed( + title=f"{self.exchange.capitalize()} Lists {ticker}", + url=url, + description="", + color=color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + # Set datetime and binance icon + e.set_footer(text="\u200b", icon_url=icon_url) + + return e
+ + +
+[docs] + async def set_old_symbols(self) -> None: + """ + Function to set the old symbols from the JSON response. + This will be used to compare the new symbols to the old symbols. + + Returns + ------- + None + """ + + # Set the old symbols + self.old_symbols = await self.get_symbols()
+ + + @loop(hours=6) + async def new_listings(self) -> None: + """ + This function will be called every 6 hours to check for new listings. + It will compare the currently listed symbols with the old symbols. + If there is a difference, it will post a message to the channel. + + Returns + ------- + None + """ + + # Get the symbols + new_symbols = await self.get_symbols() + + new_listings = [] + + if self.old_symbols == []: + await self.set_old_symbols() + + # If there is a new symbol, send a message + if len(new_symbols) > len(self.old_symbols): + new_listings = list(set(new_symbols) - set(self.old_symbols)) + + # If symbols got removed do nothing + if len(new_symbols) < len(self.old_symbols): + # Update old_symbols + self.old_symbols = new_symbols + return + + # Update old_symbols + self.old_symbols = new_symbols + + for ticker in new_listings: + await self.channel.send(embed=self.create_embed(ticker))
+ + + +
+[docs] +class Binance(commands.Cog): + """ + This class contains the cog for posting the new Binance listings + It can be enabled / disabled in the config under ["LOOPS"]["NEW_LISTINGS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + Exchange_Listings(bot, "binance")
+ + + +
+[docs] +class KuCoin(commands.Cog): + """ + This class contains the cog for posting the new KuCoin listings + It can be enabled / disabled in the config under ["LOOPS"]["NEW_LISTINGS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + Exchange_Listings(bot, "kucoin")
+ + + +
+[docs] +class CoinBase(commands.Cog): + """ + This class contains the cog for posting the new CoinBase listings + It can be enabled / disabled in the config under ["LOOPS"]["NEW_LISTINGS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + Exchange_Listings(bot, "coinbase")
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Binance(bot)) + bot.add_cog(KuCoin(bot)) + bot.add_cog(CoinBase(bot)) +
+ +
+ + + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/nfts.html b/_modules/cogs/loops/nfts.html new file mode 100644 index 00000000..216a50b3 --- /dev/null +++ b/_modules/cogs/loops/nfts.html @@ -0,0 +1,909 @@ + + + + + + + + + + cogs.loops.nfts — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.nfts

+## > Imports
+# > Standard library
+import re
+import datetime
+
+# > Third party
+import pandas as pd
+import numpy as np
+from bs4 import BeautifulSoup
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# > Local
+from util.vars import get_json_data, config, data_sources
+from util.disc_util import get_channel
+from util.formatting import format_change
+from util.cg_data import cg
+
+
+
+[docs] +class NFTS(commands.Cog): + """ + This class contains the cog for posting the top NFTs. + It can be configured in the config.yaml file under ["LOOPS"]["NFTS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + if config["LOOPS"]["NFTS"]["ENABLED"]: + if config["LOOPS"]["NFTS"]["TOP"]: + self.top_channel = get_channel( + self.bot, + config["LOOPS"]["NFTS"]["TOP"]["CHANNEL"], + config["CATEGORIES"]["NFTS"], + ) + self.top_nfts.start() + + if config["LOOPS"]["NFTS"]["UPCOMING"]: + self.upcoming_channel = get_channel( + self.bot, + config["LOOPS"]["NFTS"]["UPCOMING"]["CHANNEL"], + config["CATEGORIES"]["NFTS"], + ) + self.upcoming_nfts.start() + + if config["LOOPS"]["NFTS"]["P2E"]: + self.p2e_channel = get_channel( + self.bot, + config["LOOPS"]["NFTS"]["P2E"]["CHANNEL"], + config["CATEGORIES"]["NFTS"], + ) + self.top_p2e.start() + + if config["LOOPS"]["TRENDING"]["NFTS"]: + self.trending_channel = get_channel( + self.bot, + config["LOOPS"]["TRENDING"]["CHANNEL"], + config["CATEGORIES"]["NFTS"], + ) + self.trending_nfts.start() + + @loop(hours=1) + async def top_nfts(self): + opensea_top = await get_opensea() + cmc_top = await top_cmc() + + await self.top_channel.purge(limit=2) + + for df, name in [(opensea_top, "Opensea"), (cmc_top, "CoinMarketCap")]: + if df.empty: + print("No top NFTs found for " + name) + return + + if "symbol" not in df.columns: + return + + if name == "Opensea": + url = "https://opensea.io/rankings" + color = data_sources["opensea"]["color"] + icon_url = data_sources["opensea"]["icon"] + elif name == "CoinMarketCap": + url = "https://coinmarketcap.com/nft/collections/" + color = data_sources["coinmarketcap"]["color"] + icon_url = data_sources["coinmarketcap"]["icon"] + + e = discord.Embed( + title=f"Top {len(df)} {name} NFTs", + url=url, + description="", + color=color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field( + name="NFT", + value="\n".join(df["symbol"].tolist()), + inline=True, + ) + + e.add_field( + name="Price", + value="\n".join(df["price"].tolist()), + inline=True, + ) + + e.add_field( + name="Volume", + value="\n".join(df["volume"].astype(str).tolist()), + inline=True, + ) + + # Set empty text as footer, so we can see the icon + e.set_footer(text="\u200b", icon_url=icon_url) + + await self.top_channel.send(embed=e) + + @loop(hours=1) + async def trending_nfts(self): + await self.trending_channel.purge(limit=2) + + await self.opensea_trending() + await self.gc_trending() + + + + + + + + @loop(hours=1) + async def upcoming_nfts(self): + upcoming = await upcoming_cmc() + + if upcoming.empty: + print("No upcoming NFTs found") + return + + if "symbol" not in upcoming.columns: + return + + upcoming = upcoming.head(10) + + e = discord.Embed( + title=f"Top {len(upcoming)} Upcoming NFTs", + url="https://coinmarketcap.com/nft/upcoming/", + description="", + color=data_sources["coinmarketcap"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field( + name="NFT", + value="\n".join(upcoming["symbol"].tolist()), + inline=True, + ) + + e.add_field( + name="Price", + value="\n".join(upcoming["price"].tolist()), + inline=True, + ) + + e.add_field( + name="Drop Type", + value="\n".join(upcoming["dropType"].tolist()), + inline=True, + ) + e.set_footer(text="\u200b", icon_url=data_sources["coinmarketcap"]["icon"]) + + await self.upcoming_channel.purge(limit=1) + await self.upcoming_channel.send(embed=e) + + @loop(hours=1) + async def top_p2e(self): + p2e = await p2e_games() + + if p2e.empty: + return + + url = "https://playtoearn.net/blockchaingames/All-Blockchain/All-Genre/All-Status/All-Device/NFT/nft-crypto-PlayToEarn/nft-required-FreeToPlay" + + e = discord.Embed( + title=f"Top {len(p2e)} Blockchain Games", + url=url, + description="", + color=data_sources["playtoearn"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field( + name="Game", + value="\n".join(p2e["name"].tolist()), + inline=True, + ) + + e.add_field( + name="Social 24h", + value="\n".join(p2e["social"].tolist()), + inline=True, + ) + + e.add_field( + name="Status", + value="\n".join(p2e["status"].tolist()), + inline=True, + ) + + e.set_footer( + text="\u200b", + icon_url=data_sources["playtoearn"]["icon"], + ) + + await self.p2e_channel.purge(limit=1) + await self.p2e_channel.send(embed=e)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(NFTS(bot)) + + +
+[docs] +async def get_opensea(url=""): + """ + _summary_ + + Parameters + ---------- + url : str, optional + Can be either "trending" or empty, by default "" + + Returns + ------- + _type_ + _description_ + """ + + html_doc = await get_json_data( + f"https://opensea.io/rankings/{url}", + headers={"User-Agent": "Mozilla/5.0"}, + text=True, + ) + + html_doc = html_doc[html_doc.find(':pageInfo"}},') + len(':pageInfo"}},') :] + html_doc = html_doc[: html_doc.find(":edges:10")] + + rows = html_doc.split('"node":{') + + opensea_nfts = [] + + for row in rows[1:]: + nft_dict = {} + + name = re.search(r"\"name\":\"(.*?)\"", row).group(1) + slug = re.search(r"\"slug\":\"(.*?)\"", row) + + if slug: + slug = slug.group(1) + else: + slug = "" + + price_data = re.findall(r"\"unit\":\"(.*?)\"", row) + change = re.search(r"\"volumeChange\":(.*?),", row) + symbol = re.search(r"\"symbol\":\"(.*?)\"", row).group(1) + + if len(price_data) == 2: + floor_price = f"{round(float(price_data[0]),3)} {symbol}" + volume = price_data[1] + else: + floor_price = "?" + volume = price_data[0] + + volume = f"{int(float(volume))} {symbol}" + change = float(change.group(1)) * 100 + + if change != 0: + if change > 1: + change = int(change) + else: + change = round(change, 2) + volume = f"{volume} ({format_change(change)})" + + nft_dict["symbol"] = f"[{name}](https://opensea.io/collection/{slug})" + nft_dict["price"] = floor_price + nft_dict["volume"] = volume + + opensea_nfts.append(nft_dict) + + return pd.DataFrame(opensea_nfts)
+ + + +
+[docs] +async def top_cmc(): + data = await get_json_data( + "https://api.coinmarketcap.com/nft/v3/nft/collectionsv2?start=0&limit=100&category=&collection=&blockchain=&sort=volume&desc=true&period=1" + ) + + # Convert to dataframe + df = pd.DataFrame(data["data"]["collections"]) + + df = df.head(10) + + # Unpack all oneDay data + df = pd.concat([df.drop(["oneDay"], axis=1), df["oneDay"].apply(pd.Series)], axis=1) + + # name, url, price, volume, volume change + # Conditionally concatenate "name" and "website" only when "website" is not NaN + df["symbol"] = np.where( + df["website"].notna() & (df["website"] != ""), + "[" + df["name"] + "]" + "(" + df["website"] + ")", + df["name"], + ) + df["price"] = df["floorPriceUsd"].apply(lambda x: f"${x:,.2f}") + df["change"] = df["averagePriceChangePercentage"].apply(lambda x: format_change(x)) + df["price"] = df["price"] + " (" + df["change"] + ")" + df["volume"] = df["volume"].apply(lambda x: f"{x:,.0f} ETH") + df["volume_change"] = df["volumeChangePercentage"].apply(lambda x: format_change(x)) + df["volume"] = df["volume"] + " (" + df["volume_change"] + ")" + + return df
+ + + +
+[docs] +async def upcoming_cmc(): + # Could remove category and expire from URL + data = await get_json_data( + "https://api.coinmarketcap.com/nft/v3/nft/upcoming-drops?start=0&limit=20&category=Popular&expire=30" + ) + + # Convert the data to a pandas DataFrame + df = pd.DataFrame(data["data"]["data"]) + + df = df.head(10) + + # name, websiteUrl, price, dropDate + # Filter out the columns that actually exist in the DataFrame + existing_columns = [ + col for col in ["name", "websiteUrl", "price", "dropType"] if col in df.columns + ] + + # Use only the existing columns to filter the DataFrame + df = df[existing_columns] + + # Use same method as #events channel time + # Rename to start_time + # df["start_time"] = df["dropDate"].apply( + # lambda x: f"<t:{int(x/1000)}:d>" if pd.notnull(x) else "" + # ) + + # Conditionally concatenate "name" and "website" only when "website" is not NaN + df["symbol"] = np.where( + df["websiteUrl"].notna() & (df["websiteUrl"] != ""), + "[" + df["name"] + "]" + "(" + df["websiteUrl"] + ")", + df["name"], + ) + + return df
+ + + +
+[docs] +async def p2e_games(): + URL = "https://playtoearn.net/blockchaingames/All-Blockchain/All-Genre/All-Status/All-Device/NFT/nft-crypto-PlayToEarn/nft-required-FreeToPlay" + + html = await get_json_data(URL, text=True) + soup = BeautifulSoup(html, "html.parser") + items = soup.find("table", class_="table table-bordered mainlist") + + if items is None: + return pd.DataFrame() + + allItems = items.find_all("tr") + + p2e_games = [] + + # Skip header + ad + iterator = 2 + for iterator in range(2, 12): + data = {} + + allItems_td = allItems[iterator].find_all("td") + if len(allItems_td) < 11: + continue + + name = allItems_td[2].find("div", class_="dapp_name").find_next("span").text + url = allItems_td[2].find_next("a")["href"] + status = allItems_td[6].get_text("title") + social_24h_change = allItems_td[10].find_all("span") + social_24h = social_24h_change[0].text + if len(social_24h_change) > 1: + social_change = social_24h_change[1].text.replace("%", "").replace(",", "") + else: + social_change = 0 + + data["name"] = f"[{name}]({url})" + data["status"] = status + data["social"] = f"{social_24h} ({format_change(float(social_change))})" + + p2e_games.append(data) + + return pd.DataFrame(p2e_games)
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/option_alert.html b/_modules/cogs/loops/option_alert.html new file mode 100644 index 00000000..0a0a18d6 --- /dev/null +++ b/_modules/cogs/loops/option_alert.html @@ -0,0 +1,711 @@ + + + + + + + + + + cogs.loops.option_alert — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.option_alert

+# Standard libraries
+import datetime
+import pandas as pd
+import time
+import inspect
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+import util.vars
+from util.vars import config, get_json_data
+from util.disc_util import get_channel, get_tagged_users, get_guild
+from util.afterhours import afterHours
+from util.db import clean_old_db, merge_and_update
+from cogs.loops.options import get_UW_data
+
+
+
+[docs] +class Option_alert(commands.Cog): + """ + This class contains the cog for posting the latest Unusual Whales alerts. + It can be enabled / disabled in the config under ["LOOPS"]["UNUSUAL_WHALES"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.emoji_dict = {} + self.guild = get_guild(bot) + + if config["LOOPS"]["OPTION_ALERT"]["ENABLED"]: + self.alerts_channel = get_channel( + self.bot, config["LOOPS"]["OPTION_ALERT"]["CHANNEL"] + ) + + self.overview_channel = get_channel( + self.bot, + config["LOOPS"]["OPTION_ALERT"]["OVERVIEW_CHANNEL"], + config["CATEGORIES"]["OPTIONS"], + ) + + self.token = config["LOOPS"]["OPTION_ALERT"]["TOKEN"] + self.alerts.start() + + @loop(minutes=5) + async def alerts(self) -> None: + """ + This function posts the Unusual Whales alerts on Discord. + + Returns + ------- + None + """ + + # Check if the market is open + if afterHours(): + return + + # Get the emojis if not already done + if self.emoji_dict == {}: + # Get the emojis and store them in emoji_dict + self.emoji_dict = await get_json_data( + "https://phx.unusualwhales.com/api/tags/all", + { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" + }, + ) + + # start_date and expiry_start_data depends on how often the function is called + last_5_min = int((time.time() - (5 * 60)) * 1000) + + # Check the last 5 minutes on the API + url = f"https://phx.unusualwhales.com/api/option_quotes?offset=0&sort=timestamp&search=&sector=&tag=&end_date=9999999999999&start_date={last_5_min}&expiry_start_date={last_5_min}&expiry_end_date=9999999999999&min_ask=0&max_ask=9999999999999&volume_direction=desc&expiry_direction=desc&alerted_direction=desc&oi_direction=desc&normal=true" + + # Use the token in the header + headers = { + "authorization": self.token, + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", + } + + df = await get_UW_data(url, headers) + + if df.empty: + return + + # Only keep the important information + df = df[ + [ + "alert_time", + "id", + "ticker_symbol", + "option_type", + "strike_price", # Also named underlying + "expires_at", + "stock_price", + "bid", + "ask", + "min_ask", + "max_ask", + "volume", + "implied_volatility", + "sector", + "tags", + "tier", + "is_recommended", + "open_interest", + "delta", + "theta", + ] + ] + + # Calculate the percentual difference between current price and strike price + df["strike_price"] = df["strike_price"].astype(float) + df["stock_price"] = df["stock_price"].astype(float) + df["difference"] = ( + (df["strike_price"] - df["stock_price"]) / df["stock_price"] * 100 + ) + df["difference"] = df["difference"].round(2) + df["difference"] = df["difference"].astype(str) + "%" + + # Convert IV to percent + df["implied_volatility"] = df["implied_volatility"].astype(float) + df["IV"] = df["implied_volatility"] * 100 + df["IV"] = df["IV"].round(2) + df["IV"] = df["IV"].astype(str) + "%" + + # Round theta and delta + df["theta"] = df["theta"].astype(float) + df["theta"] = df["theta"].round(3) + df["delta"] = df["delta"].astype(float) + df["delta"] = df["delta"].round(3) + + # For each ticker in the df send a message + for _, row in df.iterrows(): + # Only use the first letter of the option type + option_type = row["option_type"][0].upper() + + emojis = "" + for tag in row["tags"]: + try: + emojis += self.emoji_dict[tag]["emoji"] + # In case emoji_dict is empty + except KeyError: + print(f"Could not find emoji for {tag}") + + # Create the embed + e = discord.Embed( + title=f"${row['ticker_symbol']} {row['expires_at']} {option_type} ${row['strike_price']}", + url=f"https://unusualwhales.com/alerts/{row['id']}", + # Use inspect.cleandoc() to remove the indentation + description=inspect.cleandoc( + f""" + {emojis} + Bid-Ask: ${row['bid']} - ${row['ask']} + Interest: {row['open_interest']} + Volume: {row['volume']} + IV: {row['IV']} + % Diff: {row["difference"]} + Underlying: ${row['stock_price']} + Θ | Δ: {row['theta']} | {row['delta']} + Sector: {row['sector']} + Tier: {row['tier']} + Recommended: {row['is_recommended']} + {emojis} + """ + ), + color=0xE40414 if option_type == "P" else 0x3CC474, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.set_footer( + # Use the time the alert was created in the footer + text=f"Alerted at {row['alert_time']}", + icon_url="https://docs.unusualwhales.com/images/banner.png", + ) + + await self.alerts_channel.send( + content=get_tagged_users([row["ticker_symbol"]]), embed=e + ) + + # Add the data to the database + update_options_db( + row["ticker_symbol"], + row["expires_at"], + option_type, + row["strike_price"], + row["volume"], + emojis, + ) + + await self.options_overview() + +
+[docs] + async def options_overview(self): + if util.vars.options_db.empty: + return + + # Gather the data for the summary + num_calls = len( + util.vars.options_db[util.vars.options_db["option_type"] == "C"] + ) + num_puts = len(util.vars.options_db[util.vars.options_db["option_type"] == "P"]) + + num_bears = len( + util.vars.options_db[util.vars.options_db["bull/bear"] == "bear"] + ) + num_bulls = len( + util.vars.options_db[util.vars.options_db["bull/bear"] == "bull"] + ) + + # Top row of the embed shows an summary of P/C ratio, Bullish/Bearish + e = discord.Embed( + title=f"Options Overview", + description="", + color=self.guild.self_role.color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + most_mentioned = util.vars.options_db["ticker"].value_counts() + + e.add_field(name="Calls - Puts", value=f"{num_calls} - {num_puts}", inline=True) + e.add_field( + name="Bullish - Bearish", value=f"{num_bulls} - {num_bears}", inline=True + ) + e.add_field( + name="Most Active Ticker", + value=f"{most_mentioned.index[0]} ({most_mentioned.iloc[0]})", + inline=True, + ) + + # Sort by volume + util.vars.options_db["volume"] = util.vars.options_db["volume"].astype(int) + util.vars.options_db = util.vars.options_db.sort_values( + by=["volume"], ascending=False + ) + + # First show the top 10 bullish options, ranked by count and volume + bullish = util.vars.options_db[util.vars.options_db["bull/bear"] == "bull"] + + # Then show the top 10 bearish options + bearish = util.vars.options_db[util.vars.options_db["bull/bear"] == "bear"] + + if not bullish.empty: + bull_counts, bull_options, bull_volumes = self.get_top20(bullish) + e.add_field(name="Bullish", value="\n".join(bull_counts), inline=True) + e.add_field(name="Options", value="\n".join(bull_options), inline=True) + e.add_field(name="Volume", value="\n".join(bull_volumes), inline=True) + + if not bearish.empty: + bear_counts, bear_options, bear_volumes = self.get_top20(bearish) + e.add_field(name="Bearish", value="\n".join(bear_counts), inline=True) + e.add_field(name="Options", value="\n".join(bear_options), inline=True) + e.add_field(name="Volume", value="\n".join(bear_volumes), inline=True) + + await self.overview_channel.purge(limit=1) + await self.overview_channel.send(embed=e)
+ + +
+[docs] + def get_top20(self, df): + counts = [] + options = [] + volumes = [] + + for _, row in df.head(20).iterrows(): + counts.append(f"{row['ticker']}") + options.append( + f"{row['expiration']} {row['option_type']} ${row['strike_price']}" + ) + volumes.append(str(row["volume"])) + + return counts, options, volumes
+
+ + + +
+[docs] +def update_options_db(ticker, expiration, option_type, strike, volume, emojis): + if "🐻" in emojis: + emoji = "bear" + elif "🐂" in emojis: + emoji = "bull" + else: + emoji = "none" + + option_dict = { + "ticker": ticker, + "expiration": expiration, + "option_type": option_type, + "strike_price": strike, + "volume": volume, + "bull/bear": emoji, + } + + # Convert it to a dataframe + option_db = pd.DataFrame([option_dict]) + + # Add timestamp + option_db["timestamp"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Clean the old db + util.vars.options_db = clean_old_db(util.vars.options_db, 1) + util.vars.options_db = merge_and_update(util.vars.options_db, option_db, "options")
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Option_alert(bot)) +
+ +
+ + + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/options.html b/_modules/cogs/loops/options.html new file mode 100644 index 00000000..9caecbb9 --- /dev/null +++ b/_modules/cogs/loops/options.html @@ -0,0 +1,615 @@ + + + + + + + + + + cogs.loops.options — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.options

+# Standard libraries
+import datetime
+import pandas as pd
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data, data_sources
+from util.disc_util import get_channel, get_guild
+from util.formatting import human_format
+
+
+
+[docs] +async def get_UW_data(url, overwrite_headers=None, last_15min=False): + if not overwrite_headers: + headers = { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", + } + else: + headers = overwrite_headers + + data = await get_json_data(url, headers) + df = pd.DataFrame(data) + + if df.empty: + return df + + # Get the timestamp convert to datetime and local time + df["alert_time"] = pd.to_datetime(df["timestamp"], utc=True) + + # Filter df on last 15 minutes + if last_15min: + df = df[ + df["alert_time"] + > datetime.datetime.now(datetime.timezone.utc) + - datetime.timedelta(minutes=15) + ] + + if df.empty: + return df + + df["alert_time"] = df["alert_time"].dt.tz_convert( + datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo + ) + + # Format to string + df["alert_time"] = df["alert_time"].dt.strftime("%I:%M %p") + + return df
+ + + +
+[docs] +class Options(commands.Cog): + """ + This class contains the cog for posting the latest Unusual Whales alerts. + It can be enabled / disabled in the config under ["LOOPS"]["UNUSUAL_WHALES"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.guild = get_guild(bot) + + self.volume_channel = get_channel( + self.bot, config["LOOPS"]["OPTIONS"]["VOLUME_CHANNEL"] + ) + + self.spacs_channel = get_channel( + self.bot, config["LOOPS"]["OPTIONS"]["SPACS_CHANNEL"] + ) + + self.shorts_channel = get_channel( + self.bot, config["LOOPS"]["OPTIONS"]["SHORTS_CHANNEL"] + ) + + self.volume.start() + self.spacs.start() + self.shorts.start() + +
+[docs] + def make_UW_embed(self, row): + e = discord.Embed( + title=f"${row['ticker_symbol']}", + url=f"https://unusualwhales.com/stock/{row['ticker_symbol']}", + description="", + color=self.guild.self_role.color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field( + name="Volume", value=f"${human_format(float(row['volume']))}", inline=True + ) + e.add_field( + name="Average 30d Volume", + value=f"${human_format(float(row['avg_volume_last_30_days']))}", + inline=True, + ) + e.add_field( + name="Volume Deviation", + value=f"{round(float(row['volume_dev_from_norm']))}", + inline=True, + ) + e.add_field(name="Price", value=f"${row['bid_price']}", inline=True) + + e.set_footer( + # Use the time the alert was created in the footer + text=f"Alerted at {row['alert_time']}", + icon_url=data_sources["unusualwhales"]["icon"], + ) + + return e
+ + + @loop(minutes=15) + async def volume(self): + url = "https://phx.unusualwhales.com/api/stock_feed" + df = await get_UW_data(url, last_15min=True) + + if not df.empty: + # Iterate over each row and post the alert + for _, row in df.iterrows(): + e = self.make_UW_embed(row) + await self.volume_channel.send(embed=e) + + @loop(minutes=15) + async def spacs(self): + url = "https://phx.unusualwhales.com/api/warrant_alerts" + df = await get_UW_data(url, last_15min=True) + + if not df.empty: + # Iterate over each row and post the alert + for _, row in df.iterrows(): + e = self.make_UW_embed(row) + await self.spacs_channel.send(embed=e) + + @loop(hours=24) + async def shorts(self): + url = "https://phx.unusualwhales.com/api/short_interest_low" + headers = { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", + } + + data = await get_json_data(url, headers) + df = pd.DataFrame(data["data"]) + + if df.empty: + return + + # Cast to float + df["short_interest"] = df["short_interest"].astype(float) + + # Sort on short_interest + df = df.sort_values(by="short_interest", ascending=False) + df["float_shares"] = df["float_shares"].astype(float) + df["float_shares"] = df["float_shares"].apply(lambda x: human_format(x)) + + df["outstanding"] = df["outstanding"].astype(float) + df["outstanding"] = df["outstanding"].apply(lambda x: human_format(x)) + + df["short_interest"] = df["short_interest"].astype(str) + + # Combine both in 1 string + df["float - outstanding"] = df["float_shares"] + " - " + df["outstanding"] + + top20 = df.head(20) + + symbols = "\n".join(top20["symbol"].tolist()) + float_oustanding = "\n".join(top20["float - outstanding"].tolist()) + short_interest = "\n".join(top20["short_interest"].tolist()) + + # Only show the top 20 as embed + e = discord.Embed( + title=f"Top Short Interest Reported", + url=f"https://unusualwhales.com/shorts", + description="", + color=self.guild.self_role.color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.set_footer( + # Use the time the alert was created in the footer + text="\u200b", + icon_url=data_sources["unusualwhales"]["icon"], + ) + + e.add_field(name="Symbol", value=symbols, inline=True) + e.add_field(name="Float - Outstanding", value=float_oustanding, inline=True) + e.add_field(name="Short Interest", value=short_interest, inline=True) + + await self.shorts_channel.purge(limit=1) + await self.shorts_channel.send(embed=e)
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Options(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/overview.html b/_modules/cogs/loops/overview.html new file mode 100644 index 00000000..cbedd3e6 --- /dev/null +++ b/_modules/cogs/loops/overview.html @@ -0,0 +1,641 @@ + + + + + + + + + + cogs.loops.overview — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.overview

+## > Imports
+# > Standard libraries
+from collections import Counter
+from collections import defaultdict
+import datetime
+
+# > Discord dependencies
+import discord
+from discord.ext.tasks import loop
+
+# Local dependencies
+import util.vars
+from util.vars import config, get_json_data
+from util.disc_util import get_channel, get_guild
+from util.formatting import format_change
+
+text_to_emoji = defaultdict(lambda: "🦆", {"bear": "🐻", "bull": "🐂", "neutral": "🦆"})
+
+
+
+[docs] +class Overview: + """ + This class contains the cog for posting the top crypto and stocks mentions. + It can be configured in the config.yaml file under ["LOOPS"]["OVERVIEW"]. + """ + + def __init__(self, bot): + self.bot = bot + self.guild = get_guild(bot) + self.global_crypto = {} + self.global_stocks = {} + + self.global_overview.start() + + if config["LOOPS"]["OVERVIEW"]["STOCKS"]["ENABLED"]: + self.stocks_channel = get_channel( + self.bot, + config["LOOPS"]["OVERVIEW"]["CHANNEL"], + config["CATEGORIES"]["STOCKS"], + ) + self.do_stocks = True + else: + self.do_stocks = False + + if config["LOOPS"]["OVERVIEW"]["CRYPTO"]["ENABLED"]: + self.crypto_channel = get_channel( + self.bot, + config["LOOPS"]["OVERVIEW"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + ) + self.do_crypto = True + else: + self.do_crypto = False + +
+[docs] + async def overview(self, category, tickers, sentiment): + # Make sure that the new db is not empty + if not util.vars.tweets_db.empty: + if self.do_stocks and category == "stocks": + await self.make_overview(category, tickers, sentiment) + if self.do_crypto and category == "crypto": + await self.make_overview(category, tickers, sentiment)
+ + + @loop(minutes=5) + async def global_overview(self): + if util.vars.tweets_db.empty: + return + + categories = [] + if self.do_stocks: + categories.append("stocks") + if self.do_crypto: + categories.append("crypto") + + for category in categories: + db = util.vars.tweets_db.loc[util.vars.tweets_db["category"] == category] + + if db.empty: + return + + # Get the top 50 mentions + top50 = db["ticker"].value_counts()[:50] + + for ticker, _ in top50.items(): + # Get the global tweets about the ticker using the API + if category == "stocks": + global_mentions = None # await count_tweets(ticker) + if global_mentions is not None: + self.global_stocks[ticker] = global_mentions + elif category == "crypto": + global_mentions = None # await count_tweets(ticker) + if global_mentions is not None: + self.global_crypto[ticker] = await count_tweets(ticker) + +
+[docs] + async def make_overview(self, category: str, tickers: list, last_sentiment: str): + # Post the overview for stocks and crypto + db = util.vars.tweets_db.loc[util.vars.tweets_db["category"] == category] + + if db.empty: + return + + # Get the top 50 mentions + top50 = db["ticker"].value_counts()[:50] + + # Make the list for embeds + count_list = [] + ticker_list = [] + sentiment_list = [] + + # Add overview of sentiment for each ticker + for ticker, count in top50.items(): + # Get the sentiment for the ticker + sentiment = db.loc[db["ticker"] == ticker]["sentiment"].tolist() + + change = db.loc[db["ticker"] == ticker]["change"].tolist()[0] + change = change.replace("%", "").replace("+", "") + + try: + change = format_change(float(change)) + except ValueError: + change = "" # Do not specify it + + # Convert sentiment into a single str, i.e. "6🐂 2🦆 2🐻" + sentiment = [text_to_emoji[sent] for sent in sentiment] + sentiment = dict(Counter(sentiment)) + + formatted_sentiment = "" + # Use this method to sort the dict + for emoji in ["🐂", "🦆", "🐻"]: + if emoji in sentiment.keys(): + if emoji == last_sentiment and ticker in tickers: + formatted_sentiment += f"**{sentiment[emoji]}**{emoji} " + else: + formatted_sentiment += f"{sentiment[emoji]}{emoji} " + + if category == "stocks": + if ticker in self.global_stocks.keys(): + count = f"{count} - {self.global_stocks[ticker]}" + + if category == "crypto": + if ticker in self.global_crypto.keys(): + count = f"{count} - {self.global_crypto[ticker]}" + + if ticker in tickers: + # Make bold + ticker = f"**{ticker} ({change})**" + count = f"**{count}**" + else: + ticker = f"{ticker} ({change})" + + # Add count, symbol, sentiment to embed lists + count_list.append(str(count)) + ticker_list.append(ticker) + sentiment_list.append(formatted_sentiment) + + # Make the embed + e = discord.Embed( + title=f"Top {category.capitalize()} Mentions Of The Last 24 Hours", + description="", + color=self.guild.self_role.color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.add_field( + name="Mentions", + value="\n".join(count_list), + inline=True, + ) + + e.add_field( + name="Ticker", + value="\n".join(ticker_list), + inline=True, + ) + + e.add_field( + name="Sentiment", + value="\n".join(sentiment_list), + inline=True, + ) + + if category == "crypto": + # Delete previous message + await self.crypto_channel.purge(limit=1) + await self.crypto_channel.send(embed=e) + else: + await self.stocks_channel.purge(limit=1) + await self.stocks_channel.send(embed=e)
+
+ + + +
+[docs] +async def count_tweets(ticker: str) -> int: + """ + Counts the number of tweets for a ticker during the last 24 hours. + https://developer.twitter.com/en/docs/twitter-api/tweets/counts/api-reference/get-tweets-counts-recent + Max 300 requests per 15 minutes, so 20 requests per minute. + + Parameters + ---------- + ticker : str + The ticker to count the tweets for. + + Returns + ------- + int + Returns the number of tweets for the ticker. + """ + + # Count the last 24 hours + # Can add -is:retweet in query param to exclude retweets + start_time = ( + datetime.datetime.utcnow() - datetime.timedelta(days=1) + ).isoformat() + "Z" + url = f"https://api.twitter.com/2/tweets/counts/recent?query={ticker}&granularity=day&start_time={start_time}" + counts = await get_json_data(url=url, headers={"Authorization": f"Bearer {None}"}) + + if "meta" in counts.keys(): + if "total_tweet_count" in counts["meta"].keys(): + return counts["meta"]["total_tweet_count"]
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/reddit.html b/_modules/cogs/loops/reddit.html new file mode 100644 index 00000000..3c968de0 --- /dev/null +++ b/_modules/cogs/loops/reddit.html @@ -0,0 +1,623 @@ + + + + + + + + + + cogs.loops.reddit — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.reddit

+# Standard libraries
+import datetime
+import html
+import os
+
+# > 3rd party dependencies
+import asyncpraw
+import pandas as pd
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+import util.vars
+from util.vars import config, data_sources
+from util.disc_util import get_channel, get_webhook
+from util.db import update_db
+
+
+
+[docs] +class Reddit(commands.Cog): + """ + This class contains the cog for posting the top reddit posts. + It can be enabled / disabled in the config under ["LOOPS"]["REDDIT"]. + """ + + def __init__(self, bot: commands.bot.Bot) -> None: + self.bot = bot + + reddit = asyncpraw.Reddit( + client_id=os.getenv("REDDIT_PERSONAL_USE"), + client_secret=os.getenv("REDDIT_SECRET"), + user_agent=os.getenv("REDDIT_APP_NAME"), + username=os.getenv("REDDIT_USERNAME"), + password=os.getenv("REDDIT_PASSWORD"), + ) + + if config["LOOPS"]["REDDIT"]["WALLSTREETBETS"]["ENABLED"]: + self.channel = get_channel( + self.bot, config["LOOPS"]["REDDIT"]["WALLSTREETBETS"]["CHANNEL"] + ) + + self.wsb.start(reddit) + +
+[docs] + def add_id_to_db(self, id: str) -> None: + """ + Adds the given id to the database. + """ + + util.vars.reddit_ids = pd.concat( + [ + util.vars.reddit_ids, + pd.DataFrame( + [ + { + "id": id, + "timestamp": datetime.datetime.now(), + } + ] + ), + ], + ignore_index=True, + )
+ + + @loop(hours=12) + async def wsb(self, reddit: asyncpraw.Reddit) -> None: + """ + Scrapes the top reddit posts from the wallstreetbets subreddit and posts them in the wallstreetbets channel. + + Parameters + ---------- + reddit : asyncpraw.Reddit + The reddit instance using the bot's credentials. + + Returns + ------- + None + """ + + if not util.vars.reddit_ids.empty: + # Set the types + util.vars.reddit_ids = util.vars.reddit_ids.astype( + { + "id": str, + "timestamp": "datetime64[ns]", + } + ) + + # Only keep ids that are less than 72 hours old + util.vars.reddit_ids = util.vars.reddit_ids[ + util.vars.reddit_ids["timestamp"] + > datetime.datetime.now() - datetime.timedelta(hours=72) + ] + + subreddit = await reddit.subreddit("WallStreetBets") + try: + counter = 1 + + # https://asyncpraw.readthedocs.io/en/stable/code_overview/models/submission.html?highlight=poll_data#asyncpraw.models.Submission + async for submission in subreddit.hot(limit=15): + # Skip stickied posts + if submission.stickied: + continue + + if not util.vars.reddit_ids.empty: + if submission.id in util.vars.reddit_ids["id"].tolist(): + counter += 1 + continue + + # If it is a new submission add it to the db + self.add_id_to_db(submission.id) + + descr = html.unescape(submission.selftext) + + # Make sure the description and title are not too long + if len(descr) > 4000: + descr = descr[:4000] + "..." + + title = html.unescape(submission.title) + if len(title) > 250: + title = title[:250] + "..." + + # Add images to the embed + img_url = [] + video = False + if not submission.is_self: + url = submission.url + if ( + url.endswith(".jpg") + or url.endswith(".png") + or url.endswith(".gif") + ): + img_url.append(url) + elif "gallery" in url: + image_dict = submission.media_metadata + for image_item in image_dict.values(): + largest_image = image_item["s"] + img_url.append(largest_image["u"]) + elif "v.redd.it" in url: + video = True + descr = "" + + if descr == "" and not video and not img_url: + continue + + e = discord.Embed( + title=title, + url="https://www.reddit.com" + submission.permalink, + description=descr, + color=data_sources["reddit"]["color"], + timestamp=datetime.datetime.utcfromtimestamp( + submission.created_utc + ), + ) + if img_url: + e.set_image(url=img_url[0]) + + # e.set_thumbnail( + # url="https://styles.redditmedia.com/t5_2th52/styles/communityIcon_wzrl8s0hx8a81.png?width=256&s=dcbf830170c1e8237335a3f046b36f723c5d55e7" + # ) + + e.set_footer( + text=f"🔼 {submission.score} | 💬 {submission.num_comments}", + icon_url=data_sources["reddit"]["icon"], + ) + + if len(img_url) > 1: + # Create a list of image embeds, max 10 images per post + image_e = [e] + [ + discord.Embed(url=e.url).set_image(url=img) + for img in img_url[1:10] + ] + + webhook = await get_webhook(self.channel) + + await webhook.send( + embeds=image_e, + username="FinTwit", + wait=True, + avatar_url=self.bot.user.avatar.url, + ) + else: + if video: + await self.channel.send( + content=f"https://www.reddit.com{submission.permalink}" + ) + else: + await self.channel.send(embed=e) + + counter += 1 + + if counter == 11: + break + + # Write to db + update_db(util.vars.reddit_ids, "reddit_ids") + + except Exception as e: + print("Error getting reddit posts, error:", e)
+ + + +def setup(bot: commands.bot.Bot) -> None: + bot.add_cog(Reddit(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/stock_halts.html b/_modules/cogs/loops/stock_halts.html new file mode 100644 index 00000000..7e6292ea --- /dev/null +++ b/_modules/cogs/loops/stock_halts.html @@ -0,0 +1,581 @@ + + + + + + + + + + cogs.loops.stock_halts — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.stock_halts

+import datetime
+from io import StringIO
+
+import pandas as pd
+from dateutil import tz
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, post_json_data, data_sources
+from util.disc_util import get_channel, get_tagged_users
+from util.afterhours import afterHours
+
+
+
+[docs] +class StockHalts(commands.Cog): + """ + This class contains the cog for posting the halted stocks. + It can be configured in the config.yaml file under ["LOOPS"]["STOCK_HALTS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.channel = get_channel(self.bot, config["LOOPS"]["STOCK_HALTS"]["CHANNEL"]) + + self.halt_embed.start() + + @loop(minutes=15) + async def halt_embed(self): + # Dont send if the market is closed + if afterHours(): + return + + # Get the data + html = await self.get_halt_data() + + if html == {}: + return + + # Remove previous message first + await self.channel.purge(limit=1) + + df = self.format_df(html) + + # Create embed + e = discord.Embed( + title=f"Halted Stocks", + url="https://www.nasdaqtrader.com/trader.aspx?id=tradehalts", + description="", + color=data_sources["nasdaqtrader"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + # Get the values as string + time = "\n".join(df["Time"].to_list()) + symbol = "\n".join(df["Issue Symbol"].to_list()) + + # Add the values to the embed + e.add_field(name="Time", value=time, inline=True) + e.add_field(name="Symbol", value=symbol, inline=True) + + if "Resumption Time" in df.columns: + resumption = "\n".join(df["Resumption Time"].to_list()) + e.add_field(name="Resumption Time", value=resumption, inline=True) + + e.set_footer( + text="\u200b", + icon_url=data_sources["nasdaqtrader"]["icon"], + ) + + tags = get_tagged_users(df["Issue Symbol"].to_list()) + + await self.channel.send(content=tags, embed=e) + +
+[docs] + def format_df(self, html): + df = pd.read_html(StringIO(html["result"]))[0] + + # Drop NaN columns + df = df.dropna(axis=1, how="all") + + # Drop columns where halt date is not today + df = df[df["Halt Date"] == pd.Timestamp.today().strftime("%m/%d/%Y")] + + # Combine columns into one singular datetime column + df["Time"] = df["Halt Date"] + " " + df["Halt Time"] + df["Time"] = pd.to_datetime(df["Time"], format="%m/%d/%Y %H:%M:%S") + + # Do for resumption as well if the column is not NaN + if "Resumption Date" in df.columns and "Resumption Trade Time" in df.columns: + # Combine columns into one singular datetime column + df["Resumption Time"] = ( + df["Resumption Date"] + " " + df["Resumption Trade Time"] + ) + df["Resumption Time"] = pd.to_datetime( + df["Resumption Time"], format="%m/%d/%Y %H:%M:%S" + ) + + df["Resumption Time"] = ( + df["Resumption Time"] + .dt.tz_localize("US/Eastern") + .dt.tz_convert(tz.tzlocal()) + ) + + df["Resumption Time"] = df["Resumption Time"].dt.strftime("%H:%M:%S") + + # Convert to my own timezone + df["Time"] = df["Time"].dt.tz_localize("US/Eastern").dt.tz_convert(tz.tzlocal()) + + # Convert times to string + df["Time"] = df["Time"].dt.strftime("%H:%M:%S") + + # Replace NaN with ? + df = df.fillna("?") + + # Keep the necessary columns + if "Resumption Time" in df.columns: + df = df[["Time", "Issue Symbol", "Resumption Time"]] + else: + df = df[["Time", "Issue Symbol"]] + + return df
+ + +
+[docs] + async def get_halt_data(self) -> dict: + # Based on https://github.com/reorx/nasdaqtrader-rss/blob/e675af99ace7d384950d6c75144e9efb1d80b5a7/rss/index.py#L18 + headers = { + "Content-Type": "application/json", + "Origin": "https://www.nasdaqtrader.com", + "Referer": "https://www.nasdaqtrader.com/trader.aspx?id=tradehalts", + "Sec-Ch-Ua": '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"', + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + } + req_data = { + "id": 3, + "method": "BL_TradeHalt.GetTradeHalts", + "params": "[]", + "version": "1.1", + } + + html = await post_json_data( + "https://www.nasdaqtrader.com/RPCHandler.axd", + headers=headers, + json=req_data, + ) + + # Convert to DataFrame + return html
+
+ + + +def setup(bot: commands.Bot) -> None: + """ + This is a necessary method to make the cog loadable. + + Returns + ------- + None + """ + bot.add_cog(StockHalts(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/stocktwits.html b/_modules/cogs/loops/stocktwits.html new file mode 100644 index 00000000..6e096304 --- /dev/null +++ b/_modules/cogs/loops/stocktwits.html @@ -0,0 +1,556 @@ + + + + + + + + + + cogs.loops.stocktwits — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.stocktwits

+## > Imports
+# > Standard libraries
+import datetime
+
+# > 3rd party dependencies
+import pandas as pd
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data, data_sources
+from util.disc_util import get_channel
+
+
+
+[docs] +class StockTwits(commands.Cog): + """ + This class contains the cog for posting the most discussed StockTwits tickers. + It can be enabled / disabled in the config under ["LOOPS"]["STOCKTWITS"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.channel = get_channel(self.bot, config["LOOPS"]["STOCKTWITS"]["CHANNEL"]) + + self.stocktwits.start() + +
+[docs] + async def get_data(self, e: discord.Embed, keyword: str) -> discord.Embed: + """ + Gets the data from StockTwits based on the passed keywords and returns a discord.Embed. + + Parameters + ---------- + e : discord.Embed + The discord.Embed where the data will be added to. + keyword : str + The specific keyword to get the data for. Options are: ts, m_day, wl_ct_day. + + Returns + ------- + discord.Embed + The discord.Embed with the data added to it. + """ + + # Keyword can be "ts", "m_day", "wl_ct_day" + data = await get_json_data( + "https://api.stocktwits.com/api/2/charts/" + keyword, + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", + }, + ) + + # If no data could be found, return the embed + if data == {}: + return e + + table = pd.DataFrame(data["table"][keyword]) + stocks = pd.DataFrame(data["stocks"]).T + stocks["stock_id"] = stocks.index.astype(int) + full_df = pd.merge(stocks, table, on="stock_id") + full_df.sort_values(by="val", ascending=False, inplace=True) + + # Set types + full_df["price"] = full_df["price"].astype(float).fillna(0) + full_df["change"] = full_df["change"].astype(float).fillna(0) + full_df["symbol"] = full_df["symbol"].astype(str) + full_df["name"] = full_df["name"].astype(str) + + # Format % change + full_df["change"] = full_df["change"].apply( + lambda x: f" (+{round(x,2)}% 📈)" if x > 0 else f" ({round(x,2)}% 📉)" + ) + + # Format price + full_df["price"] = full_df["price"].apply(lambda x: round(x, 3)) + full_df["price"] = full_df["price"].astype(str) + full_df["change"] + + # Show Symbol, Price + change, Score / Count + if keyword == "ts": + name = "Trending" + val = "Score" + elif keyword == "m_day": + name = "Most Active" + val = "Count" + else: + name = "Most Watched" + val = "Count" + + # Set values as string + full_df["val"] = full_df["val"].astype(str) + + # Get the values as string + assets = "\n".join(full_df["symbol"].to_list()) + prices = "\n".join(full_df["price"].to_list()) + values = "\n".join(full_df["val"].to_list()) + + e.add_field(name=name, value=assets, inline=True) + e.add_field(name="Price", value=prices, inline=True) + e.add_field(name=val, value=values, inline=True) + + return e
+ + + @loop(hours=6) + async def stocktwits(self) -> None: + """ + The function posts the StockTwits embeds in the configured channel. + + Returns + ------- + None + """ + + e = discord.Embed( + title=f"StockTwits Rankings", + url="https://stocktwits.com/rankings/trending", + description="", + color=data_sources["stocktwits"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e = await self.get_data(e, "ts") + e = await self.get_data(e, "m_day") + e = await self.get_data(e, "wl_ct_day") + + # Set datetime and icon + e.set_footer( + text="\u200b", + icon_url=data_sources["stocktwits"]["icon"], + ) + + await self.channel.send(embed=e)
+ + + +def setup(bot: commands.bot.Bot) -> None: + bot.add_cog(StockTwits(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/timeline.html b/_modules/cogs/loops/timeline.html new file mode 100644 index 00000000..49957787 --- /dev/null +++ b/_modules/cogs/loops/timeline.html @@ -0,0 +1,809 @@ + + + + + + + + + + cogs.loops.timeline — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.timeline

+from __future__ import annotations
+from typing import List, Optional
+import traceback
+
+import aiohttp
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+from util.vars import config
+from util.disc_util import get_channel, get_tagged_users, get_webhook
+from util.tweet_embed import make_tweet_embed
+from util.parse_tweet import parse_tweet
+from util.get_tweet import get_tweet
+
+
+
+[docs] +class Timeline(commands.Cog): + """ + The main Class of this project. This class is responsible for streaming tweets from the Twitter API. + It can be configured in the config.yaml file under ["LOOPS"]["TIMELINE"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initializes the Timeline class. + + Parameters + ---------- + bot : commands.Bot + The bot object from discord.py + """ + self.bot = bot + + charts_channel = config["LOOPS"]["TIMELINE"]["CHARTS_CHANNEL"] + text_channel = config["LOOPS"]["TIMELINE"]["TEXT_CHANNEL"] + + # Set the channels + self.set_channels("STOCKS", charts_channel, text_channel) + self.set_channels("CRYPTO", charts_channel, text_channel) + self.set_channels("FOREX", charts_channel, text_channel) + + # These channels are not crypto or stocks + self.set_channels("IMAGES") + self.set_channels("OTHER") + self.set_channels("NEWS") + + # Get all text channels + self.all_txt_channels.start() + self.get_latest_tweet.start() + +
+[docs] + def set_channels( + self, + name: str, + charts_channel: str = None, + text_channel: str = None, + ) -> None: + """Set channels for each category. + + Parameters + ---------- + name : str + The name of the category. + charts_channel : str + The name of the charts channel. + text_channel : str + The name of the text channel. + """ + if config["LOOPS"]["TIMELINE"][name]["ENABLED"]: + if name in ["STOCKS", "CRYPTO", "FOREX"]: + self.__dict__[f"{name.lower()}_charts_channel"] = get_channel( + self.bot, charts_channel, config["CATEGORIES"][name] + ) + self.__dict__[f"{name.lower()}_text_channel"] = get_channel( + self.bot, text_channel, config["CATEGORIES"][name] + ) + elif name in ["IMAGES", "OTHER"]: + self.__dict__[f"{name.lower()}_channel"] = get_channel( + self.bot, config["LOOPS"]["TIMELINE"][name]["CHANNEL"] + ) + elif name in ["NEWS"]: + self.__dict__[f"{name.lower()}_channel"] = get_channel( + self.bot, + config["LOOPS"]["TIMELINE"][name]["CHANNEL"], + config["CATEGORIES"]["TWITTER"], + ) + + if config["LOOPS"]["TIMELINE"]["NEWS"]["CRYPTO"]["ENABLED"]: + self.crypto_news_channel = get_channel( + self.bot, + config["LOOPS"]["TIMELINE"]["NEWS"]["CHANNEL"], + config["CATEGORIES"]["CRYPTO"], + )
+ + + @loop(hours=1) + async def all_txt_channels(self) -> None: + """Gets all the text channels as Discord object and the names of the channels.""" + self.following_ids = [] + + text_channel_list = [] + text_channel_names = [] + + # Loop over all the text channels + for server in self.bot.guilds: + for channel in server.channels: + if str(channel.type) == "text": + text_channel_list.append(channel) + text_channel_names.append(channel.name.split("┃")[1]) + + # Set the class variables + self.text_channels = text_channel_list + self.text_channel_names = text_channel_names + + @loop(minutes=5) + async def get_latest_tweet(self) -> None: + """Fetches the latest tweets.""" + print("Getting tweets...") + tweets = await get_tweet() + + # Loop from oldest to newest tweet + for tweet in reversed(tweets): + tweet = tweet["content"] + + # Skip if the tweet is not a timeline item + if "entryType" in tweet: + if tweet["entryType"] != "TimelineTimelineItem": + continue + # Ignore popups about X Premium + else: + if "itemContent" in tweet: + if "itemType" in tweet["itemContent"]: + if ( + tweet["itemContent"]["itemType"] + == "TimelineMessagePrompt" + ): + continue + + await self.on_data(tweet, update_tweet_id=True) + +
+[docs] + async def on_data(self, tweet: dict, update_tweet_id: bool = False) -> None: + """This method is called whenever data is received from the stream. + + Parameters + ---------- + tweet : dict + The raw tweet data. + update_tweet_id : bool, optional + Whether or not to update the tweet ID, by default False + """ + formatted_tweet = parse_tweet(tweet, update_tweet_id=update_tweet_id) + + if formatted_tweet is not None: + ( + text, + user_name, + user_screen_name, + user_img, + tweet_url, + media, + tickers, + hashtags, + e_title, + media_types, + ) = formatted_tweet + + e, category, base_symbols = await make_tweet_embed( + text, + user_name, + user_img, + tweet_url, + media, + tickers, + hashtags, + e_title, + media_types, + self.bot, + ) + + # Upload the tweet to the Discord. + await self.upload_tweet(e, category, media, user_screen_name, base_symbols)
+ + +
+[docs] + async def upload_tweet( + self, + e: discord.Embed, + category: Optional[str], + media: List[str], + user_screen_name: str, + tickers: List[str], + ) -> None: + """Uploads tweet in the dedicated Discord channel. + + Parameters + ---------- + e : discord.Embed + The Tweet as a Discord embed object. + category : str, optional + The category of the tweet, used to decide which Discord channel it should be uploaded to. + media : list + The images contained in this tweet. + user_screen_name : str + The user that posted this tweet. + tickers : list + The list of tickers contained in this tweet. + """ + user_channel = None + + # Default channel + channel = self.other_channel + + # Check if there is a user specific channel + if user_screen_name.lower() in self.text_channel_names: + user_channel = self.text_channels[ + self.text_channel_names.index(user_screen_name.lower()) + ] + + # News posters (Do not post news in other channels) + if user_screen_name in config["LOOPS"]["TIMELINE"]["NEWS"]["FOLLOWING"]: + channel = self.news_channel + elif ( + user_screen_name + in config["LOOPS"]["TIMELINE"]["NEWS"]["CRYPTO"]["FOLLOWING"] + ): + channel = self.crypto_news_channel + else: + channel = self.get_channel_based_on_category(category, media) + + await self.post_tweet(channel, e, media, tickers, user_channel, category)
+ + +
+[docs] + def get_channel_based_on_category( + self, category: Optional[str], media: List[str] + ) -> discord.abc.GuildChannel: + """Get the Discord channel based on the category of the tweet. + + Parameters + ---------- + category : str, optional + The category of the tweet. + media : list + The images contained in this tweet. + + Returns + ------- + discord.abc.GuildChannel + The Discord channel. + """ + if category is None: + channel = self.images_channel if media else self.other_channel + else: + channel_type = "charts" if media else "text" + channel = self.__dict__[f"{category}_{channel_type}_channel"] + + return channel
+ + +
+[docs] + async def post_tweet( + self, + channel: discord.abc.GuildChannel, + e: discord.Embed, + media: List[str], + tickers: List[str], + user_channel: Optional[discord.abc.GuildChannel], + category: Optional[str], + ) -> None: + """Formats the tweet and passes it to upload_tweet(). + + Parameters + ---------- + channel : discord.abc.GuildChannel + The Discord channel where the tweet should be posted. + e : discord.Embed + The Tweet as a Discord embed object. + media : list + The images contained in this tweet. + tickers : list + The list of tickers contained in this tweet. + user_channel : discord.abc.GuildChannel, optional + The user-specific Discord channel. + category : str, optional + The category of the tweet. + """ + msgs = [] + + try: + # Create a list of image embeds, max 10 images per post + image_e = [e] + [ + discord.Embed(url=e.url).set_image(url=img) for img in media[1:10] + ] + + # If there are multiple images to be sent, use a webhook to send them all at once + if len(image_e) > 1: + msg = await self.make_and_send_webhook(channel, tickers, image_e) + msgs.append(msg) + + if user_channel: + msg = await self.make_and_send_webhook( + user_channel, tickers, image_e + ) + msgs.append(msg) + + else: + # Use the normal send function + msg = await channel.send(content=get_tagged_users(tickers), embed=e) + msgs.append(msg) + + if user_channel: + msg = await user_channel.send( + content=get_tagged_users(tickers), embed=e + ) + msgs.append(msg) + + # Do this for every message + try: + for msg in msgs: + # Post in highlight channel + await msg.add_reaction("💸") + # Send to user DM + await msg.add_reaction("❤️") + + if category != None: + await msg.add_reaction("🐂") + await msg.add_reaction("🦆") + await msg.add_reaction("🐻") + + except discord.DiscordServerError: + print("Could not add reaction to message") + + except aiohttp.ClientConnectionError: + print("Connection Error posting tweet on timeline") + + except Exception as error: + print("Error posting tweet on timeline", error) + print(traceback.format_exc())
+ + +
+[docs] + async def make_and_send_webhook( + self, + channel: discord.abc.GuildChannel, + tickers: List[str], + image_e: List[discord.Embed], + ) -> discord.Message: + """Creates and sends a webhook. + + Parameters + ---------- + channel : discord.abc.GuildChannel + The Discord channel. + tickers : list + The list of tickers contained in this tweet. + image_e : list + The images contained in this tweet. + + Returns + ------- + discord.Message + The Discord message. + """ + webhook = await get_webhook(channel) + + # Wait so we can use this message as reference + msg = await webhook.send( + content=get_tagged_users(tickers), + embeds=image_e, + username="FinTwit", + wait=True, + avatar_url=self.bot.user.avatar.url, + ) + + return msg
+
+ + + +def setup(bot: commands.Bot) -> None: + """ + This is a necessary method to make the cog loadable. + + Returns + ------- + None + """ + bot.add_cog(Timeline(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/trades.html b/_modules/cogs/loops/trades.html new file mode 100644 index 00000000..3fc199d9 --- /dev/null +++ b/_modules/cogs/loops/trades.html @@ -0,0 +1,538 @@ + + + + + + + + + + cogs.loops.trades — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.trades

+##> Imports
+import asyncio
+
+# > Discord dependencies
+from discord.ext import commands
+
+# > 3rd Party Dependencies
+import pandas as pd
+import ccxt.pro as ccxt
+import ccxt
+
+# Local dependencies
+import util.vars
+from util.db import get_db, update_db
+from util.disc_util import get_channel, get_user
+from util.vars import config
+from util.trades_msg import on_msg
+
+
+
+[docs] +class Trades(commands.Cog): + """ + This class contains the cog for posting new trades done by users. + It can be enabled / disabled in the config under ["LOOPS"]["TRADES"]. + """ + + def __init__( + self, bot: commands.Bot, db: pd.DataFrame = get_db("portfolio") + ) -> None: + self.bot = bot + self.trades_channel = get_channel( + self.bot, config["LOOPS"]["TRADES"]["CHANNEL"] + ) + + # Start getting trades + asyncio.create_task(self.trades(db)) + +
+[docs] + async def start_sockets(self, exchange, row, user) -> None: + while True: + try: + msg = await exchange.watchMyTrades() + await on_msg(msg, exchange, self.trades_channel, row, user) + except ccxt.base.errors.AuthenticationError: + # Send message to user and delete from database + print(row) + break + + except Exception as e: + # Maybe do: await exchange.close() and restart the socket + print( + f"Error in trade websocket for {row['user']} and {exchange.id}: ", e + )
+ + +
+[docs] + async def trades(self, db: pd.DataFrame) -> None: + """ + Starts the websockets for each user in the database. + + Parameters + ---------- + db : pd.DataFrame + The database containing all users. + """ + + tasks = [] + exchanges = [] + + if not db.empty: + # Divide per exchange + binance = db.loc[db["exchange"] == "binance"] + kucoin = db.loc[db["exchange"] == "kucoin"] + + if not binance.empty: + for i, row in binance.iterrows(): + # If using await, it will block other connections + exchange = ccxt.binance( + {"apiKey": row["key"], "secret": row["secret"]} + ) + user = await get_user(self.bot, row["id"]) + + # Make sure that the API keys are valid + try: + exchange.fetch_balance() + except Exception: + # Send message to user and delete from database + await user.send( + f"Your Binance API key is invalid, we have removed it from our database." + ) + + # Get the portfolio + util.vars.portfolio_db.drop(i, inplace=True) + + update_db(util.vars.portfolio_db, "portfolio") + + print(f"Removed Binance API key for {row['user']}") + + task = asyncio.create_task(self.start_sockets(exchange, row, user)) + tasks.append(task) + exchanges.append(exchange) + print(f"Started Binance socket for {row['user']}") + + if not kucoin.empty: + for _, row in kucoin.iterrows(): + exchange = ccxt.kucoin( + { + "apiKey": row["key"], + "secret": row["secret"], + "password": row["passphrase"], + } + ) + task = asyncio.create_task( + self.start_sockets( + exchange, row, await get_user(self.bot, row["id"]) + ) + ) + tasks.append(task) + exchanges.append(exchange) + print(f"Started KuCoin socket for {row['user']}") + + # After 24 hours close the exchange and start again + await asyncio.sleep(24 * 60 * 60) + + print("Stopping all sockets") + for task, exchange in zip(tasks, exchanges): + task.cancel() + await exchange.close() + await asyncio.sleep(10) + + # Restart the socket + await self.trades(db)
+
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Trades(bot)) +
+ +
+ + + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/trending.html b/_modules/cogs/loops/trending.html new file mode 100644 index 00000000..600a6b3f --- /dev/null +++ b/_modules/cogs/loops/trending.html @@ -0,0 +1,609 @@ + + + + + + + + + + cogs.loops.trending — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.trending

+import datetime
+
+# > 3rd party dependencies
+import yahoo_fin.stock_info as si
+import pandas as pd
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config, get_json_data, data_sources
+from util.disc_util import get_channel
+from util.afterhours import afterHours
+from util.formatting import (
+    format_embed,
+    format_change,
+    human_format,
+    format_embed_length,
+)
+from util.cg_data import get_trending_coins, get_top_categories
+
+
+
+
+
+
+def setup(bot: commands.Bot) -> None:
+    bot.add_cog(Trending(bot))
+
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/cogs/loops/yield.html b/_modules/cogs/loops/yield.html new file mode 100644 index 00000000..e5c7153d --- /dev/null +++ b/_modules/cogs/loops/yield.html @@ -0,0 +1,604 @@ + + + + + + + + + + cogs.loops.yield — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for cogs.loops.yield

+# > Standard libraries
+import os
+import datetime
+
+# > 3rd Party Dependencies
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from scipy.interpolate import make_interp_spline
+import numpy as np
+
+# > Discord dependencies
+import discord
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# Local dependencies
+from util.vars import config
+from util.disc_util import get_channel
+from util.tv_data import tv
+from util.tv_symbols import EU_bonds, US_bonds
+
+
+
+[docs] +class Yield(commands.Cog): + """ + This class contains the cog for posting the US and EU yield curve. + It can be enabled / disabled in the config under ["LOOPS"]["YIELD"]. + """ + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.channel = get_channel(self.bot, config["LOOPS"]["YIELD"]["CHANNEL"]) + + self.post_curve.start() + + @loop(hours=24) + async def post_curve(self) -> None: + """ + Posts the US and EU yield curve in the channel specified in the config. + Charts based on http://www.worldgovernmentbonds.com/country/united-states/ + + Returns + ------- + None + """ + + plt.style.use("dark_background") # Set the style first + + mpl.rcParams["axes.spines.right"] = False + mpl.rcParams["axes.spines.left"] = False + mpl.rcParams["axes.spines.top"] = False + mpl.rcParams["axes.spines.bottom"] = False + + mpl.rcParams["axes.edgecolor"] = "white" # Set edge color to white + mpl.rcParams["xtick.color"] = "white" # Set x tick color to white + mpl.rcParams["ytick.color"] = "white" # Set y tick color to white + mpl.rcParams["axes.labelcolor"] = "white" # Set label color to white + mpl.rcParams["text.color"] = "white" # Set text color to white + + await self.plot_US_yield() + await self.plot_EU_yield() + + # Add gridlines + plt.grid(axis="y", color="grey", linewidth=0.5, alpha=0.5) + plt.tick_params(axis="y", which="both", left=False) + + frame = plt.gca() + frame.axes.get_xaxis().set_major_formatter(lambda x, _: f"{int(x)}Y") + + frame.axes.set_ylim(0) + frame.axes.get_yaxis().set_major_formatter(lambda x, _: f"{int(x)}%") + + # Set plot parameters + plt.legend(loc="lower center", ncol=2) + plt.xlabel("Residual Maturity") + + # Convert to plot to a temporary image + file_name = "yield.png" + file_path = os.path.join("temp", file_name) + plt.savefig(file_path, bbox_inches="tight", dpi=300) + plt.cla() + plt.close() + + e = discord.Embed( + title="US and EU Yield Curve Rates", + description="", + color=0x000000, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + file = discord.File(file_path, filename=file_name) + e.set_image(url=f"attachment://{file_name}") + + await self.channel.purge(limit=1) + await self.channel.send(file=file, embed=e) + + # Delete yield.png + os.remove(file_path) + +
+[docs] + async def plot_US_yield(self) -> None: + """ + Gets the US yield curve data from TradingView and plots it. + """ + + years = np.array([0.08, 0.15, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30]) + yield_percentage = await self.get_yield(US_bonds) + + self.make_plot(years, yield_percentage, "c", "US")
+ + +
+[docs] + async def plot_EU_yield(self): + """ + Gets the EU yield curve data from TradingView and plots it. + """ + + years = np.array( + [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30] + ) + yield_percentage = await self.get_yield(EU_bonds) + + self.make_plot(years, yield_percentage, "r", "EU")
+ + +
+[docs] + async def get_yield(self, bonds: list) -> list: + """ + For each bond in the given list, it gets the yield from TradingView. + + Parameters + ---------- + bonds : list + The names of the bonds to get the yield from. + + Returns + ------- + list + The percentages of the yield for each bond. + """ + + yield_percentage = [] + for bond in bonds: + no_exch = bond.split(":")[1] + tv_data = await tv.get_tv_data(no_exch, "forex") + yield_percentage.append(tv_data[0]) + + return yield_percentage
+ + +
+[docs] + def make_plot( + self, years: list, yield_percentage: list, color: str, label: str + ) -> None: + """ + Makes a matplotlib plot of the yield curve. + Each dot is the yield for a specific bond. + Connects a spline through the dots to make a smooth curve. + + Parameters + ---------- + years : list + The years of the yield curve. + yield_percentage : list + The yield percentage for each year. + color : str + The color of the plotted line. + label : str + The label for the plotted line. + """ + + new_X = np.linspace(years.min(), years.max(), 500) + + # Interpolation + spl = make_interp_spline(years, yield_percentage, k=3) + smooth = spl(new_X) + + # Make the plot + plt.rcParams["figure.figsize"] = (10, 5) # Set the figure size + plt.plot(new_X, smooth, color, label=label) + plt.plot(years, yield_percentage, f"{color}o")
+
+ + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Yield(bot)) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/discord/ext/tasks.html b/_modules/discord/ext/tasks.html new file mode 100644 index 00000000..35bb0cf3 --- /dev/null +++ b/_modules/discord/ext/tasks.html @@ -0,0 +1,1202 @@ + + + + + + + + + + discord.ext.tasks — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for discord.ext.tasks

+"""
+The MIT License (MIT)
+
+Copyright (c) 2015-2021 Rapptz
+Copyright (c) 2021-present Pycord Development
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+"""
+
+from __future__ import annotations
+
+import asyncio
+import datetime
+import inspect
+import sys
+import traceback
+from collections.abc import Sequence
+from typing import Any, Awaitable, Callable, Generic, TypeVar, cast
+
+import aiohttp
+
+import discord
+from discord.backoff import ExponentialBackoff
+from discord.utils import MISSING
+
+__all__ = ("loop",)
+
+T = TypeVar("T")
+_func = Callable[..., Awaitable[Any]]
+LF = TypeVar("LF", bound=_func)
+FT = TypeVar("FT", bound=_func)
+ET = TypeVar("ET", bound=Callable[[Any, BaseException], Awaitable[Any]])
+
+
+class SleepHandle:
+    __slots__ = ("future", "loop", "handle")
+
+    def __init__(
+        self, dt: datetime.datetime, *, loop: asyncio.AbstractEventLoop
+    ) -> None:
+        self.loop = loop
+        self.future = future = loop.create_future()
+        relative_delta = discord.utils.compute_timedelta(dt)
+        self.handle = loop.call_later(relative_delta, future.set_result, True)
+
+    def recalculate(self, dt: datetime.datetime) -> None:
+        self.handle.cancel()
+        relative_delta = discord.utils.compute_timedelta(dt)
+        self.handle = self.loop.call_later(relative_delta, self.future.set_result, True)
+
+    def wait(self) -> asyncio.Future[Any]:
+        return self.future
+
+    def done(self) -> bool:
+        return self.future.done()
+
+    def cancel(self) -> None:
+        self.handle.cancel()
+        self.future.cancel()
+
+
+class Loop(Generic[LF]):
+    """A background task helper that abstracts the loop and reconnection logic for you.
+
+    The main interface to create this is through :func:`loop`.
+    """
+
+    def __init__(
+        self,
+        coro: LF,
+        seconds: float,
+        hours: float,
+        minutes: float,
+        time: datetime.time | Sequence[datetime.time],
+        count: int | None,
+        reconnect: bool,
+        loop: asyncio.AbstractEventLoop,
+    ) -> None:
+        self.coro: LF = coro
+        self.reconnect: bool = reconnect
+        self.loop: asyncio.AbstractEventLoop = loop
+        self.count: int | None = count
+        self._current_loop = 0
+        self._handle: SleepHandle = MISSING
+        self._task: asyncio.Task[None] = MISSING
+        self._injected = None
+        self._valid_exception = (
+            OSError,
+            discord.GatewayNotFound,
+            discord.ConnectionClosed,
+            aiohttp.ClientError,
+            asyncio.TimeoutError,
+        )
+
+        self._before_loop = None
+        self._after_loop = None
+        self._before_loop_running = False
+        self._after_loop_running = False
+        self._is_being_cancelled = False
+        self._has_failed = False
+        self._stop_next_iteration = False
+
+        if self.count is not None and self.count <= 0:
+            raise ValueError("count must be greater than 0 or None.")
+
+        self.change_interval(seconds=seconds, minutes=minutes, hours=hours, time=time)
+        self._last_iteration_failed = False
+        self._last_iteration: datetime.datetime = MISSING
+        self._next_iteration = None
+
+        if not inspect.iscoroutinefunction(self.coro):
+            raise TypeError(
+                f"Expected coroutine function, not {type(self.coro).__name__!r}."
+            )
+
+    async def _call_loop_function(self, name: str, *args: Any, **kwargs: Any) -> None:
+        coro = getattr(self, f"_{name}")
+        if coro is None:
+            return
+
+        if name.endswith("_loop"):
+            setattr(self, f"_{name}_running", True)
+
+        if self._injected is not None:
+            await coro(self._injected, *args, **kwargs)
+        else:
+            await coro(*args, **kwargs)
+
+        if name.endswith("_loop"):
+            setattr(self, f"_{name}_running", False)
+
+    def _try_sleep_until(self, dt: datetime.datetime):
+        self._handle = SleepHandle(dt=dt, loop=self.loop)
+        return self._handle.wait()
+
+    async def _loop(self, *args: Any, **kwargs: Any) -> None:
+        backoff = ExponentialBackoff()
+        await self._call_loop_function("before_loop")
+        self._last_iteration_failed = False
+        if self._time is not MISSING:
+            # the time index should be prepared every time the internal loop is started
+            self._prepare_time_index()
+            self._next_iteration = self._get_next_sleep_time()
+        else:
+            self._next_iteration = datetime.datetime.now(datetime.timezone.utc)
+        try:
+            await self._try_sleep_until(self._next_iteration)
+            while True:
+                if not self._last_iteration_failed:
+                    self._last_iteration = self._next_iteration
+                    self._next_iteration = self._get_next_sleep_time()
+                try:
+                    await self.coro(*args, **kwargs)
+                    self._last_iteration_failed = False
+                except self._valid_exception:
+                    self._last_iteration_failed = True
+                    if not self.reconnect:
+                        raise
+                    await asyncio.sleep(backoff.delay())
+                else:
+                    await self._try_sleep_until(self._next_iteration)
+
+                    if self._stop_next_iteration:
+                        return
+
+                    now = datetime.datetime.now(datetime.timezone.utc)
+                    if now > self._next_iteration:
+                        self._next_iteration = now
+                        if self._time is not MISSING:
+                            self._prepare_time_index(now)
+
+                    self._current_loop += 1
+                    if self._current_loop == self.count:
+                        break
+
+        except asyncio.CancelledError:
+            self._is_being_cancelled = True
+            raise
+        except Exception as exc:
+            self._has_failed = True
+            await self._call_loop_function("error", exc)
+            raise exc
+        finally:
+            await self._call_loop_function("after_loop")
+            self._handle.cancel()
+            self._is_being_cancelled = False
+            self._current_loop = 0
+            self._stop_next_iteration = False
+            self._has_failed = False
+
+    def __get__(self, obj: T, objtype: type[T]) -> Loop[LF]:
+        if obj is None:
+            return self
+
+        copy: Loop[LF] = Loop(
+            self.coro,
+            seconds=self._seconds,
+            hours=self._hours,
+            minutes=self._minutes,
+            time=self._time,
+            count=self.count,
+            reconnect=self.reconnect,
+            loop=self.loop,
+        )
+        copy._injected = obj
+        copy._before_loop = self._before_loop
+        copy._after_loop = self._after_loop
+        copy._error = self._error
+        setattr(obj, self.coro.__name__, copy)
+        return copy
+
+    @property
+    def seconds(self) -> float | None:
+        """Read-only value for the number of seconds
+        between each iteration. ``None`` if an explicit ``time`` value was passed instead.
+
+        .. versionadded:: 2.0
+        """
+        if self._seconds is not MISSING:
+            return self._seconds
+
+    @property
+    def minutes(self) -> float | None:
+        """Read-only value for the number of minutes
+        between each iteration. ``None`` if an explicit ``time`` value was passed instead.
+
+        .. versionadded:: 2.0
+        """
+        if self._minutes is not MISSING:
+            return self._minutes
+
+    @property
+    def hours(self) -> float | None:
+        """Read-only value for the number of hours
+        between each iteration. ``None`` if an explicit ``time`` value was passed instead.
+
+        .. versionadded:: 2.0
+        """
+        if self._hours is not MISSING:
+            return self._hours
+
+    @property
+    def time(self) -> list[datetime.time] | None:
+        """Read-only list for the exact times this loop runs at.
+        ``None`` if relative times were passed instead.
+
+        .. versionadded:: 2.0
+        """
+        if self._time is not MISSING:
+            return self._time.copy()
+
+    @property
+    def current_loop(self) -> int:
+        """The current iteration of the loop."""
+        return self._current_loop
+
+    @property
+    def next_iteration(self) -> datetime.datetime | None:
+        """When the next iteration of the loop will occur.
+
+        .. versionadded:: 1.3
+        """
+        if self._task is MISSING:
+            return None
+        elif self._task and self._task.done() or self._stop_next_iteration:
+            return None
+        return self._next_iteration
+
+    async def __call__(self, *args: Any, **kwargs: Any) -> Any:
+        r"""|coro|
+
+        Calls the internal callback that the task holds.
+
+        .. versionadded:: 1.6
+
+        Parameters
+        ------------
+        \*args
+            The arguments to use.
+        \*\*kwargs
+            The keyword arguments to use.
+        """
+
+        if self._injected is not None:
+            args = (self._injected, *args)
+
+        return await self.coro(*args, **kwargs)
+
+    def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
+        r"""Starts the internal task in the event loop.
+
+        Parameters
+        ------------
+        \*args
+            The arguments to use.
+        \*\*kwargs
+            The keyword arguments to use.
+
+        Raises
+        --------
+        RuntimeError
+            A task has already been launched and is running.
+
+        Returns
+        ---------
+        :class:`asyncio.Task`
+            The task that has been created.
+        """
+
+        if self._task is not MISSING and not self._task.done():
+            raise RuntimeError("Task is already launched and is not completed.")
+
+        if self._injected is not None:
+            args = (self._injected, *args)
+
+        if self.loop is MISSING:
+            self.loop = asyncio.get_event_loop()
+
+        self._task = self.loop.create_task(self._loop(*args, **kwargs))
+        return self._task
+
+    def stop(self) -> None:
+        r"""Gracefully stops the task from running.
+
+        Unlike :meth:`cancel`\, this allows the task to finish its
+        current iteration before gracefully exiting.
+
+        .. note::
+
+            If the internal function raises an error that can be
+            handled before finishing then it will retry until
+            it succeeds.
+
+            If this is undesirable, either remove the error handling
+            before stopping via :meth:`clear_exception_types` or
+            use :meth:`cancel` instead.
+
+        .. versionadded:: 1.2
+        """
+        if self._task is not MISSING and not self._task.done():
+            self._stop_next_iteration = True
+
+    def _can_be_cancelled(self) -> bool:
+        return bool(
+            not self._is_being_cancelled and self._task and not self._task.done()
+        )
+
+    def cancel(self) -> None:
+        """Cancels the internal task, if it is running."""
+        if self._can_be_cancelled():
+            self._task.cancel()
+
+    def restart(self, *args: Any, **kwargs: Any) -> None:
+        r"""A convenience method to restart the internal task.
+
+        .. note::
+
+            Due to the way this function works, the task is not
+            returned like :meth:`start`.
+
+        Parameters
+        ------------
+        \*args
+            The arguments to use.
+        \*\*kwargs
+            The keyword arguments to use.
+        """
+
+        def restart_when_over(
+            fut: Any, *, args: Any = args, kwargs: Any = kwargs
+        ) -> None:
+            self._task.remove_done_callback(restart_when_over)
+            self.start(*args, **kwargs)
+
+        if self._can_be_cancelled():
+            self._task.add_done_callback(restart_when_over)
+            self._task.cancel()
+
+    def add_exception_type(self, *exceptions: type[BaseException]) -> None:
+        r"""Adds exception types to be handled during the reconnect logic.
+
+        By default, the exception types handled are those handled by
+        :meth:`discord.Client.connect`\, which includes a lot of internet disconnection
+        errors.
+
+        This function is useful if you're interacting with a 3rd party library that
+        raises its own set of exceptions.
+
+        Parameters
+        ------------
+        \*exceptions: Type[:class:`BaseException`]
+            An argument list of exception classes to handle.
+
+        Raises
+        --------
+        TypeError
+            An exception passed is either not a class or not inherited from :class:`BaseException`.
+        """
+
+        for exc in exceptions:
+            if not inspect.isclass(exc):
+                raise TypeError(f"{exc!r} must be a class.")
+            if not issubclass(exc, BaseException):
+                raise TypeError(f"{exc!r} must inherit from BaseException.")
+
+        self._valid_exception = (*self._valid_exception, *exceptions)
+
+    def clear_exception_types(self) -> None:
+        """Removes all exception types that are handled.
+
+        .. note::
+
+            This operation obviously cannot be undone!
+        """
+        self._valid_exception = ()
+
+    def remove_exception_type(self, *exceptions: type[BaseException]) -> bool:
+        r"""Removes exception types from being handled during the reconnect logic.
+
+        Parameters
+        ------------
+        \*exceptions: Type[:class:`BaseException`]
+            An argument list of exception classes to handle.
+
+        Returns
+        ---------
+        :class:`bool`
+            Whether all exceptions were successfully removed.
+        """
+        old_length = len(self._valid_exception)
+        self._valid_exception = tuple(
+            x for x in self._valid_exception if x not in exceptions
+        )
+        return len(self._valid_exception) == old_length - len(exceptions)
+
+    def get_task(self) -> asyncio.Task[None] | None:
+        """Fetches the internal task or ``None`` if there isn't one running."""
+        return self._task if self._task is not MISSING else None
+
+    def is_being_cancelled(self) -> bool:
+        """Whether the task is being cancelled."""
+        return self._is_being_cancelled
+
+    def failed(self) -> bool:
+        """Whether the internal task has failed.
+
+        .. versionadded:: 1.2
+        """
+        return self._has_failed
+
+    def is_running(self) -> bool:
+        """Check if the task is currently running.
+
+        .. versionadded:: 1.4
+        """
+        return not bool(self._task.done()) if self._task is not MISSING else False
+
+    async def _error(self, *args: Any) -> None:
+        exception: Exception = args[-1]
+        print(
+            f"Unhandled exception in internal background task {self.coro.__name__!r}.",
+            file=sys.stderr,
+        )
+        traceback.print_exception(
+            type(exception), exception, exception.__traceback__, file=sys.stderr
+        )
+
+    def before_loop(self, coro: FT) -> FT:
+        """A decorator that registers a coroutine to be called before the loop starts running.
+
+        This is useful if you want to wait for some bot state before the loop starts,
+        such as :meth:`discord.Client.wait_until_ready`.
+
+        The coroutine must take no arguments (except ``self`` in a class context).
+
+        Parameters
+        ----------
+        coro: :ref:`coroutine <coroutine>`
+            The coroutine to register before the loop runs.
+
+        Raises
+        ------
+        TypeError
+            The function was not a coroutine.
+        """
+
+        if not inspect.iscoroutinefunction(coro):
+            raise TypeError(
+                f"Expected coroutine function, received {coro.__class__.__name__!r}."
+            )
+
+        self._before_loop = coro
+        return coro
+
+    def after_loop(self, coro: FT) -> FT:
+        """A decorator that register a coroutine to be called after the loop finished running.
+
+        The coroutine must take no arguments (except ``self`` in a class context).
+
+        .. note::
+
+            This coroutine is called even during cancellation. If it is desirable
+            to tell apart whether something was cancelled or not, check to see
+            whether :meth:`is_being_cancelled` is ``True`` or not.
+
+        Parameters
+        ----------
+        coro: :ref:`coroutine <coroutine>`
+            The coroutine to register after the loop finishes.
+
+        Raises
+        ------
+        TypeError
+            The function was not a coroutine.
+        """
+
+        if not inspect.iscoroutinefunction(coro):
+            raise TypeError(
+                f"Expected coroutine function, received {coro.__class__.__name__!r}."
+            )
+
+        self._after_loop = coro
+        return coro
+
+    def error(self, coro: ET) -> ET:
+        """A decorator that registers a coroutine to be called if the task encounters an unhandled exception.
+
+        The coroutine must take only one argument the exception raised (except ``self`` in a class context).
+
+        By default, this prints to :data:`sys.stderr` however it could be
+        overridden to have a different implementation.
+
+        .. versionadded:: 1.4
+
+        Parameters
+        ----------
+        coro: :ref:`coroutine <coroutine>`
+            The coroutine to register in the event of an unhandled exception.
+
+        Raises
+        ------
+        TypeError
+            The function was not a coroutine.
+        """
+        if not inspect.iscoroutinefunction(coro):
+            raise TypeError(
+                f"Expected coroutine function, received {coro.__class__.__name__!r}."
+            )
+
+        self._error = coro  # type: ignore
+        return coro
+
+    def _get_next_sleep_time(self) -> datetime.datetime:
+        if self._sleep is not MISSING:
+            return self._last_iteration + datetime.timedelta(seconds=self._sleep)
+
+        if self._time_index >= len(self._time):
+            self._time_index = 0
+            if self._current_loop == 0:
+                # if we're at the last index on the first iteration, we need to sleep until tomorrow
+                return datetime.datetime.combine(
+                    datetime.datetime.now(datetime.timezone.utc)
+                    + datetime.timedelta(days=1),
+                    self._time[0],
+                )
+
+        next_time = self._time[self._time_index]
+
+        if self._current_loop == 0:
+            self._time_index += 1
+            if next_time > datetime.datetime.now(datetime.timezone.utc).timetz():
+                return datetime.datetime.combine(
+                    datetime.datetime.now(datetime.timezone.utc), next_time
+                )
+            else:
+                return datetime.datetime.combine(
+                    datetime.datetime.now(datetime.timezone.utc)
+                    + datetime.timedelta(days=1),
+                    next_time,
+                )
+
+        next_date = cast(datetime.datetime, self._last_iteration)
+        if next_time < next_date.timetz():
+            next_date += datetime.timedelta(days=1)
+
+        self._time_index += 1
+        return datetime.datetime.combine(next_date, next_time)
+
+    def _prepare_time_index(self, now: datetime.datetime = MISSING) -> None:
+        # now kwarg should be a datetime.datetime representing the time "now"
+        # to calculate the next time index from
+
+        # pre-condition: self._time is set
+        time_now = (
+            now
+            if now is not MISSING
+            else datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
+        ).timetz()
+        for idx, time in enumerate(self._time):
+            if time >= time_now:
+                self._time_index = idx
+                break
+        else:
+            self._time_index = 0
+
+    def _get_time_parameter(
+        self,
+        time: datetime.time | Sequence[datetime.time],
+        *,
+        dt: type[datetime.time] = datetime.time,
+        utc: datetime.timezone = datetime.timezone.utc,
+    ) -> list[datetime.time]:
+        if isinstance(time, dt):
+            inner = time if time.tzinfo is not None else time.replace(tzinfo=utc)
+            return [inner]
+        if not isinstance(time, Sequence):
+            raise TypeError(
+                "Expected datetime.time or a sequence of datetime.time for ``time``,"
+                f" received {type(time)!r} instead."
+            )
+        if not time:
+            raise ValueError("time parameter must not be an empty sequence.")
+
+        ret: list[datetime.time] = []
+        for index, t in enumerate(time):
+            if not isinstance(t, dt):
+                raise TypeError(
+                    f"Expected a sequence of {dt!r} for ``time``, received"
+                    f" {type(t).__name__!r} at index {index} instead."
+                )
+            ret.append(t if t.tzinfo is not None else t.replace(tzinfo=utc))
+
+        return sorted(set(ret))  # de-dupe and sort times
+
+    def change_interval(
+        self,
+        *,
+        seconds: float = 0,
+        minutes: float = 0,
+        hours: float = 0,
+        time: datetime.time | Sequence[datetime.time] = MISSING,
+    ) -> None:
+        """Changes the interval for the sleep time.
+
+        .. versionadded:: 1.2
+
+        Parameters
+        ----------
+        seconds: :class:`float`
+            The number of seconds between every iteration.
+        minutes: :class:`float`
+            The number of minutes between every iteration.
+        hours: :class:`float`
+            The number of hours between every iteration.
+        time: Union[:class:`datetime.time`, Sequence[:class:`datetime.time`]]
+            The exact times to run this loop at. Either a non-empty list or a single
+            value of :class:`datetime.time` should be passed.
+            This cannot be used in conjunction with the relative time parameters.
+
+            .. versionadded:: 2.0
+
+            .. note::
+
+                Duplicate times will be ignored, and only run once.
+
+        Raises
+        ------
+        ValueError
+            An invalid value was given.
+        TypeError
+            An invalid value for the ``time`` parameter was passed, or the
+            ``time`` parameter was passed in conjunction with relative time parameters.
+        """
+
+        if time is MISSING:
+            seconds = seconds or 0
+            minutes = minutes or 0
+            hours = hours or 0
+            sleep = seconds + (minutes * 60.0) + (hours * 3600.0)
+            if sleep < 0:
+                raise ValueError("Total number of seconds cannot be less than zero.")
+
+            self._sleep = sleep
+            self._seconds = float(seconds)
+            self._hours = float(hours)
+            self._minutes = float(minutes)
+            self._time: list[datetime.time] = MISSING
+        else:
+            if any((seconds, minutes, hours)):
+                raise TypeError("Cannot mix explicit time with relative time")
+            self._time = self._get_time_parameter(time)
+            self._sleep = self._seconds = self._minutes = self._hours = MISSING
+
+        if self.is_running() and not (
+            self._before_loop_running or self._after_loop_running
+        ):
+            if self._time is not MISSING:
+                # prepare the next time index starting from after the last iteration
+                self._prepare_time_index(now=self._last_iteration)
+
+            self._next_iteration = self._get_next_sleep_time()
+            if not self._handle.done():
+                # the loop is sleeping, recalculate based on new interval
+                self._handle.recalculate(self._next_iteration)
+
+
+def loop(
+    *,
+    seconds: float = MISSING,
+    minutes: float = MISSING,
+    hours: float = MISSING,
+    time: datetime.time | Sequence[datetime.time] = MISSING,
+    count: int | None = None,
+    reconnect: bool = True,
+    loop: asyncio.AbstractEventLoop = MISSING,
+) -> Callable[[LF], Loop[LF]]:
+    """A decorator that schedules a task in the background for you with
+    optional reconnect logic. The decorator returns a :class:`Loop`.
+
+    Parameters
+    ----------
+    seconds: :class:`float`
+        The number of seconds between every iteration.
+    minutes: :class:`float`
+        The number of minutes between every iteration.
+    hours: :class:`float`
+        The number of hours between every iteration.
+    time: Union[:class:`datetime.time`, Sequence[:class:`datetime.time`]]
+        The exact times to run this loop at. Either a non-empty list or a single
+        value of :class:`datetime.time` should be passed. Timezones are supported.
+        If no timezone is given for the times, it is assumed to represent UTC time.
+
+        This cannot be used in conjunction with the relative time parameters.
+
+        .. note::
+
+            Duplicate times will be ignored, and only run once.
+
+        .. versionadded:: 2.0
+
+    count: Optional[:class:`int`]
+        The number of loops to do, ``None`` if it should be an
+        infinite loop.
+    reconnect: :class:`bool`
+        Whether to handle errors and restart the task
+        using an exponential back-off algorithm similar to the
+        one used in :meth:`discord.Client.connect`.
+    loop: :class:`asyncio.AbstractEventLoop`
+        The loop to use to register the task, if not given
+        defaults to :func:`asyncio.get_event_loop`.
+
+    Raises
+    ------
+    ValueError
+        An invalid value was given.
+    TypeError
+        The function was not a coroutine, an invalid value for the ``time`` parameter was passed,
+        or ``time`` parameter was passed in conjunction with relative time parameters.
+    """
+
+    def decorator(func: LF) -> Loop[LF]:
+        return Loop[LF](
+            func,
+            seconds=seconds,
+            minutes=minutes,
+            hours=hours,
+            count=count,
+            time=time,
+            reconnect=reconnect,
+            loop=loop,
+        )
+
+    return decorator
+
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 00000000..20cb8c25 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + Overview: module code — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/main.html b/_modules/main.html new file mode 100644 index 00000000..70d5f73d --- /dev/null +++ b/_modules/main.html @@ -0,0 +1,537 @@ + + + + + + + + + + main — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for main

+#!/usr/bin/env python3
+# Python 3.8.11
+
+##> Imports
+# > Standard library
+import os
+import sys
+import datetime
+
+# Discord libraries
+import discord
+from discord.ext import commands
+from dotenv import load_dotenv
+
+# Load the .env file
+load_dotenv()
+
+# Import local dependencies
+from util.vars import config
+from util.disc_util import get_guild, set_emoji
+
+bot = commands.Bot(intents=discord.Intents.all())
+
+
+
+[docs] +@bot.event +async def on_ready() -> None: + """This gets printed on boot up""" + + # Load the loops and listeners + load_folder("loops") + load_folder("listeners") + + guild = get_guild(bot) + print(f"{bot.user} is connected to {guild.name} at {datetime.datetime.now()} \n") + + await set_emoji(guild)
+ + + +
+[docs] +def load_folder(foldername: str) -> None: + """ + Loads all the cogs in the given folder. + Only loads the cogs if the config allows it. + + Parameters + ---------- + foldername: str + The name of the folder to load the cogs from. + + Returns + ------- + None + """ + + # Get enabled cogs + enabled_cogs = [] + + # Check each file in the folder + for file in config[foldername.upper()]: + # Check the contents of the file in the folder + if config[foldername.upper()][file]: + # If the file type is not a boolean, check if it is enabled + if not isinstance(config[foldername.upper()][file], bool): + # Check if the ENABLED key exists + if "ENABLED" in config[foldername.upper()][file]: + # Append if enabled == True + if config[foldername.upper()][file]["ENABLED"]: + enabled_cogs.append(file.lower() + ".py") + else: + # Append the file to enabled cogs, if its value is True + if config[foldername.upper()][file]: + enabled_cogs.append(file.lower() + ".py") + + # Load all cogs + print(f"Loading {foldername} ...") + for filename in os.listdir(f"./src/cogs/{foldername}"): + if filename.endswith(".py") and filename in enabled_cogs: + try: + # Do not start timeline if the -no_timeline argument is given + if filename == "timeline.py" and "-no_timeline" in sys.argv: + continue + + # Overview.py has no setup function, but should be considered as a loop / cog + if filename == "overview.py": + continue + + print("Loading:", filename) + bot.load_extension(f"cogs.{foldername}.{filename[:-3]}") + except discord.ExtensionAlreadyLoaded: + pass + except discord.ExtensionNotFound: + print("Cog not found:", filename) + print()
+ + + +if __name__ == "__main__": + # Start by loading the database + bot.load_extension("util.db") + + # Ensure the all directories exist + os.makedirs("logs", exist_ok=True) + os.makedirs("temp", exist_ok=True) + os.makedirs("data", exist_ok=True) + + # Load commands + load_folder("commands") + + # Read the token from the config + TOKEN = ( + os.getenv("DEBUG_TOKEN") if "-test" in sys.argv else os.getenv("DISCORD_TOKEN") + ) + + if not TOKEN: + print("No Discord token found. Exiting...") + sys.exit(1) + + # Main event loop + bot.run(TOKEN) + # If the bot randomly stops maybe put back old code +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/afterhours.html b/_modules/util/afterhours.html new file mode 100644 index 00000000..7b99884c --- /dev/null +++ b/_modules/util/afterhours.html @@ -0,0 +1,459 @@ + + + + + + + + + + util.afterhours — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.afterhours

+# Standard libraries
+import datetime
+
+# 3rd party libraries
+from pandas.tseries.holiday import USFederalHolidayCalendar
+
+# Get the public holidays
+cal = USFederalHolidayCalendar()
+us_holidays = cal.holidays(
+    start=datetime.date(datetime.date.today().year, 1, 1).strftime("%Y-%m-%d"),
+    end=datetime.date(datetime.date.today().year, 12, 31).strftime("%Y-%m-%d"),
+).to_pydatetime()
+
+
+
+[docs] +def afterHours() -> bool: + """ + Simple code to check if the current time is after hours in the US. + Source: https://www.reddit.com/r/algotrading/comments/9x9xho/python_code_to_check_if_market_is_open_in_your/ + + Return + ------ + bool + True if it is currently after-hours, False otherwise. + """ + + now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=-5), "EST")) + openTime = datetime.time(hour=9, minute=30, second=0) + closeTime = datetime.time(hour=16, minute=0, second=0) + + # If a holiday + if now.strftime("%Y-%m-%d") in us_holidays: + return True + + # If before 0930 or after 1600 + if (now.time() < openTime) or (now.time() > closeTime): + return True + + # If it's a weekend + if now.date().weekday() > 4: + return True + + # Otherwise the market is open + return False
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/cg_data.html b/_modules/util/cg_data.html new file mode 100644 index 00000000..06c55346 --- /dev/null +++ b/_modules/util/cg_data.html @@ -0,0 +1,797 @@ + + + + + + + + + + util.cg_data — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.cg_data

+##> Imports
+# > Standard libaries
+from __future__ import annotations
+from typing import Optional, List
+import numbers
+from io import StringIO
+
+# > Third party libraries
+from pycoingecko import CoinGeckoAPI
+import tls_client
+from bs4 import BeautifulSoup
+import pandas as pd
+
+# Local dependencies
+import util.vars
+from util.vars import stables
+from util.tv_data import tv
+from util.formatting import format_change
+
+cg = CoinGeckoAPI()
+session = tls_client.Session(
+    client_identifier="chrome112", random_tls_extension_order=True
+)
+
+
+
+[docs] +def get_crypto_info(ids): + if len(ids) > 1: + id = None + best_vol = 0 + coin_dict = None + for symbol in ids.values: + # Catch potential errors + try: + coin_info = cg.get_coin_by_id(symbol) + if "usd" in coin_info["market_data"]["total_volume"]: + volume = coin_info["market_data"]["total_volume"]["usd"] + if volume > best_vol: + best_vol = volume + id = symbol + coin_dict = coin_info + except Exception as e: + print("Error getting coin info for", symbol, "Error:", e) + pass + + else: + id = ids.values[0] + # Try in case the CoinGecko API does not work + try: + coin_dict = cg.get_coin_by_id(id) + except Exception as e: + print("Error getting coin info for", id, "Error:", e) + return None, None + + return coin_dict, id
+ + + +
+[docs] +def get_coin_vol(coin_dict: dict) -> float: + if "total_volume" in coin_dict["market_data"].keys(): + if "usd" in coin_dict["market_data"]["total_volume"].keys(): + return coin_dict["market_data"]["total_volume"]["usd"] + else: + return 1
+ + + +
+[docs] +def get_coin_price(coin_dict: dict) -> float: + if "current_price" in coin_dict["market_data"].keys(): + if "usd" in coin_dict["market_data"]["current_price"].keys(): + return coin_dict["market_data"]["current_price"]["usd"] + else: + return 0
+ + + +
+[docs] +def get_coin_exchanges(coin_dict: dict) -> tuple[str, list]: + base = None + exchanges = [] + if "tickers" in coin_dict.keys(): + for info in coin_dict["tickers"]: + if "base" in info.keys(): + # Somtimes the base is a contract instead of ticker + if base is None: + # > 7, because $KOMPETE + if not (info["base"].startswith("0X") or len(info["base"]) > 7): + base = info["base"] + + if "market" in info.keys(): + exchanges.append(info["market"]["name"]) + + return base, exchanges
+ + + +
+[docs] +def get_info_from_dict(coin_dict: dict): + if coin_dict: + if "market_data" in coin_dict.keys(): + volume = get_coin_vol(coin_dict) + price = get_coin_price(coin_dict) + + change = None + if "price_change_percentage_24h" in coin_dict["market_data"].keys(): + if isinstance( + coin_dict["market_data"]["price_change_percentage_24h"], + numbers.Number, + ): + change = round( + coin_dict["market_data"]["price_change_percentage_24h"], 2 + ) + + # Get the exchanges + base, exchanges = get_coin_exchanges(coin_dict) + + return volume, price, change, exchanges, base + return 0, None, None, None, None
+ + + +
+[docs] +async def get_coin_info( + ticker: str, +) -> Optional[tuple[float, str, List[str], float, str, str]]: + """ + Gets the volume, website, exchanges, price, and change of the coin. + This can only be called maximum 50 times per minute. + + Parameters + ---------- + ticker : str + The ticker of the coin. + + Returns + ------- + float + The volume of the coin. + str + The website of the coin. + list[str] + The exchanges of the coin. + float + The price of the coin. + str + The 24h price change of the coin. + str + The base symbol of the coin, e.g. BTC, ETH, etc. + """ + + id = change = None + total_vol = 0 + exchanges = [] + change = "N/A" + + # Remove formatting from ticker input + if ticker not in stables: + for stable in stables: + if ticker.endswith(stable): + ticker = ticker[: -len(stable)] + + # Get the id of the ticker + # Check if the symbol exists + coin_dict = None + if ticker in util.vars.cg_db["symbol"].values: + # Check coin by symbol, i.e. "BTC" + print("Cg_data ticker:") + print(ticker) + coin_dict, id = get_crypto_info( + util.vars.cg_db[util.vars.cg_db["symbol"] == ticker]["id"] + ) + + # Get the information from the dictionary + if coin_dict: + total_vol, price, change, exchanges, base = get_info_from_dict(coin_dict) + + # Try other methods if the information sucks + if total_vol < 50000 or exchanges == [] or change == "N/A": + # As a second options check the TradingView data + price, perc_change, volume, exchange, website = await tv.get_tv_data( + ticker, "crypto" + ) + if volume != 0: + return ( + volume, + website, + exchange, + price, + format_change(perc_change) if perc_change else "N/A", + ticker, + ) + + # Third option is to check by id + elif ticker.lower() in util.vars.cg_db["id"].values: + coin_dict, id = get_crypto_info( + util.vars.cg_db[util.vars.cg_db["id"] == ticker.lower()]["id"] + ) + + # Fourth option is to check by name, i.e. "Bitcoin" + elif ticker in util.vars.cg_db["name"].values: + coin_dict, id = get_crypto_info( + util.vars.cg_db[util.vars.cg_db["name"] == ticker]["id"] + ) + + # Get the information from the dictionary + total_vol, price, change, exchanges, base = get_info_from_dict(coin_dict) + + # remove duplicates and suffix 'exchange' + if exchanges: + exchanges = [x.lower().replace(" exchange", "") for x in exchanges] + exchanges = list(set(exchanges)) + + # Look into this! + if total_vol != 0 and base is None: + print("No base symbol found for:", ticker) + base = ticker + + # Return the information + return ( + total_vol, + ( + f"https://coingecko.com/en/coins/{id}" + if id + else "https://coingecko.com/en/coins/id_not_found" + ), + exchanges, + price, + format_change(change) if change else "N/A", + base, + )
+ + + + + + + +
+[docs] +async def get_top_categories() -> pd.DataFrame | None: + html = session.get("https://www.coingecko.com/en/categories").text + + soup = BeautifulSoup(html, "html.parser") + + table = soup.find("table") + + if table is None: + print("Error getting top categories from CoinGecko, no table found.") + return + + data = [] + for tr in table.find_all("tr")[1:]: + coin_data = {} + + for i, td in enumerate(tr.find_all("td")): + # i == 0 -> rank + + if i == 1: + coin_data["Name"] = td.find("a").text + coin_data["Link"] = "https://www.coingecko.com/" + td.find("a")["href"] + + # 24h + if i == 4: + coin_data["24h Change"] = td["data-sort"] + + # Market cap + if i == 6: + coin_data["Market Cap"] = td["data-sort"] + + if i == 7: + coin_data["Volume"] = td["data-sort"] + + if coin_data != {}: + data.append(coin_data) + + return pd.DataFrame(data)
+ + + +
+[docs] +def get_top_vol_coins(length: int = 50) -> list: + df = pd.DataFrame(cg.get_coins_markets("usd"))["symbol"].str.upper() + "USDT" + + # Also add symbols that give issues + stableCoins = [ + "OKBUSDT", + "DAIUSDT", + "USDTUSDT", + "USDCUSDT", + "BUSDUSDT", + "TUSDUSDT", + "PAXUSDT", + "EURUSDT", + "GBPUSDT", + "CETHUSDT", + "WBTCUSDT", + ] + sorted_volume = df[~df.isin(stableCoins)] + return sorted_volume[:length].tolist()
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/confirm_stock.html b/_modules/util/confirm_stock.html new file mode 100644 index 00000000..b0b0e934 --- /dev/null +++ b/_modules/util/confirm_stock.html @@ -0,0 +1,466 @@ + + + + + + + + + + util.confirm_stock — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.confirm_stock

+## > Imports
+# > 3rd Party Dependencies
+import yfinance as yf
+import discord
+from discord.ext import commands
+from discord.ui import Button, View
+
+
+
+[docs] +async def confirm_stock(bot: commands.Bot, ctx: commands.Context, ticker: str) -> bool: + + # Check if this ticker exists + stock_info = yf.Ticker(ticker) + + # If it does not exist let the user know + if stock_info.info["regularMarketPrice"] == None: + + confirm_button = Button( + label="Confirm", + style=discord.ButtonStyle.green, + emoji="✅", + custom_id="confirm", + ) + cancel_button = Button( + label="Cancel", style=discord.ButtonStyle.red, emoji="❌", custom_id="cancel" + ) + + view = View() + view.add_item(confirm_button) + view.add_item(cancel_button) + + # Can also use ctx.followup.send + await ctx.respond( + ( + f"Are you sure {ticker.upper()} is correct? We could not find it on Yahoo Finance.\n" + "Click on \N{WHITE HEAVY CHECK MARK} to continue and on \N{CROSS MARK} to cancel." + ), + view=view, + ) + + res = await bot.wait_for( + "interaction", check=lambda i: i.custom_id == "confirm" + ) + + # If the confirm button was pressed, return True + if res.data["custom_id"] == "confirm": + return True + else: + return False + + return True
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/db.html b/_modules/util/db.html new file mode 100644 index 00000000..7c0c8145 --- /dev/null +++ b/_modules/util/db.html @@ -0,0 +1,752 @@ + + + + + + + + + + util.db — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.db

+# > Standard library
+import os
+import datetime
+from collections import defaultdict
+
+# > 3rd party dependencies
+import pandas as pd
+import sqlite3
+from pycoingecko import CoinGeckoAPI
+from yahoo_fin.stock_info import tickers_nasdaq
+import numpy as np
+
+# > Discord dependencies
+from discord.ext import commands
+from discord.ext.tasks import loop
+
+# > Local dependencies
+import util.vars
+from util.tv_symbols import crypto_indices, stock_indices, all_forex_indices
+from util.tv_data import get_tv_ticker_data
+
+# Convert emoji to text
+convert_emoji = defaultdict(
+    lambda: "neutral", {"🐻": "bear", "🐂": "bull", "🦆": "neutral"}
+)
+
+
+
+[docs] +class DB(commands.Cog): + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + # Start loops + self.set_tv_db.start() + self.set_cg_db.start() + self.set_nasdaq_tickers.start() + + # Set the portfolio and assets db + self.set_portfolio_db() + self.set_assets_db() + self.set_tweets_db() + self.set_reddit_ids_db() + self.set_ideas_ids_db() + self.set_classified_tickers_db() + self.set_options_db() + +
+[docs] + def set_portfolio_db(self): + util.vars.portfolio_db = get_db("portfolio") + if not util.vars.portfolio_db.empty: + util.vars.portfolio_db["id"] = util.vars.portfolio_db["id"].astype(np.int64)
+ + +
+[docs] + def set_assets_db(self): + util.vars.assets_db = get_db("assets") + if not util.vars.assets_db.empty: + util.vars.assets_db["id"] = util.vars.assets_db["id"].astype(np.int64)
+ + +
+[docs] + def set_tweets_db(self): + util.vars.tweets_db = get_db("tweets")
+ + +
+[docs] + def set_options_db(self): + util.vars.options_db = get_db("options")
+ + +
+[docs] + def set_reddit_ids_db(self): + util.vars.reddit_ids = get_db("reddit_ids")
+ + +
+[docs] + def set_ideas_ids_db(self): + util.vars.ideas_ids = get_db("ideas_ids")
+ + +
+[docs] + def set_classified_tickers_db(self): + util.vars.classified_tickers = get_db("classified_tickers")
+ + + @loop(hours=24) + async def set_nasdaq_tickers(self): + try: + util.vars.nasdaq_tickers = tickers_nasdaq() + update_db(pd.DataFrame(util.vars.nasdaq_tickers), "nasdaq_tickers") + + except Exception as e: + print("Failed to get new nasdaq tickers, error:", e) + nasdaq_tickers = get_db("nasdaq_tickers") + # Convert the dataframe to list + util.vars.nasdaq_tickers = nasdaq_tickers.iloc[:, 0].tolist() + + # Set the important database variables on startup and refresh every 24 hours + @loop(hours=24) + async def set_cg_db(self): + # Saves all CoinGecko coins, maybe refresh this daily + cg = CoinGeckoAPI() + cg_coins = pd.DataFrame(cg.get_coins_list()) + cg_coins["symbol"] = cg_coins["symbol"].str.upper() + + # Save cg_coins to database + update_db(cg_coins, "cg_coins") + + # Set cg_coins + util.vars.cg_db = cg_coins + + @loop(hours=24) + async def set_tv_db(self): + """ + Gets the data from TradingView and saves it to the database. + """ + + # In case the function below fails + util.vars.stocks = get_db("tv_stocks") + util.vars.crypto = get_db("tv_crypto") + util.vars.forex = get_db("tv_forex") + util.vars.cfd = get_db("tv_cfd") + + # Get the current symbols and exchanges on TradingView + tv_stocks = await get_tv_ticker_data( + "https://scanner.tradingview.com/america/scan", stock_indices + ) + tv_crypto = await get_tv_ticker_data( + "https://scanner.tradingview.com/crypto/scan", crypto_indices + ) + tv_forex = await get_tv_ticker_data( + "https://scanner.tradingview.com/forex/scan", all_forex_indices + ) + + # tv_cfd = await get_tv_ticker_data("https://scanner.tradingview.com/cfd/scan") + + # Save the data to the database + for db, name in [ + (tv_stocks, "tv_stocks"), + (tv_crypto, "tv_crypto"), + (tv_forex, "tv_forex"), + # (tv_cfd, "tv_cfd"), + ]: + if not db.empty: + update_db(db, name) + + if name == "tv_stocks": + util.vars.stocks = db + elif name == "tv_crypto": + util.vars.crypto = db + elif name == "tv_forex": + util.vars.forex = db
+ + # elif name == "tv_cfd": + # util.vars.cfd = db + + +def setup(bot: commands.Bot) -> None: + bot.add_cog(DB(bot)) + + +
+[docs] +def remove_old_rows(db: pd.DataFrame, days: int) -> pd.DataFrame: + """ + Removes the old rows from the database and return it. + """ + + # Set timestamp column to datetime + db["timestamp"] = pd.to_datetime(db["timestamp"]) + + return db[db["timestamp"] > datetime.datetime.now() - datetime.timedelta(days=days)]
+ + + +
+[docs] +def merge_and_update( + main_db: pd.DataFrame, new_data: pd.DataFrame, db_name: str +) -> pd.DataFrame: + merged = pd.concat([main_db, new_data], ignore_index=True) + update_db(merged, db_name) + return merged
+ + + +
+[docs] +def clean_old_db(db, days: int = 1) -> pd.DataFrame: + """ + Cleans the tweets database and returns it. + + Returns + ------- + pd.Dataframe + The cleaned tweets database. + """ + + # If the database is empty, do nothing and return + if db.empty: + return db + + # Set the types + try: + db = remove_old_rows(db, days) + return db + except Exception as e: + print("Error in clean_old_db:", e) + print(db.to_string())
+ + + +
+[docs] +def update_tweet_db( + tickers: list, user: str, sentiment: str, categories: list, changes: list +) -> None: + """ + Updates thet tweet database variable using the info provided. + + Parameters + ---------- + tickers : list + The list of tickers. + user : str + The name of the user. + sentiment : str + The sentiment of the tweet. + categories : list + The categories of the tickers. + """ + + # Prepare new data + dict_list = [] + + for i in range(len(tickers)): + # Remove emoji at end + change = changes[i] + if change: + if "%" in change: + change = change[:-1] + else: + change = "None" + else: + change = "None" + + dict_list.append( + { + "ticker": tickers[i], + "user": user, + "sentiment": convert_emoji[sentiment], + "category": categories[i], + "change": change, + } + ) + + # Convert it to a dataframe + tweet_db = pd.DataFrame(dict_list) + + # Add current time + tweet_db["timestamp"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + util.vars.tweets_db = clean_old_db(util.vars.tweets_db, 1) + util.vars.tweets_db = merge_and_update(util.vars.tweets_db, tweet_db, "tweets")
+ + + +
+[docs] +def get_db(database_name: str) -> pd.DataFrame: + """ + Get the database saved under data/<database_name>.pkl. + If it does not exist return an empty dataframe. + + Parameters + ---------- + str + Name of the database to get. + + Returns + ------- + pd.DataFrame + Database saved under data/<database_name>.pkl. + """ + + script_dir = os.path.dirname(__file__) + db_loc = os.path.join(script_dir, "..", "..", "data", f"{database_name}.db") + cnx = sqlite3.connect(db_loc) + + try: + return pd.read_sql_query(f"SELECT * FROM {database_name}", cnx) + except Exception: + print(f"No {database_name}.db found, returning empty db") + return pd.DataFrame()
+ + + +
+[docs] +def update_db(db: pd.DataFrame, database_name: str) -> None: + """ + Update the database saved under data/database_name.pkl using db as the new database. + + Parameters + ---------- + pd.DatFrame + Database to use for updating old database. + str + Name of the database to update. + + Returns + ------- + None + """ + + db_loc = f"data/{database_name}.db" + + # Convert everything to string to prevent errors + # Using map on each column + for column in db.columns: + db[column] = db[column].map(str) + + try: + db.to_sql( + database_name, sqlite3.connect(db_loc), if_exists="replace", index=False + ) + except Exception as e: + print( + f"Error updating {database_name}.db: {e}.\nTried to update database:\n{db.to_string()}" + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/disc_util.html b/_modules/util/disc_util.html new file mode 100644 index 00000000..2411db45 --- /dev/null +++ b/_modules/util/disc_util.html @@ -0,0 +1,604 @@ + + + + + + + + + + util.disc_util — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.disc_util

+# Standard libraries
+from typing import Optional
+
+# Discord dependencies
+import discord
+from discord.ext import commands
+
+# Local dependencies
+import util.vars
+from util.vars import guild_name
+
+
+
+[docs] +def get_guild(bot: commands.Bot) -> discord.Guild: + """ + Returns the guild / server the bot is currently connected to. + + Parameters + ---------- + commands.Bot + The bot object. + + Returns + ------- + discord.Guild + The guild / server the bot is currently connected to. + """ + + return discord.utils.get( + bot.guilds, + # Return the debug server if -test is used as an argument + name=guild_name, + )
+ + + +
+[docs] +def get_channel( + bot: commands.Bot, channel_name: str, category_name: str = None +) -> discord.TextChannel: + """ + Returns the discord.TextChannel object of the channel with the given name. + + Parameters + ---------- + bot : commands.Bot + The bot object. + channel_name : str + The name of the channel. + + Returns + ------- + discord.TextChannel + The discord.TextChannel object of the channel with the given name. + """ + + for guild in bot.guilds: + if guild.name == guild_name: + for channel in guild.channels: + if channel.name == channel_name: + if category_name is None: + return channel + else: + if channel.category.name == category_name: + return channel
+ + + # If the channel is not found, create it + # if category_name: + # category = discord.utils.get(guild.categories, name=category_name) + # return await guild.create_text_channel( + # channel_name, category=category + # ) + + # # Maybe read the category from the config file + # return await guild.create_text_channel(channel_name) + + +
+[docs] +async def set_emoji(guild) -> dict: + """ + Returns the custom emoji with the given name. + + Parameters + ---------- + bot : commands.Bot + The bot object. + emoji : str + The name of the emoji. + + Returns + ------- + discord.Emoji + The custom emoji with the given name. + """ + # https://docs.pycord.dev/en/stable/api.html?highlight=emojis#discord.on_guild_emojis_update + # Could use this event to update the emojis if they change + + emojis = await guild.fetch_emojis() + + for emoji in emojis: + util.vars.custom_emojis[emoji.name] = emoji
+ + + +
+[docs] +async def get_user(bot: commands.Bot, user_id: int) -> discord.User: + """ + Gets the discord.User object of the user with the given id. + + Parameters + ---------- + bot : commands.Bot + The bot object. + user_id : int + The id of the user. + + Returns + ------- + discord.User + The discord.User object of the user with the given id. + """ + + return await bot.fetch_user(user_id)
+ + + +
+[docs] +def get_tagged_users(tickers: list) -> Optional[str]: + """ + Tags the users with the tickers in their portfolio that are mentioned in the message. + + Parameters + ---------- + tickers : list + The list of tickers mentioned in the message. + + Returns + ------- + Optional[str] + The message of the users that need to be tagged. + """ + + # Get the stored db + if not util.vars.assets_db.empty: + matching_users = ( + util.vars.assets_db[util.vars.assets_db["asset"].isin(tickers)]["id"] + .dropna() + .tolist() + ) + unique_users = list(set(matching_users)) + + if unique_users: + # Make it one message for all the users + return " ".join([f"<@!{user}>" for user in unique_users])
+ + + +
+[docs] +async def get_webhook(channel: discord.TextChannel) -> discord.Webhook: + """ + Checks if there is a webhook in the given channel and returns it. + If there is not a webhook for a channel, then it creates one. + + Parameters + ---------- + channel : discord.TextChannel + The channel to check for a webhook. + + Returns + ------- + discord.Webhook + The webhook for the given channel. + """ + + webhook = await channel.webhooks() + + if not webhook: + webhook = await channel.create_webhook(name=channel.name) + print(f"Created webhook for {channel.name}") + else: + webhook = webhook[0] + + return webhook
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/earnings_scraper.html b/_modules/util/earnings_scraper.html new file mode 100644 index 00000000..f5b63a26 --- /dev/null +++ b/_modules/util/earnings_scraper.html @@ -0,0 +1,491 @@ + + + + + + + + + + util.earnings_scraper — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.earnings_scraper

+## > Imports
+# Standard imports
+import json
+import requests
+import time
+
+
+
+[docs] +class YahooEarningsCalendar: + """ + This is the class for fetching earnings data from Yahoo! Finance, built by https://github.com/wenboyu2. + """ + + def _get_data_dict(self, url: str) -> dict: + + # Sleep 60*60 / 2000 = 1.8 seconds to prevent rate limit + time.sleep(1.8) + page = requests.get( + url, + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" + }, + ) + page_content = page.content.decode(encoding="utf-8", errors="strict") + page_data_string = [ + row + for row in page_content.split("\n") + if row.startswith("root.App.main = ") + ][0][:-1] + page_data_string = page_data_string.split("root.App.main = ", 1)[1] + + return json.loads(page_data_string) + +
+[docs] + def get_next_earnings_date(self, symbol: str): + """Gets the next earnings date of symbol + Args: + symbol: A ticker symbol + Returns: + Unix timestamp of the next earnings date + Raises: + Exception: When symbol is invalid or earnings date is not available + """ + url = f"https://finance.yahoo.com/quote/{symbol}" + + try: + page_data_dict = self._get_data_dict(url) + return page_data_dict["context"]["dispatcher"]["stores"][ + "QuoteSummaryStore" + ]["calendarEvents"]["earnings"]["earningsDate"][0]["raw"] + except: + raise Exception("Invalid Symbol or Unavailable Earnings Date")
+ + +
+[docs] + def get_earnings_of(self, symbol: str) -> list: + """Returns all the earnings dates of a symbol + Args: + symbol: A ticker symbol + Returns: + Array of all earnings dates with supplemental information + Raises: + Exception: When symbol is invalid or earnings date is not available + """ + url = f"https://finance.yahoo.com/calendar/earnings?symbol={symbol}" + + try: + page_data_dict = self._get_data_dict(url) + return page_data_dict["context"]["dispatcher"]["stores"][ + "ScreenerResultsStore" + ]["results"]["rows"] + except: + raise Exception("Invalid Symbol or Unavailable Earnings Date")
+
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/exchange_data.html b/_modules/util/exchange_data.html new file mode 100644 index 00000000..eb2c9a98 --- /dev/null +++ b/_modules/util/exchange_data.html @@ -0,0 +1,585 @@ + + + + + + + + + + util.exchange_data — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.exchange_data

+import traceback
+
+import ccxt.async_support as ccxt
+import pandas as pd
+import numpy as np
+
+from util.vars import stables
+
+
+
+[docs] +async def get_data(row) -> pd.DataFrame: + exchange_info = {"apiKey": row["key"], "secret": row["secret"]} + + if row["exchange"] == "binance": + exchange = ccxt.binance(exchange_info) + exchange.options["recvWindow"] = 60000 + elif row["exchange"] == "kucoin": + exchange_info["password"] = row["passphrase"] + exchange = ccxt.kucoin(exchange_info) + + try: + balances = await get_balance(exchange) + + if balances == "invalid API key": + await exchange.close() + return "invalid API key" + + # Create a list of dictionaries + owned = [] + + for symbol, amount in balances.items(): + usd_val, percentage = await get_usd_price(exchange, symbol) + worth = amount * usd_val + + # Add price change + + if worth < 5: + continue + + buying_price = await get_buying_price(exchange, symbol) + + # If buying price is 0 then it is not known what the price was + owned.append( + { + "asset": symbol, + "buying_price": buying_price, + "owned": amount, + "exchange": exchange.id, + "id": row["id"], + "user": row["user"], + "worth": round(worth, 2), + "price": usd_val, + "change": percentage, + } + ) + + df = pd.DataFrame(owned) + + # Se tthe types + if not df.empty: + df = df.astype( + { + "asset": str, + "buying_price": float, + "owned": float, + "exchange": str, + "id": np.int64, + "user": str, + "worth": float, + "price": float, + "change": float, + } + ) + + await exchange.close() + return df + except Exception as e: + await exchange.close() + print("Error in get_data(). Error:", e) + print(traceback.format_exc())
+ + + +
+[docs] +async def get_balance(exchange) -> dict: + try: + balances = await exchange.fetchBalance() + total_balance = balances["total"] + if total_balance is None: + return "invalid API key" + return {k: v for k, v in total_balance.items() if v > 0} + except Exception: + return {}
+ + + +
+[docs] +async def get_usd_price(exchange, symbol: str) -> tuple[float, float]: + """ + Returns the price of the symbol in USD. + Symbol must be in the format 'BTC/USDT'. + """ + # Directly return for USDT or when symbol is a known stable coin + if symbol == "USDT" or symbol in stables: + return 1.0, 0.0 + + # Helper function to fetch price and change + async def fetch_price(symbol_pair: str): + try: + price = await exchange.fetchTicker(symbol_pair) + exchange_price = price.get("last", 0) + if exchange_price is None: + exchange_price = 0 + exchange_price = float(exchange_price) + exchange_change = price.get("percentage", 0) + if exchange_change is None: + exchange_change = 0 + exchange_change = float(exchange_change) + return exchange_price, exchange_change + except (ccxt.BadSymbol, ccxt.RequestTimeout): + return None # Use None to indicate a failed fetch + except ccxt.ExchangeError as e: + print(f"Exchange error for {symbol_pair} on {exchange.id}: {e}") + return None + except Exception as e: + print(f"Error fetching {symbol_pair} on {exchange.id}: {e}") + return None + + # Attempt to fetch price for each stable coin pairing + for usd in stables: + result = await fetch_price(f"{symbol}/{usd}") + if result: + return result + + # Fallback if no price found for any stable pairing + return 0.0, 0.0
+ + + +
+[docs] +async def get_buying_price(exchange, symbol, full_sym: bool = False) -> float: + # Maybe try different quote currencies when returned list is empty + if symbol in stables: + return 1 + + symbol = symbol + "/USDT" if not full_sym else symbol + + params = {} + if exchange.id == "kucoin": + params = {"side": "buy"} + try: + trades = await exchange.fetchClosedOrders(symbol, params=params) + except ccxt.BadSymbol: + return 0 + except ccxt.RequestTimeout: + return 0 + if type(trades) == list: + if len(trades) > 0: + if exchange.id == "binance": + # Filter list for side:buy + trades = [trade for trade in trades if trade["info"]["side"] == "BUY"] + if len(trades) == 0: + return 0 + + return float(trades[-1]["price"]) + + return 0
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/formatting.html b/_modules/util/formatting.html new file mode 100644 index 00000000..9eb68160 --- /dev/null +++ b/_modules/util/formatting.html @@ -0,0 +1,655 @@ + + + + + + + + + + util.formatting — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.formatting

+# Standard libaries
+from math import log, floor
+import datetime
+
+# Third party libraries
+import pandas as pd
+import discord
+
+from util.vars import data_sources
+
+
+
+[docs] +def format_change(change: float) -> str: + """ + Converts a float to a string with a plus sign if the float is positive, and a minus sign if the float is negative. + + Parameters + ---------- + change : float + The percentual change of an asset. + + Returns + ------- + str + The formatted change. + """ + if change is None: + return "N/A" + + if isinstance(change, str): + # Try to convert to float + try: + change = float(change) + except ValueError: + return 0 + + # Round to 2 decimals + change = round(change, 2) + + return f"+{change}% 📈" if change > 0 else f"{change}% 📉"
+ + + +
+[docs] +def human_format(number: float, absolute: bool = False, decimals: int = 0) -> str: + """ + Takes a number and returns a human readable string. + Taken from: https://stackoverflow.com/questions/579310/formatting-long-numbers-as-strings-in-python/45846841. + + Parameters + ---------- + number : float + The number to be formatted. + absolute : bool + If True, the number will be converted to its absolute value. + decimals : int + The number of decimals to be used. + + Returns + ------- + str + The formatted number as a string. + """ + + # Try to convert to float + if type(number) == str: + try: + number = float(number) + except ValueError: + number = 0 + + if number == 0: + return "0" + + # https://idlechampions.fandom.com/wiki/Large_number_abbreviations + units = ["", "K", "M", "B", "t", "q"] + k = 1000.0 + magnitude = int(floor(log(abs(number), k))) + + if decimals > 0: + rounded_number = round(number / k**magnitude, decimals) + else: + rounded_number = int(number / k**magnitude) + + if absolute: + rounded_number = abs(rounded_number) + + return f"{rounded_number}{units[magnitude]}"
+ + + +
+[docs] +def format_embed_length(data: list) -> list: + """ + If the length of the data is greater than 1024 characters, it will be shortened to that amount. + + Parameters + ---------- + data : list + The list containing the description for an embed. + + Returns + ------- + list + The shortened description. + """ + + for x in range(len(data)): + if len(data[x]) > 1024: + data[x] = data[x][:1024].split("\n")[:-1] + # Fix everything that is not x + for y in range(len(data)): + if x != y: + data[y] = "\n".join(data[y].split("\n")[: len(data[x])]) + + data[x] = "\n".join(data[x]) + + return data
+ + + +# Used in gainers, losers loops +
+[docs] +async def format_embed(og_df: pd.DataFrame, type: str, source: str) -> discord.Embed: + """ + Formats the dataframe to an embed. + + Parameters + ---------- + df : pd.DataFrame + A dataframe with the columns: + Symbol + Price + % Change + Volume + type : str + The type used in the title of the embed + source : str + The source used for this data + + Returns + ------- + discord.Embed + A Discord embed containing the formatted data + """ + + df = og_df.copy() + + if source == "binance": + url = "https://www.binance.com/en/altcoins/gainers-losers" + color = data_sources["binance"]["color"] + icon_url = data_sources["binance"]["icon"] + name = "Coin" + elif source == "yahoo": + url = "https://finance.yahoo.com/most-active" + color = data_sources["yahoo"]["color"] + icon_url = data_sources["yahoo"]["icon"] + name = "Stock" + elif source == "coingecko": + url = "https://www.coingecko.com/en/watchlists/trending-crypto" + color = data_sources["coingecko"]["color"] + icon_url = data_sources["coingecko"]["icon"] + name = "Coin" + elif source == "coinmarketcap": + url = "https://coinmarketcap.com/trending-cryptocurrencies/" + color = data_sources["coinmarketcap"]["color"] + icon_url = data_sources["coinmarketcap"]["icon"] + name = "Coin" + + e = discord.Embed( + title=f"Top {len(df)} {type}", + url=url, + description="", + color=color, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + if source == "yahoo": + # Format the data + df.rename(columns={"Price (Intraday)": "Price"}, inplace=True) + + # Add website to symbol + df["Symbol"] = ( + "[" + + df["Symbol"] + + "](https://finance.yahoo.com/quote/" + + df["Symbol"] + + ")" + ) + + # Only these columns are necessary + df = df[["Symbol", "Price", "% Change", "Volume"]] + + df = df.astype({"Symbol": str, "Price": float, "% Change": float, "Volume": float}) + + df = df.round({"Price": 3, "% Change": 2, "Volume": 0}) + + # Format the percentage change + df["% Change"] = df["% Change"].apply( + lambda x: f" (+{x}% 📈)" if x > 0 else f" ({x}% 📉)" + ) + + # Post symbol, current price (weightedAvgPrice) + change, volume + df["Price"] = df["Price"].astype(str) + df["% Change"] + + # Format volume + df["Volume"] = df["Volume"].apply(lambda x: "$" + human_format(x)) + + ticker = "\n".join(df["Symbol"].tolist()) + prices = "\n".join(df["Price"].tolist()) + vol = "\n".join(df["Volume"].astype(str).tolist()) + + # Prevent possible overflow + ticker, prices, vol = format_embed_length([ticker, prices, vol]) + + e.add_field( + name=name, + value=ticker, + inline=True, + ) + + e.add_field( + name="Price", + value=prices, + inline=True, + ) + + e.add_field( + name="Volume", + value=vol, + inline=True, + ) + + # Set empty text as footer, so we can see the icon + e.set_footer(text="\u200b", icon_url=icon_url) + + return e
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/get_tweet.html b/_modules/util/get_tweet.html new file mode 100644 index 00000000..cce4e12d --- /dev/null +++ b/_modules/util/get_tweet.html @@ -0,0 +1,468 @@ + + + + + + + + + + util.get_tweet — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.get_tweet

+import json
+import uncurl
+
+from util.vars import get_json_data
+
+# Read curl.txt
+try:
+    with open("curl.txt", "r", encoding="utf-8") as file:
+        cURL = uncurl.parse_context("".join([line.strip() for line in file]))
+except Exception as e:
+    cURL = None
+    print("Error: Could not read curl.txt:", e)
+
+
+
+[docs] +async def get_tweet(): + if cURL is None: + print("Error: no curl.txt file found. Timelines will not be updated.") + return [] + result = await get_json_data( + cURL.url, + headers=dict(cURL.headers), + cookies=dict(cURL.cookies), + json_data=json.loads(cURL.data), + text=False, + ) + + if result == {}: + return [] + + # TODO: Ignore x-premium alerts + if "data" in result: + if "home" in result["data"]: + if "home_timeline_urt" in result["data"]["home"]: + if "instructions" in result["data"]["home"]["home_timeline_urt"]: + if ( + "entries" + in result["data"]["home"]["home_timeline_urt"]["instructions"][ + 0 + ] + ): + return result["data"]["home"]["home_timeline_urt"][ + "instructions" + ][0]["entries"] + + try: + result["data"]["home"]["home_timeline_urt"]["instructions"][0]["entries"] + except Exception as e: + print("Error in get_tweet():", e) + with open("logs/get_tweet_error.json", "w") as f: + json.dump(result, f, indent=4) + + return []
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/parse_tweet.html b/_modules/util/parse_tweet.html new file mode 100644 index 00000000..5cb9f096 --- /dev/null +++ b/_modules/util/parse_tweet.html @@ -0,0 +1,648 @@ + + + + + + + + + + util.parse_tweet — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.parse_tweet

+import re
+import json
+import datetime
+from typing import List
+
+# > Local imports
+import util.vars
+
+
+
+[docs] +def remove_twitter_url_at_end(text: str) -> str: + """ + Removes a t.co URL at the end of a text string. + + Parameters + ---------- + text : str + The text from which to remove the URL. + + Returns + ------- + str + The text with the URL removed. + """ + pattern = r"(https?://t\.co/\S+)$" + return re.sub(pattern, "", text)
+ + + +
+[docs] +def get_user_info(tweet: dict, key: str) -> str: + return tweet["core"]["user_results"]["result"]["legacy"][key]
+ + + +
+[docs] +def get_entities(tweet: dict, key: str) -> List[str]: + """ + Retrieves entities from a tweet. + + Parameters + ---------- + tweet : dict + The tweet from which to retrieve entities. + key : str + The key of the entities to retrieve. + + Returns + ------- + List[str] + The retrieved entities, or an empty list if the key does not exist. + """ + if "legacy" in tweet: + if "entities" in tweet["legacy"]: + entities = tweet["legacy"]["entities"].get(key) + return [entity["text"] for entity in entities] if entities else [] + + print("Tweet contains no entities") + return []
+ + + +
+[docs] +def save_errored_tweet(tweet, error_msg: str): + print(error_msg) + # Get current time as a string for the filename + current_time = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + + # Write tweet content to a JSON file in the logs directory + with open(f"logs/error_tweet_{current_time}.json", "w", encoding="utf-8") as file: + json.dump(tweet, file, ensure_ascii=False, indent=4)
+ + + +
+[docs] +def parse_tweet(tweet: dict, update_tweet_id: bool = False): + reply = None + + ## TODO: split the below logic up into functions + + # To be able to get the tweet and the reply + if "items" in tweet.keys(): + reply = tweet["items"][1]["item"]["itemContent"]["tweet_results"] + tweet = tweet["items"][0]["item"]["itemContent"]["tweet_results"] + + elif "itemContent" in tweet.keys(): + if "tweet_results" in tweet["itemContent"]: + tweet = tweet["itemContent"]["tweet_results"] + else: + save_errored_tweet( + tweet, "Error getting [itemContent][tweet_results] key in parse_tweet()" + ) + return + # For long tweets + elif "note_results" in tweet.keys(): + tweet = tweet["note_results"]["note_tweet_results"] + + try: + tweet = tweet["result"] + except KeyError: + save_errored_tweet(tweet, "Error getting result key in parse_tweet()") + return + + # Ignore Tweets that are older than the latest tweet + if "legacy" not in tweet: + if "tweet" not in tweet: + save_errored_tweet(tweet, "Error getting tweet key in parse_tweet()") + return + + tweet_id = int(tweet["tweet"]["rest_id"]) + else: + tweet_id = int(tweet["legacy"]["id_str"]) + + if "core" not in tweet: + if "tweet" in tweet: + tweet = tweet["tweet"] + else: + save_errored_tweet( + tweet, "Error getting [core][tweet] key in parse_tweet()" + ) + return + + # So we can use this function recursively + if update_tweet_id: + # Skip this tweet + if tweet_id <= util.vars.latest_tweet_id: + return + util.vars.latest_tweet_id = tweet_id + + # Get user info + user_name = get_user_info(tweet, "name") # The name of the account (not @username) + user_screen_name = get_user_info(tweet, "screen_name") # The @username + user_img = get_user_info(tweet, "profile_image_url_https") + + # Media + media = [] + media_types = [] + if "legacy" in tweet.keys(): + if "extended_entities" in tweet["legacy"].keys(): + if "media" in tweet["legacy"]["extended_entities"].keys(): + media = [ + image["media_url_https"] + for image in tweet["legacy"]["extended_entities"]["media"] + ] + # photo, video + media_types = [ + image["type"] + for image in tweet["legacy"]["extended_entities"]["media"] + ] + + # Remove t.co url from text + text = remove_twitter_url_at_end(tweet["legacy"]["full_text"]) + + # Tweet url + tweet_url = f"https://twitter.com/user/status/{tweet_id}" + + # Tickers + tickers = get_entities(tweet, "symbols") + + # Hashtags + hashtags = get_entities(tweet, "hashtags") + + quoted_status_result = tweet.get("quoted_status_result") + retweeted_status_result = tweet["legacy"].get("retweeted_status_result") + + e_title = f"{user_name} tweeted" + + if quoted_status_result or retweeted_status_result or reply: + result = quoted_status_result or retweeted_status_result or reply + ( + r_text, + r_user_name, + r_user_screen_name, + _, + _, + r_media, + r_tickers, + r_hashtags, + _, + r_media_types, + ) = parse_tweet(result) + + if reply: + e_title = f"{util.vars.custom_emojis['reply']} {user_name} replied to {r_user_name}" + text = "\n".join(map(lambda line: "> " + line, text.split("\n"))) + text = f"> [@{r_user_screen_name}](https://twitter.com/{r_user_screen_name}):\n{text}\n\n{r_text}" + + # Add text on top + if quoted_status_result: + e_title = f"{util.vars.custom_emojis['quote_tweet']} {user_name} quote tweeted {r_user_name}" + q_text = "\n".join(map(lambda line: "> " + line, r_text.split("\n"))) + text = f"{text}\n\n> [@{r_user_screen_name}](https://twitter.com/{r_user_screen_name}):\n{q_text}" + + if retweeted_status_result: + e_title = f"{util.vars.custom_emojis['retweet']} {user_name} retweeted {r_user_name}" + # Remove the "RT @username: " from the text + text = re.sub(r"^RT @\w+: ", "", text) + + media += r_media + media_types += r_media_types + tickers += r_tickers + hashtags += r_hashtags + + # Replace &amp; etc. + text = text.replace("&amp;", "&").replace("&gt;", ">").replace("&lt;", "<") + + # Convert media, tickers, hasthtags to sets to remove duplicates + media = list(set(media)) + tickers = list(set(tickers)) + hashtags = list(set(hashtags)) + + # tickers and hashtags all uppercase + tickers = [ticker.upper() for ticker in tickers] + hashtags = [hashtag.upper() for hashtag in hashtags if hashtag != "CRYPTO"] + + # Create the embed title + + return ( + text, + user_name, + user_screen_name, + user_img, + tweet_url, + media, + tickers, + hashtags, + e_title, + media_types, + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/sentiment_analyis.html b/_modules/util/sentiment_analyis.html new file mode 100644 index 00000000..c10967cd --- /dev/null +++ b/_modules/util/sentiment_analyis.html @@ -0,0 +1,518 @@ + + + + + + + + + + util.sentiment_analyis — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.sentiment_analyis

+##> Imports
+# > Standard libaries
+from __future__ import annotations
+import re
+
+# > Third party libraries
+import discord
+from transformers import AutoTokenizer, BertForSequenceClassification, pipeline
+
+# Load model
+model = BertForSequenceClassification.from_pretrained(
+    "StephanAkkerman/FinTwitBERT-sentiment",
+    num_labels=3,
+    id2label={0: "NEUTRAL", 1: "BULLISH", 2: "BEARISH"},
+    label2id={"NEUTRAL": 0, "BULLISH": 1, "BEARISH": 2},
+    cache_dir="models/",
+)
+model.config.problem_type = "single_label_classification"
+tokenizer = AutoTokenizer.from_pretrained(
+    "StephanAkkerman/FinTwitBERT-sentiment",
+    cache_dir="models/",
+    add_special_tokens=True,
+)
+model.eval()
+pipe = pipeline("text-classification", model=model, tokenizer=tokenizer)
+
+label_to_emoji = {
+    "NEUTRAL": "🦆",
+    "BULLISH": "🐂",
+    "BEARISH": "🐻",
+}
+
+
+
+[docs] +def preprocess_text(tweet: str) -> str: + # Replace URLs with URL token + tweet = re.sub(r"http\S+", "[URL]", tweet) + + # Replace @mentions with @USER token + tweet = re.sub(r"@\S+", "@USER", tweet) + + return tweet
+ + + +
+[docs] +def classify_sentiment(text: str) -> tuple[str, str]: + """ + Uses the text of a tweet to classify the sentiment of the tweet. + + Parameters + ---------- + text : str + The text of the tweet. + + Returns + ------- + np.ndarray + The probability of the tweet being bullish, neutral, or bearish. + """ + + label = pipe(preprocess_text(text))[0].get("label") + emoji = label_to_emoji[label] + + label = f"{emoji} - {label.capitalize()}" + + return label, emoji
+ + + +
+[docs] +def add_sentiment(e: discord.Embed, text: str) -> tuple[discord.Embed, str]: + """ + Adds sentiment to a discord embed, based on the given text. + + Parameters + ---------- + e : discord.Embed + The embed to add the sentiment to. + text : str + The text to classify the sentiment of. + + Returns + ------- + tuple[discord.Embed, str] + discord.Embed + The embed with the sentiment added. + str + The sentiment of the tweet. + """ + + # Remove quote tweet formatting + prediction, emoji = classify_sentiment(text.split("\n\n> [@")[0]) + + e.add_field( + name="Sentiment", + value=f"{prediction}", + inline=False, + ) + + return e, emoji
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/ticker_classifier.html b/_modules/util/ticker_classifier.html new file mode 100644 index 00000000..ac137215 --- /dev/null +++ b/_modules/util/ticker_classifier.html @@ -0,0 +1,626 @@ + + + + + + + + + + util.ticker_classifier — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.ticker_classifier

+##> Imports
+# > Standard libaries
+from __future__ import annotations
+from typing import Optional, List
+
+# Local dependencies
+from util.tv_data import tv
+from util.tv_symbols import currencies
+from util.cg_data import get_coin_info
+from util.yf_data import get_stock_info
+
+
+[docs] +async def get_financials(ticker: str, website : str): + if "coingecko" in website: + _, _, _, price, change, _ = await get_coin_info(ticker) + four_h_ta, one_d_ta = tv.get_tv_TA(ticker, "crypto") + elif "yahoo" in website: + _, _, _, price, change, _ = await get_stock_info(ticker) + four_h_ta, one_d_ta = tv.get_tv_TA(ticker, "stock") + elif "forex" in website: + _, _, _, price, change, _ = await get_stock_info(ticker, "forex") + four_h_ta, one_d_ta = tv.get_tv_TA(ticker, "forex") + + return price, change, four_h_ta, one_d_ta
+ + + +
+[docs] +async def get_best_guess(ticker: str, asset_type: str): + """ + Gets the best guess of the ticker. + + Parameters + ---------- + ticker : str + The ticker mentioned in a tweet, e.g. BTC + asset_type : str + The guessed asset type, this can be crypto, stock or forex. + + Returns + ------- + tuple + The data of the best guess + """ + + get_TA = False + four_h_ta = one_d_ta = None + + if asset_type == "crypto" and ticker.endswith("BTC") and ticker != "BTC": + get_TA = True + ticker = ticker[:-3] + + if asset_type == "crypto": + ( + volume, + website, + exchange, + price, + change, + base_sym, + ) = await get_coin_info(ticker) + + elif asset_type == "stock": + ( + volume, + website, + exchange, + price, + change, + base_sym, + ) = await get_stock_info(ticker) + + elif asset_type == "forex": + if ticker in currencies: + return (100000, "https://www.tradingview.com/ideas/eur/?forex", "forex", None, None, None, None, ticker, True) + ( + volume, + website, + exchange, + price, + change, + base_sym, + ) = await get_stock_info(ticker, asset_type) + + if price > 0: + four_h_ta, one_d_ta = tv.get_tv_TA(ticker, "forex") + return ( + volume, + website, + exchange, + price, + change, + four_h_ta, + one_d_ta, + base_sym, + True, + ) + + # If volume of the crypto is bigger than 1,000,000, it is likely a crypto + # Stupid Tessla Coin https://www.coingecko.com/en/coins/tessla-coin + if volume > 1000000: + get_TA = True + + # Set the TA data, only if volume is high enough + if get_TA: + if base_sym == None: + print("No base symbol found for", ticker) + base_sym = ticker + four_h_ta, one_d_ta = tv.get_tv_TA(base_sym, asset_type) + + return ( + volume, + website, + exchange, + price, + change, + four_h_ta, + one_d_ta, + base_sym, + get_TA, + )
+ + + +
+[docs] +async def classify_ticker( + ticker: str, majority: str +) -> Optional[tuple[float, str, List[str], float, str, str, str]]: + """ + Main function to classify the ticker as crypto or stock. + + Parameters + ---------- + ticker : str + The ticker of the coin or stock. + majority : str + The guessed majority of the ticker. + + Returns + ------- + Optional[tuple[float, str, List[str], float, str, str]] + float + The volume of the coin or stock. + str + The website of the coin or stock. + list[str] + The exchanges of the coin or stock. + float + The price of the coin or stock. + str + The 24h price change of the coin or stock. + str + The four hour technical analysis using TradingView. + str + The daily technical analysis using TradingView. + str + The base ticker. + """ + + # Try forex first + forex_data = await get_best_guess(ticker, "forex") + if forex_data[-1] == True: + return forex_data[:-1] + + # If the majority is crypto or unkown check if the ticker is a crypto + if majority == "crypto": + crypto_data = await get_best_guess(ticker, "crypto") + + if crypto_data[-1] == True: + return crypto_data[:-1] + + stock_data = await get_best_guess(ticker, "stock") + + elif majority == "stocks": + stock_data = await get_best_guess(ticker, "stock") + + if stock_data[-1] == True: + return stock_data[:-1] + + crypto_data = await get_best_guess(ticker, "crypto") + else: + crypto_data = await get_best_guess(ticker, "crypto") + stock_data = await get_best_guess(ticker, "stock") + + # If it was not the majority, compare the data + c_volume = crypto_data[0] + s_volume = stock_data[0] + + if c_volume > s_volume and c_volume > 50000: + if crypto_data[5] is None: + four_h_ta, one_d_ta = tv.get_tv_TA(ticker, "crypto") + + crypto_data = list(crypto_data) + crypto_data[5] = four_h_ta + crypto_data[6] = one_d_ta + tuple(crypto_data) + + return crypto_data[:-1] + + elif c_volume < s_volume: + if stock_data[5] is None: + four_h_ta, one_d_ta = tv.get_tv_TA(ticker, "stock") + + stock_data = list(stock_data) + stock_data[5] = four_h_ta + stock_data[6] = one_d_ta + tuple(stock_data) + + return stock_data[:-1]
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/trades_msg.html b/_modules/util/trades_msg.html new file mode 100644 index 00000000..9d9fb370 --- /dev/null +++ b/_modules/util/trades_msg.html @@ -0,0 +1,620 @@ + + + + + + + + + + util.trades_msg — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.trades_msg

+##> Imports
+import datetime
+import ccxt.pro
+import pandas as pd
+
+# > Discord dependencies
+import discord
+
+# Local dependencies
+import util.vars
+import util.trades_msg
+from util.db import get_db, update_db
+from util.vars import stables
+from util.exchange_data import get_data, get_usd_price, get_buying_price
+from util.formatting import format_change
+
+
+
+[docs] +async def on_msg( + msg: list, + exchange: ccxt.pro.Exchange, + trades_channel: discord.TextChannel, + row: pd.Series, + user: discord.User, +) -> None: + """ + This function is used to handle the incoming messages from the binance websocket. + + Parameters + ---------- + msg : str + The message that is received from the binance websocket. + + Returns + ------- + None + """ + + msg = msg[0] + sym = msg["symbol"] # BNB/USDT + orderType = msg["type"] # market, limit, stop, stop limit + side = msg["side"] # buy, sell + price = float(round(msg["price"], 4)) + amount = float(round(msg["amount"], 4)) + cost = float(round(msg["cost"], 4)) # If /USDT, then this is the USD value + + # Get the value in USD + usd = price + base = sym.split("/")[0] + quote = sym.split("/")[1] + if quote not in stables: + usd, change = await get_usd_price(exchange, base) + + # Get profit / loss if it is a sell + buying_price = None + if side == "sell": + buying_price = await get_buying_price(exchange, sym, True) + + # Send it in the discord channel + await util.trades_msg.trades_msg( + exchange.id, + trades_channel, + user, + sym, + side, + orderType, + price, + amount, + round(usd * amount, 2), + buying_price, + ) + + # Assets db: asset, owned (quantity), exchange, id, user + assets_db = get_db("assets") + + # Drop all rows for this user and exchange + updated_assets_db = assets_db.drop( + assets_db[ + (assets_db["id"] == row["id"]) & (assets_db["exchange"] == exchange.id) + ].index + ) + + assets_db = pd.concat([updated_assets_db, await get_data(row)]).reset_index( + drop=True + ) + + update_db(assets_db, "assets") + util.vars.assets_db = assets_db
+ + # Maybe post the updated assets of this user as well + + +
+[docs] +async def trades_msg( + exchange: str, + channel: discord.TextChannel, + user: discord.User, + symbol: str, + side: str, + orderType: str, + price: float, + quantity: float, + usd: float, + buying_price: float = None, +) -> None: + """ + Formats the Discord embed that will be send to the dedicated trades channel. + + Parameters + ---------- + exchange : str + The name of the exchange, currently only supports "binance", "kucoin" and "stocks". + channel : discord.TextChannel + The channel that the message will be sent to. + user : discord.User + The user that the message will be sent from. + symbol : str + The symbol that has been traded. + side : str + The side of the trade, either "BUY" or "SELL". + orderType : str + The type of order, for instance "LIMIT" or "MARKET". + price : float + The price of the trade. + quantity : float + The amount traded. + usd : float + The worth of the trade in US dollar. + + Returns + ------- + None + """ + + # Same as in formatting.py + if exchange == "binance": + color = 0xF0B90B + icon_url = ( + "https://upload.wikimedia.org/wikipedia/commons/5/57/Binance_Logo.png" + ) + url = f"https://www.binance.com/en/trade/{symbol}" + elif exchange == "kucoin": + color = 0x24AE8F + icon_url = "https://yourcryptolibrary.com/wp-content/uploads/2021/12/Kucoin-exchange-logo-1.png" + url = f"https://www.kucoin.com/trade/{symbol}" + else: + color = 0x720E9E + icon_url = ( + "https://s.yimg.com/cv/apiv2/myc/finance/Finance_icon_0919_250x252.png" + ) + url = f"https://finance.yahoo.com/quote/{symbol}" + + e = discord.Embed( + title=f"{orderType.capitalize()} {side.lower()} {quantity} {symbol}", + description="", + color=color, + url=url, + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + # Set the embed fields + e.set_author(name=user.name, icon_url=user.display_avatar.url) + + # If the quote is USD, then the price is the USD value + e.add_field( + name="Price", + value=f"${price}" if symbol.endswith(tuple(stables)) else price, + inline=True, + ) + + if buying_price and buying_price != 0: + price_change = price - buying_price + + if price_change != 0: + percent_change = round((price_change / buying_price) * 100, 2) + else: + percent_change = 0 + + percent_change = format_change(percent_change) + profit_loss = f"${round(price_change * quantity, 2)} ({percent_change})" + + e.add_field( + name="Profit / Loss", + value=profit_loss, + inline=True, + ) + else: + e.add_field(name="Amount", value=quantity, inline=True) + + # If we know the USD value, then add it + if usd != 0: + e.add_field( + name="$ Worth", + value=f"${usd}", + inline=True, + ) + + e.set_footer(text="\u200b", icon_url=icon_url) + + await channel.send(embed=e) + + # Tag the person + if orderType.upper() != "MARKET": + await channel.send(f"<@{user.id}>")
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/tv_data.html b/_modules/util/tv_data.html new file mode 100644 index 00000000..64d73281 --- /dev/null +++ b/_modules/util/tv_data.html @@ -0,0 +1,817 @@ + + + + + + + + + + util.tv_data — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.tv_data

+## > Imports
+# > Standard libaries
+from __future__ import annotations
+import re
+import json
+import random
+import string
+import traceback
+from typing import Optional, List
+
+# > 3rd party dependencies
+import aiohttp
+import pandas as pd
+from tradingview_ta import TA_Handler, Interval
+
+# > Local dependencies
+import util.vars
+from util.vars import get_json_data
+from util.tv_symbols import stock_indices, crypto_indices, all_forex_indices
+
+
+
+[docs] +async def get_tv_ticker_data(url, append_to=None): + data = await get_json_data(url) + + if not data or data == {} or "data" not in data.keys(): + return pd.DataFrame() + + # Convert data to pandas df + tv_data = pd.DataFrame(data["data"]).drop(columns=["d"]) + + if append_to: + # This adds additional information to the dataframe + tv_data = pd.concat([tv_data, pd.DataFrame(append_to, columns=["s"])]) + + # Split the information in exchange and stock + tv_data[["exchange", "stock"]] = tv_data["s"].str.split(":", n=1, expand=True) + + return tv_data
+ + + +
+[docs] +class TV_data: + """ + This class is used to get the current price, 24h change, and volume of a stock. + It also includes methods to get the TradingView TA data. + """ + + def __init__(self) -> None: + self.stock_indices_without_exch = [sym.split(":")[1] for sym in stock_indices] + self.crypto_indices_without_exch = [sym.split(":")[1] for sym in crypto_indices] + self.forex_indices_without_exch = [ + sym.split(":")[1] for sym in all_forex_indices + ] + +
+[docs] + async def on_msg( + self, ws: aiohttp.ClientWebSocketResponse, msg + ) -> Optional[tuple[float, float, float]]: + """ + Parses the message from the TradingView API. + + Returns + ------- + Optional[tuple[float, float, float, str]] + float + The current price. + float + The current 24h change. + float + The current volume. + """ + + try: + # Try again + if '"m":' not in msg: + return None + elif Res := re.findall("^.*?({.*)$", msg): + jsonRes = json.loads(Res[0].split("~m~")[0]) + if "m" in jsonRes.keys(): + if jsonRes["m"] == "qsd": + try: + price = float(jsonRes["p"][1]["v"]["lp"]) + change = float(jsonRes["p"][1]["v"]["ch"]) + volume = float(jsonRes["p"][1]["v"]["volume"]) + except KeyError: + print("KeyError in TradingView ws_data") + return None + + if price != 0: + perc_change = round((change / price) * 100, 2) + else: + print("TradingView returns price=0") + return None + + return price, perc_change, volume + else: + # ping packet + pingStr = re.findall(".......(.*)", msg) + if len(pingStr) != 0: + pingStr = pingStr[0] + ws.send_str("~m~" + str(len(pingStr)) + "~m~" + pingStr) + + return None + except Exception: + print(traceback.format_exc())
+ + +
+[docs] + async def sendMessage( + self, ws: aiohttp.ClientWebSocketResponse, func: str, args: List[str] + ) -> None: + """ + Sends a message to the TradingView API. + This needs to be done before any data can be retrieved. + + Parameters + ---------- + ws : aiohttp.ClientWebSocketResponse + The websocket object to send the message from. + func : str + The function to call, all start with ``quote_`` followed by the function name. + args : List[str] + The list of arguments to send in the message. + """ + + as_json = json.dumps({"m": func, "p": args}, separators=(",", ":")) + prepended = "~m~" + str(len(as_json)) + "~m~" + as_json + await ws.send_str(prepended)
+ + +
+[docs] + def get_usd_info(self, tv_crypto, symbol: str, suffix: str): + if not symbol.endswith(suffix): + # If it crypto try adding USD or USDT + crypto_USD = tv_crypto.loc[tv_crypto["stock"] == symbol + suffix] + + if not crypto_USD.empty: + return ( + crypto_USD["exchange"].values[0], + "crypto", + crypto_USD["stock"].values[0], + )
+ + +
+[docs] + def get_symbol_data( + self, symbol: str, asset: str + ) -> Optional[tuple[str, str, str]]: + """ + Helper function to get the symbol data from the TradingView API. + This data included the exchange and market this symbol is traded on. + + Parameters + ---------- + symbol : str + The ticker of the stock / crypto. + asset : str + The type of asset, either "stock" or "crypto". + + Returns + ------- + Optional[tuple[str, str, str]] + str + The exchange the symbol is traded on, e.g. "FTX" or "Binance". + str + The market the symbol is traded on, e.g. "crypto", "america", "forex". + str + The symbol itself. + """ + + tv_stocks = util.vars.stocks + tv_crypto = util.vars.crypto + tv_forex = util.vars.forex + + if asset == "stock": + stock = tv_stocks.loc[tv_stocks["stock"] == symbol] + if not stock.empty: + return stock["exchange"].values[0], "america", symbol + + elif asset == "forex": + forex = tv_forex.loc[tv_forex["stock"] == symbol] + if not forex.empty: + return forex["exchange"].values[0], "forex", symbol + + elif asset == "crypto": + crypto = tv_crypto.loc[tv_crypto["stock"] == symbol] + if not crypto.empty: + return crypto["exchange"].values[0], "crypto", symbol + else: + # Iterate over some USD suffixes + for s in ["USD", "USDT", "USDTPERP"]: + if data := self.get_usd_info(tv_crypto, symbol, s): + return data
+ + +
+[docs] + async def get_tv_data( + self, symbol: str, asset: str + ) -> Optional[tuple[float, float, float, str, str]]: + """ + Gets the current price, volume, 24h change, and TA data from the TradingView API. + + Parameters + ---------- + symbol: string + The ticker of the stock / crypto, e.g. "AAPL" or "BTCUSDT". + asset: string + The type of asset, either "stock", "crypto", or "forex". + + Returns + ------- + Optional[tuple[float, float, float, str]] + float + The current price. + float + The current 24h change. + float + The current volume. + str + The exchange that this symbol is listed on. + str + The url to the TradingView chart for this symbol. + """ + + if asset == "stock": + website_suffix = "/?yahoo" + elif asset == "forex": + website_suffix = "/?forex" + elif asset == "crypto": + website_suffix = "/?coingecko" + + website = f"https://www.tradingview.com/symbols/{symbol}{website_suffix}" + + try: + symbol_data = self.get_symbol_data(symbol, asset) + if symbol_data is not None: + # Format it "exchange:symbol" + exchange = symbol_data[0] + symbol = f"{exchange}:{symbol_data[2]}" + website = f"https://www.tradingview.com/symbols/{symbol_data[2]}{website_suffix}" + + else: + return (0, None, 0, None, website) + + # Create a session + session = aiohttp.ClientSession() + + async with session.ws_connect( + url="wss://data.tradingview.com/socket.io/websocket", + headers={"Origin": "https://data.tradingview.com"}, + ) as ws: + # This is mandatory to get the data + auth_str = "qs_" + "".join( + random.choice(string.ascii_lowercase) for i in range(12) + ) + + # Send messages via websocket + await self.sendMessage(ws, "quote_create_session", [auth_str]) + await self.sendMessage( + ws, "quote_set_fields", [auth_str, "ch", "lp", "volume"] + ) + await self.sendMessage(ws, "quote_add_symbols", [auth_str, symbol]) + + counter = 0 + + # Check for response + async for msg in ws: + counter += 1 + + if msg.type == aiohttp.WSMsgType.TEXT: + resp = await self.on_msg(ws, msg.data) + + if resp is not None: + await ws.close() + await session.close() + # Convert to USD volume if asset is crypto + return ( + float(resp[0]), + float(resp[1]), + resp[0] * resp[2] if asset == "crypto" else resp[2], + exchange.lower(), + website, + ) + + elif counter == 3: + await session.close() + return (0, None, 0, None, website) + + elif msg.type == aiohttp.WSMsgType.ERROR: + # self.restart_sockets() + print("TradingView websocket Error") + await session.close() + return (0, None, 0, None, website) + + except aiohttp.ClientConnectionError: + print("Temporary TradingView websocket error") + + except Exception: + print(traceback.format_exc()) + + return (0, None, 0, None, website)
+ + +
+[docs] + def format_analysis(self, analysis: dict) -> str: + """ + Simple helper function to format the TA data into one string. + + Parameters + ---------- + analysis : dict + The original TA data from the TradingView API. + + Returns + ------- + str + The formatted TA data. + """ + + return f"{analysis['RECOMMENDATION']}\n{analysis['BUY']}📈 {analysis['NEUTRAL']}⌛️ {analysis['SELL']}📉"
+ + +
+[docs] + def get_tv_TA(self, symbol: str, asset: str) -> Optional[tuple[str, str]]: + """ + Gets the current TA (technical analysis) data from the TradingView API. + + Parameters + ---------- + symbol : str + The ticker of the stock / crypto. + asset : str + The type of asset, either "stock", "crypto" or "forex". + + Returns + ------- + Optional[tuple[str,str]] + The 4h and 1d TA data as formatted strings. + """ + + # There is no TA for indices + if ( + symbol in self.stock_indices_without_exch + or symbol in self.crypto_indices_without_exch + or symbol in self.forex_indices_without_exch + ): + return None, None + + symbol_data = self.get_symbol_data(symbol, asset) + four_h_analysis = one_d_analysis = None + + # Get the TradingView TA for symbol + # Interval can be 1m, 5m, 15m, 30m, 1h, 2h, 4h, 1d, 1W, 1M + if symbol_data is not None: + exchange, market, symbol = symbol_data + + # Wait max 5 sec + try: + four_h_analysis = TA_Handler( + symbol=symbol, + screener=market, + exchange=exchange, + interval=Interval.INTERVAL_4_HOURS, + timeout=5, + ).get_analysis() + + one_d_analysis = TA_Handler( + symbol=symbol, + screener=market, + exchange=exchange, + interval=Interval.INTERVAL_1_DAY, + timeout=5, + ).get_analysis() + + except Exception as e: + print(f"TradingView TA error for ticker: {symbol}, error:", e) + return None, None + + if four_h_analysis: + four_h_analysis = self.format_analysis(four_h_analysis.summary) + + if one_d_analysis: + one_d_analysis = self.format_analysis(one_d_analysis.summary) + + # Format the analysis + return four_h_analysis, one_d_analysis + + return None, None
+
+ + + +tv = TV_data() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/tweet_embed.html b/_modules/util/tweet_embed.html new file mode 100644 index 00000000..41e08e42 --- /dev/null +++ b/_modules/util/tweet_embed.html @@ -0,0 +1,818 @@ + + + + + + + + + + util.tweet_embed — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.tweet_embed

+## > Imports
+# > Standard libaries
+from __future__ import annotations
+from typing import List
+import datetime
+
+# Discord imports
+import discord
+from discord.ext import commands
+
+# 3rd party imports
+import pandas as pd
+import numpy as np
+
+# Local dependencies
+import util.vars
+from util.ticker_classifier import classify_ticker, get_financials
+from util.sentiment_analyis import add_sentiment
+from util.vars import filter_dict, data_sources
+from util.db import merge_and_update, remove_old_rows, update_tweet_db
+from cogs.loops.overview import Overview
+
+tweet_overview = None
+
+
+
+[docs] +async def make_tweet_embed( + text: str, + user_name: str, + profile_pic: str, + url: str, + images: List[str], + tickers: List[str], + hashtags: List[str], + e_title: str, + media_types: List[str], + bot: commands.Bot, +) -> tuple[discord.Embed, str, str, list, list]: + """ + Pre-processing the tweet data before uploading it to the Discord channels. + This function creates the embed object and tags the user after it is correctly uploaded. + + Parameters + ---------- + text : str + The text of the tweet. + user : str + The user that posted this tweet. + profile_pic : str + The url to the profile pic of the user. + url : str + The url to the tweet. + images : list + The images contained in this tweet. + tickers : list + The tickers contained in this tweet (i.e. $BTC). + hashtags : list + The hashtags contained in this tweet. + retweeted_user : str + The user that was retweeted by this tweet. + bot : commands.Bot + Discord bot object. + + Returns + ------- + None + """ + + category = None + base_symbols = [] + + # Ensure the tickers are unique + symbols = get_clean_symbols(tickers, hashtags)[:24] + + e = make_embed(symbols, url, text, profile_pic, images, e_title, media_types) + + # Max 25 fields + if symbols: + print("Adding financials to tweet embed...") + e, category, base_symbols = await add_financials( + e, symbols, tickers, text, user_name, bot + ) + + return e, category, base_symbols
+ + + +
+[docs] +def make_embed( + symbols, url, text, profile_pic, images, e_title, media_types: List[str] +) -> discord.Embed: + # Set the properties of the embed + e = discord.Embed( + title=embed_title(e_title, symbols), + url=url, + description=text, + color=data_sources["twitter"]["color"], + timestamp=datetime.datetime.now(datetime.timezone.utc), + ) + + e.set_thumbnail(url=profile_pic) + + # Set image if an image is included in the tweet + if images: + e.set_image(url=images[0]) + + footer_text = "\u200b" + + if "video" in media_types: + footer_text = "Video" + elif "animated_gif" in media_types: + footer_text = "GIF" + + # Set the twitter icon as footer image + e.set_footer( + text=footer_text, + icon_url=data_sources["twitter"]["icon"], + ) + + return e
+ + + +
+[docs] +def embed_title(e_title: str, tickers: list) -> str: + if not tickers: + return e_title + + title = f"{e_title} about {', '.join(tickers)}" + + # The max length of the title is 256 characters + if len(title) > 256: + title = title[:253] + "..." + + return title
+ + + +
+[docs] +async def add_financials( + e: discord.Embed, + symbols: List[str], + tickers: List[str], + text: str, + user: str, + bot: commands.Bot, +) -> tuple[discord.Embed, str, List[str]]: + """ + Adds the financial data to the embed and returns the corresponding category. + + Parameters + ---------- + e : discord.Embed + The embed to add the data to. + symbols : List[str] + The symbols (tickers + hashtags) in the tweet. + tickers : List[str] + The tickers in the tweet. + text : str + The text of the tweet. + user : str + The user that tweeted. + bot : commands.Bot + The bot object, used for getting the custom emojis. + + Returns + ------- + tuple[discord.Embed, str, str] + discord.Embed + The embed with the data added. + str + The category of the tweet. + List[str] + The base symbols of the tickers. + """ + global tweet_overview + + # In case multiple tickers get send + crypto = stocks = forex = 0 + + base_symbols = [] + categories = [] + do_last = [] + classified_tickers = [] + changes = [] + + if not util.vars.classified_tickers.empty: + # Drop tickers older than 3 days + util.vars.classified_tickers = remove_old_rows(util.vars.classified_tickers, 3) + classified_tickers = util.vars.classified_tickers["ticker"].tolist() + + for ticker in symbols: + if crypto > stocks and crypto > forex: + majority = "crypto" + elif stocks > crypto and stocks > forex: + majority = "stocks" + elif forex > crypto and forex > stocks: + majority = "forex" + else: + majority = "Unknown" + + # Get the information about the ticker + if ticker not in classified_tickers: + ticker_info = await classify_ticker(ticker, majority) + if ticker_info: + ( + _, + website, + exchanges, + price, + change, + four_h_ta, + one_d_ta, + base_symbol, + ) = ticker_info + + # Skip if this ticker has been done before, for instance in tweets containing Solana and SOL + if base_symbol in base_symbols: + continue + + # Db cannot save lists + if exchanges == []: + exchanges = None + + # Convert info to a dataframe + df = pd.DataFrame( + [ + { + "ticker": ticker, + "website": website, + "exchanges": ";".join(exchanges), + "base_symbol": base_symbol, + "timestamp": datetime.datetime.now(), + } + ] + ) + + # Save the ticker info in a database + util.vars.classified_tickers = merge_and_update( + util.vars.classified_tickers, df, "classified_tickers" + ) + + else: + if ticker in tickers: + e.add_field(name=f"${ticker}", value=majority) + print( + f"No crypto or stock match found for ${ticker} in {user}'s tweet at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}" + ) + + # Go to next in symbols + continue + else: + ticker_info = util.vars.classified_tickers[ + util.vars.classified_tickers["ticker"] == ticker + ] + website = ticker_info["website"].values[0] + exchanges = ticker_info["exchanges"].values[0] + exchanges = exchanges.split(";") + base_symbol = ticker_info["base_symbol"].values[0] + + # Still need the price, change, TA info + price, change, four_h_ta, one_d_ta = await get_financials(ticker, website) + + title = f"${ticker}" + + # Add to base symbol list to prevent duplicates + base_symbols.append(base_symbol) + + if isinstance(change, list) and len(change) == 1: + changes.append(change[-1]) + else: + changes.append(change) + + # Determine if this is a crypto or stock + if website: + if "coingecko" in website: + crypto += 1 + categories.append("crypto") + for x in exchanges: + if x in util.vars.custom_emojis.keys(): + title = f"{title} {util.vars.custom_emojis[x]}" + + if "yahoo" in website: + stocks += 1 + categories.append("stocks") + if "forex" in website: + forex += 1 + categories.append("forex") + else: + # Default category is crypto + categories.append("crypto") + + # If there is no TA for a symbol, add it at the end of the embed + if four_h_ta is None: + do_last.append((title, change, price, website)) + continue + + # Add the field with hyperlink + e.add_field( + name=title, value=get_description(change, price, website), inline=True + ) + + e.add_field(name="4h TA", value=four_h_ta, inline=True) + + if one_d_ta: + e.add_field(name="1d TA", value=one_d_ta, inline=True) + + for title, change, price, website in do_last: + e.add_field( + name=title, value=get_description(change, price, website), inline=True + ) + + # Finally add the sentiment to the embed + if base_symbols: # or if categories: + e, prediction = add_sentiment(e, text) + else: + prediction = None + + # Decide the category of this tweet + if crypto == 0 and stocks == 0 and forex == 0: + category = None + else: + category = ("crypto", "stocks", "forex")[np.argmax([crypto, stocks, forex])] + + # If there are base symbols, add them to the database + # Also post the overview of mentioned tickers + if base_symbols: + update_tweet_db(base_symbols, user, prediction, categories, changes) + + if not tweet_overview: + tweet_overview = Overview(bot) + + await tweet_overview.overview(category, base_symbols, prediction) + + # Return just the prediction without emoji + return e, category, base_symbols
+ + + +
+[docs] +def get_clean_symbols(tickers, hashtags): + # Remove #NFT from the list + hashtags = [hashtag for hashtag in hashtags if hashtag not in ["NFT", "CRYPTO"]] + + # First remove the duplicates + symbols = list(set(tickers + hashtags)) + + clean_symbols = [] + + # Check the filter dict + for symbol in symbols: + # Filter beforehand + if symbol in filter_dict.keys(): + # For instance Ethereum -> ETH + new_sym = filter_dict[symbol] + # However if ETH is in there we do not want to have it twice + if new_sym not in clean_symbols: + clean_symbols.append(new_sym) + else: + clean_symbols.append(symbol) + + return clean_symbols
+ + + +
+[docs] +def format_description( + AH: bool, change: list, price: list, website: str, i: int +) -> str: + if AH: + return f"[AH: ${price[i]}\n({change[i]})]({website})\n" + else: + return f"[${price[i]}\n({change[i]})]({website})"
+ + + +
+[docs] +def get_description(change, price, website): + if not change and not price: + return "\u200b" + + # Change can be a list (if the information is from Yahoo Finance) or a string + if type(change) == list and type(price) == list: + # If the length is 2 then we know the after-hour prices + if len(change) == 2 and len(price) == 2: + for i in range(len(change)): + if i == 0: + description = format_description(True, change, price, website, i) + else: + description += format_description(False, change, price, website, i) + else: + return format_description(False, change, price, website, 0) + + else: + return format_description(False, [change], [price], website, 0) + + return description
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/vars.html b/_modules/util/vars.html new file mode 100644 index 00000000..b7e0c36f --- /dev/null +++ b/_modules/util/vars.html @@ -0,0 +1,587 @@ + + + + + + + + + + util.vars — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.vars

+import sys
+import os
+import json
+
+# > 3rd Party Dependencies
+import yaml
+import aiohttp
+import pandas as pd
+
+# Read config.yaml content
+config_path = os.path.join(os.path.dirname(__file__), "..", "..", "config.yaml")
+with open(config_path, "r", encoding="utf-8") as f:
+    config = yaml.full_load(f)
+
+guild_name = (
+    os.getenv("DEBUG_GUILD")
+    if len(sys.argv) > 1 and sys.argv[1] == "-test"
+    else os.getenv("DISCORD_GUILD")
+)
+
+# Replace key by value
+filter_dict = {
+    "BITCOIN": "BTC",
+    "BTCD": "BTC.D",
+    "ETHEREUM": "ETH",
+    "ES_F": "ES=F",
+    "ES": "ES=F",
+    "NQ": "NQ=F",
+    "NQ_F": "NQ=F",
+    "CL_F": "CL=F",
+    "APPL": "AAPL",
+    "DEFI": "DEFIPERP",
+    "NVIDIA": "NVDA",
+}
+
+icon_url = (
+    "https://raw.githubusercontent.com/StephanAkkerman/fintwit-bot/main/img/icons/"
+)
+data_sources = {
+    "twitter": {"color": 0x1DA1F2, "icon": icon_url + "twitter.png"},
+    "yahoo": {"color": 0x720E9E, "icon": icon_url + "yahoo.png"},
+    "binance": {"color": 0xF0B90B, "icon": icon_url + "binance.png"},
+    "investing": {"color": 0xDC8F02, "icon": icon_url + "investing.png"},
+    "coingecko": {"color": 0x8AC14B, "icon": icon_url + "coingecko.png"},
+    "opensea": {"color": 0x3685DF, "icon": icon_url + "opensea.png"},
+    "coinmarketcap": {"color": 0x0D3EFD, "icon": icon_url + "cmc.ico"},
+    "playtoearn": {"color": 0x4792C9, "icon": icon_url + "playtoearn.png"},
+    "tradingview": {"color": 0x131722, "icon": icon_url + "tradingview.png"},
+    "coinglass": {"color": 0x000000, "icon": icon_url + "coinglass.png"},
+    "kucoin": {"color": 0x24AE8F, "icon": icon_url + "kucoin.png"},
+    "coinbase": {"color": 0x245CFC, "icon": icon_url + "coinbase.png"},
+    "unusualwhales": {"color": 0x000000, "icon": icon_url + "unusualwhales.png"},
+    "reddit": {"color": 0xFF3F18, "icon": icon_url + "reddit.png"},
+    "nasdaqtrader": {"color": 0x0996C7, "icon": icon_url + "nasdaqtrader.png"},
+    "stocktwits": {"color": 0xFFFFFF, "icon": icon_url + "stocktwits.png"},
+    "cryptocraft": {"color": 0x634C7B, "icon": icon_url + "cryptocraft.png"},
+}
+
+# Stable coins
+# Could update this on startup:
+# https://www.binance.com/bapi/composite/v1/public/promo/cmc/cryptocurrency/category?id=604f2753ebccdd50cd175fc1&limit=10
+# Get info stored in ["data"]["body"]["data"]["coins"] to get this list
+stables = [
+    "USDT",
+    "USDC",
+    "BUSD",
+    "DAI",
+    "FRAX",
+    "TUSD",
+    "USDP",
+    "USDD",
+    "USDN",
+    "FEI",
+    "USD",
+    "USDTPERP",
+    "EUR",
+]
+
+# Init global database vars
+assets_db = None
+portfolio_db = None
+cg_db = None
+tweets_db = None
+options_db = None
+latest_tweet_id = 0
+
+# These variables save the TradingView tickers
+stocks = None
+crypto = None
+forex = None
+cfd = None
+
+nasdaq_tickers = None
+
+reddit_ids = pd.DataFrame()
+ideas_ids = pd.DataFrame()
+classified_tickers = pd.DataFrame()
+
+custom_emojis = {}
+
+
+
+[docs] +async def get_json_data( + url: str, + headers: dict = None, + cookies: dict = None, + json_data: dict = None, + text: bool = False, +) -> dict: + """ + Asynchronous function to get JSON data from a website. + + Parameters + ---------- + url : str + The URL to get the data from. + headers : dict, optional + The headers send with the get request, by default None. + + Returns + ------- + dict + The response as a dict. + """ + + try: + async with aiohttp.ClientSession(headers=headers, cookies=cookies) as session: + async with session.get(url, json=json_data) as r: + if text: + return await r.text() + else: + return await r.json() + except aiohttp.ClientError as e: + print(f"Error with get request for {url}.\nError: {e}") + except json.JSONDecodeError as e: + print(f"Error decoding JSON from {url}.\nError: {e}") + return {}
+ + + +
+[docs] +async def post_json_data( + url: str, + headers: dict = None, + data: dict = None, + json: dict = None, +) -> dict: + """ + Asynchronous function to post JSON data from a website. + + Parameters + ---------- + url : str + The URL to get the data from. + headers : dict, optional + The headers send with the post request, by default None. + + Returns + ------- + dict + The response as a dict. + """ + + try: + async with aiohttp.ClientSession(headers=headers) as session: + async with session.post(url, data=data, json=json) as r: + return await r.json(content_type=None) + except Exception as e: + print(f"Error with POST request for {url}.", "Error:", e) + + return {}
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/util/yf_data.html b/_modules/util/yf_data.html new file mode 100644 index 00000000..6aada772 --- /dev/null +++ b/_modules/util/yf_data.html @@ -0,0 +1,529 @@ + + + + + + + + + + util.yf_data — FinTwit Bot 1.2.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for util.yf_data

+##> Imports
+# > Standard libaries
+from __future__ import annotations
+from typing import Optional, List
+
+# > 3rd Party Dependencies
+from yahooquery import Ticker
+
+# Local dependencies
+from util.formatting import format_change
+from util.afterhours import afterHours
+from util.tv_data import tv
+
+
+
+[docs] +def yf_info(ticker: str, do_format_change: bool = True): + # try: + stock_info = Ticker(ticker, asynchronous=True).price + + # Test if the ticker is valid + if not isinstance(stock_info.get(ticker), dict): + return None + + stock_info = stock_info[ticker] + prices = [] + changes = [] + + # Helper function to format and append price data + def append_price_data(price_key, change_key): + price = stock_info.get(price_key) + change = stock_info.get(change_key, 0) + if do_format_change: + change = format_change(change) + if price and price != 0: + prices.append(price) + changes.append(change or "N/A") # Handle None or missing change + + # Determine which price to report based on market hours + if afterHours(): + append_price_data("preMarketPrice", "preMarketChangePercent") + append_price_data("regularMarketPrice", "regularMarketChangePercent") + + # Calculate volume + volume = stock_info.get("regularMarketVolume", 0) * prices[-1] if prices else 0 + + # Prepare return values + url = f"https://finance.yahoo.com/quote/{ticker}" + exchange = stock_info.get("exchange", []) + + return volume, url, exchange, prices, changes if changes else ["N/A"], ticker + + # TODO: ratelimit exception + # except Exception as e: + # print(f"Error in getting Yahoo Finance data for {ticker}: {e}") + + return None
+ + + +
+[docs] +async def get_stock_info( + ticker: str, asset_type: str = "stock", do_format_change: bool = True +) -> Optional[tuple[float, str, List[str], float, str, str]]: + """ + Gets the volume, website, exchanges, price, and change of the stock. + + Parameters + ---------- + ticker : str + The ticker of the stock. + asset_type : str + The type of asset, this can be stock or forex. + do_format_change : bool + Whether to format the change or not. + + Returns + ------- + Optional[tuple[float, str, List[str], float, str]] + float + The volume of the stock. + str + The website of the stock. + list[str] + The exchanges of the stock. + float + The price of the stock. + str + The 24h price change of the stock. + str + The ticker, to match the crypto function. + """ + + if asset_type == "stock": + stock_info = yf_info(ticker, do_format_change) + if stock_info: + return stock_info + + # Check TradingView data + tv_data = await tv.get_tv_data(ticker, asset_type) + if tv_data: + # print(f"Could not find {ticker} on Yahoo Finance, using TradingView data.") + price, perc_change, volume, exchange, website = tv_data + + if do_format_change: + perc_change = format_change(perc_change) if perc_change else "N/A" + return ( + volume, + website, + exchange, + price, + perc_change, + ticker, + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_sources/cogs.commands.rst.txt b/_sources/cogs.commands.rst.txt new file mode 100644 index 00000000..1eb4eb02 --- /dev/null +++ b/_sources/cogs.commands.rst.txt @@ -0,0 +1,61 @@ +cogs.commands package +===================== + +Submodules +---------- + +cogs.commands.analyze module +---------------------------- + +.. automodule:: cogs.commands.analyze + :members: + :undoc-members: + :show-inheritance: + +cogs.commands.earnings module +----------------------------- + +.. automodule:: cogs.commands.earnings + :members: + :undoc-members: + :show-inheritance: + +cogs.commands.help module +------------------------- + +.. automodule:: cogs.commands.help + :members: + :undoc-members: + :show-inheritance: + +cogs.commands.portfolio module +------------------------------ + +.. automodule:: cogs.commands.portfolio + :members: + :undoc-members: + :show-inheritance: + +cogs.commands.sentiment module +------------------------------ + +.. automodule:: cogs.commands.sentiment + :members: + :undoc-members: + :show-inheritance: + +cogs.commands.stock module +-------------------------- + +.. automodule:: cogs.commands.stock + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: cogs.commands + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/cogs.listeners.rst.txt b/_sources/cogs.listeners.rst.txt new file mode 100644 index 00000000..8c70ed1d --- /dev/null +++ b/_sources/cogs.listeners.rst.txt @@ -0,0 +1,29 @@ +cogs.listeners package +====================== + +Submodules +---------- + +cogs.listeners.on\_member\_join module +-------------------------------------- + +.. automodule:: cogs.listeners.on_member_join + :members: + :undoc-members: + :show-inheritance: + +cogs.listeners.on\_raw\_reaction\_add module +-------------------------------------------- + +.. automodule:: cogs.listeners.on_raw_reaction_add + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: cogs.listeners + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/cogs.loops.rst.txt b/_sources/cogs.loops.rst.txt new file mode 100644 index 00000000..c9f0cf5b --- /dev/null +++ b/_sources/cogs.loops.rst.txt @@ -0,0 +1,181 @@ +cogs.loops package +================== + +Submodules +---------- + +cogs.loops.assets module +------------------------ + +.. automodule:: cogs.loops.assets + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.earnings\_overview module +------------------------------------ + +.. automodule:: cogs.loops.earnings_overview + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.events module +------------------------ + +.. automodule:: cogs.loops.events + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.funding module +------------------------- + +.. automodule:: cogs.loops.funding + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.gainers module +------------------------- + +.. automodule:: cogs.loops.gainers + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.ideas module +----------------------- + +.. automodule:: cogs.loops.ideas + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.index module +----------------------- + +.. automodule:: cogs.loops.index + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.liquidations module +------------------------------ + +.. automodule:: cogs.loops.liquidations + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.losers module +------------------------ + +.. automodule:: cogs.loops.losers + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.new\_listings module +------------------------------- + +.. automodule:: cogs.loops.new_listings + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.nfts module +---------------------- + +.. automodule:: cogs.loops.nfts + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.option\_alert module +------------------------------- + +.. automodule:: cogs.loops.option_alert + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.options module +------------------------- + +.. automodule:: cogs.loops.options + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.overview module +-------------------------- + +.. automodule:: cogs.loops.overview + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.reddit module +------------------------ + +.. automodule:: cogs.loops.reddit + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.stock\_halts module +------------------------------ + +.. automodule:: cogs.loops.stock_halts + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.stocktwits module +---------------------------- + +.. automodule:: cogs.loops.stocktwits + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.timeline module +-------------------------- + +.. automodule:: cogs.loops.timeline + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.trades module +------------------------ + +.. automodule:: cogs.loops.trades + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.trending module +-------------------------- + +.. automodule:: cogs.loops.trending + :members: + :undoc-members: + :show-inheritance: + +cogs.loops.yield module +----------------------- + +.. automodule:: cogs.loops.yield + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: cogs.loops + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/cogs.rst.txt b/_sources/cogs.rst.txt new file mode 100644 index 00000000..94556711 --- /dev/null +++ b/_sources/cogs.rst.txt @@ -0,0 +1,20 @@ +cogs package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + cogs.commands + cogs.listeners + cogs.loops + +Module contents +--------------- + +.. automodule:: cogs + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..d3151fc3 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,20 @@ +.. FinTwit Bot documentation master file, created by + sphinx-quickstart on Sun Aug 27 20:30:38 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to FinTwit Bot's documentation! +======================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + Modules + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/_sources/main.rst.txt b/_sources/main.rst.txt new file mode 100644 index 00000000..eace87b8 --- /dev/null +++ b/_sources/main.rst.txt @@ -0,0 +1,7 @@ +main module +=========== + +.. automodule:: main + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/modules.rst.txt b/_sources/modules.rst.txt new file mode 100644 index 00000000..e99e6557 --- /dev/null +++ b/_sources/modules.rst.txt @@ -0,0 +1,13 @@ +src +=== + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + main + cogs + cogs.commands + cogs.listeners + cogs.loops + util \ No newline at end of file diff --git a/_sources/util.rst.txt b/_sources/util.rst.txt new file mode 100644 index 00000000..b571ab62 --- /dev/null +++ b/_sources/util.rst.txt @@ -0,0 +1,157 @@ +util package +============ + +Submodules +---------- + +util.afterhours module +---------------------- + +.. automodule:: util.afterhours + :members: + :undoc-members: + :show-inheritance: + +util.cg\_data module +-------------------- + +.. automodule:: util.cg_data + :members: + :undoc-members: + :show-inheritance: + +util.confirm\_stock module +-------------------------- + +.. automodule:: util.confirm_stock + :members: + :undoc-members: + :show-inheritance: + +util.db module +-------------- + +.. automodule:: util.db + :members: + :undoc-members: + :show-inheritance: + +util.disc\_util module +---------------------- + +.. automodule:: util.disc_util + :members: + :undoc-members: + :show-inheritance: + +util.earnings\_scraper module +----------------------------- + +.. automodule:: util.earnings_scraper + :members: + :undoc-members: + :show-inheritance: + +util.exchange\_data module +-------------------------- + +.. automodule:: util.exchange_data + :members: + :undoc-members: + :show-inheritance: + +util.formatting module +---------------------- + +.. automodule:: util.formatting + :members: + :undoc-members: + :show-inheritance: + +util.get\_tweet module +---------------------- + +.. automodule:: util.get_tweet + :members: + :undoc-members: + :show-inheritance: + +util.parse\_tweet module +------------------------ + +.. automodule:: util.parse_tweet + :members: + :undoc-members: + :show-inheritance: + +util.sentiment\_analyis module +------------------------------ + +.. automodule:: util.sentiment_analyis + :members: + :undoc-members: + :show-inheritance: + +util.ticker\_classifier module +------------------------------ + +.. automodule:: util.ticker_classifier + :members: + :undoc-members: + :show-inheritance: + +util.trades\_msg module +----------------------- + +.. automodule:: util.trades_msg + :members: + :undoc-members: + :show-inheritance: + +util.tv\_data module +-------------------- + +.. automodule:: util.tv_data + :members: + :undoc-members: + :show-inheritance: + +util.tv\_symbols module +----------------------- + +.. automodule:: util.tv_symbols + :members: + :undoc-members: + :show-inheritance: + +util.tweet\_embed module +------------------------ + +.. automodule:: util.tweet_embed + :members: + :undoc-members: + :show-inheritance: + +util.vars module +---------------- + +.. automodule:: util.vars + :members: + :undoc-members: + :show-inheritance: + +util.yf\_data module +-------------------- + +.. automodule:: util.yf_data + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: util + :members: + :undoc-members: + :show-inheritance: diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..e760386b --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 270px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..3f423940 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '1.2.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/_static/fintwit-nobg.ico b/_static/fintwit-nobg.ico new file mode 100644 index 0000000000000000000000000000000000000000..23e89b015bae1a45701e15d02340544a1bca8a29 GIT binary patch literal 125222 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 T>J8Qr+zH$Y?-dtr;>-QNxqhz0 literal 0 HcmV?d00001 diff --git a/_static/fintwit.png b/_static/fintwit.png new file mode 100644 index 0000000000000000000000000000000000000000..bf48d01804d4c7762c13901bfdc824893a121cb3 GIT binary patch literal 254738 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%0DOqFxt2kRD`LUp^dqmTN)H+qRTTGqx+8 zwpFyM2%tV#WMtLJEH2{e9bsmPVkx`$sA7Hvo~qVy^s_MG??+hEIscUm|7UjcKZD9- zkp+ma<&UNIFCsPT#qo*q*zQkLN;L#t7$BE(ip|sBO`CZ=YsrrlC)FC!S;~(M-wBz& znBy=iX{od9JJoc5s5AM~mzbZ=XR8oxH5=r>dKo?)cyXBC~Q%hQ;iB(Ye* zH1-mnWFFk6>wDyFmnj?G39CF!WySzxBi;$LKX_IOt2K$El7N{FsdX-1ZHZ2*uD*Z# zEn(Hq^VBS554z;Fnqny*(JW51UT2nYs`(w?wgA(@DB{bt=@Hq`Hl84jDwhX!TuQXs z!eQM4mNr({*Tk3sAp0~&Wu7J*6?KUnN zz_uGQp8?OeuV@cCv_=T+Z|u)L7n8x1Ej+(FLyiV~GTqOJBW5=SPFD%@#%zkjZIng? z_Fa3@23=pGcbFQwQruNAJ)$e$@@U)(L-h>2SYkT+YnqQ#DwR_I0l;p%J^iY z$*ifL{ViF{)`Dk#aFWR)K zqC;KQHXN&(AY9e=k9DnAj8^>>NX_%lk4My~1XWl+fwZz73)%|A_;tOGNvphn%*CUK z_gr@dHsF3n&G>q4y6Z)=|g_5ZZrd>$u^MZF~{Fd#C6F65W(gn&|lE047ZBWNr3TUuq*Xm7=Qz z7mW1&!$$vGt^I45|FjmI%ejbiW@oMn6O8@VTjsP}J1ELlP zjy`LlC-;2Db5++q<)V@DQ#V3|Xv=ja+3Pck(UqO*bjVh7RIH^H%4CEV}IC+URu>|TUA(Y z&p5+pf;jaVKPW{SKU5@Qh>SrHPQDFSkzOLG#6fovYFBxaJGaFY=7ORxNt_{L6l_vVdiU<> zq|>^Ki%@fKQK*%o<3?%7%uX{w_0r%O4-DzVsFSYW%N_8?zBAf8gEK6Wdgi_3=vD#h zahBGI6DK%`%*S~-s^5y~jLhkCD!X6E0iba4V?uMHUiJPi1*IakWWYC=QTV(={NZZBnbzVh6T{8B^{grin>NGAeCDIccwXTRyHtZc zkN8iMhF?v|UiB9%A5&W1*o{iOYxR3w=}Ul(q)pSM*@r__7S*xfJTw1@aO~O$btzMM zr!C2>(+>R_>u=Si?k~fo(zXdEz&j6QKmL$rD)z^d@h_?5<>f~j#McP5kqvD%0Zi7V zmvU(?zkV9LAq`cQy8XlA7~js?f2e(Rcr|>5p`^kVtl7cd3>$R%Em!<$@-*b7nnEJE zpr8=h?jcd&2dqPTxWPf5k@Bf>#Gv`_b)W&GHu{GC_BoFAcACQNGNM{-Ar2YPl1p?g z`==AWFH|lZC{m=H!Wug+D!h*C_1xU9()8w*z1DpEn$O0^426R53hJ2-Cp`1};bpSL zvY-k!W1JpccbGz#(gu~s$Fk3b>z+bJ-=|5ANe^*jqOlnl&5B&sEIW9#K@lWH_5h1+ zc6Ab38FWbNqHTJ!Qfn|HvO1|QR9?x}@DYz+FyGRhvr#IBwEY_~9NWUg_neUB58&qA zo<}4+^9Z_ob_5-#^-kW06sE@%lwY~h1=gF=Kn708IVJB`f$~!$tqxrJ{u_?{rZPt~ z_K_5Y>c)DPxZ+E?+%`1beLA?=N_q%2b-f6bvudd5*E_W6%agN?&@d#-evYtv~BqCb~umCk7_9&Dx{ z>Ty22OAV1G3RE&g#NUNRo4|7Y-2L3#l4Fdnvpw#`l*aJC-%h#vbhqH3Q$Ce>1(f~46%Z)sgtH_Dk)~1u* zPGT-Md*;$8-c!*F=koEZ^_0z#p)}Yf zDHNHH3=d&MwGy_J#5quKQ2j+b=)N@IKI4VYY`4@;l=M0rB{|QjeoBxg61m+qYb-Qe zQd_6~O?QHtUHPFduJNzoIT>2aIZ&CO2mKBcgGv`uPR!|qQZ5j~$%Z29_t0>*>h-PX z<}5}j`EUnA!Dyl?22VA)9)&%y@u1;$K0o>zoP9T!P%>>a$AAaC;6kO9@V4c&J+mZU zv6ri*-HR1Ra^9>9cfC!karzE*AcAR*Wu;IkS{+f8a;L{ggo{*te2|nldy)nL zYe5DQRy)}q7!ROrD6HRQJWy7)UU-y+CryL0TvQ$Og}$2*o#?%=*W$S2HE0RE9??0} z==UaYy8CsC2cXL%r--?Y53yvwSl-k*(d0?2_@(3gxSGp(GK54VYRjWix^E)EA>T4? z^0}fQseHdtXQUkq20gCQ6Kf5uy#W?WjkKopxx85#jYNgaW7bJ%^paGG{>>kvP+et^ zQtypltdQg4yHUP@gF1KR5U0mr#Etn*MQMbjH$0{tx1;>vgQbS&m;uON+ev>Dm@?j< z*0tHGPvWi!#sPz5Dn#7aZ#Vy3y)8rEUGlmuxpYKI5ipL!QtVQuYMj2sR= zI+^P1uN@i8uQzAgrXLsHR6^_u-x|KJc=E@>1SopZz7Ldrp80to{v6t?Rn zs8xj&I&4`=gR{n@Ye!ADMMj49nS{rn&V~-S7X0oXB1>yU35nnzpZ8nyL4(0ibjxTg zeH>4z5(X^W`z^~i8gXW+Hx3-pxBDMiE@lZc<$3___RRdHQlwM1>Y3_7I)<%b=^8pM zKdcNytXA&6dOsqn`K-VgtAzhq`(Y!}`$j?Z@4ZxD))Jq%X{_@o;^vqn?GWsr$qsXz=g5#!Z_&+I`7CS8T!;DgXP~bkjrW$hqtcSPc7jXJEponbnkN5*{q9BW(H5ReksLQ zy+>tRJ3cyE^=Vw81V(CZm0MNO%8hQuRe2~5Xh=zS!P_+;S}uF-(Efz(PY(^^)EmCq zK^~|UTpB*7Q;Y&eDOSZAf4GVPIrz$IJfqtC+bLZkNJ-tXf!@FJ^44n&wrc!wAyp|^ zIq>u&Djc=`#93svr<=o9UY}@9Gx#i_Xp+AIj*gC`A1SeAL(>02 zIb}p^i475hJ>rpMB!5)@nw8snkB9CD6$}F!Y+x*9df9ZJU_I9eH(s}ki&HI-K9=w$CTCswt) zcZ6xz(UfgH)ewru>A#xFZ#|ykJZJ32R+v-p8ANFJDP=%*0V%KAM7?@EH@4pr`%c_j zT%qHBbLUae#taDS(f36G!cO%WZp(tTyx1>`t8RSbp1gu+sDSH!+&o8?=Ppw|Iz6x zD%{&kwMZ;NG_( zkw$hUhCCKI5{R_;lO2F&c66{X=d;Ps%DGgnNh*tAz%loMiY+T zNil%;%|=-FQi*Sf7-%cPk^Q;MuZ;D)_dc60dwj_hc33W`U+uX1og=|f`vvltF(bZU zoYk*F@=XsGQ}v8$gRvQfENUNP}9tysT)t^Jwl z)-GX3u!zDT&ol%g$AFg`yC9S|?$URxHwnZ2jKF?yqDby~S7QlGo?>abAK<^VkJ`A- zvVY~w7~Ea6G)i9K0kALIh6T%~7tz;(V>)t6UzD-N%FKwU3T?&r%cy=~Nf^yFgpVcGP3&u=# z@IyahzC{q@F+M+Z@;&oU@6=?)!j5AO z5a-~->g|jArrX7fHJ!#-?8wH%imfeuoKQ(o+1gOny0PYaer(uyskLy#BhP0f>-%(w z7t3-Az43Xf&@ByUzJ@qJ3zwAHWo`b7nGP3|IrKiM=00P-iT~qWOyUJdh!Ve`OAhDh zCRZzW7}U`R83_0$w%8#4%m2<=E&IObjK^WQX`z9$Pyi2|2z?jUfk&^MXfdp2wD~b$ z&3-0-BTn@lD)}R6LWqxYg9iZ5eCns^*_2Hi~O{`u-_psivl+(bo zK%!FyfhSR=-clVWAxo)g1Ex~vJb8=nM&gi=ki3qXce>H21ls7nbYzxYYjyAxzCfmO zNWHuC9pN5vTd8yI9pP!8S!*0~rld!V$Z3j8kPs#X`oubT7k&7)Svl+mK1)UoYKDQ{ zQR#g2w`14v{@8>HqbW&)OjZ$`LTGbOg9XWH5V>`u>3q(M=fcYwD0DlYwox$WLq-dx z=Y;3{dJdw)?Lth6{2kS^{nZA%ON_vYBSHSsbX(z4Ji)0REV;hAXr0{Qr@+LI&A6B%dEiDEcb?AmRz>6MX=|}L^+%8mQ2MxV zZxooLp&+6I2r)yiFPAMFiw>mSC3Kpox~>m+$yQ{Oq&rmxACxP_6JZ!=fUDgg)Fr_{ zZ*Bj|NG<5XTIY^s7Gkz*DsXS|P&^lH)faSDALRhgBn-c}xE0zz5)7;|TTG}7!j-g% zUb%YF@vz+@+;HJQj5uOP`#MZ^c)La)$)6mm+&k*FSL;1l88lR9`3=ayxtG?qR^aqQ2JP0G~qibRq~PkC{NWz>YiTXM#C8 zI`m*hK`O%3gNda4P5DC^9@{u^73rR2zHpKSbao*nOwBaU&|kX zb2Sb?<)O(BGZjjf`pLMLuYd?lOmaF&k zJ=OvEz3#oR204DEH)d&}7J3=?thkMYa_qtnR2-K1LJ}cDTpJ_68$7b}L6OqeT~ARA zfwiz8VGmLuaJ!r&jq=0GmpgmS^$QiQ)M;^kl&D>I+YQ;$Vw>%BLxoQz1i?Q>7Be?o zwv59~ z;<=sH;Ro3bbW0&*5zC?RgS(WvaeP<9vIu*A43WIWK@hYfgC=JW_TihalFhbgER|zH z%zd~5^aJCHW1zwL*ZfHM&Lt93-RT%kRcMyI>doEpJtP;6iWShlCdiR(9%;ox0pp-( zNSqF?-Fpw19yRS`S)021_Cm4c4bW4EUe_#?@4%#KgD28LqtTRJEg#Dl2->n^6vr2> zSLQEfKC|jM%Oq>}#%$!U8I1pwfF8jv#jpEuqSr||*HsZeiQc|ij}LN^AAVEV;7e?~ z8ou9I2~{1f`jA`4fasZwkv-t9y%IN zuYUx6Oc7J$$gW##a2=8M>(mvcqs2pVrA3wv0OY?-BL>*TNXvKe33ee!N1QQ`Ej>}3 zWN9CgLJ0Lsl)b0zSt&DR*|hA+n(2-e6zl9uIWGKpmp2?e0&mMxkx$R+UcUu&ssP&V z`_%a>OW)N&1RzkFajKvszx{64GK$PON~iPCsZEu;5CG;b7HiaA}uuw^MXgWDGjMY z+qG!ePdW!AJAb7PmFmBZgQuJZt9(HN)IM)}LnHaUzIYgf3uGb8--|EePy5p$Cf<8YbFRj7RHVBk4MEy3Johnp zrXOG7Ysgjzq&(PR(@;JbkeXjTzlx5!R2A{oSw(vSE*$%lSn zBh%!+6UtMwUN-cn9|={Uj?S(5g5L<}wqar8S|H2j-FI|Ud2gv&w;wvZ)S@cZn%pR@ z*iF+bCE;Y_B8Ztq2DH(E34cj{#QCqjXJu9G(Dl!7-&j|5YOB5sXUTH+_+AWXteT}ds7qoN*K+vqZWnRc?GW6Nz* zu1+s6K7?<}nHDh#rwlZ2q7ky_bcm5_kHvimK?M@?(K%sU9$8_Ci>J*_erM^5RPHf| zasHmIjc;SkX!XZgWc#|(IS(1=r@p#EX7x-ww!V>=dafU9NIu-2X-Hhmgkn=bp->-F z<&hdHLxZF@OC6{9TFIA>@MD1h)=x`e!{f#gM>t#*fr1SZsV;*U_1GQAhx+A*Dg7vc z0*TphI_@3#okqYpP7oPTDKiqPby%9^2a9ajo8A(@lO|mwjlRdY0MK>iGs2#gcBYJ! z2Ot9|c$8ef1Tgw!bH6&SvxDKWQiiEBCEZ@&H`kr6ZbgSg)Tcp3nndm_5Lf$ev(H^Uka zhl(5@7boYCrF&(C2TQ)H5Q&rEg(QHA6KV=kfUGJ`Hl{%4Up@{?Jy*F^olb_PYqemh ztK~;vDd?^xSn_Ilq|AhxloI}jC!1Htxztb~Poz?ox1(Ws>8ABcO6ITJ0SRC@>|vLN z*AZOEw&Ya2$YEyqC zl9=ic>2mV(+w=2%E;CZYs62oD(9HESGg}G0087A@k#Hq{!L8HYwx3vUzc%0E!H*d` zw*7V>Hi_{$N~77U25l9U62Ztegdcbj=A=3ZLY)Xd5~D@vRYPIsX)p(lP6T+trqBI~ z&^t`Pc1p?fH2+E;bf zc#ierO5I))YP6l7j@ZwZFSNMYb0?#*Es2SRqK0v*+U5O7;3bGdg>M6Q0^+z>IuVqt zZ*KlL7NGN}qY6(Z2TxY~Zj`_CX3Xg^kI7r*Zb6+%s{DypO@UXqK*y9Ux+1y;Tljp^ zkhIWJ``f+oktAxL&7TI7BV{iuUe#__`Q1U*sJ#w;4$zpwwnYKjsfD+wD0YSTnnckQ zUzsU-dE=ioXzXnnDE&MjxNys*Hz$Q|Db>6hRP|dXW|_t1vydwH>#UF=ro7oyZ;@)mO|W31^o)Uh zMPY(AIzA-~<(Ic<9v-^9K;aj!$-o=fVdI+f02ORDKgxH6+RMf5qi93B`HCmlH9*N@ zxS^c(GUcm`LW5Mj_u7oln7H$1H_7+a{zKdx3@;@NSC{xR)jo*ax%kt`#X_wHz&xHg zuSbMXc_t4w65iF6txI8AWi(NJR*Ys`zZeTHNQQk z>;_J5_cMLrW5XBYq!C$DWUB7uvlWI9oADZV_g9?{uy{Ye+hf1A#W|i}mnUS> zF-hk>i9>H5g#^A?GGII8ppQ$UbWk@aULp5>q&ag!y^Xw;?9VLK z`NF;`n4nHj8{oZ(DB!v_Qa((zGN{dFd6FvxNev+XQaW*CBW8^XDt1As37$}S!=Fm? z0E@m{`Vc)ua{W99e!$@rEQlU1{{uE0M*x@@!nRFmJgz&?Xgq&a`T1NfST{PZsG#38 zh|_v@WRF?-fOR?uK(Zk1hFv~Y%&|51NE)E;Efx9CgQRY#Ffp);*P1r?f$SWO-0BgY zx@4@Is-)gQmB<3!4__1TeNqFYuY-wpH3=h~76ZU0gBRQd1%Tp^-i0O?M-=C1fdDY=fQg{nTONWJ;g)8x?MS>~kw{(GZ77S>O zs!Q<(7PxvFP6(em11%~Vn;DMk}to!`g5Z2P&S zfo{h+3nD;?xBknHl<#Rg%Gr)`0FENUkjxuwH6UxMcjY)yw@Ms3pYOKpJRsq=R%on!Xs1sn<<|NC-Zxu)>$*&^PW{E8Pc3wISNA#EfTOkJbK90O3*#285+*n?U| z57$}>eC(%$0lcD1xi%CK6$4z@KiZ)L1bhKa7rg4Xog%W_>0Z}A!$^2c>fBH0muiNo z)8>&G=Rbk`L5NzkcJvhqmP=HJhp@IcSG8bYSZ)~QlQ;V_^-h(>tw+xre<-VJ^Zn$H zvOQln$**ak{I2&$W&8U!YA-mY(*dB1X5Rg(ul4)cu;!Bc01mJ0i$VT%O4#A z>uMSuit-cqUnX%imkpoT{UpJc#*~r2TJu}OSES_gf&TKA5vidFVF*A*O#>+J@nRg5 zk?Y_|dg07LDnE{X^J!na#-i(-v9ROvuf;NZB@SAGXH_VQNjIOvum$KXO75KB;tnX3 zJWe?mi8Q>DU`KUbw;cOSZ~=22%-4RlNn2W9k#Ww@(zdr*@ib**WJV;s>s?Q(USO5Z z;9T{$tg@-b5q_9j)4($h7M<*Sv(oHfAE~J?LK?ZJu3z8!^~yslN>1fXyPr6lA|jVg zpz2?C5|*H0@n{XcBVi!mmbfIuuBy1%xWWcHG4a;ApFUV$8rxODSTSylOBi>)Phyo@yRjKNX^mr7WW z&st*vYZe08;VqGCaDP2-EVo#?**)QQmu{Wj_XE^tlwXP}Ei2pE*2Nu5sn;LDPtNXe zT+)<^g7d;rN@0vfX->1_(G}!VZST=z zTm9+?#>YKYa2)!Y$XH;fCxaZ5wAJFso$YtjZ2d@1E{w*vscbpIrFdTDg;BKCQtY`q z%|mt;1aZ$usI_;wd4TG}`N;tF+`{VRaH%e2)hOG!rojO}hWWXCcNsNp&y&X1WeB=f zK%sl~BfdDF{M}2g*Wzi-QEaSvmJa2&@Fc}~>1KjU+C03m$23>L?z|~_JpB|YT^z8H zM4OxW;0#P?MSJpC;a{hnvOJ9GI%;;b9*Jg+FVG$UNd`Td1|>;}LpuSK0O zSmQ9~3g_SsDmW|+1%@Dn$gqXTD`zl^JXe>BG8y@@&W0^7PZ!L@gzw_Et#{C{1aD&3 zO{Mob7*xX?KW}sWs2uOp&9WfK)_GXZar*(I1f;_!ds5I zWhSrghV3d|NM@6O49b3xa${;;qWS~=Dr_Men>Ipb!@bYG7WJ32ot)vYQw zKtXS?d_)Htbd|7O$Vc=)6xILdYS_+b3`Cv$rI*KToj2iyj+j(mk&(K z9+_ap3L!1stT?*~o-mN;g2+)%KxN3zOH`CDeorKpzz`!)aqEkQ6fS*09+ z=X%RraHXLzs_SwG3*M(27iUL3G%T+4>kG|gy-1RMc-e5Fwo}y&_wF z)n5y9gsx{qEfZ>;vBlQ^9=`43Y3BCsWo-?up+{xj&?Z{SEjqu)+VEVrwqp@rHfEw` zzOToOu&?DbenJ#3MuN6oh2WkVGYq;YeAIdzK-^lsYj=F`)}F(bFR8_fu)IlN#YJn3 z0 zoj@5Y!9}~-MT>JF^)p5b{}YmdPYBZGTYQPhIg8VwJW3(nYWv9=zTb+yufpCKHhZsf zxj)>kNr6REnN*WLeyodZr#;59!%G)BKtld$qM-qnL8Ee+(Ne_0!I8X}nTGculImXP zuo`T4_tOCKXCQpOH=YlZr(W`0SZUXij0wVB?oU}rft0iC#_5*5D@z@6ASI=kv=Y1x z-#7T$oUg<~CdvsCQuNhsOvV+~uZZeKxy?*{FmFPV0J*KSI;vDXaYMq?jz=J=j z;7aG$_df8)Z$QaM1K`4nazSl##@BMR2k4v>JYS9=wSqkcTcyL<>t+7^6|X`=Ks& zygWB|HChX9$x0u4Q%Jj?&Hxu$M_9_DzCfFP{(rG0ed~pUb4!k2Ggp?OH~!y6$9_&9yB~ zE~JiaeYi1dUgmWifLV_uZl)pjfD-5c)@Ryz((wAoY-xNr5k~Dk(G6GKy>AMWQNvJc z7s_W_+FW_V^DN~*lXgn*y+hNv(I3oNQDOx^S-1#|hQvgt$bzt5zJ;YBnMiye*XmwTzS@U|h^J|7=bp+(f%pGSiZ+BQTvI~8q)O9rcKHb&(LRvhIs-PXsa z37CMKNj4H#$p+|OjK*)I@6>0y*+BNYJCu<<w>SZy}nOwNNyEl-&8zLy{0$YK`Oabje$)5;V1bG15^aBUp4z>YZd zDi0t_mj}PSm1GbUWl~w#(qQcF>z~pu_?D-0U{j*|`Yfi;Uf5!6TAOP`-<{d)llC}! zL#50qr{FLJP}C3*Y=pan3ry)aDJQa|wx{SH3J+ySD7`(dN_}6HYwF9bR1CCum@%Iih69-W*;u~tmI_i%9aOm|r?yDa_ zG_yAK1pv|gC>VMN0DMgr0*B=B$LB+e;5bf8W(>MmLOxW9zV{)FlqRD~S}V0x-7QG7 zuhI|&QAu?+dj3Fph}xhwVDqR~X~@w1zaG?ocMTz#zi4~Dx6$?~129z;6M>5Q+G0tk z_21kY>A!O&1D3*wHPQu*TYL|WH^;;gE7K0oa$XMz@92vevUAiHGf-T+dO@y%ljvS+0k-F;V zxf=qh{kGupV0NW={V0$PK}JHmGlW#>G#&e94{{BbCx?Px}Bt={d`0}AT(tNN4e>L z@TNX=%0lCClno1K2U!*YClh80RjIWuvtH(WE`?lQBK~b=!89gO9)W{IZ~720Za@jY zW+jWMSW>#xrjSP2=LdZIv!vdwzsZCNgS z6d}l7qOVSpX^^_l7-GB8`&OFJ%A*~0T6of~k&PU>e3k+V1m06|Vv9LWKCMXwEd0tU z-}k4OeQ16j^EI`%iR`aO^ReX6_U`C=2twUgZP!M^sY7vM-u@gfawA9a4 zh(6ZoN59zn(+IMS3i~01bV3#_4!9P^ITrbx9Kg!hqwexkqZDB0l1~CG_Tmq|QNquZ zcg=GuH#kU#j-s&Tc50-1@toXLL@tPLj&n_>uaTSbMrI1|rBcK|cX#(FQ_*lfHwk4s z#MkC14IPAOUO4NKl3z1L?0)?q1XGE)%fA_(lv&EW68O=V5ZWLg^V+otb!1X$T2j>~8J59__-Uro$HQC+EG;<$Q zgzv9%)Hm=gO6q@r*tM;_PfY@Fk+yt)KI!3DEX4tP_#;_IF>vpE!*!-9^>IK_hYztK zGb+VhZ~^0yxzl#FcpclaCPWbiNg4=-TBW4xrQL$au?FGZMcd0P*2?u+e z+{*jxvUeCYP)TDBmL^;QZ)yOb`hX6IUr}Y3n_^+&|6VB& znIUd}x+mbBDep81VkP5aW1t5WFqCnB;xQCZ4)3egc!edRVgA&8v8z{4{4W+@3F`Mx z?57yetZ!4-1nP?@ZSBOj@4r5qYzwkSR|U^an7k{s>}SmCwB-^C17^dIxyW0ITlZiYR&Si0Q{gdSgug`7k-BlV>WE`SDzn=lyQZlO(TI#ajp* z{wmkNKQ&MYT%TZm;k@m2D|df|Is!@~t2R*;#4*v~d_L0QG;Q)2&1@S~}H zaSnnoD8~(!bT2P25mSWV_A~@UQ6Rf~rs@EcyPjbbtn)%^3k47^^7~#i+9${_`F0+@ zWLzVQn-?}6Z6>0=4?y|mJ`CZfbb@MMY~OFMy*r+N-2(}b%lJOVjc#F~@|3`-w-~2F9&_`m>IJjx!26pX~-!wTnIk~|O zYUS|19Z}3~?F69Cj6R+RkDk=rqbe#hX>_P0i2_)Ks%m-Ew&F|mCqM)qht8wCd2@AE zHkt>~qS}3(2V!|1pJ;mD^}JgosPQOXmg55t^|U`fxxu9+A6r%py~v*>R>hOg;FcRS zCoGwE(EYY=)VG&dXjrx`3<#Vrk8)b z`-ze3bvFT7Iwl4f=`fMY`6YG%GjA+zKkwN;a79RnNIs~i3#FzxKpOo&mB`@Wn<+QF zKc+a076`_*`0+9a{Q2CQ_kH7afbG5L=ZHp|ozeqO$8`*bvcj@WA^F#}ta0AI=k%=M zZf9&;p#41MIx{{u!mhIYYkO%LPffzsest`&kvoAx9b^vSR>pJ`XHj#+5J_6QbcX-` zrT|)TA1j_2y8*5`7sxi>gXLsxnS-+xo#)80+MA)Y&ij;@kNsEO5)r`d`>#Ql$DF1r zr!S1%ToZ`>9&~k~l_lllXzima+OBkHAeWRMnzw zXlTO+JM5Rhd__o%v88bEwZI$$H=NFJug*qpqKNAGZdjqHjB5m?~Yr z;B|kmsllbw@@D?jSrbz4d_ybnX1iVLJ-O59Z~(cHNU%>E!qCjiHDJL{EgQkNtK(ykhh|eN#~;^N9^blKk}`Rn)pyYdf8H;(a2PX-KkGdm)Q&Sy z0IFNUc%2{DXtNYA2Wl~n{Pb((aUIe^xqQo|)Nab=SyNakk0P4;^p+A}3W|goq$P{< zm`*MR2=EEoSRNDUA86kknfrCYYb%zTQR;tQWS*xauA5Zg-K#cbPkvRV-qt-m(?IcK zB#D|Jx^CmV6A_UZ9!|KYYrPa{5W{Ql8N0DR$%xh2W3_i~&<@m8jzkR>b*G?ju*#q*GB zxAHC*Oz}g%8cY#dD##78nk#yq49z%dsKHO*A%pv9Ws<1a@~Tb`jX~V>?oZrv16iF9 zoLu5KSl4-p9(CveU4Gr$mzony8U=$g+D#*$C%a=+Pe8*e@ae@Q)V%lO%@Zlk6CFnh zh{VjA5H3m|32Id+grtOo>c@XCbVuVxuJ7-fqm98Ea#Cv6;>8D*Q9MsX;#?@+>1wQr zJg(mP<#*op!mh~0ICoK$6!%2&+_ADki%mULMMl@SBt)NZgbQWA$k%r#AwBTGr$^@e@=fbnL_J+jC%z&)KkEJ#62+a#gQb3vKnN14FpKcc z5XYo|Hp9_x@pNI6#qlaDngn`@7t4jbaoj5hssdKVbX1zVo49FmsbkH|!-=ZEfnP5@ z8e0zQ^_fifS0#Ro``z@Q6-|y|^Bc9vSZ6_ZAXqv_wUws4>J8s){8l)fwh?Jryv#`# zdPYRD$XCnVREd^0%zjk(E;9C4&xcZXY%s@AQ3_|FW|QYVQf;UP zI}PC}o=Ag-IQvS4fA8XF@eY(E2ghj18)ur!E_ddkBLHTSb||oI8?`&KDs|)L*j>8z zH*;q9H?RtTZ{^qeSLnaed;k3jZh|5h!zqqa)jN7*GcvwvhtCfJpxH=nJj5hPvG=s@c7ya}Iy);P$h2wg3&9$`vJ_a{`) ztP+=_LaKjsHrI<>u`GAkc1K50#k2caIinLfLu+PJbD-tBfKbp%adi?;0VE~0zxHhYRgh>;7 zapNH`Sun$ja8VwX1%EZw%9L|~Q|3lHwSU~Ro$t$vithj9O!(sko~TX~@pFpOtJ0ay z$bq9v_tN_(-)vM^EMtuor!$a2$A|yqyW%%_4CqBX2)tx)~~A!U)1(<#DdEYhC5Z=TVdv^?tLHh(QWVJ{`WB{HcjOD&@0pR<;TV z;`E3U1){i!Mt0s5UtJNw*kgX*wL2%;qG_87#%YaWpHKj!cG$at0;>8ZN{Q$IM2cyH zGs>%5nBKy(Lm^WQ1SaZo3$8#bOZ;GR;uo z`kaWLDur}U{qeV^!lg#^Gjq5AeSi%}Jrr&{>m(DA3;wknNevSK z$k|}3Q0h<^;Th?CEj-;d@XBf7?6fUtQ^1n%yFzID2*)xAExl<;un5lA>~SYkwy<`n zl0W^v_hV|Obt-pv_k6W=-(Nck@-VB;#6XUyJ?T`>gy8*&*Uaa#^_iIkJSD!`ZZ_>6 zdFi4MQ`xUCdn5q#kZ)r2z#X zi--NfjR$XUL+>X3Zg)fH4cz}x+%v(SFyXjz;kA4I2McBas;-U{S^fV$T$&Fas|u57 zm&`(FKmeK|QNG14n*=bEBBhfhGx26_^irwYCsQt6^!F;5P1ZzRszd;E%f_y2xZ~KjaaKK%k%z%Zp^zlwnv;`9yFTu)C5|7`}+IeXiSDs$?MpO z(*fTrW$g#cHRjz%!dPCukI4;WM19}`Dv?_t8NJ{B#mI0rZNRHvln+y7HZ|*nmR|^3 z&vaGX{#^+-^dnV;KuMfCx>Y4aj8y=rV3nxkc-}T$tu{~kK(^0(BVjmU-uaH=s|DJ> zZ0uEO;-HZeCyvrSi>lX=`XHhWz=|VjR8@)-6!)foP{>a|?c-a!;0qMetoK;;fmn6? zF`lwGTJmYd%G=Ib6Kf>oHtFSHtHmjw2b-^cKcZ9_;7!aYB)F_}{BD$Zr*$L@y7;>x zC#c7w_l`*vht=Ds?Y1O;fEI29s!V8|oeAUmY$%YU5k(ad^bDhJAfw-f) zE@Cs4`p=G{-o}w;W_(b3man`c)MIoZ^J$6O`<&iTVQOKxCESFh9(x6zpk6Z8M-{O_{^MPNyIP4J45?REV z*Y8-OHg=|1l(8j#5iM1K)d-=b`2&SUgDYZ^ukf3Ngeh3o&mY6@DisMJiH0gN3u&x> z(deBmUv%?AAP!~kgYdECx=_Et{_eFR2Z?hHKdGkKeu6$7aPv|#b`<3>U3Y=h$=7p$ zL7*)!%n9{Xm8y$NLJ!m4pt#dPLhR(LCDuP6AOT9p?wcD`f@H#5la+fX$IS9DdF3oU zU}In0t)bi9$di+Z(!SfKwX^Q>eZXP-rAm+_>gG+^S2hZ_{tiR{ z5WVyHNujI++0*GqWOT7vpC7A58n_>X?S5mN^#_w#k~Zs}DG+Gj+K<0&I?w)^t<~Rr z3sb>YDIYG>9B=A+_rbqAfu`}7r{=~9^wE7s8$)wT z$4|9sYrkhx3x3sI8!-{O;4P%>H)^@sX#n5*N9TP>lhS0p6GNh2EPKR5L8GkHP3kKbm>x<9)4kbowQ=wKF-;8)?$ZwP;1n+>}GXpb2wX?t{ z+xS|B(rcif@C@Cisb?&prM}yS4g!zT#2O-OUn?C9z9FI=$mEV<8-&cNtJ~T{o)?T1 zhf|t5y!|{T$}4~A>Zr#4C^#4&NuP~J1z-NQzRp@Mzd#+Zkl4LsTF$aX-Us7}i)S_ob2bfg_g&{>?~<_X zU-- zf?;~jD#O1Ui2iQZzy1S^Os(H`Z8`2FE32;;7$x~Ji;up}9edyBF&$ICQIXz$ZHb>8 zK;Sm|i`T*%RRXb|MN}X+4k2Ry_Jo=w9Hd?RrJg=$@Ka_nHhp(MhNsnOcYTt1jX*Gl zqVscEb}jaGGdVd$0rakl+l5@sMo!%f5D90~y)4(IbXdKhsqBG`uTi9FL5i+UZ-@6b5}P5>Lo+hPFc(?%sXU-YndF zc|4|lByTC8Ws+`ZSt@h?ou2tt46gbPIctGLeVToezt3+jlcXa+vnL?FzVU_DHtg?8z*nn2H&a#;0c(u+Msu_CzQigX_)tR4@#aw{LdD%N?9C~44~AYWAT z3`pv56vAja4tT}fWi9`d->Jclc{30E%KF;#dbxVE=xvM8LuH)++W}|9*53SNk+LT=@O6Co!} zu#Q;g)93qqH9}KT=-b~%li6YDalWa*<05hYfWWDzNJ`z`FX)Fd_1>X8RI*;#Ol(#O zi41V13s1oQL%U@jtX$c?&9i-9e&1o`E$YuX-{v=9txvZP{ zb8YV!a>li4PSL%8;M{ByBUeFNm>3p#`z?w>PSu2FO@IiTRT(mNTc+L%%1()NCJ(iB zIa$aERAs3N7D2)fjF*kwcl{$>F4rZh$$SjzZJs{nM+ILM-PTeOGzdTgVD0);X8CI7 z7mLEM-pNzchU@8?0TJNlyxDYd{8XpuZ25g^RhX@~QmyNM+5u<@fi+n~5xgFLemFlr zZ`G*s{2G?<29ee(;d98m-?^RaZk=;}n@HAxNt%4n8<)t-4Y@5a|B4d$hQ%_j2SY z#qi;+@JX1#<&%BAFMoDL5(pyDd<#$ej7+39v3dEEP~ag+b+McINx|1D1>EVNQvKJcb#yf@3yJleIgIRD$Yh7BqKvx$j!sYGbQv`%Ylca%QHAUYt z_RhO?dJb9=I#QvvfrdYqm+wX7a9y|LpO!jw-y-=pjbfGgES)B1Xa8Y1zNH;jq+9?= z8pne7g(HXo>|zx6t$30d@y|{*8A=~>zD9w=6Xbb=mX7iiFZPvPh^Wp~^80N`8*>qq zq&HW|VslvMEb3*`$N_LOcwti1?gCCe%eI3Psma!}!+sD+oQEF`{3Ai9tHG_XRwOv& z@7}~$r06>dm3ZDe1syW~rskKL=-s_NFh_q1q74CMT3NC_+NPBQV@QBqy_fpq=qM=x z9hI(XTJ?*+=L+!KVLBUr}0ne6t_LPQs5aWOeqrh^DLq_Pbe+z(MP$3N5dvq@R1T=x7G2%q~zw(3K;&* z5DP40@$v7!tbwutq`(taudaZFqdlFV!>8Illz`qoGP97v zN5Zy%6CT_g_8?i1eB#&Ll9})?1=(6PzWO}2ahzLUnSpVqFY%v5?wlUQ9M3H6p3opu z*0AsDWnT4WEURNN04(|)kI5OkTGoBJ6#s{4+hI<&_m`vNm@ ze6nqS=M_FE^{}_H$n~$M-;d2J4CX{=!Le88k4JyOb>>AjXb4ScKj-5(J_-V!x7=bY zwm^Gr1=Cv*fFqFvv|UvQxJbAl7cfB8VEmdzgY}9srNZ&*)BoVqmi1ey;4@JK>qoH6 zQt7`uasi|RdtzVQdNo=saH^xS1mj^K2(51@UTfO>>NM4M*@q@jB^{ZcN51HO2i8;A z;jb{JExsd##AJuwFUR?ezkGSP02{cz_#t>xR@i@ilh?KNBt6qvTRq^@;tayvkrXV} zqx|~*lg!lzuPe@t(3r|G)&-jPK*x<8{h_YaqOeaVnJTRlj{YKV$f9P$2}At7yFDMJ zfp|dx(hh?#qkEV&^`1fMEFAxz#(@Pw%9gXyFNjkLt+lqY$Icx~-V!Cf3&XW6)@x-= zf0|0%1T_Ro0)yu%-Y8ytNMH{7*})j2?pV$i6t?uN+xnrPAw)w!Au?>6Z&w;Tyz0@p zha9^GT{TPx*Q4(ydhxVd zy~CtDcC_;t{bgH1lWVF=gpuE0Wbv6lX=S~%>Ko@1@SZ>42T!Y3ZC5@sj%8-TQi2(3 z5aUK(cw0SjT|rq_eGL8s%v@vJ*!#p`Dh7v5=D7UEqqGhjd;Y?r{pwW z_FMGU0UwFp&107=@U_-r#ZH6x=Pl-MH3*SFX$8YI2CMNek4A33*d3&$mA}c%%rBi; zo-@#-60r}8*1`Deif3ba=;>NNCwzMdh*-gAQ$9bHA9@0C9K!P?$OTAJQ(s{t^J9T$#Bte1#IKbjVO7?{bshhm0&*r=MVUTSEV^B%gw;E(O` ze$&=bxqj=J?>Bbyf5f>dA5-&10M=wGA)s=)9Zs2Z<*In$)BDS6^G0I*L&lfBEp`7g zAD?DZYI9JWTap@xn?I@yUZVvyuGJ1B?9OuomcgzT@8O4tlr{98=sFWy#GqLaE{qtD zZNodMkwpvgsg3O$Hy&`e%h0@D-j$>h$tOT(tq0xdJrRqLn1xPxg+op;7>st#VdPNY z-Y3rZM2J}8SC4(I8!^v@Jht~ppK)JD@ z?Ia zq7)*4NMDuD#nD)q*azf!!8NTNj2E_g&t2^0e$J7fV}e430HS^PBce6^LdQK}CANoR zx5E=BM@uyR;2okGO?!NZzOo=CpZIoaLIH*ZNe^`wek3ACn$F-?Y0ChZt zh4=DdE&E|dhC6jdlj>GiaAzU|nC$NyP#-m_*H1t!-9-8%Uj?8Wvcs49vNq8gpVEJn5`^O;y00>FpsMco7cdgzB(ZX>r`3kSa zA{{m$zc%QZ#{%)|>F;96174n<{7g0W(Ka_t&xf3jUp{8J#`4ChCPmAafj^S(Zb_Xj zPZn*bf8g%S^*iu=pEm3+_)_?Ti))UI&~#LUl4@v5ZR^~`V{a&oos_Rsb2IaS%{nXo ztSm0;{>xfp%kvWHy#GQPAla7+0fvr>Q-ET4@IfJQI@q_iS1D{7ebGV?l9bK?5IA&K zHYVW_&j2}6?Fw0Y%p)zWVim!8CFfv8F#$j(I;7@(pc3BxRBm*YL zEBDw-K#3#Cw}IdqIR})0;}!YQ`oQdW_c2iD;u6wOe@OHK;?noO_&OAupQX}Ey{5s) zuKzKb({KS34ge{K|0J=79PcI@wFf4Ga(h9QftDl%se^WJ#>$4yPF#a3>Q4hLRgJSV zzc_@wFrO6|ofFl*p3cM%M9aeN>EmtEtercW^Hvv8tsm-6sZv*3nZxXx&aYVMTqLee@w zD9CesCQ;YXZ3!v{Cf5ZxPDFi)lt|d1cXbzc8s8XI8DC9Bv2Oj{q0zrk_d~K4morP| zb#LZJB8;^BU6awS&kVoE9p_E=>SbW5H%`J`=s#3)R);A@G6YGpg^ClB7{o5XR;1N!?Kg?$JFuiPZ-}K9W!oFIOKcZ6?ApKgUm}^Y=5Q zRVoUDVBHx5?n$IO<7Lb>QVu92Wwh2AJ27R^j6q^^eGk#Ct>ScpIfOuQAK|;YJT^)T z2mtdjvd5(G1z#NH)$4;^zRKDO>z5i7i7UWAK=$x#(e*p%^F>VzAwc5Qb*oXt`X1Qex`b3ME`h3lt~@VtX zR*Sdq0omDjZhAozPY`)d?z(-_ z^nRJ{cU37CLls{fte>Gf!T^@%&1;z|JpdGLw1g$ z0r{3Z67j~Cjq?P>gR?8lTp8S?#Z3(L7}t>TEeDvj{wy2-rjN=DBk6 zJ5kZZVXx(HwYc5|R2?kD^|2X$E^|}x4??3DCT@L{12QfF)!D!zi{MMMzfF6WEZ*PG z!pNGMn{(^FE_V(@wio;;L~rE<^j7t!TVPMtiD!rJ>;er z7dx6gwJt=Vz{j6QasafW`DZkRaJ(x$lf~<=Z5T_NG69((mFUiz9P(|c%eBhp+@N)o z9~qrD=6D(r`D5Y_$yFS*ScqQO;&3YJ4tSo3+m&A1ofsy7xh&-Q_G%9*NJVqMe*nC1 zUIig)T>u7CB5BZlUh8{szL7mPpY}|hH$UikQ`WYC#yes%dij%Maqv9SvSJ%S<2Cpc zysPOt;79>PA6o_E`>rt9UY%h7k(LLrMb+_-(m=*phH{8jR>Qpiwur*X=FsKQ6!Nrr z_VxB{=5b)OM!eCivO*}ipO&72!MHcWwL*6Q74qx{t@o z*%O!5hHYj21Me47yh^~sagp>7N&0`WsnbyFy#h))yjqv_?-Oscho0#O&BMDDW41o` zuAp4oRyk`uqN6+dt&^0_)NhgYyHC1455}1LR4r6n_vUzS?l_Y{#>YutvB$%SAS7mW z4kO}2a!26ibr!)w)9eKSn5hbwCiYkOJ$7d z{#N2p+a`JIp0uv23ZT#)NGZ2pA`-l2BQ*z~-EC-V3~H?|2Z2k;SZ06^YuP1M#^>Zy zq~&)s8%U^Yr1Owu?JHcNDkbP|K~!$g#pXE~{Xq7ETh_;~>*|(ovA^i4u^-9_=w%6J z$ZyjI&BH+QR)4g4ozW?>H#L)uN@#IA3W~VIlmU6*SjT|vZ>6iCtN>r_u zlI*@(2A;SkTneT4$9=r_?^Dkwh5&l{Q3OCqYKjuy+}y;;o=@9$?3ng|iCx<|kpBv- zFJ%4>GT4=kxQnX5ZBt75LshrBsb6a(4{x!t>z|K`Dxnl_rdWG66v8%wJxtebkpu4_ zPxMtek~%rq`Vo1-2ay5jHrIbl>rhryMDB(<-&nROr`wTaJDAL9c)iOO!O<8XI{W zddEh1L(rt<7<`_`dg5-E*e&@!i))hIi1{mA*5}rh(#8>gKz$;^APmh>4GMt;i@DL2 zq`dAJc=?Uq!VR~U-clsAop?>(B)V3zg@&tNV>FMGpML!2bhY0RW$-Z?4R$ZK9qhWc z9rwMmMxo3s)HPHe7~BOo91`z7K9x;rC4iU*;kQdNC9z9{XRs8M&5Uf<@P z((abMcSLDjq)!{o?gsM{cE{6h4&UllF81@LO;Bd4bz{Zs!8eAgs8HY4rTjo znESLItlvi16#yLOX^hU&!}slC1hkn{Q-#Ri@;zGKpXl?t&-sApQQVaEcGMb$Qa2D% zh`p`1A^BA1>Sa0DV8ph2+So`45H9P)%uDGjguavcYxDXYkd_$PQ4sl_&Ky1x$!Pjw zbU^<#FCG}w{DOz#Jv*;?F^`% z=;h_;vb@^EfXC>Dwv!z6YZS;TqjG+&a*+;4L=>x$l7$PgmedDzQZahmEj5AK%(yQL zVC-VrtIa<8eP2}fBO(Gi2zc3x>p)G6Jp#rT`5&1I3aRJW#d7>*AOf!|jR8pRc4=h( ze7F=tbo1GvxkH6$v_4h+q&Ck}29JNoLhpSRaJ5>C88UZHYKq#GIUhLVQjqrP)zAmjsbIvOQh9+8%l$7p`c1J)_n%6=||0V2m zH^56a_9&IcP0o>hFOSsEqaD;VWGE6TJg9tgnWj1SiIBadRxG_XyNEN7|`Xq-+GJ3-ptQT@Q`^ z2MZ6+wOkWZDHeJOT1GRvgzf(7T93N zPn?#b?j&~MG6r5Qz;78Zu~O`&@9-LQ1xunKAEFJ*pIgspVv6o%;ONj{C>kT-anvw{8-5GFv`qGfE#-4S|EqMiTyW}rtnzK_Mlz%UVJ=UcnXEe>|gWu zZKeP}w!sSzX>U2K#UG4(&^Mt?uBVO?ml zdukdGwT*R-&3H)(yxYq$#Qk~jt?K(u?bYd@aD;14|NO93hj=VqoIB3vuf=@s}i5%%j@FnB{^&F+IE5F)f61fwe7JyD~? zNA$MejQbHXGBSdWSFC3SJhc_IEe$U(|uyC9A3&vv?@JIHFZ(7-5dxe_G z0eGOe4rBr4^JFpQ-v*`Ex~%h*+{jZeoj71Oi1smMxyZe@w{!j1Gf!^c_w@84fK_w$ zBz1bt9R&UUD_%#V!AVEI8T!sbEQyEa6L=N#tG#UFs5@@$eOIUEjUIM7J`F3t;G9%-Nq(>iK85!xvOnU{3)1<_K(y6}zOunN`9`46K@YP7nwKlwrf|;tSv@F;5qmkQtC%2_u-pj<{ zLN$3ze|QOLm>|}gDz9m&Q|W>B2Ms9_Bp}5z1q&0?XrjN_4?4KIqBBS5M&@gN>^x|F zS~iHX9<2L3CL=R5J39+@E_-^`ZPYT(A|N9o&+aJe*KZ`PMbRS~B;{e0vKW%Ut68}r z-Y^6_YrCoFu?~vv$#UfPQd2E!^q0s#H`lPB>*CVCF@|}U5XNT#P=^3b>&iw8`1Zgd zIgHZ%DTUXx;np9W+&COKU@}+ZPX`0Bs2AfA{qk+k>EFR1@Doe098)9^B%npJhP*Jx zpVWEjx3@j@8-3OV7Q8`_Sd|QVv@syN23DnjU0mE`>vql>`5{Z;jYT;&%z@lDhWDhe z@3mrZ0qDSgL%?gpToKGC+`-LqR&FEe8*WiO|0Oa&O>{*URzwX7H`qTL$YIpT6(Tua zt&kl-Z<~&rqAJY45e>@z=^#PAX}WOXEu7`$-0c5^^i-A@ydZ=FMD^3SquyS-E=3Li z97OTBKd5hInA5@R2j7Z{Y`{B%s5{1$zMe|cB7~fLm z2-O=4NnnoU%Pm-F@tVa1pAq&W>h)iF2Do4h*vHzzg=wEIKMj;?!7@ZoFR!v!mhuoi zM|Xl4D4RgVgVr(EmZcubpuftq7uPh<2ImsMSlv?NHXW$GuRWr-5Vh}-;Gb2Z-V}BJ zW~eQCmIvR;yshZHGKcvyHZqav>NEycpshs9=<~N|YJF4{sF0D7)s{U^`NuO=3Zx}( z*t3v@!nT(i^~8ecc?ja2@6-^f1LXOsGu9V%M}yKQKb0v1c4ByuXA{A={+!^=oxgR0 z{ZlFXM`_27Bm^X~O-=V*<_{Z(f~}((Wp|Kp`&lKvq~IH{6&yW4#Bqa<$ij0evSIEt zD&44+IXk|>qCiDLTHwlZCl*yl4?I{J*jN7V^@RPQ&rXXhw~+=`dmLW&Pv>|uiQKPY)I7OFJY zwVLX5#Xr6Z+CFZ0Y)<%@Cc*H`S~bo(1)Wg!l7``X#M1zOVQI`Cuq#T>iTalLiJ{wWIhj1^sR=Mu3!RI)Vki;G`g18Sr=YAMn zj&y#x3t0$QsroPM+;xJnK_uxfsc;Hz+ zLT8nYy6~&xNM|IynOg_1;tAgn#<`#I>Wb~N1Xx{j|FZZ2?>8|0Wd{;}pg$VLvOWm@ zaj@JCj#RL|jvs2f$sRte$>u=Hp=5c>n$3bNsru6*7eFxl^bV;C&GRb$Q6RLveS)x@_D zO@b0Myq-A*z3-;YCd%z`KT-ohJL5*UR^_C9uFt&XUSpZiLM3esXKI+OY|7|gR z?(V>$uHm0s7T6}ACA(5L211Kp=K0Lm^i^J9;|ES>y^8p!F;%b^<@ zu9n_do-Mxt@p{{DqlmEG^_9uk0t&v=$6MDUVQd@~l6+pP1yS(r+xTVa4sY)`0LFp3 zqh2Ink{(p+ebw`AP+>cD;;WD&7I_HQagrAmRaMEH(r^2PpLoyV07wQn!U*$U_5_BTKoBSaSl&`5v znEmSHl^MNqmF^868hG82^0;DJ-@bOt`{d>$Frp>8{7KR>S_+2}?9Y{VLTG#WJs}mQ z;$Gwh1Ae<3d2K;gJ#ge4X8b4foxMaAn zU)Q3~4%lNX%l=ren?MTv(kzx}*7KWzO38u4(O4mo$A2$!qw4(Ulf6a^5{`U-bB%<9 zb5~Wg#okhYyjX}`Ccu9_@LX0z4t;rgT5kAg$_-KpXtzYk>QCQ$)5FL6JNv=*%ywk} zt|VQMk4jJA*(Upz&-8oMZT|Lz*<1$dI$g65ye?`m%h!VGY{{C9Hs7U(|UB`!R zxjb}I^w{mGxLu1f<4HC~cy5AId_yFo&Gy$Qn9&EXdvF8q-3F{UNg-l_2TzrQbh?;k z=AL_!!$=g`*M$Pp&HPc|Sj_@wYB4cs#l-%_jH(BrX5PNRncVzcoV`rx-l(DSsf|ge zkun#g*F8qYPOAZ796E+-m4oMkP6y)=hLbo%g6;lC8&3fI=D(Z*cjYNB|o6C zV6wLnT;%Nsve&U}#Q6?~mrfJzftKn5zL&D-rftwjJej#&<>Tpn9&|`9^{VUz3M?f# zmKSkC86h!&8+iehLUxT#R@0JH-IDKHF@IKWTjJ9(t@ysO+`!3S#Y(&qs%>l$P<=?J z56BjP%3gNRg=0L8%+K~zBm*F6Osyk#3eJ)XH=g7p z_$2GHDXZvA-WY0O%QEQct_5XtR5#{1xW!LP0VzRcSNUbpn<=*(m)AN<;hfx&xsyz;emL+FeBCpg>8-XDB82 zPH5tx(5p}}a`g;%L9&Bd4Plha6m4WDlytuZ;ux3GiAlaGPEe$g9}3Ig@UzeMzWCLf zw+K9^4tZz6nWpD>-}Szn>+=IV z>b-Y=j@p%Z!Q*Du_N4tc;{}XhKL^Lq5mYaHk_H~#&;;fiXlCybCzt+Z1wx=VK=vxl z{hlgYzO@Bf6*la@KK@xMc*h1rWRB&f7SfW_vWK6FYm)vj1+S~As0zl&pGt3JH|U@@ z`F+Nqrj?enr$5~cnH2DmB0;M&39n#EL^L1YedOo!4~huepp%=b-9_S%V6`@`kW2&& zcpKyh`X5vm(j?Yc;af51i;9=wb~k`wb>pJrof@Qkv#+ey>bU~DH%1e?lDBs@(uM~~ z^-N0=~waH_%-^hWDok?kH*jL$7)8$pPnZwWw!Vo$KJ9damGPujD ztGCOq>5={r38-NATBoj8!rQ>Z_EWvig3&2dgqY(gPA|V&w{I{v7kss>5*l`v&TY9D zD@t5qK*xs=zO&K*U;AqL45sb`EBKruNhbE}SHGTm%8PA8g{-itH`pHgdoSlp-5n<8 zE}3VBYSk*rrh^LS5i3J7M=l`RTI&+v;NUP#%~N!PN!9p90Ri^8Wo(x?ToNUAx^CGTFS*{Fm8i8-E!~q@&gz3$^g;Ng^Un@ z33DS$Q|uBuU`+X3uA)?paO?9^|DCr^ZYnT#sQi>X*2OhocC*Z7wneQ|6+z7*kG#XC zj@AEZe^1iVwCzWHX~Jv5pQh4c8>~-QUv#}~ zF!Ub}<_mFB@L%ykV$AyyVS#v+`ZDf9Xn^#ud4?TVb}fApjp)JCQ*musmm&GneFA27 z50rCjMG>I#ck@*&JhkLz zu@bQw`)eZjqi05ece4fvLsHVv;KE#z@>p0Q^YnedWPWsBX>OH(qUR4*NTu|B!kD*x z%5)m`_ods7^Z5xFT-I5~GASu!*Ez(`mB`DoU#{g2&jfixK zNS8E-fYRMCgfMjXFf-qIzdyj5b=KiL&%N)xuN@=1e!VRE10^r>kGjILun_rlP74^l z6zwsA4f+i+RIC*(g;`O(5&z^gq>o1;=O7hqa-vk-7I0T|ht85=Hxw+S5fvKyIWyI?eYVC9NkXV z0*`qVxy2K2vi4W&l*Y5(XV9EqC^F$zn}_E}-(~X#ovAH4kJUSe>0%0K6s3RBh?j8^ zdib3J8(n%tq*rqHgvbm!Bl!jgGBxo)9{`lUYyos`RV^^8ZVX?@D8~Q!=&zQRnfZB1 zB2wALMx6K+(a7Nu;+AOsMl)MLj1K@zDyeo4uI14DI z?y{Vm$dPzbp03*k0TZ6p3l8j{8$v5+`x|HTPp0H@v@jtc9M;D!jfh0uE{=ayH%O;s z!-}2aAEt)Cd*KwznhrX5g%AFbYkdXNkTD`bhi#Y{PUvVQJvK?w0WFWYu(9vcEFa42 z@iFRY9qbNkki+wN0mrOMA#wb|)yIz1A+O%V*VMFL4}johr^K{Z6a}vs6ZQGae$;T|z@ER7-Qbaa1AV@xrIWNm*~`W_KW&Lv0^M!fylRWeDh}y$ z?T7VXJq?2yz7d(Lv|7)@-9@7Gebtbmssn9GqYB4wEpZ&!PT=cJ(1)FtrKRQiMOtRZsRW5DLH=GeS-qSnD`7@^2a4%3O(K?8 zG&dfS;~1d*PwEI{N7K{NUYZ#?nO}eDN>|NT&QoE)1t)MHu&ZY@R|>=AwpJX?9-VBR z^NQCJb)VIBS$ci#1G;W$5~glKOjskN@QFsJ&n<1dH}yTC>TI1%Bag=Q>ebdOshKF}JQ z{J8q_%%v%31jVnLm9#`KRMbMB@r3c4gb` z`OH)tUpvM>Q7b7;2ZPgbSua4?W=6ofX(Js9>5BUqOx3>nQni}}7vFL3xG^g^JjaT| zTOW`L1T~B}t%4xtHB8}Z(7t-98FlX4E(28@cCHt)SdFc2%X@Zt-Y1BgrluCGK!o`` zIR_@buz0?R^*Uqxox3S$#1+!OP-6mDI_>Rk7p-Q~EaP9KY?io1p^YE@ZWi|Tu5&A` zpEW?oONhpAv4t<{oHSMZykTf4=>5QD7F1$KUjN z!NZx8yOaVr>{@mm_A$dQPNQO>(KbdGU#bT&DWJKRvujUdW+`h>vv0-wM#^_BIEjAx z;*LmmG>!nsq>v6(T1RtUYdh*7;PG3pA@=yMot&@#vyIwz>lRSp!r|;cN;eJZ+~Bk0p+F1M!>W92IKpZqvu4Km+I` zO|_wHYf!b04=Xc1XXM=TtYMIZFh$8h`a%559!-gUeWZZX}1BDFXikr3bC4 z-P+qJxK^FT%cFfK4G_N9lyv+xU8q7=`|h{Wsul#cP01FTf0HX`7TjW<(5spFi-CYEZ-4Tmv)LvV z4YciI*lzvx;dKRDaRLyoOk!Vkakt`7o+l8zuaEv`r=c#|fb$$tk-nq$81cBY#_uN3 zGGuStj0Ze>S*tdGqE`*FUR4HHS5j2ia8LjF)6{5yJJaHFFx4NoGxpU1O&KVSdjB|0 z5`*NA6h}B~+!N3*r{ttia=;g+!#eQ!;GBQosk5}^WX=*fr56iG&fA>GJkKj{LJFm2mk?DbO_+K|Lu(AnLU;@|uTSyBl5mCTx= zf86X$A$30RIke()URguKbgDrzb;Z+6insr$K<|%v6RHdDYSjHGX2{1t+syaT9!Hd} zjlw~G%H>IAI?MOOu9UOL4X!i$O?Z=3R?6ci8g1z(_*k3tpmnI%AEwen8Az@YMi#UO z#Rc-=4=~Vs{G{)SPS0Eam{`9rr!GZOCNtfn^2dL}tOHLP(W4h?*?FIecBw8u_`WouAJ=R3m zld;Udx$r^7`7;pu=^6I!(NRWbPubJrQWP&>tC4@dI35(x`{<(aytOs^H=e*bF1X56 zJuu^e%w^m8Y%dA#X8s_4k)VG&dlCXiFg*19eFyMsgGJfi^q+(l6Fc2Eewm4+Ey<02 z6`m#*F(|D^=U!fG^uK_Bb~J#I`&yQHU>eX3XJ$mhU(W(n!!h43FH@+#u%*s( zt!&fG(jf3WMBShEjdyoY146L!g8@|a zR^D)+3+CPEOMx$qwM3v%BrDMIOcSG`?~(NGvw5?lM~{O*HC>&pj~_^*Xm)X8p1<*Q zaXD$*#1FH_Alo%KNWqm01!VKrK^^HFy!n6isFyR^<{cP*gVIC1KvzkF1+K2HW~v+N zL=!79$%=s3!Ukm^y-ifp#G&f1hX8Nq?UYTDlp-)R@66LIGy7OjCv=LWL-_9crVDlL zHJ&j=`HKy|?1Kf|4h#toa8v)QUvy@hmfr~jKJ0yCO*v-`$Jjiw%e$-g?Q|-DOf)-> zZym|1TX#+y;zwx9vgdl9S9AGz%daOjeXZHzBnE;)84&+ukRJxkHAQOhW|Rk@*bORu zgXww_9-H=A{H1*u+T;)CS>5MS^@6$$6?w9uhKGLWlA&%vs6f$G&j!Vkdh(O^HnIO@ zmSgh-wl7>-gC2ePhiQE`S;n}ct%i9HweQ?k7gC+^AK$!@#RlxKp& zaRSOTrQk<)FoFKZZj%9e4XmZ;e#iZ9EN`a_@1vrAs7H}}`bH|4B3hlH{-!ko0Ns$1 zAfOEd3rGUEIXO=7LUso-T=ey5nfn$nm7e>-nLVxI-;oz#kB}#j{Je8kUQze)>LRt{ z#~6WZ^?|>#Y1G)9&hql<)h8M!dmVdQ3=$8uui^0fA*VP(kjTcq$2 zfzZX3-{4Y^?~_B?EK6<53JVr~nRvODn1_fsF+CEW98!5(mX!P)I&W6arzb-`m&8Dh zZ*A=S3c$BtOdJMRZtn_JD7H^c?NM5Me@J>TZtqa50dNM??gC|MCOSrNiBUOn$fQ#^ zH@U=Pt+M=K)@iPU517Dwo%fu>@BJ0^_*KdK$3&fBLAZ*h(c#m0Um|`Y5+%&gS!FLw zmumNQ!GF>4=+g}vnX{u4L~Um=O^;kEZqd8AK`o`Snev>8?(d$#dPqCn5a-|i*?t5Q z*OlDI{O?qKMS`qBix2lOwa$vuZRDQ`krZ^S9v%r^_+`2DsN7@w6|D_zAa@L%hM>Y_)QQk zvn*Q4D~yi&dm2l`{X9YSftU~HN_85VKv$~hkB5tWry8ui?#4NBI+!>G8skFqj6BFJ zO3#es2yaYE8;`bHLd^sH{h!(M0?zNUEUlAcu8>0uIvkU-duA zomItP*E-+jRaS#WxrcFQ5hv3it8ji8z2{>gaLf`)0ZX&%zrbHbi+11xc;I4)i>LkH z>PeTJe>H_Qe5ky_UtvthnSljhW&y;ncZ-)BGMOhr7!ixlxwNh{%Hbf4oQ&T1&vJq+q zFkzmlh4N36q63%2kH^|~=}^cm20MZ5>*nUz2KxWPZ4s%XwxuMIa%l|BAbYup&2!8n zT^H1p>%GQa(kOFot2vOS=-MQq_3Xa(a@NjXDsv9wD49?_`nfelo+M#Ff{Sh%o2s`W zx7K}fAonU5whY}nI2gC}GD)MTA4zjnmeDeyWVUT+6@>;}WZUJ<;RCXj`jT;xvY3g# z!P|AMHpfCH7Ute?J?&Q&*%z*W64MWMdKA1Tl%UF2zDqaarGNm5Ea^{hPV33&M)kSS z<_3l(nqt`P2?{E@pc2RL?>iI~9o*|+f&iy`68uiK4Rr)-pPimfZDqCG~$f`oc z(A|{Emdr2DPNkwreCu-BZv5~u<}2&6%zYco0$f_thpQS$=Rvgunl`wMyDP7of5NA1 z8WkzGkumEO9-~V_`LASZn779I0IlEI z^_jOYpP1hy*pP8wY5B61c3kJbAIUAE;FJp%3#m1Sp1dk`HIGO8udYhm^?qUSk)#zX z30hb>CG$QwzQJ*9ghqVKeL0CzKVpwb=M2us+trQo(C`hf_)`Z9nsvs*zF9E^^?(_J#(=B+ja7rPbMu*N#87UxI`Odgs$AhZ*A@7@n`*WAV?o(h z(a&pZXSn9)CNbo}rn+jh{%GnhJEqJ@euig(Qf;q>U~K7W&FkN2vbt@xDb;S;3|TP| zBzHlYoR;gSi4oe+mfm);OpRp@S?xWe(_eQIL3e7FD{lG0LG#1ni4?z@ovRuFrU_uf z%PaC*sPbSHgx1lyBHTKJQ_FC>y9bwn(Ccxs`=&qkcXsHPAbJehn3=>kzh^L3A?Op~ z&5s_b@8$Z;J+)h4ifY<13rX0Rv6lmwsV--_Lrwxs@GU62?y2aP>{P!BVT<#L_u2Y`$iCZ+Eap1z1|Dcbcn_x%we^;(v-JkwdEcd|Qoa z(5QWbI5Nck-^XY9iZ?ZU+5XcD!Q{^&3mx%Qx7dt`w%RwB9!?xCTy)jcM;|hSX+wj3 z%tQLF=10HqcXrokaVe-SPm#r-W0e(PePpgoeO*OgnIuU|2LrCGw=#M{o{phx4GTWw3P`%$b4K0pq99Bsz?w!kbUw z8|0O}II1XuP@jRjI~3w%Np!8&MF)m!%fkCg`vNd=D&3tkg`BN_BnG82VRs~Z-tU0+ zJG242&d=)7VH>*&0fhQS;(l?s~nmp_!EE=*# zYX$u+P1{UXk(zrHE@;-c-7BY3$fZo+{?{vd6hwtF&bWTY<|Y1 z9Sya)BWjff!LUH+)wdVRy15^Ds^ZqV4Qe`(OQPLZ8uKnJFX@=cbQdxaLtHh^YaRZ# zg(h}U)YlV_!<0^c?az;kAN*)PXetWZ(2;T zjoU5Wu3hHAR7CJHi4HEOEMpiz8bpFsM3N!BlKa{71Zvo&MUC0k<@>8{(AH%c{WRUR zNhL?W)(j5?hoI)?q2(Dq<}J=LZqu{TJXv8#jm%{%cQ6f^l5pxp95oGx+=D(w8`;7vt_Hez)Gdd&p@0c+?^QX6Zy;B-XnG zIs0HE$n*BOLRU>&KtRaFGBo6184A`Tl6gqkev^(pfy>3t0ii!m>eZ6zk3Ur(l^_oC}d3t!YQdyj?&+rq|r-L-c(Z42#xNWR~xsi8bKulj* zS=U#|P4>5$@$vY&UNYMD<{j3Y$aBGa-RvGK6No8N5bR-HNB6z-j0r_ zgfJG-L(I)Cajz&H_^`KcB(*rZD?1S&&1x85Ei>YkDIX>Ec?XC(W*@GLd=YyPd;4*J z=!mRal=kO$aNzOy%5B;b0%8VEeqLy5IlW#Dd=OUTKEmnf*x$Y}_ur1=t&wv`nPk#y znbt1N0%qptEiLLsx1Dya8!?26)*~2MR=iI>!oK>3pw^Sq{yrjnWSlANy3zejhM@Sj z6wSDsL-={gkvNzv*M~=aKy1x<3BS=?ZU)QfIvEizYS<)Ur|qFrVjUQi%;JNwY>fI z$aLxB?d!R|e6TjaoUnkuQ@hru_Wm=KgnbGegN$aSJU=snOQBgX?dsS5 zo8b)^V}Wkhdx_SdzyT`F@*! z|2tdHx>4pvB4K>G%eXq3M1=i1&F|rP-7W_Pz=VnDWX?jtF-0ib1T`*3FT(`x`m=|d z8aEl(rY{FYYcoio<#upTU>#kwHpy6rvcip*`ozzZqHb!KS(iyCA@7HhRF}Qg@A5ma zzVREVI_BpzVFOUZDvx?cW*6a2cE*XBylR@7?&ApZM4IP>eqEI+prLlrKtKQ4R5VDw z{Bcjl_%E7@nbzeGQ(V!YC5lu$yU>I`S#QbBX_(3~LL&_DN<1bBaZYW7S}}V!*ac&^ih+j=F5;VaC@HDYMtz!mIo2*wf6- zNdj2-ZS;d|E%W~dZBC+4ZBMl!rVOnX1)Q zi!&e_gB~3f1zJELs2(EGmD5qTV5h#aQE}x-EMfJrXZy)QL8JiD`tx(Nkh24*S|gVTz$lQ_wpkvGny>lBurao8@1$ zZuqt%RvwRqM0;s~etH+9(2^ii6N!;8h($0ro_^oY3t3%Uv^}=(GSdWy-l!&S74Zc}5hdhh>&j=w;KO2^y;>o1YjIZ8u zVB^l5c-mhiJZ%`Jz`(Led5>(-J1xjUFnNM;k6KDU5}dI(i! z+^$w;#R4h_iM6puW7s=?8pLfGm4li7dyrl6wugEBd#M^~0~I_$9SGWKd_VBSHu-ot zzF1C2%4CoZeTjRjvp=D1ul)#TBS%>|k!V~j{oM;Hxk|`o{IyzzY9naBw@o^1}Q>1?Qzfn=t ze?>LHt4@}IfBT>0`VZ`NowJ|`GKSB+p#FDwEHN7TyCPF0N||Meky!;l;v3q-eOR>g zbzKr-zuFS-l?9amS=*90E&BhO3TIhAUER9IF?_btvx9q5` zEe0Hlfq|`gORH%lSo&H~0x)JTRaWK|K+EZg9Ff^FsK2YthpiR!~aK{r{|xQSkVm)qBTx@rr*cCY{fk-_X!z&{RsqP=23&hzp$rK{|q=Y^mT#cp&4!-T?&Oynz;N@g0x(TXdhs=JxVpEH_a`Gm(N!G zw&s_XCDP;vRbD8xY3Mp&&t_(SN{-=a+x_L7%pdI}n$h%vRq}qfSWEhfg^cu*e?%s! z4h?t2kAZEXL0ZJIZ|6nqa80rUk;noy@EIY5p~7aTbcY*0uB z=>1L$Pv>sYFi+R*SFf=nlSXASK%!(u`ID%309p9kT`meZHh#-$$g&1VE1NgJ`K_`E z@6wOOGHD)(pJN7uNdtR>)SifDb^Chz7&|@DCU^hH0?3Acb3}l4uJ!+rdTeRWIJd-@ zRMX(#3t*KOHFMFw)Ycj-(zCg0cn)z;PuiMj*B!SAE%w`@tJCOl z!S5!LefJ`!5t5R92ZA5tKE6J&Hs27#b8@x|gsaeKB?DdX$=X2U+niCOj}esXg;2>K zO_?FB3aC{tYClH9UU0p@8G$X) zE*?p8;lmXlEdCpaY9(WbT*Dy9ILn9n&SJqywnXv|Vs=7T)C?2STi^Cb2w1*+@g^45 z@^-Dp2tB*SvIC7vS0U~}1Va~Z>CB2vOHIOF$!%Pc6#PA3=Q~zSmZ!8bwu3(&T-ZoK$a*G#U-fHPLQ7+ z`W8#?=a{;kfxFnL)iBFns!pHTec@q45&(lh{4h2YQ-$F40i?`=q$w6$+T!8D_nyv= ziI`PbNObjwSo^L=yWwT1i?Hr7zoAh>R!j*a{nv>F!DbYF%sdZMnWfaNly$hYGl*LTI=jt8nJN9dLa%bve!P2KevJ;rIQ#Cbqeu;5OS zM;YS)6(gRvkw!d*LmBlAwFL1_{%mr%m?VF~$RG%0w{>&=?Z5Knr6ngFq8MF^DE-fi z)H%aDni5AsP{Zh>JnLG*pYIPc`P%556HY1&&3#wlYj8tV6~>6CIcW=G zudOiUAi}G{UlG*{bPm`Z~N>x0* zib;PZWyd3MIOY-|WoKeGy_W25sPrl}?U%)ay_cBhOd3jPXs7Gdsw>|C$Ws_F3lBQr#{bU}^WS zbJ_>s0$u2foWa&U{~ ?*&}QB7xr1}EmLdIL^L_@P$}I#mh?KKuJD+KDoxpS;q{_*=l%4j`cO}n+79ekscO1nZoKk)b^CkuL876F{5#yA8dNDB%K>KyGB zCq7jAb_ZU?ZV%Dz(dotr+&u!z%UZ=e3$6Y{1uz=hR$XoO#|68sO{0`loO`9OXUHL! zgOGVgQV=i`HS@?#mK#Ep1iw3qNNu^a^Q%y1tK0cYvxaCL*{<3>)0G1RaAvGruPZRqdN0-sx}r03Pgnc7jtUuo8Z z3g_B%s~&~0C!kC-_)dRK1YTXuKT$2^4Zh=A_C4G^+>cH3TblMH2t6ui-9O9pLzuol zaP-eT3!Q(9E&F?WI1}t1)!95Q3O$T}d2eu%yrunfxlYZ1 zwn<(iR0XXkY#LQ{{(Y<|w%_2Sd={Et-HNt_!>D0vL)OJP`H3PtJ1{UX^*1i2N{qvP zVWcuyAt_el`#u@S0YkNM*TYBDDKWgQy}{*Z$pkw-%}6N0NAk0v&<;q`9uzg)>NKoG zWC?zUk{ITM!NbSGK z-U;O<{B*|~G{JFxlTRGdO{;!M+!IoOeIJl0b^gaD(X5;Coc7plbhbDiDl= zWtzIRUl{scUEQ#MdL;0ajHJ`En~3x5uW?+O=~Bgkm|pmW3tOnZpvBL`Z#ZM zDG@tC*)ojfKTZTMdnhd)a2u4@Ljl4V`)nL)jh?-}FH?e;KyqqHL6-T2>Bx0j{@sY) zxFjsP5badT^59(?;qHgXxP_^lv68%t+dWi8|D&{ykjgCaP@A56Z8jUQpVrO(VwPns zjQE~>T!G;PDWU!1Xc>y39Fs6GkBRSmO*cr$Jh)g0?PIKCMWKB?gm#1c;AT=Lyn0*LI&jCo4WBeaB)Ae;<>Yu%B!z3`K!Z~<`jo<%4s;t+66_43Z*UV$FlmYQ4npf;Z zp+Br^;^s7Tc3kpO)#c$~Sw%zPnYnrHAZ?%!@F%l9)SoH0hlr4(tvxU*00Mh%+{vi= z!E@lL9WCVHjBq5uM&v7Byqf6VNaUZ7WKkO)G$JoLB*jQcnE|daHSr}&?^SPb_wxj~ z@frdFdlWx3w}ds}t~{XE0+jof<*BOpfNr*D?G#HM?4@R0SQyf}4!0*Jc~{K5wBnQQ zRKHk+``FX+;LASL`I4czB@ZD7D{Z0JCKVPuv5&ZX?;n4LaSW2?4ReO)_ASw1jai*Q zoqF`av^;>m&}paTW@$-fyU9WQu`$)oi)0}Nwh2eR1oF^N1K~{#?Y_DyXtK`<9_NhGRaP|8;)`o~0K@Jh zqzA~o*9`X!GIv{!P3S-j)!?LkOV&_Fk~wvxS00ScdnV;B$YN>(Zbc;fOZ!D;k=WBE zN-yG4F;2swR%ahD(G(<0GWObvF#%~e6pmi|-Z#(ZdeN@NsLT4*>-S}fU!bp3#%(Qq zX5(b8{%SqIU~Ya_mDO&;Fm*BvQ!SO^w4R#?39;I!#j?a_85BT?r?z#~ds|M0CKFD| zIOX3X7ryJ8v@yu&E}y}sqt=!Dhe-y+_A)Xxo0O3-TKwd2o1~X>?2XY* zoKFO)TsFn_j*b$FZ~^z~C2~1T!08Zt31}gQ{CnERFIk5(c{&fL)TCH{nAaLiosE+T z)(Za!t3o?c^WS)xgS_fPJtWiwUvayITL}Xl8vSxW!YSb~I%$PT#hl};BG}`A&ZkjFf`NCqZ;z_Zf}1eMu{ASaP!Hj2V4S4glZk4!eU#dfOVZ!@c4 z`E=bEO7yJx+j;!fM5t!>J-o~JT!o!9R3vlf;L^--lC#us@voxV_{>tjro zKTD)xlb1Du4RrIF2`Y^X*7ZJRFcaGFRy6JnVp@iM6;8(#<5tJnFTzehkEWv{#hB?l zUkYa4^EXcL8{a_{IhHDwC}Ol7j9|0ql=3(lpF<_5MKbFq1M{g_|AFjsQ$UMkd`j?h zbHxU^y6KL`%5VvcyHT70V^9>`X03EERW!mk(f@%I1nkyid_M!Pa0OHdF) zfrdfS(%w`IMPdA%$P@IrR$v3SY7qst#!W zX1HZS?m7t>&6N4#_46`Wa*MQP4}r+;(o~y=opHm|q2p*C%SZ&AG=*D_`b?`&kRh=I zD>M??b{!rFxwyDgNlr%2A&*wPzWy%t#7mBec%R31^mUCKP-^WT&g2CNcF(veFz3EVCyBbf z6|a_w+#S3Lu6ReQq?W}ag<5%n7b>P{6{Z&4>%J@O+=M4yXEdquwP`$@$nB0t}05^nYaOaVBx2SP2es=Gd%h!NiX`$_olEgv<*I< z3$tKHOhAd=|DAMxT$M_^rm(+{iyei6N-B#-{oV*jPue03BVde^A}V~&#Q2fdYCE+W zyq8{IZ~?mCzCx`K;KKKv3)a+mT#Hp%npIMUNo zM@E`0>Q5Y99Q5HtNtV<3LqFomJJhLY!HMOW+(_SWp7v`!Zgvv_rZh1f-80%8%tOb~ zgt@Sj8jDtZK+>PlZhoef?}#I~q9FtadcE zwOi?zW7Km5=pv4C>A-6T^h}c3wIKXM-Iq%rcVSE>=7=uI?cZCX&SJ-3oGgL{bA5v@ zOrasC8_`_bx+Ivg@3Iqw0a=Y7^c5xly)#kaYG4b{_3^SOAKyggxY17IwG&$r6NwC-DeDlT`;Igj)?{+b1El`*f!oVB(sBDd59;SqyT7wOfb{I`fp6HZ6Ua88+yOvZ=BmA_NrW#dNvUF%IZ-xx> zyyvnPdBV0N^Bk9G+hgU|3;*JeP$fYGTbL;sfi=*i%Ko27jg7AW>>eKeQ1D1Qp}Ky7)7{&kWco?)0Y2@WRy9yMEj(!T7Cn@I zJc`{q;7i$8waSxW0!dm$?C0sG$S%XMjUBn&;!+PgZkSB}DR9e}64v5gU?$|Kg z!J3>5LN?+o5BK)=c1>7Ret%+mm6-J@+B7KznO&|SGkCyXU;w5eSCrzXYa!*gR&1j; zGW5ja7=s?Pw`-(MbDPRLn64g6BMmDB(GpMcUi^Lets{f2sSpn~w zRaLA+R9-}20xqkQE3~HL*+=>GfotJ`el{}UXHn;gds+UpT@c&GOu!jYvp_WpEIRMB zWp#hps#Eg!V(#YG@}Xr0Zu8Iq$9Q$qrA6dsWlhIE$ZGaG?X_IT*NZfkp{f7`hH?sYf^?42zLxKUVRG4fN zb_#(J)jR=~!dYrL8J6_(OVwU8{#vxl3%SlNN6lpzBEgsB?yrW->cfa&KN6`f!WGlV zKPMm}9#{7W+r!7TT|pw=%c1i0_5y7uj_a!_xRH5HhsVSidJaxmqU?&r>iKY{xK{_S zh(HTX6BzaaFCIwn5Ij2$JO(K&Gb$q^ELbJNU%Vb(3ixvGn{);U6S4EisD}xilUTBi zd7I3bGz|a|S1!2Z%pzHIc9`<=>VbfXd{kx_H?UCVJ+~A(7Y6Izc~c{9Jxbljh)4KZ zRZ&XVgM}VFjI{AB>uJ26rsQ1-IVdUL=YvK^Ein8Pc-%UF^N|rZB0aMTOA)XJz6;?{I823v(hb0$1)$y z_hX;<19aw#f=s2&V{L&5<({qigb|;)zEy`J^5KMkAL4REGbjy4&aEOh7=@+KabFF5 zj^l0HZ8NlRLSsP(aMeF1aoRE$?EO&u)Rsv03v@D_!64^zydh^YG8frkK>FIa$;7*$ zd!G78^VQ!BTcSX*|1Z6;xc~Iw54E^KyKDwE$v(@4J~i{olK9<=nvV{9ifs!-33FvZ z{#hdL5GR3JhIXc8^9yVd@m7mpK3tWpS|+|s{`RKgO(p2@JHx1{z$iWO3|iSU7dD$%It!*@h-i``veG`w6)m1rmZlr#$-fiW|5b-iPyykicA_q zR@+*T8w@sg0`0au_yA)dOA?cu*3IYn9ZGoTe5wSw z#bJQYC(+lratqMp8Wnr2d^y**^=IHL1+Nc^@-yoxlFq|f=8c>&LD1+{DT5VOHOqyr z)n}*g#J%??xZ6brjA#b=nFCZ2V+T<8xu@j^LjUsF2>DjTTc@|)VjNO>H(Lh$?YI27 z;f1=2L` zjuNvhJwbBDCDhQ|qf}OYI8T4i{-$-$RZ5G_{~xI;Clj&nxm@h6f84nHcGIshlt&-=)wF*+7-1wj{=1vJNuwajBGo2C#A3PK42AJv@=Sh)*xKq zt^4--tcT@66;>d!^{S~@XckVK)ap+wDZEB(qGb|)b0F!= z+iuq{J0K^Lb~i19XlT%s@pf<9SocydM(sV)P^$ix?m0oEg8u5mSec;a%e!=F$dOa` z)v6n6Gt;!C^Hh#4ORJGmj@kt+G(ua~Ax+js8&k9nn@IWq?@yh{CLDHlWz~kAsS!8* z`^DW4QgN#$IpK=eTNIs@2Qo|hhp46(|(1>_p%w59L_)EDdajFYmqf9u@yHN6h?Csp6{ ztCaw~7bB}MZhKg@xoJK$;OC+hb3JIPz{wFK?%bfdD4$vHCwNR(7tkLFMI1vAr_sw^ zt&XiM8S-IY5}L)|tS%DxlLDDFMk&MF@=Ho!)&Q;N2EnS9DU@F|Dm+4*@S&CB+z@0W z7HaFqaAKT~xKSzA;{Jb+H;=k@;;g#Pw&4gP5%=6!wEKD=cGGp^m4A2XWC>a*u6>%t z34^p$a(|EbV9MvKev*IA2`InU{KQWQI5sU2@H$&2Gq_K*CzS=ivsKy<;4Vp?CejS$ zwkL8fl*$jD^{%-9IP1g1Uo@m3?g2JNcA-?e5+7!yI?}vVRjv7)c8%)SU@wb5O3)eD@HaG ztGaPt(IghJJo%@{X#`cuckRKz9!)#OsN+XUsZglOFa4N-~F}D(d40i)NEL7w&6b{z_3~0W}rZ`1@D!*S-H5} z8BMh>lKm2$nv2t2_?esZu8To|l=L}kBuSM?-lX@emp1JW>t+wy?*03NHAJ;9eN#vk z4kH?cwW_$3=mx-q3!upYcN36d8mN08$O^n9J=_z`SYv@ie=y9 zg*qD?p&|b#i8@aBiRRbVYovLPG|@qNs{1`>`NItib&=~9Xu6A&q54>H^4uz*QTn5@ zGbn6Vn-6B{dA;P2i=C}BnyWOM_$Vato&OLw;@o{Ia)I8I@dxSj)U{4A|r+o zrj^Kuh_J4gHE;Zbrr+Px1lYnK*kYl}kaj@>YvNW}Z zB5~8MhcwxWI#)3cjt&dW)AF~}I-OIapbQ}(Al(QOA}J!E2qMDJLrO@hw93#eCDPqze}CtHo%7BY zUYE|mv!8w6Ypu_kk0ug_-Ln6@#u1Oy@9b^z&^deu9iHkip1hsL^Cp$?lW|ta^6jTD zQntI2mr(HL*i+YxN1_PhIXm(A%R`C(wz;iALM31A4;p8)=CwLi+k;tFK_x>p0X?c>d%IP{>xgcTV*=$=(rf=p-Ir@iifH zgqZ&L2Gm&5F5u9iOFsg~PYxIQr%t6`@b2sLgo|Lc62miWUE1#7)Hh=yH6h>2t=T!* zZrTUj|K6qp+OJN8tLQGd0c}3rxTrAC<(ntb($z0JjP5ugalzM*ypvL5?fp@^=-aNS{^_2oaNmpiW~%KXCT-hxjNQ zqsd-M39~bPup_o^QSxCAS?>mMGVW%|@vnrqPM%EM~= zL+dMQByiFS5@>3l!TD3_k3U1{5G-UCsi{d#&ieV)NV#1h3sdl`C&T}aBo|lX0*w?* zH6L@Im(9rp{3hdk5mrYZyRyFVZvVl5TF4rHT{B3UmM7cacU!gKA6@e&)Vlv2z4)zP zVUxb47*m`Q`(#g9kQl147S@6fPP+XuWmWtqmCoO;-xG|6zFY=9zAP#8xD}jP>sL zNrlC@`&bF7zK$A7iuU?J|LWIkU3UZFuizA+$nlz)8iw<2l~dwH2$fT0cx4n_%a z6}G`p+Vs0dFDvK+movx&Y+1u~bcDijt|sNSn^cpa)unM^@K%Y%L`|cH(=z_q$@d#V zl&@bk;A)kR49JduP|+g3avn^dA6pVI&Ma@XYTT4x_QWtyWuB>!KZkz#Wu`9N$^+A+ zDKjq4hV&xFDbRjfwr$r+#1-i!n&6dqezdoIl-39Synv;+lAzP__DeGUtR}yL?xov&y99s5 zE@5ri4Dbl!L49NZ%O=vaw_9R$WA#tzD;9`bb5;vEF%+S0b-cFZbh;KENAb-mexndeukewn(-3!rhUvw?V7hP1D)oHpj)tnr=}T0)EigFm~qJ(O4? zVV~KI%mICO>L|OUY59U{)CVln5UIYl8qpCa+uKI|q1Cf%3GMAEx`h%~B4;ni>?k&A z^k1zbcyK+*8K9r!Gt&)4snl$Clmvp!s1#Jjk$dBcqmr8DTl)CR_Nkar&GbB`7LC4= zpb4BZNPt+l9nQNM-os#QjGArL&zD6Lv-GY;FuoS$rODA_XR<5{n*K&uIX?oBs4kWB ziX{2VNt;@vf}Ap@Ro9(gt-ZPaM~`!90D45(^Ix&hzq*x@^USH!3A6Q@usoJVS4PpW zOOBuPuS~HYk49&npIQn*3fzNiP%HtkaZTQvOVh@To9isk9(1yQfAmyWFAK`8XjvvSrL))F7zIsO=V#mU{$9JEQ}QrsFgpL2FoJgE`F-JgQ$i3t zJY?N(IW+d^W%QxH3)uZ?7&yC33Gp%^u$JiZ=73YO^UY0wx@)_a^)5G^x$^ND+Z~4Y ztPBtDBH*D+NEk3;wFMpYQ%xPlS9@1h-xo;%KGo2x6%H9Ic()dd*P6^7Fd`?9MxMDn zuDE^=Fgp%fOX~JUFWf5Pp|nQW+h%N?_AM`6`D)S+N#4xE9`^#DO^>YjVHm)PO`(n+ z$))k>hvfQZf6fq1uTs23FNb;%+Ip`%{U4a_y;eu(qgzE@duR*U#7b(yW~AzgqdIw^ zz?ao?N%I}GL~x7%c`#`WkR5@0$n|uaeg!u@iC&=OI$Y~6@o(>XX!LY$NE&?M}uN-YNa6bIWA9U0i zw9~lXxDHz2|2nyL9k@ri4>PvL52L8C@4rvneG~2?`dzj~=0f`>j4Wb{?QBIi=y1>5 zf4-w+ZKS>g-*8)4A2%2Ob;UP6jKS?SFW==yLAPu4^9RVDtOPpvu2JQG$M;BjrbbnG zz$DD#^5-)DR{5$_cxHY-IkP2AM!@R#*ZsgYh;}GSEnE6LZralMWg*39qryY~NxPi~ z25@N{J_Qp;dS54&5)wkbLZJCm>^_>(Zk6U)B!p@`>%iI{n;DyX9WCbrUcAbAw0_`E z(uk3wGPwcHjGii-&fA^V1&+xf>G$VU%a(6^iXRO5%cU`^=MJdQd1u4m#yA02g|k@? zi=8Hw#sb`c^^k$|7UcXhMTYL1(=D4|>W&95PZc!n)~R&;iYU~ynAB@$ z8T)4`b_jbL)8Di_9|d;M$jhF5WD)ly})w&JTf`qpJ^9;q%Ur zS4EXS#SmaGFF?XV57K!d6Z^`rn_sCsvu_p2y<|>$Ier?~{7STf0N4mJ#H$8)BRn2U z*!s~0UUyvY7R>^jCzwncqf?N0D7tNLdOJ1ATcgy4w^NAPI9 zUN$IRNVmDkhvra);3SY2!P`7&l_2i-T{rNoB0FH0>)fO7Cc`e>j$alV`Fld7)^A5y zo(Si^BfPe4z8`axcc4g*y4!ag2t=^2mC&SWyfVmZL&sB$tp)YG&)Q{&%qwPUY`%|5 z-1U;!1%es*D2FP4u_F!>`B`>CJ=Tqd?)iy_BN&n!nW;t$_1*>APz@Idl7Hv``+`KJD#j zZ9D-7MJ2Uu2e6Hm&7%b{uzj3%j;JXwCpQ;L{=HLDoF*wG6fzV0OaE{jl)cBf25hS+ zpKVS~DzhX$_usQLfOtvs`RkdliN8mb6K$nTUnQo;GO1faxE{N-hex&IfzhJ%-|u3+ z9omR!X>Bg|1+gknd#Sio$bMsuOFrR%$r;u7&~M^5L#MU*Soc7cC9STJSI3?=i8eFytvcxzYFsI zpnV2ZUg_tes%l5IMO`G zM7DxMWxm9JFo3ys#6|+bR^8!Hh#;M7d(w^aQ{wUPOEYB+$GjlmhQNq$)S2yjBfGEf z`BKm$X~g?7BKv7Amy6)Vn4r_@pfrXP;rNU8xBb{VZ)c_!KYMQO+hTw7Jkg_1X?@&d zYUgOkb);?=fUlQY-V4pPN1lESzLg1<74(XzWqgw10L%!S0ws!lW( z-};@!^iDh_nsUi=uZw{9X;}wTa2ete`xnbQlFd$<=P2U-g{pSE?j~3W0wHIv;LsrTM_<9u?(Le5Jhz z+wP~L)<)x4?1Cioz<%_IYPV$5M=PuuW7Mlp9j!1d@;u+ZqEcVr< zmia3Y`IEUV?(aK-h;i|Q+O3xD-j!7m)X_IS2GCN=pmeg4q_0#9Bp*loN0IxtG<)gP z;PkZcvPtKEjW@&T;GNNSa+(!oAv-Myle(wNKl60uoNufQ&&4YRwr=8#Oa6XaV4+@? z)j=aUpm&j0>3TDk!qQhxJw61cz$sKo-$84Q=R?54>EKekOT=>fSq&2>zd{7T4ozav zPv-iJH#$F`X95|YHH+PboDmuarX2&8m}X`ud|UbCimiGnF@D)?V*OcG8ZsIq9BtFZ z9r>O>yaapX49xguKrQ-=#UC}65cW{6-J7Hyh*XZ_jJ4zRW|f7d56ZjI`s1E`6X!=| zLG>$ZegLN!CGo@G^ zqEb7+1LYnN0FU#7&IEY!@YUZ1|aN)R9OKzUE3O<^u^%;Nf46UyFmLePeRIF?rP9`? zT^zCaVZ)8?a6o_n)kH6Yof_sxmCQ0ILp=Jvno^=!q9S?9=)xILCmTRDEyzCGSz_1nfo7fCo%WBi^p;h4$no3$YW8j zLF(i%zYyB-zbOb^a)BecRn^-R;wMO2xar8QGX{(#m>q@5>Wys?!Y@4xVgP}rxueT~OX^o7Wv z4$BZQh|boWd2Tnzkp&lP#3Uj&Q6-{j`&v2xUCcuK;16jrt-ey_ypj0MXzhB+&G!XS z;g%f1A@IP!vDNIO?)?Aq)6mB0JCS#23o_r>i&9e#Bg zqOeeb+QGMO1D5Nbf}|Px;Md6gO`D0Gf2if7LG- zA&~<1W)ofY4fD~L_`fQ5rUIRKYaOgv(3p?WiPW2N&61!a{Hu?6-Ab=w^55U1bXLK{ z(&piSWoDKA<$hI`A2G99K4=}I&-Z!wc?a!1kXpa?ntc!Bh(B_CZdodr(m0jZ~noYQt56dMt?Yl4BYl6)kggdrI<#voERJ5*4%^tgp3|*(o;GrC{+P8%5Y$V%BAg7TAT{#d(_i?JIaas>YK95Y%jaaRn|OQcZ;mL&6O3iE-yds zf`Zqa67U$uRLK)qj3_mf?2cBIP-tqGrHeh3N6GzAhaTBi?WV|I7%&q4MGabit&&KX z<`k3X3@-Pg`DoPPdZW|ZD2psu$#(t`a;GY>J2Z%fdQWAr|XWbT*R z_GzC?iHiP`xMk#A;h}Ziz4Q;jzeVl!>r-Y2XP2H1>g*5mv7aAgX@x!yTj=9IegL;H z-v7w3q)uYt^ILFNC{3q$;+Is0CR3itHKHRyCr=%%PSf?T_d@B6?_y)aqa_BuC-0I8 z@4^*8{fo**<9^Wo*i_?ED6Me2bslC5McoYk9oAs#&UsAsGQ-5@GyRQYsxtG|v-BTr zv)VV@PwNVcYj|Hc_9K3_eJ_4$^Z6R0>TEpUF6POpY|)Wb;BXzag$xn209;=?ls3p;~vAaXZe!yL9*Rb zn+G>*@^^fE+|PEP?Ir;S-UHQQf&W%EPs&o`sP^Kcu0Weqe3P zeh-jz=^zZCIm_8RC19+oh@8+9(ct1!dEhx52!E8$jPE(AyclkYx2j4+Bz=GD^%pEH z4g~(gX}t}ajCm1p=2*KVB1BC=Y7&FuB-@yA6uy_b~ zu5irW?jGPycto0-)h8&wBN*W>X$(ZY12@xTnow|!=qgHT!>)7$X&NANS&+l!$x`ui z{>oP(c^DaAEeb&hbX8H3?ZaYfvR4RRS_HpNL}sQlkgAKs12#eC@?=pQKv-K;v+r0} z88Kl365%i1sZq{CnTD*9e2juA3W0N*BF19bdQK>Zu)XC5c^9$kvtM(bvoPfTEJC(A zEx!9mj!7;AE&}(tytIdV3&0n2>x$pgfmsbuz2(NuO#OjCl)i0{GP?Gk2k@NkM}pq; z-{c)I(=m2_57LfK3WPzGZ!lJa;H4F+2|BOhy8b*qqD)aD?GRmkpEILV9ZIl-lpN!`#pGE_v zjah%#gndao?FFm|SvJs2`3%!b*@@y@pnf_?-}p0AvBWu^;5GO;1?)1W#8P6^mi)5N zZ>MVl=_$X4QTsVQ4kD0>J{hg+#F#2%!Jq_Ba;=V5iI6OQBpldvf0QxkW~WM0O@?l30DCa8^rg z<3-#<&r|SSADylToj+GOnF-jRdY&690d$P;Qcuv9zIR}{Oy%esdqk}npTaVh} zVvTM~?BBrKz>li!OPWAqTT#na)v$3e_fp)FS^62fROnCe64QcH=ja%cK|*MFxD{M5 zZ@eEuch9?nAO4y?IidpO3*wb3?g~Eo;84!%(Q)%KIUMTXbjFjKj1TU9lyBU;mwnPU zc6K6eRDv}v1S>js?m@Uym<-BOvw+SUiB^Z%fW#fQ-{5`YK}SNXZ>SX6R=;IjB?Tg7 z;4s0eJp~p$(|8ur=f4oCE8g(0-E#}Tl}NCH+y;_kO_4%44^y-1dx@CLaH82)t$fk@>@X<4>4{DS3<~l;vx`q7v|@HSK}hyyDb$R=|XkZmMY4 zpDiNRJ2LfA&pqVm<6V7C4=RG@u5^Sb>cKzfByVknK!4Ag)jO|Nu!%1H1rgLXY}KQ8)&`4Rj8Cy-GpR&9&(|D0{=&GHR|}SKH91#vKtf;aY8KzX z`8zD5WrZCQO}^B<*gILdJgX~aTID|i9*Vog$p$dB7Dr+j@`)kn9xY zED0aK)4+gLOjnqvIbAR^d<&JiBz;H8`4>li=ir-U_u+44GOxLQ2>h1KhS#%-2YPj- zI#fHo%5o5`HbTgMvsJiW|9r0R!t2b_2$*^PD6$!tobpzNiVI&7Z`TJgbY@DjKxS1@ zEuj<$S~%C$mNoTdT8pzES58N=CwD5cQ*ea%UNI(NZ5e3^IIkqxEB21m7?{kLvciF~ zP}uDT5z*k@k0NwwZZ+@RzPway4Et)dwc`dfj3bgJCKH?P5m*&RdCrf?_r?EWTh>&ktU1S@sLyV%$KUEsB10UI91`HRJ$vj zBAs4DxT<5V{*({}=RPg2Hcsc*hZ^c)aI8kW!#>v;8CNMJH2ZhXZoUVI>4p z#V(2fZ07)1>2F|61xLu_#V_m-vt?rh;l@SgyMVmrJhm5_SWYm1*p-yAI7zK)!w=iE z6AxIf^IU=DugravT8}O03S`K1Vd|%?_d5OcTxH96-S1pH@KD$On|*n3Gw~?Pl_@ML z9IeG{&}}6m=keLC#c-(({&Dff^Nzlru^KT7NCq->F)#z4Eb)}G$dS{9FO^(zzF9mAmyHQOnmKsV#IXKEQ-_9w_91Scwj=BJ zrQUrm+>XKp4sn_a4KpheuE0l$g^@OAt*vEG_KoPPCJ<8tN;8x|8eBjExuCuONi?4|9N!sz z*20mW^(EnVgQ2ipO9Dv|0a`j#=Joxd3&Mul>IiEV4UF~&Lh=vx+v-SdruJ8he$ZXK zVUFOQQXi*Ccx6X&*TpJTG`*;VGy=vGtj3jzjAx1Bj~)oN^}Vo9=O`;jB|ZK5_9ZGf z$*_S?{XK*cCI&)=D6@4%{G2yYUv~MqEYU9i;p#JSU z%Wj`5q`EspwWe}<8lanc?P0BBinJ?o*y<ZBtICmLJ$q_DKGXRR+n#W6xKQlY#uB4`K}GA0d&G&6iV1#X_JHWA<8 zFQEF$FQPSZJmFw*iR~J3Vhv{0hP`JNP5F&I&T@_AQEM!T(gNQjHA0Yo!aP?&I~*Ps zHWHbZHjVnZ{dGn&}uDU$X?p68)`cHeeVu`xWMJ0aZVAfgDi(igDm3`ytT_` zifno4-N-ym-YjymZuc<5YaP8Nh1fBR(LgSUUAXS=8a6_4L>%7FE47&5aJldSCaw1-&Vq&z)WTvGgxk&@D zzEBv7hUwM#tv8+@zi$c}8#~`B$j>g|*M8me3eH+HSxF1U@;P0rAsc!X=v!3Y`hVXY zcr=BfPv`Lj%3$huAiD;R3a91rcG?d#TDS{PyR~G-cL`FntGrc4LatGp!FOS=x)zf2 zb@CDy55YCY2~J*2g5U}dyWI@aLZb8$7?$>sbrJr`Dpmz;83^&7sJVhn`;M*3GPJpK zG4r@LuriV%kkawwn*@88&QHdbp$Be%!Rz09GyEN<)PnDEbGczw`J_wa4;}*cacpvtG1?tx4 z&_+CtwbMjCb6KeZIC8S{1O0RVp$YF}E6Tj&tTcA#YkW))4+lLA&&2lcKgJHf+W=(#GSN{9wo_1X zPV%F#|!`L=M>}Ix?q@TkgD!eT>pYA_SvlS#O<$-Rj)=k z6FEZ!$t7pm|Iyr2xOhET0BQ}Ijdv@W-;1a38lU*(xBtNV^x&4ta05=B?2r4Qlh}~V z@S=oFce~F2fj^RjHqaw&)Do$V#Q6+? z2+O$l)T;ORW_LwjmA5C}yjC3+5-dieH+SyCb?Omzqv(u5ZTOy`|6U|l;QId5*0;Hr zT=q=rTq4OY=GyLkezAf;zg$l?r^i)CZMEY_J{hdy%ezrL7!Y4~`W^(Y& zzLxy)=|6we`;g|3PgqF$rIJ_z+FR}$TdY!rG)F)Ij0VDXMUupF0-A-~DksS?PdIM} zbp@JLDC;|^B);CUsO`XgO}xw8!ZlfOLObdZWVDx^hKEc>Rq@#*?uBZ4HjqdZYK z-Uc5>hy@TIL$P^xpU&ZKs5@3Vp*{WmAlEjz&E1esLR7ktIw1VVg8-RZkFJF~@URaF z&*S@)$eP#gM!+au$!hATg~v-i2FgP<3o=|QksXS<3p2%Q?5k`{>w}_Fs>^}7=5h6@ z*H*e6E(C*@;4eYV2U%Bq!h|e?U+D*v!$TGeWWg?HmT0kakutaB+L-T$0~4aNFlt*S-OLI!O z?{A5z$_2C5$b@pMnweo6Kt(PG?c&-(SwUxqSp3y!NDb!R_=}*y5b;EzRx2nKYJLQY z_|PreVS>ayh@b=k;FZ3T_iMUHUK_v1szB zrRq2^plOFiZplsHnYrjlLv59Jm4?Gkm7`7Om&){XF>K;p zi=@1^5YM-z&EpabB;n8|RiE$7$nivfz1h3*JgoX;#;j>`)%E_ILPp3T6={IV`cxl=6~xKYZ>L0bIB$G*lT z*u$LdK4x02fpk1vou(jH>SOQEdw8RODYjnmq)&sNb+m=-*dswTO$5A!y@#%YJr7CB zUg(+Frp>GM2%ndn;+V&(fe}_ihO>l*B4j!wD%SnLvPgpX>_?2FtZEuPuZb#T`C3iw z=|+diO|6jiE1rK|r9E_OA2hZQjToM4a6NAf7+q@K&tvI$q>hd>AiptJ&;Hizn13Sx zhfkJD>M_CXuTA((0a&10=jvFsa|O}MOZ11WbXCF6$Io{9lH4|Fn_piFI_Utiu-{Z; zfR68OR)U+ddtskj78Goy`I|Ea0^X_FgYWrSb%zcrP2iNj5BF6Y9vGSVSr`6zN9B1# zw62#4pO&K>S>8_&tsC@iSW%ifTl1!AZ^_Y!i4y!g2$kMh^68gc^Cy9x^GduKIB-l| z`)`fb9UV-WkfdO51GXMH@l_|GBoN`#0;WeRmp~lVDu@CLnXc}461oGdyW$ws+_0^g!V#75BM|fw{33M!XZnW zSU)5Yt7ff68rSZ#o|CL#?mwIhrvI*0%DJZp0S)_!3*GP}kx% zZU}!o^D?llJMJcAz66{~RJyRRFE|)o>UG#n&G8EFzT1GCJ9eqs`19b!!l9=N9uoG; z8p0mbWXKy<;byK)rxwav16++85sm6<@cFUP$^q@ST0)mPRjQtuYW;uLS5xXo*UYAX z4{*e|?et@HDNP6^msI?SxGc!>+Ej=kI2z5}nYH*B zFOpIYR~HgzeikjKqm5KY3x!L5#NZ>XEJeL536E_8rkK@Dx*FG?TbUFX`cI`{kZyFr z+KDipsBi4LFqfU-w-@X_KBD8wR;QUc;gEgtfCG`;1%@Jyp@-fR-vzRfQC_R#_phY@ z$2GN36gr2wZ@*G9o-89|B?6#rsd|nA3$ho;;FyLhSXiE+p92+{OF-b5zDwP5as3*Q zM5+Kd!EVc0ou4}d zI!x{r8=g(SoIG*`yVno;sAUQs>iKfr?qIFnKQmwd>i*xT0L0YK!5xHehClL}x5b8O#XYWD_phQT0h?oMq|7!di$gNafQm;!jvVrl$LF76VH?QTWMyu5kI@D?egHgB$F zS5~qClXS(pPt=mn!#r%v95B64?Z=kgiMqSew*3XmVp=7(YAT$P3nw`C+@@t!@9sQ= zm?Qk=MAqIJUGeUgOtT0apdG57NBRWE_O=0et%jz^dNVBXi$g)lQ zP6H}BTp%-Zy=;#LiwXnoT{cSiz&#qq zOEWLwQre0B$_&9NI{Ywy`}jG9r0>5S@8=~aGrgVz^%P497+)P~o*(n&>&HDZirY$O z8PhPojWH|`nfHIcxhT?megAEmxkpdO+{K|y8Ks_zm)xHZ@-OGyRDzmcCs z#tEFv63zBkr>-23#MKzV|)|mDovN zM_Y-?Tp`WXvXsjSg1U(1BunC~LBMy>a7t#7h=~5k-T76>{QrIvTTw^PO=%vHzqfI? z!oFltZNF;jkf{MVammtb`x)BO`K7=kpzK!~5ELYz)BhL%R?rC~>I2Fjr`2xidO+`;zzFtVV?R_v=F_C zu*!Fb8zTJsC@pT9C@skIvy_b<98dzr1Ns_NK<<2Rb!v+6Z1eA6SLZGxs$gEn4x7ax zZ5F&1awZ-y-;vd*%)=rlw5>)Goa(dhkVW~lIY8xy;kj z!5y9aMDB8yyErbVt5;}AnRy306=xa)j*A=iM*EFvgV(FpETamney|8Tf!HN@h)t~%f0u-wi{Zog$Mi*0Y$X{^F^o9OI!Oz9iLe^dQg z;N24b;-+XjiGP#~A)a}Bw==xgA_H$5-nv!%fGe^#`((!<5S|DhWpAO>y=qek1wWx1 z4QjKxpqdm%{fza?@kZ^lu$4CQN9i1@TInVM=V1)vCo$QsfeYz*x~XCh%EP0IC~RUY zh!9j|px5EyvGteqdzT~`Sh*C4tCO&U%Z>dzzj(_*ex{x+=l0y|Q zQo8Fys&MB-@z)R8uP5nYm&qiy_@-^0+D*2pK1x*E)-5RBz5*>_QKIRkhV+vpdcx;2 z6%h1^QA1+8S@pai|E({ur@`4;kf^7gsAen|WqeYuQ6WVjB2TI#-0E%#qxxMPDQRBG ziVU*p<3_lxC{>87XtIt*-!_im$Zr3c@CM#8NKcU!I}0{MefP9;BH-FNDT&4L?O0ZI z!yk6`tm=CVbWfi?ee|JX#GFL~{D4eEw3v9te7;QEtZYB=O&%~5f+3>dh@FNM5u=BJ zBYEOC&QJJWDyYLf*Vl`UKoMPeI-N8#DG&RHX!O0EDRgD1jfSxY0;vw+L`k3NFufvp z?H4~_Ky9ejGT|QvJ*{exmXssl-*waJ z&A4Hp?5+C(+Od*Gup5Ve?)e^3T?TD%{3(d9VxL=H)k)TNFAWPoqR!b1)5n&@(#+bl zs+Q9d8F=Dnunw7K2-*m1Y*g<0;kqZ zUs7-t>uVv?JjZiikK=l4-R0J<;2WPb#a1zfk$zB;YW={)F4HVPtfJ=rJxJmf#s>f9 z9fzJk~k1AY@93QuEI$9m8Q0H;qlqGbe6FcIfC@0C0KBi z(ZpqT**Oy4B>%*&h?o`|D(Op(ImxkgGC~K976r-C-=uCC&_P7bzHOa6sR}|Wf!HzZ zBq#li(NHXHLNvmPz0*J}O(3UnaZ}zQq)+-`*OyC0gj05Y%5FHon!Jk#fi0 z<=~Wr=?kB{zQn^TL=9<8gTAwJyA@*Rm7r@IN~I`6B_)37NkX45r>(aYqox}s;n9J$lg!k(D^4{r_&7bpG@7l213O_&O()-)DVeRc{4^H+jop1NJ zmRq4Y`Ov88t8K)8i$D?m{ck&WuU-AcSNa)p-?#jVN&l_<^p5OH1Ahob4A*;u8W3|} zNmZj&zeMLziy?LZ=8oP~nGTTuC3nxPLBlo52*mw>Y9<52o4E?{d4^f}xyggr++=G? zo^YE(+o}|u`$B#D4UUA=uK7eHb?7@R{=V>kg^oTC*^j`LDz%G056NgJ_1*)ios*ZW z%_MEsxBa4>Nt%r&LL$R-1F}Tdgx*BoaAJ0jyRRM|(&V-KBL-A>v^VX}{?b>(=jZzk z^{p#f5e2B?&AG_o|d01FqrF?!edl9JL7Bsg%j@>ux zS-IC%o2hASUkddBEdbcJ(h}g&8TXE2Z+6ND|F`4%Z)KfM1vNjIA~ms8O~&j!-7hjP z+bWXoBe%rDg}*|_`>S1x?x2w&iRKasXr(uyC1UN@*4MpnksLZv!U@F2D{XF=Ypkfg z)(*ibbFn1k8!e>vwY;CBh(yV&89g;3PrbYx!CXKiEind+@;+H`Cg}4ZRymsur1H4$ zz}4Z@ecRW@B)aKXXw(IHw75PVHtl%Ef0#K8mbQ!8=>6A=USxN8l!7h*oW(Iiyn$zEOVSb%0l8d!``cKf`AuWUbh;T@-s&( z(QB(tikrDfF14$v)@53u@DP}fVA74X0($zvAFVZtHeiO`{QENF$Cs6H+qeOeM;uz; z5Z#6!mL`wa>vxG}b$&;H%ux3U#~A(bT{e%l%m8T)dzSR3iiu?~=zN%^QaO6h!t~DP zlXrvM6$W$S|DJ&VExkQxAel_Ny6&eI`s=#IKH6q-=Inl#Aop5xr~+FTue61zH*#}pqS>uA8u91JNp9qoyuWy617!d*2L*4S@0hpWimbk57YKi)~Z@XSt7rS0gQA( zsowka=)gy3EDE*c@W<$*HrwjgYaPJi;Ndhs_E#xc?8mFOxF3V~e(Zuc^!jZOyAfWA-8M1d z=e+gLv$DtjL?T(*)syIWVz1v>ju z{y&KUeA^QA6t_E;Bw*F3|7E6O(07qx|0M(0aj*dlM zRSjp+x+$Bu@~Vv-bfc1%a9Sxrs&L8$E7$AuLoiQyrB=ftv_EYXAgLACa z9%^d;G{Db$%6&x?)Pt$M{$Bi`zGI(-H)^KGtUy0IRjI3r9$GsDSkYvNQJo8_Ns<%KkEVd-+#^^30MT&fMtJwe{tIf z=WJ%CSU~|!`%BbH5Em|5B8hsUe@Y>~@=o&B(eZJ#)(TpSqWSdnboJ1@%+QN%KVXWz zi>`6qv?~f+tZvd1N8M}bVDmZ@)G1qz26#+?Nfq7n4dxcTtMR;D#C?C~ohbDXpXxLG zDD!3GV^eCoSg(`!jhM^_K$;~ie=aOhKCS%4>gl;QQD1E?EK5J_W4KmXJM>hhWb7KA z+Wo_uY{uq$G{cmQY_|q%^p%fJsuszn%*y<)^Gfpanb@~>YoS9!=x*k*u8vj&txpED zTT^R=*A_iO58SR#$w+e}?nYhU<00V=_ye#^#s8flVDFTi`25;{grLO7p0}(JGQ{DFQ@`o2@$e18nI*;fEB}osw=Y)Aya-I;&>)+JA@2 zJdzb~U^xM%d%Bz6mGMhIvYG4)li50>7kz*n*|P_9fu0kZR)jWF9tuw=JQqs+G2-;m z%41g5ReSHPZxS&z2h@HJJk$3Pv9C zER_4{sIFM!G+-?+cf@hcVm*(moaMB09DDZtqyGqLM16C*t@P}&pKY@f6g=~dyhyhA zF*&2Z#o9!}3dnyBH@Qv$0o{M<7O!(`0*1?mj*nW?8W_I0#FFPk$Gq1^#~w&?+e%>) zpR8Mns(w(Z6cT&et`r;hmxWYe;nN3WF*CO@=V}g87C_?Xh#Y0_c!WrQ2tg8**@Ar> zg|d-Wi)t@4k&wMDB@chXGp{Zp2oCF(VXpsuJtvjk{78g6uDQCvTjg~rh6~5&QhT~r zzt$)~ce6@Q%jUqr>9oum2SHe_-|fN8AFLyMt)ty~+wy+Z`gAO;U+IdJsO;ERiBNE+ z=SJm2mc8to)R&d!WXywf6xp9|edvY|dDo1EG2e8>>dj$l3ZM2+SMcO=p$tN{WhSvX z!`t9-f56(a`_C5`(6KOWq9NLkc+ z(J!S~;ko3E>tgmWI}YIPVUTg#BPWTa!0GuZ*U!|ASkJ;8Zd|f=qTHl1d-;q-vj!=WVH5E2#U~{$+7;O^1mb{GYL5 zH(y<^>X->&uMy{#v&2)QlGp_=26^sJG#&Q18Wo!mTuwPjAH5xSmtN4bbM%3?It2&0 zt%&YVoP*Dt9@5bk^9*ig4Jy?0nT++GkOq5P70YaVE*p(`tDOnORU;S!pF@OV>?&eio7 z*ixlnKp{*)!Y3pARc#F3$fK5wP3TsS4=se9l#dsUih`*jBwBx5>=OU7kt6?Q=0bjUps!dOAk+PiX8UH*Cp27+ zHM&D$<$1!omjcAz>LF={qvK)~B%Kn!*p&hP_E3DLE}J;E{8^c*rHy|cWztvZT2#>> z5z8x_TIjd8|D_b?di$o_kl013V7qrS(!cDLSZyiM{cm2XtS#}27)^rA#Etm4f!j*Z z9BbpHUdVF%4<)sb63?GheBb#$2bB`y?1y+Ixg=_?Xs(!TqD$XJ$Z~}A8&;CM4Oz~Nr2i+XPeYP4(tbL-`@6oFhhB&Cab%=#n;y2A z&_Zt9RBG@}Nk-v3VD0%&=*|0$rBbuROLky!aRcgF_HrFH?jUNWGcYmHL+AFkCg*(5 zzu4q+jmi*q(Y+QKO;Tzu2HtUf+m&)_FFLTu7fDDMY2P+?M#ADTksa-Xb7fY)iltSH zvaQqvmU0}v$5slR)6~?*PPR1s%WdcTCeISHf+1vyvF0#e|K!Kqg@CiGGCoU;2M3-V z2q=x@rCkP;ENHwTD99oxd+X#X)Ep*&jdyaRhago*9l_i(xI_c`Zvo>Oke9uL`8U-XuZ zNrb?SqvnUtxmT`K3&B_vpf)%(69P;NdH3c9Gxl*PWKx@N>Lzm68=xi zKo8XBn0ClqM22}MQ z`W>qdch|~+j7^C5@{9MWv&%ui-Y_RzO=u}mTQ1{LhhPyM zFF{rV*(d5fwNteeq%-f6I;aF8tW~IDwi{C0DFf8&9vY09y?wnioYws3>plGe2PrC0 z8Ua*ovO?Tj`LaYEp}H5h1v*}d#j~?P>tEc(-YHBh)hr z7#)-+h(ZQtKg%^oJoeN!wFqv!lLV%a;0w&!2EZDnw-=Pnrey#C&2etromUREOHdEu!v;^jnhlUNmmDd&#r9fLyy0CJ?3GQ zG2oOm9clJj>ts^NCzw?^m0pY@n6o-bb(>yTo@004((3G%=y|%jN}jF{4!Arl)$bT0 z??g$8X%7gv%1kV?2;Nb2KKb?Hro3vS;!J97{|)%_CxlZ#+;gR73JcxR(I$hd4ymH& zf<<^2vtF7w=kE(S8wJ0BAuB@413L_Wy{^yc6Zi zqhyqTDT2+~&%ng_FhrckAL1EGr#KxA3OUku5~rs*LIk@)36B(f0aK$F&F0#xgv zHXr`o-a#eIl)rF_6eQ(aXh?~bU;uGQHz3xC)6o=8;7x_z5dhx&j8Ao)KuB=K>6khR zX|ndjEC%=P{#AIItN=Vo=B1~Fr0o`;T??7fPx@xWrz@l~Lta|?X%j&E1@q z7D|8Wl1}2S>ke_CzJhhX!m4o{z=(s_8~uks_gJcEm8Z6W3c^RNt%2Uu#DE_bU2?gh z(aU!5IJ-+NSgaT}EkxWKT>Rp5%5J_N4Gepvmc^-~=IPy|gBEu=3jsBcBF`ILPdVgJ zyQd(C`uw9u)6Dp9f2VwFXMCLHGG;)hA0!&Bljd@I4r+@BHR@oUsmyd1bf{;_oOStM z?&R`;*Z$iPnU6GY=^)4#K^^>H%^Fm?)sx)Q*Lv5!PD)8s;f7Dm8E~0l@TiC`0PWwe zZ(1w9U(bjCs+|ny|9JFNuKV0Va5hQlw41@zbt=t2$}{F?dvxn9<+`1G>je|=I1L>L zj{4<0u5c$NiVT)O;CtPdHNRjk@MOCS0DC1IW5AchYqAqAvY|* zb7HUYEM5PQFq@YZXK#>IhWQ!m?fzrwCDZI2gJub_2mmK()9*jMkMtQMb|fzU z1l}b8<7*ZfVo)EuUb@Sflp3oH?uLL>1rq{TGeHzDdQJ~O{+lt0`sBK*%b{;cH$6Ub^d~%v%Uh)wQ#| zO)MIfIQyP}`~(JVHV7-58s&2ApkTxvdXo2BJCCi+ajTv=wQ-l<-j)xNL(Za~U$4gW z?BpdVG?bQ3hqnECY7i5Jbzw%tQ1kFH^B5Hck-o1^A0Dzh1_z&qsIg9w{@rid5x?$! zKIH}vZy5Ns6LOp>J!t-Qbo)!^bHjNZ&Qihy9_NU!J#}$QzJqTGMJM-8ho>K%In8@@ z>u7hT)px86E$?pnb$ltzC`0pZ5RdolIoWQV%e2ovl!t6%qEU3y-#O+?R0O|(l@ku^zJSADWC=5RCDk>^b{81kl?we!K4AL;(|D3P`4wagVE`q>Xt#UafSZ&`jd;E&h(9;VD zK{%Zdp6qxo0%1q-d+BKfH6vxx#$TJW(mgyZsWFcqQSf(wQam6R7%m3Q#G^$Sk zDPHo?G!@O@Zf;$TZ%;y6)1S*8c239flou|egf*8}SD7_%eV1uq5pRr3`5=~m_meA} z9}K`8XKx@t_rg!Q^*>+^=uaQn#O4YP7=!G*DbWH>2|16#2a$;_+-L?m6$kTU-4~x{M8@{&*~|Kl`JqWPSL~j zKgHu1+~e$=c=0$&id;b?%jdhQ7x}3BW_!`+5s=gTPJT$BC~NSh7dS+7j$o8^rE2m zVbceF;*Bz8qpQtp7ZVDbGTjMqlGmI%T0MlfArf`4DC!0RBl{mq4&-2SYig@qw9>5FXC=Y|gwim45xyRA&Q1wok%k#WaG>3jx&SwUu=uxM`hIQ)lICPO!cc!neq638a>Tr8JQWPDslbVWAv}9)=&Q}{{QBO-S6BHsFm$irwH}iVjzCbPm*!s`0Aq1ycU`PY z;5_kfD>N_^gtY28*1i$m-W4pjsH!%I8?l@Sd8hy4-Ow*RSgg`DP>DKKR#rxOJ!33* zjuw-zogkaR*jW1|mB$3B<@8sJ@x5ii_z^51pR2Y1t?y z2tN1;|12Qzza$Ry{teV-dq4%k@h5G*g>4&|fPz7K`+!6d@6E_JhGl|6uM`=iHFPkd zJO?dEmfM0MGZzI}&OfRhVunm%n{04{Ft0G+8|_cw1OBt{wiH+B+>5Q$%Ft*9#3r z=~(tHufsa{hs-hZqX@Vt6${O)II@QX(GWx6AA;WH#}Jq`=Tk5j?sn49s|a{qPRHC} z>aK~~t);!;oDjbpq-pD*c3uOft1Zy44HvVi`CSQsySe3J6Hyy;v6O>@AllPZ>a&+3 z;9%X#y02(f|B+_0r+e|mU)4fLv)(WWK;`}=`cn@hfDxx6QEKREWS;U@#zzdZ+O!Z0 zOdV-dltRj$e5N6`>R!x5{D{^!D}lnPzd_BnenaUn|k# z)iG~k-jin@Xyik?~%3P&T&w5@+>YbnSnVGe9B{b$4FC}8e%+c!W`fO9%kE! zrj%6JArMpvA=0oAlfUy312rMm0V)x zm+jfI)z9S7&?Ns7e(}+%%K5d=fl;gfKjNT-AnqQV)7m=YE1ge)O=pkvJ|E9SBq>Xp zey=!+{tOhg4ogYricL)NK5i)o_(|{KTwStdK4?J~3*`Ng8$u zooU|vVBiN@oEitbkW^U7(a1;Rlz?}A4b%dyv6?Z;=?_=Fj9333xS$A0G<;D_Kxgqc zH&26R9rfVg!;l_G%;kMIP9()$%%sqmnr`f+g;1KxVDj)*h)}dcO+VKMgDd?!!@$`f z^{PZt8*@9xoLp1WBv}LcGBi%i!7OOT&(GrNrv|6)|98`O#G@EH6Tz^3KUM(m;x(9t zyc}@5l(0rC7GPaG>X(G5@k#o#y-Tb9<@-1@P0{(wF$ikAdQ0m{2T3XcB_Y;lgm|-9^-hFk9@-xU|y?w&1#YL^~ zP;*|G5W%Cc(sfp>{`cm+JJ(+UE#>#o>5&@|l&UT*ZHtGQKOVEYXLJAJ;KZ>gJq=hJ z-Y6yvD*YQa$mN&hLGg`IlLC2kKZ@zp;fjtA?->h!hq$eXfQe zBH6*gg<=WRz7TTBWQ}nD!=^}i-CS2ZXRYUW0{~l;1mHhx>&HJbt11j%Na!I{Vkz1d zJ?Vg0*XP3=7}cN0ID1U+Yn_I=t(DOXr-Obz6#)A8i;+D1IF$FLVmH&1TU9sx{eXoCMKJR*@Li#o@qlsKo5imJb#H83#XMz|8S z9@=Us9Buc8u(Y=3g~5RNC2ZD$j4UvN85CrC0_gv?9)Yq4XbOvLSHFeC2wRB#7a$w2&>Sk~ySt#`x!R>1DC!VBY z*3+H0Ku=1{t;MPZH$pFi-4#Ha^ls&&TGswG7Fi{ghugj>Zxz2Y=YEBp^frR#$uGIN zvJKg?3knG&{>NiS`ppB|Y7drXKfd+(8+z>zCxZ?|X#fmBwpTHFIz44JzQ_A)*}gmD zEhGTSRRxF3OP_tg^N1nhBjUD>gz1~>c?gL12PY+;G$*=UN=i`OB0I1lcrDvV#H7Tu zg71zD3&m*~2Og&j>Dk?Jv%kV%P^H@W4_ntBtWAxjku8(XL=PFWP$Hm77dZA+HeVD( zeWq#HaETwApt`dN&RBN?ld*Yy)kj#`rvP5)=wq< z5}Rc48<4Hv)ZKN^&6d(z9aW(NbtBY&3iogb9URbfN}}$J)LlOVY;}``{HI!K!F37rp))V+1JgjU zRs}rb$sOUBZwljLI3?v+PHPPpUp|Olxu!g5Gn_)}a^Z}`!`&)x1$4Yte!|YwD`$^} zgAc9eJ`lj|pb{lUYwZ%R#mDxQkMH?~#(w3FzWJX|o?Yd?IEIL8han3EpZ%-w;Yn86 z`g%_$Qp1h%;ge89?3?&gyq*X9;;Sud*8!Z5jk!`KHIIi12COB1kY~fgbAvl8wiZfh zAh0R8gs!$z3qaqNx1GfpZ&9>m(m=^fUr68v$U_=mCKX+J(rrhrC!?Ao5-`>%0P$_Ksk&eG%jx;l4f4@cc@3kyYk1ne!WXKm?MI0erO zSZAAgjXTJrKw0OWo!Qlmv9Az}S`SgZW6uZTJW2=zh4UKx=7%l%QN(n?qLe;IJgY>2 zzyB$9_?TPD{d!Zes}?m)c~yDgZ&Y=Y+)9h$>HY#P?Uefa@I&75%`95-i0`}Z+@ici`+(qVA(03 zFhf`xm3IX9i$RMyT6(G3)f;=-Wvr6%#}pvlgi0gi@Y+o1-iwk$$VVqe0D%%1 zbL_5Q;zE!2ORcP|H2IQ0X&d}Jyui$!r!u&x%|32yM9mX1a=(N#45kjNOclTw{AX?dH!s_UQDA=zog^ka%nq`f ztjSAII;aD|u7RU;65URaOh0f>WPE1#$W!q2!q9gzNGxgs!$_|iVNQyiO)?TT;uB`2 zfxd11wG>P1^1-7%ktX9Lqctd)qj4)99)MSP0Z)pt+;S{56-_~+U_wT$qmcK;+#P%q zzivqdFP@)OAE;B zg-bF>1K}J@!=yf+kc#T|$^7gTI+%=l0Y`ll)YXB}yidh%mJlXhwmhOd{b|vOE;8fn zPvPs2Hn8*MjFqe5>rBj&x-3Eq)I+VsO#x(I7k4&!4_nPd6q4Utb~{ESO}u_DmtHO_ z^nqB$c$M>7_H;0A6Jbi$3}Q))Cw6|i;UqXvvgVIre*ybsPT%HZI9U}u_UrBI>tUT0 z^|!hgyUg|YEmxSBh`AIzJkoU+44imaVhrPROD$@1aIp;&=iX#B|M9+Y1Iy9e8ucb$ z;n&N&a*&dfeCK+FDdJn@f+DDQ0uyWE9bFuX$dT!Nbo0W2s5f-i#1ZrARWv;1{!&Zu znks3^7bs7jxo!g)mrI^*FG}`cdF;fwCxN>xe1co!hvw0P?N<|@PYRY6pQh7;B`Q5S z#``~)RSin$_Rz`4M(rqmT{+-}Owto4y9{s6t}q_Aj2A+e6q4`Me?%mc>In&`2%U!C|I$3{;m4*3jDuM;C<6vY+O8iImG(X6>i7hw#suq zSHgcjui=Haxy%R79qB6{_XDTR9{PD-&2&?cBf*@RKSmQ~SN)jmWZ@M`-Sg^s&@4sU znPb|xiY32;4tnvB&1C(zx*BkT+@I+(#-Grqkd%`l;4c&yp+jdqQQ#VjT2(jWdp=50 zC#0`%3Ib{tR;WoDYV-K+Rmz-c(-Jbi+w}JJv5fQ2_EXS+(&MQ6Vt9s*cOyGLGHJuU z)Kr!g?c1*&UgzyHzX>Zd7D1O*$$>0LZ$?2!q7vSL^+TK_In$7Zjz%4%aNsJ7gq^cU zf$FpR1tZFXjv{y*_KV(#^@PY~rlc_NBfl%-gl@7s#*N;dE`*r*jg0PhUz4|2g(1*( z@COn4Eo-$N#W)VeCld&7+qhD4ucC9D>tw3b`5vECXAt@7Z(o@Td;*{C|ar1Z?|{W&&Z#eD8sYTbChURXo}<~(KU`?JJ~>g>Zobh~IS<7v-DroXexqs4(Q6rCg58&ebGhbG~BHysTgQ0zBse7^^IpN8&Gv|XcvrW!v$Hsx>p ziyPNHa-nIbYt!DmynD+cm%&v}CE;FhtO-1y`cn4y(HYUwjyr_!Zv-$g7Vnu5Ja(qh!|#GhKs zH#gp>zQLi8aLJWz9BM#)S_b-p)P?y#>WH6#?d;;uA|$`--NSI>zbd|sr;GGXDMg^o zYKn@U(f^=TM?5B)L$kSCg&%kr6iHDm0|3zXeiVg35A);Wi2n1z)?7W^6V1)d{JQWL zl1`;7B~%I|^;z(GnsLVKB(i9;w`>Ro#wxF;o;0nFfWVroKm`oAMB(mlD-Tbeu)bMc zZj!a5*l74POwL~r^dX)e^c`TIFX^Ob8wlGjwh3HDU+k-L4+xO&GmfjwyFjsew{7df zn)1>|aP;bG>yF5^hHPkE+3QRbV@FfaQTh<)uKq;sm^^$k3r{h|Z8mt6SvF7T2OA)` zLVlhvJmxi23PfOQ_tJdTKK}4M)3O%6NVRrCXgwnTt5L5u4o+^~4CdX+;L&87F*#@y!|nUOec#o)s@q1or^LR)`KdP zYc-hVJBwvau`5#;XuW0+G3)Os@8xy~*02>8O2{LJKQaHY;*kHnZuNHsdeq}0cJh0} zj+{Zq^fE0o`@GewnnLop`*FXt4L)vFP_$K#NfY^X=S2N7kBf>X3nYn*w3jqXirnNf&N(vE zf*UO%(jdJXD8f1sy#mdFJld|B15Ia0s*> zHELCbg|wCl&+H}xj=^U@$gy}qDvpo0&aihk1nn^HEJ!m1&4Bt%htm;vk>lj;)V^qUi?OBWsB~u$^zB93tm}n1DNvjc zlQe94jVl2R7ZZ!Qf+hZq!NEjRBzw*+Oef;QrP;MaN$*A<`4F`KyTg&ki#iie$LK<4 z<5!M58_sI|Q8~^Q$lfE^oNR}+25c|?vagruR-)B-h(dWLZ{M9Y&lI}pPHgZW8%FpeK7_}> zYsIY(K>fUBv)6IZePCZd)zmO$QKxPxF zNOf`0NqTTgdC+Up7^V-iE4dsMW%sS-jKXYYn&Y@kJlKJ8N$Lo5Qg2aQo&taL&lm^8;^6jTiC1RojKIGVw`M`Xswr|BcGlBzKI&!;Lq#WJ_f=a)x=u@s)95*LqDS`w5`U*nTG_tv+;trhPF8H9C zr>Xriu4x;y$PDQ=2KtbR-SdOWM}JIO{`PMk$V4kDd14Si1AVk7B#Ia+@ z1!A!syHw*fDCEqpA}MnVgs^w%&IM;v-8I)03LP{-@d650oh}8bND|D^#sCBPdr^`0 zXgTwmN)Jakb@3?DTU~JD$Tq;aHg7(_aIZ{wP?0{uw%(jZ-L`s(U;RlW1#iCVWb<@$ zf$PCh*iCgh%5Z|Y60OzppklxP9WRX6e>&gQEl#qVM-4$!EGiPq$B4Txugo56J3fA3A3GdMuO8Z^rl1;@)GSho5Pm4m|Qnpz{jwN+<14OekzLmo2Y)BA?G@pGjyv zR*Hv+%xXwF_4nQJK}#e0p})OOCdyU8X6bKc-4aso%1IK%F z+t%)&#LhtTh~(;lLt*R8#X2RF1CHHCsk&9_bC9O3RFB_j*euQUs4+Zv;vM<`l>XrHEPsj63LIdd;Qg|M~MX~oSe?e;&&%ZNd;Ab)5t9D|W ziXdq9*Usr|FD9rC|GfPNtCZrKy^BXM-k;jvKW;x!zs7zKg7T?zE)~4KcG#cR; zA+)eFP~%TDjr?i;V>j#l?95e&hdRqYZtLRp!_Hk$l?A#r1R(^PE->KoY1ke1!}61r z*28wD*Rk{OGPua58)N$S0KNAAM|+z!7YKUtxDA9=y}DIu+&Av9Sw9$7HWu6HI!n93 zJ9HjF!JqHRp$bbc_*7|RGG~Mo0rPX@kN0^Cng%Z&U%@ja&(lz#`@t-~v*42HJ5Q*< zl>(?`4$77I=Wthpv&A}E$>$A$ToR4Dux$PcVv*qK(%YO4^j;j1N*Wo07W4-Ht2jC=+nC0MgfSn;GlWX7m4Rm41D8!Tc}7~IptV?XR; z-FqmGPJ^9CEL9I3XB^~ae~VqHMK$icPX z{oVzM)8f{3@2a(Xp&?$yMZLN_MB9&?SLC-*6^3F!87_x5?z8$N} zvJqf+5z6tL2>%=0deVz2|6uSCPO(nY;c$|2Mf(U!r1TW36*MU4*v+hkqy4)_%#o^C)wm>cMFl2;#N#n0bE01w4I- zcw9Vs8p?N1F*XH%Qlaqhpy=ssW{7)o(G*qTz{U+V$k(_@VaW z?3tNV61UZ6*^c&A+BcmO@|4Llt zOUqio{(U)-XW@VJPtx}9vtRhu`hQC}!1pzmUck<(U&wQjDu1?E%>Y&$3-TRd!2B@w z^~vA7i=oRoPX^C!zOfcBl;OEQ5L*fG*UXB}e(`7a1A&uB^i@&_!IDzVAl2%DsgY67 zcM466he4{AwW;$dHjl~s0|Yso6g5NVJ5hkItnGVDmX()-nbrp~HDsj@NXOqI<2oEJTKny(k>JL!ao3~gxIG^7{3XviBWC1VE zA&`A9Y`WV2-aU=Un!+Nbe<~!>Ry`H)DL`}lt`l<4_{dy59B61rX$-jpk0KE~IHL_R zyUbFVkx)e;7!xK6w5UC**AAl$#Mj9M%-QnR7UIwDJ9`aMy083B@ds(USX@BLuME#& z&QE;hlcm+YC!Al`SEnfI_EoZ;OypQ?Z17Rp_Y`I3bT0UZd&@VlU*hq>w!I`?vFJ~hSne4L zP2#Gfs^}mOoLsM_K*tjcJSW$VKfP8y8F}5M>sAs>D%lC3#WI6oTM;RwZeL2*xqfp{ zHGJvC&F_cbcc)~i4(}*g)tY))6h3R%&pTO}5jU_?a+;)tsx+TWPJ8zZZ+w`as-+FnmOq** z83{Wp&%vBIz3Yj`iJFY>5O+*k4r&qe+++L4hWDFp{N};=^&s|+i9{l>J4|Ki<7Lq} z)P&3#JZmQT>ye87SO2rxIfOD-4*a&C(y2BX-A;*;;&eDth`#f#&{^t7Yll_i?iH3h z!`ASZZJR!=%S1XzBOfJp3|gQt(xAtSw_og88Atx1Jl&Czi+BF zx&#x_iXF2_23)rbl0Vpw*Oaa+{|F3&_p`-HFa$SWaK42(N2AXA82Je#_(=>vQ}4mx zpbVo#pP@wcR?Egc6%`j^>tJ$@b&s)mF#|O?^n#~;!P$%b98(Xpv)idyXQuQko(KB< z({I-jQG_!JZ8HD(+ z;r=e3x8G%qx%7Mg_3>YuBS3L*UXG%zsiJHnt57`ObDBC9Q!cx3vJn!MP@6R{Jvd#> z-Q;`Phws1l&7sEk?=H9s1h{IQJYZ_em>J+kI{tgrCjFrGy`F7tPg>+=;E`@DbnfPct;D;cBGFihxN2kEuyy&EEGm<#`z2iY z&fJn8>Stj2nD1;pj%bEAOm&2PyZ9$`aAs5NF8YS0Q!x(%I|x?4^p)2KOZ|l_9r@!-?LPUjTfiAA zu2NVd`QhN0O2B}>FKbrl@6V;!1OB9v}avpG8K5Ms;WSvgGz8sUN2hU%<0vjCs>Sm&MFL(Em;;nJw17wwo~FyiT`!S z3AbmgGsMME^4!H--@CP&88gSkLF4VyqdrHvF*c*qK#4TkYu*raLj=xpC zs<0xa)Z&ZEpER@L<@r0dcCnMP*lG4xi^`O;kuu4IF>XbLK`qIzsgrM!^rPN2ks&-2$Z(!&c>?MwANs%##gqR(qE z>Q&0e&B-!zHW@e6?;op9Wcqrm`3lsc_?tif7UQu`JDMp|^hODpnB4l>jY4KdR*!*> zqwD)0FQtY3$-DTuz!$RR?pvI=GnLm>O$EWQ$R*IE72WRbcl=ioz4D{}dhS}d1@z_V z1$NBtl`3iHf#6If=ckh-eL=1qRpvdoSBw}Y8AX-QE=zq zgJC-zJ=b@J=-I?rB36l7xdE&KQxxV{uMo0+%kj$EPLK*&=6g^L=}bum6T{n+m#Iul z2PGZg68axi^qCTZpmmiO3*#xp;9wdpKWN#7(jqg_)Iw+!a*(atWrnF*GWnCAhsRY| z$8@vG4?0pCB3>tne+jk414B3>$$a9Q%1n`PX6?KUKPB029^SZ(cH2ZpcE^&jwK4zC z>|4s^my!)ZwBLgPin>uZMUMDf!QV;){zj4m*QliU7fVu5iX`R1V)7M+ywwH^47nkD z;FoJsS!p?Ep301uV`FXYO^{k?hu}ar)SjV-ko6L?3bW7WZ;EI}V%-kzTvY5P<8H_44vNkq4YF;~OQ!+9BJQ%*w*W|WYX?#9T zi+AzNx!;kZ+o|{_vaxKF?Al59&1GxnlP5EGVCIt*>+KZOcI)i|s_W}`z2M|e?sB8% z738UiwcDOSiAMpXUXH_#LF+p2 ze|Oh^%=;b2&9HpO-ppv`??6D<@tnAdDi#CHiVeIEwncyECNln>YRU4weUB8lF+Lb7 zce=ClEo`P9YNHg4oY~t6IQctxqBo_NCHo&O2u9diUI}ipKbTvBAS@PVfc)48ye)KV zBd-I~DPPk1tGAUcI+k4Re63nMp^s@Vf1)%tbE9A|j?x~Gk|rb%`13##F%oPh&i@;X==R9*?3 z1NL7ZYt}Hh0*_VI!guzlAIG-TyfL=ZzN&xQ(a%+sI2cQ02h5Z;;J>TyPQAD{_JM*V z%17Z4lV6zOPH1|#!Nu|hB2FAS=P0^ZV^IYun-(+21LD>}k}!?;tDgF6RH}@@jd~*$ zXX#kF_rN|;^+eq2;Nz3)4_RaRX0O4Z-#rPQO`j=`k5x}hRKqv$Jg~O$|K%T#SYCdA zd}^K*nmRrjm?%p+nt%OwZvzCC{i*0F@&7~NewBL~jVxSQp6tHcD=;F&7U63!ww*I+ z!u0`^Q?%ElbpDWddR9ZHe2mN=6a}|K;2f6@!?=31%#iq~b>%@$#>0UIK5+mXnx%Fl z2G0_ecr_>Ge7F>`MMW^)>@^STX5pwY@^(?fvP{I|PG0AxJSsYUYV}zt4iww9M-v;0 zL`u)`=1W6&lDm}BoY{H#SGF6NJqH3)9go`ED5zV!vM}idqJ77x! ze3C+8Z>1|oyVeMxocw}*+@3^WA|wQhD?KLn?X&rH&Bn%s&vxX+x#AC-vk9rv1@Y?0 z^a4E2)7_mlu%fD&it!-wk?);{$KBnpDvLsARDFNBr;<8{TqL+~M$=@bl!~b>Ex41B zqD|?`b*H|C?|u#aTTAw4wQgQYHYtP}Y#gWB%{B$Kh^&rZyzQNoer>bfiF?N}GOO75 zd2QXbOZuUHX!KEviS_uz_Z5D#wGFWcq#o`sVN-hI$D5#5{z7>?D4|5%#}2f&BYs|u z9RBgK95eYKz^kU_!4+2O%*HH-&z*)q>b06tE(hAdqk+?t-Ig6dkR)@L102Q-Nt zwlKxxMwWi-4TjYFe$c41YdQo*jGgiV|M$Jc;oDEb{TwlUTOkTk;z9q5X8daK2A2Fs)HE?KnZ{nj(Sf#-CkUz zp*avO#{mU3XB)eDcrOPh}38>>h-qxvyAFz=L6#%Tc1|V;fwZtHy=n=Bx*qQF3Q? zn=Yj9}o({!vT5gF-l)aB`tSjB4tg`8T@bI=b z>r6KG*Um|>X2JqUW&XR@qpJ97pxkm?Q*+=Y+0=g5P33+JV|$J-btY~Tlyq(Yp7RBN zq3>*B-JPH%VrN8*+7K^A>~xphoI^$BK1(?6cX6cEf+yLF#l~l3MqnK5f9(Dw;BtBA7twbp81PKHPqB-!%B-Lwv;uN zuIFSX78RBFx2)-33HBIqFgq{c!_{L>mUtF)R9%)1>d7zuxi%akemiAY%iw3=1ubS$ zCJ)h{YGA?4Gmb7I@vc3^9DhakURav~TZi-nJR(~hs`%)up_A$ey~)?q0B@U|lUxQb zFQ~F%?db-#Hg`EvW}4A$A&&{U+erl;?9NXzU(4tUqJbBS?nmYm15KEjn{F3{H$LiLs-R4L$TCeW;4Io&rjPtpmWiU&N1yIkVTxL}Rz(rAoH6DNwt4O7% z&>5D&2^-Ojg6XO6$PsH?mx;VQ_%<@eL%y#uO&BX_YP_PV9uPtcoh>%nnbQ+r z`~CYfTd}Bk!N`JpMbWY`)ljb@PgIUi7VE&(TeNqU<@|esmtRnFU<7k8^Mskb_!)+y z+5L-0??m;GOX^oItat6KDm8z-^q^i-X#4N;@Xbd8s*AKaoky;2_x-Ny$6RXre?+}` zJd|zR{(o5t5tG~*`x2pKDcP4O+mtkF>`Rhl8T-D+kgUbvmMt`@VaPIM-)a;YAv4*x zTT{lG?A!0`d0x-&```UncdzEU&g=XfpW}ERrvhM7AekEsTQ=GJy1-6T&SP?y729%P znI4-0@4tLl>(OzBc9W`si9dS*zKVv|jv|`Awmv2@#jP02$U8(Xl{xJ6yrgO@_#WKi zgWey91ZT@E>JFz1k9y@4gd2ZMYe z%=1W3*_sYG!aeb(`rQv!`R|3(_HjYFzuNmb-3FCi{{SKaA;W0tB&2HaE3IH}U^IAt zfGdjCD@A_V^8hk0F5c|a?lik=Nv&M3~gb5)4LGSSX zq&hV|%3a`6j>dhd36F@F;OhMB-_!6CYxFh_7J}rC@ezE2e1%QS_+aUt%+@L0&mQv= zN(u0Jq24}!UJ7gA!?UehIaN>2M8z55@%*U<`8h8XNvA=}So?Yzp2v2(b3H=msnZC) z`pu8^kmocA&Rk?>9ejwpkyhGL;Nz)Y===#Oe7)v1HudN;hEsms2~VI8F=sKNknSJ0 z$l14P+H2oWM{QU{S^XF1XCAC~K|l4j&$vk%udo+i8^e(}1Y>r(X-BPW-W?q(+>vz_ ztjc}itzsBUHa_z;Kt^F+5Jq4^{Cc2m*rP&)>j5C_45a!_oAsfc4QxNJvdLgW&kUxQD8|lVMWu_^1A=k!yh9e2Xvi% z72rd;4kf8PqIF$S;RtS-$qRc+k9y6#Wj@wj5$fI5zr?!}=Fp(C?34d3@D|?5+qUpc zkOBl2v}_EJW~*0wRlm+;@vUfYo33gNkHhFgb_|T1GrSA@gz|qm)^_1Pi-}iPBUhmD z-wBP*Hmn16W;QlinP#C%g2x;a5|BI>2(b!+9LH-$zNksrggl(PiR398##8BAVG+Q& z^F>_>17dt8LrN)$#b5+qD0Fw913f%)lH(&IuDCzIC4cc_@!k`l+{CR;!=sL#gZDK|z&yu&|^A zs~&9}2$?xGE@h3ED4e?o^S+XrrUHG^>zeZYeoi+p*dTHS9$w0~a+h(jcJKTbTsSUC z{sRTZ4xsE<9KT06*$sn?1IMf?xn|s2Ly(h6$I`*(H3nEMedSowHT%O{`_+sxYCXY8 zulwv0IlJtpt6f%R6?r{;CZs?_8a=CoOYXNQo4-H&DezJ*&6(J6Ip-!VBgDMENBl%u zP-yXc)6;rAZ?8l%L+zuUiBvlauAxUn!7*$^6GN39YS5h zT)Nc^+2U^13wv$PHU+nP9&dXRaNClL{-CJ&mKK}0ZRp6dy8Z^G-SWh!*Ps=LCz)hs*H?@S(D z6R9rnq$_!v;W$`6>y;}uX=oJgeQ}|5pG|z!{yeDo0iJ2UM(3#4D~e{S3F?H*o~khZ zm^;H|dYyU%#GX}UouD!K5cl=G(_`ePX7Q8Frr-W&#`jv<(P_xy6a2S0=u5zLkA+=e zKK+?np#EJTMT7*+5N9?AjnQwjEr1%;p5dys6#ivoWUOH_(6d(Nv)eNh!R@R z4fL2dp4JkUS@SQaUSm%l@A~WyGM8`zlcE3gCvOZ@pQlnO560xqB!QZ|W33|bjuT4L zwxjeoU4@)QA95M~M3=%Xaqo&uf?1;I+Xa5MO_tckw@8t}G1q;qjQ&7|};BO7Z6r($4tSeKxosrynvl6MZta=Kj>tFEgfsnt`EBp_#168H$OGuXhIwHZ}sW_?-$62%1nEP4Vw zD%MmJLJHC>q<|JuaJO2Z!Cv-E$-o=eT!~cLy1pG%7hY#_Lpp$yQRg<|v>wi^Y#y%z zf8=za;6{8q8VC|_J-%7aVe9k5`JX4n$6kE7@)eKzr1=11Ru8#>U{k$bOKsq?%@!MP zHkwA~`aUu06#DcV5@o=ZQyxa3|I|lXd0(q6vKrzuvh|Gir-n=OU*H#+3vZp{ za%f&%dtFdHU0%cdS;J=rRQ60A9&8mJ6w!9b4P)PWCi7&epHW|@a>k-=oY~G`gtnqm z|2onb+CMWv&Dt|y`-sz;a!CemI(M_rrs2O`^|(&Ph`fvRu_%4ec_DFdi%AZ!2kJza zlRoNQI0iZL{1c)JY&sv1wYG98iph$9?V-2%nL9j`*-N~M)**V{at~`A`U*6hrh%h|mVE!u|SP1Pb+QuQfmpoRj@s3bJJj;NUfrncB)6 z3@NySU@HRY@DO_?w1AWkw^U1{mvU}4N(S@j#TnRMi!SmErLkH%samt9sw3`dj2`RM zdRZeL-=n6IbF*5Oi3KRkNbu)h;ZJ3i46wVSN5^k6MMXrk;Lqzl0xgGtx7H~4gz4uN zxOGwik!wAioz z$y9p=pAmW)o63o)zX+f5arUkc<;Xu4W5ia(jJemZxtg8j>dJa5sGSGW2 zhG@TK-G-^j9B6vd3Km3|B!v|XApF9y}Q$S%0l_(5(M`35V3hlPkW&(f0-hLcA z3WY=f6@U`jU|XOO)mi}@b64sv59Qx1om+eFYUA`JTzhI_qK1U=Nd~l&+&Wycm3wUa ziY+GW;4m0{vGa@2)bsmhRfW^KW0LUrE`^&z z-_#zxAi*_+({k&;?#BIc{>@~fE=>9@9)JBhE!wB9wbedr*q8YGpdEfk<)q8aU||=J zFc+$t0_lf71H>z@%}myJygN)(jyG$xy?AP1t0l5cC7;|=eRHT_K%JvYwqF|O!>vJU zWc#7OT2~*`sUVm}Dp;8h0q(x^bGf-Tq<@J-5Vc34lB-2IV%p_@FDy zZZ55zjbVaxFXi_w)D2K>ldu#@qjaiJ%w0TwVcz#2!>5XM=vON&<^p9C-U`)cSRFY( zEqllj_I7|K*+^_@sK4BAj=IpIz<5385rf*x!1t!xQ@p%7ddX_6R>6B;Vgwy|S7`j4 ztTC`fEz@Sh8#@3Z@*{){mof}yl{yv6C3Z|#;``@f4R&(tpILc%+xH6AHWomkSBC}n zZu{{-qTFZ>*nNh;z@Xh0U3s?2!{RC?7dtzZsr}7*`_Oq*|JIZ$^u3TG8ho-}ZEq`z zUN2XmlLW;~2W##FxaAQj)opgo1%*CVh|g;EWIiWIkQVM>vT>N>7sgb1Yhi@{EOX)F zGJvRMtc0xW!9(7KnAA7Ezb>HV@UU&qG}5cpGGOG0u(sqX)lzf8i!$)0!W~CcRK7Jk5WJY@&Z!xZ zNui9UKDnIRzZ=^M(*srG9Lb6()Gp2fM1Q2+t5 zmRRS?FNwO~{MzrlCJ>ct%vp{RZE$ zXtEqZI`Qz{vkyKhRTF*J>IocOFg9_qIie5pOxMh9*OFRu#A{d3!5NGu;OWKExus&@ zRqiiryPbK(DaNeP(%F$G&L{3*&RDu6LsNzEWM*AW3(^+|(__)FrsKM^C!e^<{eI=`z_ zjmsL(=m}=NZ$1e{M|3Ml0Qz4}-w&F+=9iH%5%%%`XS=95o2k&XOpEul79Wvl&LO+A zp^ur|j}E<#HskzUxXNF(pI+>Y^DEr>LK6=8Gw}I=@6PXkUKz0lkloPgPKhku$Hv;+ z@aVD9ox$var(M%REMVbNVLP$wa5&QDK?F$U^T-xqhB|q-FZ$ zVGXd@0h5~+ja7Ji^^IU=`CR^gy11~(Wjv_9Gxvlr>e1G_q z6!zA+)8u1fpVZ`g({G#c#F<;mT*)<|l1fIxZ?Vqg^}Adq+~w{DS1|M#Sa-d0#i!($ zyylz+AIO>=P2-GGbfe}K)>wd%DpoQ`BRL^{xe@gw%z8vw0YzfazOl9Ll$7yMZJ8!h zbYq3V+$nK&?}C+;<;B4G54>`=3Zq|WVAz1iBYFCMtRKEKqMcu;^5uAMZiYBE^r+9{1C z_)n`(DKYy`zcMQ!5^sGam{f9ZVOC@$PkBc~5cUFTU)QfWmn+>?TH5sAUHGxN?4g|_ zSJSMVDt1bHO5~U_|LUi2{|PWb%g?kX=Tj0OPMzP+K~61rZNw$&eM^gpjMnUj0Cw3h zUi!fE2tTql;h)#Q$y=t8!4>hxsg-8sp6bEp#RLg*C+%@MVMK>1=%wZSChy7DUUX3? zMzO+U0c;m-fXZVccp6e=PMU_Xax?xc-&vYETw^?OBj@x<`wN*ySk%@s$xqcv6cGS&DE6s|&fakVlj(lfLm{lcGe91thNgPyv!pv6WLiSWLDNFGjJqJE>y%ZIS zMcJ3f>@n?+gbdd_l+g?7{nd47++C}JpfAr963wkjiN{%-tvH`tBW(CK{EleLOuzgv zpGevaJ&^6!I1?uLv3EF;n_C~*EjMjjOZa>DOJUCULzEGT#E-rW4GnBin&xlmIoZVG zG7FVB+A+uPwHKXk0$S26iaeq2*h=cPSM`=!+Q$+sgPn~aelrX=_mN?5)1lDTWK2Y z^S)1kqF{`B1yuQDgA!0x6e6!=;*@8S{@4B=?Bb{FAPZdU(uw5&OfB(BI#GecS|+Lb zu9<<4M5f1!c*V)OYS$N~nXZ*8a1I-p%)53PAJs*%N+ub+R(2}n27JYw(#0OV;K|KR zPSH9v9dvCYBx}SgbL%{t80KobW8V#zJ`YvTznC@hp!I!Yi-sMRsCSN0H2TlI zhtrQp;n$6XHv^Q@5?z+EH&JEnj71nT31;D_KZD&mYoFV8a#!TD{l4=~|4R34*1j~} zxBqJ(csWE^^>yZ-nCah{7ZD?2lk1ql#KnH5sh_y;gO!OeR!EV;m~PNhjTcd`H>gMw zSvLByZ>~8$!O>1_-RaAA7-NCzpFRm*&*^Y~#D1!S->;U*d+CBKbYF+9Z9A@C$M;uN z;n5%NTnuw27i1U*k9NU+cul3X5BMNd z-Mv`8rOCm%?G8>&peLVtX^W_Oc82oGE;OyGyl-0k+4n<#OBiY%DG7ZCwI7s@M78Z4 zf2kohF$Dy0w#i%RC(``{7bE=V zU11qrMxc*hA-mPs%0=kB!JlVk#QpKjQ6z=As>3&POMgjgCHq}}m5c*BEKcY}V@}`0 zu9>gWV5iB0iS2l1=9bUiY2?~cmQ?BwyC^c6R}oecJf_s_A4ElQL`|Wodc&IDiX+b&NZ%mb`I1f({JtW^W}- zw$6nCS6Q~Q$z?caSc^R}GjHAVx1*O=8vAqSoS*(FiQ}j3hm@VI^pS9VA7mDwW@9L& z(g+;Zj}r=ODF7L_2_;~*9UmV*pO~ju=kqX@+xiU7L((Y^ruPi)RI8kZv z`x8s7q~jMZMkl*5K*zKCW_G?qOD>%KyYyM&ME*tPpx5#alj8+)XF~LwpM`v3Oo*V` zLN2EU+)^+x1A7Z=+6)&{T?1j=|o1VkFYcKTd^CdqhENY zPg{HVcc+#;X|r)>h&Ed&KaFkuJ)0DZJQ_TktzIPHUOqi8ob$2K^Z4`|C1u^KE)0p~ z-th%iD-6-YS6NlPH(!pdQ9i8QI`)$euq3^eEu>?H@0Ejm{(CJj`~D-s5XRM)GJ%A= z@1A9S%>7gF+vL#)Cx?(9Wl1B8ylvZRVi=!_lsdAO^d@BIzv8KdU=@3i>Edv(U=e2Q zD;>NmdWM1POMO#Qwi}eREq}NgbuhVgbS~M+{Y%FpXX~x1#bA1XMRn8K>1of(GXeYi zGw<0dx~QkWY3{akfA(9h&7adaY~9g3u}a?B@$r3m`uEe&XHYwEwQVrY=MEpVByG(R z5GR7W^A0VymW2LzP7s*rk892|;`SS8g|wKc+0!DlTAR}|hy90ZyNsq=rltv>A5_=1 zPCECaP-5vxydw9LHN3McZ2>9y4qg|7#}fxY_Tl1Rt;pJa;ISFL@Z8+mceVOfZClyP za+<)veum881fCK*6o9tBssJd(%t5QH%Oo&UhZkP1APRFFXJ!)v<(_q}AnQa*rBd>k z5RZmxq4r}g$yV(8NZIUOQC7Y=M>yKci`Q9~4}AVPx<P`y=GU%z*tJ>QH5cWIVbU6e&j zq=SsU%&_&@>0l+fq`$R9o8Ra1{AhwSLD*jGW3y20!b&d>Z?H1;<&5ttBAUyX@Y%@m z7SK-V)l$ri$@=j4{5bEnDfh7~pELSCu;T$kfL^F7op`^&(iYUi^{?m0Zlt6%R)EW@ zl+&nEAMQpr1_nCmlg%1sZ!j0v3jblTDsV4JDi+6B)6~Vj;awX^tY(a?1vHC*xT~7| zDkM=+ag#xNd-L)2i!Xg%CSNh?DILk}3p$sP?a(laxZ{4UffYWIhX3x<_i4bkN66<) z``I#27KF{PHn<{iY5A{pgI7Qstlc<2JD%{-`bT6kJkRjz2_xx{rJnh2;jr?l6%`XE zPW2Px#An`4Dv;OYZ{MmDv5{@G%f4uqHMy~Wg4QolP$ug{ukg9d-bx>_uMh$v1M4Z=WpNXS?4uF-S%_U{4V*U>)KYHp!N6VP{b6fVOSRcf%sr|k! zDwV3e{~t@fAG3(U#g|=7lFSDngmWpexY12G^yiE;M|SW0BSIm~dGy9uofyW_1!wl; zv%ejctY+|6sD*T8uE77C@(ByeWshpLRE?G}=_y{sC!?%nBrSTK;mlwlY_YFAyX)7{ zq3g2N^shV&>59q(YQP`NlLYHr5C`K%U}$2xxPWe+#EkDiAh^g_MZvkwvglqIYiZG< zEslEe1w{JB_cm$o3``8-=x0gVzNFO;}GEI3jss)*D^R z!4eFGVh8m0t*l=CHXTdZANiO0oyOrAE+e`Xi>zY$$m6<}!^0$!LD01I1(tXJ_Q%0W z)~u;wR)lKVUZH4;wYyNfn3zQGrT+EOA#gRJSn9dnQ7I4+z5kpSdnss}k!qJ(T4C7r zoJqRu604q!q}X@*#+)jij>jWNmM=E3W(S8ar|6aD7vwOj`^ETLViKyZrKYJ#CI6wm z{S6(2w3h6P(^r3D^-l@@N{m}hsF97Xis$a+^Mkl__P=_ozUXtQYzfRUi}dG*uRUOZ z{82~6vJR0OnWo|Ua|J7EE<_5p`fOpG&0(bfHPTP#I>8jm>B4}xDBo`8pO`{tX0+|F zvcujF6^91joj_ zM_&K9jLtTUI9NQg4(PTb5QleH_J4SV=IOi_iY|fL)n8)Ddv>|N;#K|(yBvmi#w-3E zH8)W3s?+;eok_L_uq{59g90zwzdNLZK->RW>o+$sv_IdLet-2tOlT|CZ`3m93lBa6|A5qlc7O;mjR?6pG|QkDW92JzPxi zIqJy-Q0DV83@<+=$@b5w@}-ghP~`-~towCk>r@dQFJX*<8ISsr`yTrByun^FK5ZJj zzLSTn12Id!iIEXDkWXOhU++}N>njC%X~K8<)-w=+^f`lZv@%D?m~Lauu0Oys+_R(l z0F?0Pw~M%10kvml%gak41y7;_h9q21K%@xoDYn-sVQuN6WBLISxbjp3uxeaiw6>g2($ik_YxUg-g+texv02fp4ip4 zDJhH_L4D;iIRwTGbT0LLjN^6IU*~*kWTh|)BoPo2p7;G#PT$yF|5%&}5>&+2;59Lc z507f<>$B(8M45HBA9L;ZgliY})Bb)lRvr=7HKmMLi%f>h^mJAqb9tb>n63(TW7F%< zY+`z>Gfyj=#WIzEvyx=#Lh!qd3Q#%aNua~;m?3C+0|o+P2-kAI{Eu=tgbe{bTsIb$ zk>Bz;voGJgq@ zpxZH{k04uFUVW?9Ba6vOY-yf@{K3=8HhY*jYOHo+gk69++SbCeyIvF_Fo!SQ*;Q!+ zX)6_WXq*l_q}Bc5%ueHH(=`7y^hX>5AIj$&@-5PsEl~(kb=xk^EXIn?`d4qo^+$R- zMF^eJD4S;CheXYw?T@{chm$KuAS_IN@3+SU1@8?nO&8DnpFj2bH8LRt2cAm?2-^E* z6_fD&E$y~J=3VeVFrC_K-Lh*89+>2feA)fJR4AJo&D4Ht?RD;jzIE}$5_Vqsz*jp; zCP~e#UET_C(ffa6R@<#@Dmfaoo@$=tG=q|gd+`{5+?sS^jOa6jfg^T4#3~DHUb#A( zrIjiDi_1${&Lb;ZS~K(d+CRBM4+?5LFXg{7?okB{WTar9iyu9Y@`An98pQaVdAfC{ z-ODuNOqKS^qiORPO*0i_dPmI}RY{MAl)yPmpp?2= z>j2SzBA_1N8n_ppai5D;aK3lRrrCF=hg~PC!41ohJo`+HLE`vRpNg|1ck~3s8P%hi zhR@!^SkmNd->*Ie>mY%t{VePWhH|w}cB^vyP*8#tC)9Y@51y%7U`yIQZTby;gH$?xVc**n3sB(o19l7aPRDOslZ42$B+R=ln#uN9bLjO=~;c0O9>#rFkp-P;|}F zYEUF?k#szQN#iQ3c)R!aqwPB)V-sUx%R3_7KECe;XAXo}0B7xVv6I|zK*Y@A2v`s( zIq^J)G~bv+Upw)Par=;~V2>KL?G-*FgmV8#)w%qCcBrtegaXEBmU1TV^+R#UW5wfW zdc;L%zbXx#r&i-&(bz_96;MmvV}m*7S)fqnIFW6WA!f&Kb%gYubxpc#Nx5`lwQRoM zbToTwQ~eF;Ut27H(_krzfk&Osn`K|rxxO%{OZPd>xlkJUbM~5XI_nG=>9R8Yl#M~% zBnVa0!mQmTQQ34u$8VA_AHmC{2O!4B^KoE#(*C&%cJ1@zD_7j~{Z>l}AK>F1s!iHy z#si>|2k=M!$RkvjSJ2mj^GaQZ0AI`UMJd4}Lg&$ffeat>W1=!^%!o%3(N$wfRE(Uj z;X+tS=Ip7Kz(}~7Wf47`;qjzV@&&Xi|D_&`RJAOf>$o)#Q?)V~@ zc<%`(f9k<)&^!Yi-qonUUxSh^7hN=;0+CRa7Lvsdg68NkBOGbY$Ic?3ok85Jr&D@- zV&abV6|?~On{zTGD<*xc?h+>J0;d>ANW#JkmH!iT{sPEDK9qoKGx=x*adpu%UximA zOKU#P_Yp4GXrwSK#NE98_|Y?k7?M@YRnL3FL0SwFC2ON3(n#C(dYh$OcI%2p*p_Y6 z*oV`!JgwyOm_c5L(484Wk&*AJCAGhjyErrEG&Cuk&?zDXGbrBA(&fVqYE3$X8KC8J ze*a~JTg@d_?~>|UcYqK!+s6R8Ea_~E96s?A5U8{m_%4!UX3gGXP%`scXZ=^1jfPI> zVaU|FOwp#c3Q==$K=mr*l7Bh=?2gXSL|d;~*t@J>HjEHcLUh#c!oyi$1Fl#JGkkmG zHT~uP4=w)B9&9c*XF6&AH&_qPfV}*|mr;IkOD3*F#Z)cX7_h&L5e5Hn8_YqFRlyA$ z5s(8qLaZ_oZ|huTijozlKxAEwcbft53l|>3^{r)`JYL(Zu(Yo1xV~`1$&|RJlOKX4 z+CY^_vGmPBp7wv7b!9-wTv>)(hu@)UAbschR2^t!V#4d&q_!bpN3=;J0A~`9^n|L z(uyA`z=7d_NL-tow12&HS&%bD5xh^j3j8O>%+a2nrLLhfojb+Ad=f;tVlw2Dvd~EG zjX@glUHgDPFZ3R$YEAW0Ajmw^>09-Va_d}%>aDz3fVGGJVTqk_BUV$cBg=`USYML$ zi1^FF9xg_A^w7814jAkeHef;;r!G=!U+I!OVEx+F6pFP$(JPB?CV>K<4i$#=Danux22PJ*Nl7Tmj}4oP;=k34w?;b4vCj z^Y=;jRhpWK^_T5j?-}!}LRIoV{p8T&!G0bc+#F6`JnetH~PEHe6)PM*PTTtY7 zaav?QDQaU*T6Ip)!lwpDDiIh$vm%p0hpkwLogU& zbhdLvG!Ndsei93EFAQr8GfYDHUJ}fqEt8oSEIJir@%+$%C@dz8bQbrm!H?D@oo>bm zs5P&!L=zjH2D2A_!rKKR%;d(%HV2iHMYVWPd@xp*fH8aF%*Q<_!l7wei|&OBam0t% zV}UrPPpin>427lzuOn@F;+Vj&^7(-oXgq%*y15%c#$Xfu9vd~HOx*M5`EdT~IVX=Z z5&kLq#uW|;avE9{T)@4k6H1LY8G^g7Ynl!!rOquaq4j?5u=D;{kNewFLlaFb{`??a z;jAm;`4M8f9Ufn%7j0TB#A&w`>RT~={4*S19rlKl| z_6`nQj5__kh@92twcv4)Ke+(uB&I7P$t{j~hKOOB*{javyy~3>E3Pe0&esZ42CQ0?hM_8%T)>%)s@k9e>?{gse&jJ=Q@nr!HJj15E`73+ z!U2`K#_L+ND65*2wL9rwDNUJwqA!y^_>2cUYJcoUwxm90a8H@YZff3Ca!B&nn-zV; z@QT9u^cW+CoIO&OUAee?v8R!*^5m75RaEUC?F_0XFc3F0%WA=vX6Ua$r>TRX6>F!a zv68d442>$nY|D3Ve>aVIt|+Hjr7~hesOF0LSu(*ZRGm7GHVi!l5&RNf?HSonP3#J_ zY96yPr;6v*15c^Og8*tjv`?lpMW4+LIP=`viB(Kj@SumKV0>~k@)s^!8t3g$ueT-t zW*}?Hv^>^h4A`gJfn`fqZMBDLtU%+$H}vOt(pQKs}(UuOnz zJ9F1cslFenKol%X@HIwZE4;z--RH7OGDK%Pvogq?5IITw30%7Bnd6)CH zDyNv1L8$a^{kbttR`vIrs;slw#|d-@iiV7@>)us|PJDRmbFcg|$>cni&3g}CyVcv%Evt$Eh^bAj1>mU_oKi&zWWEI8~obwqp^w4f3u{k^D=p}r1b<0 z^xDP3jRao??BL;lx(_~-u=pISaII}S2DJy(KaHsGy4lSF1^-vFr~WAc!zAGf?w8sd zHN26VgE!=u@lK1`krTr$)&``u0L6G*F35NW!}%9idf(QbWSZ;23fII3zGj`>3h*j*8yzGGv^-!6%f^L&~ip-eO=V(yTmv$nv4l6M84xE89<1p&SD~JU=-wvbX8L z^gLD;Yr(<)k(6_J`sM#pR2-8}KIy8Rds}K>k)bfpGEae|cMj1;=PR+Km6{4=AAxOQjM*3P8zdG7cNVL?FWK zVVHm+Z#i#PbxMsj-Y5B!MfKFqoq#sica+(a`Vt}@q>EW#QUNJkK8c_XY zE5-9`n$C6y*U_-U;qZ?i=7N%=@Sv%a-zG1en#?XaSUFlglKG?MKQSNRJ;ptBATZd3 zSu)hw`PH`LaMUswkM!lu{{62|w3GVX2@42H4Z15eo8~{>{4=1x3*_UC=TpF3)_NTB z>S78}VcQZon5=&}9MGMEIk?m)vTade3pN+6TZaxind`iA(gZch3B$FH_bTr$!{+l4 zxRe3#6Y3*1cfVgt__n?;x;iKAmK*5jcZc;~@ry;5 z=VMq{(?qUJku^Qyv#6e&_8&klKCdrxvCPaHsQn~0nx3UQ+zH%k0AJ*@kCa0f!0LdD zB?K}8R#`ybDnrF57^j2iW8djY0^y`#S0BNI`Df@Dt5RK5E`mC)>L=`pL2+)6Lnv)_CX}!{S(Kg}EbKycp}N3b%I29JI@6YdVwX=T2DJ zYh4K|po%^IOO*Un{SR1;jiwyMJAOy*$E_|jF5JFu2gcbejLN#J@xEQ1@CS}(bX4q z1-GQqbk_cbaeQ{%4XWK^@$NHmi~6;qnaJl1-8Pq$d|#z4k)RUH)~@xAr8S4~56Nlj zB-9Q+;EJLJkqMPk`v3d`sp_)4Q|7k~{jW-ACqw5g{t|2KEvrp!hqxy1=Y*{O@UkqJc7_Ze?Xl%(i6 zP&*l!<(v<-4Tie7NF@TR%0^$AMVF|i-;bM8gE7EGu+!RBQ*#z~6IciE_AitJPIo^i zR#X7`CjAGKOC>%Oy;%PrN785`3mS$lsW(wzVx2sKSL7cfTY57cwYt7SR5=G{IX zGX2pkA#+qP=t_Xvo5$@3MpJ%z^bpPb9gl7Z1Aw%?@Ad@IWE0;}ttqnXpT06SF zC*RJ*8ZlHyA00*=aUK1V2h6z$J+^|cS*CwS{Qph$+i^k_Rl*YtCr8!CWLPSy=WF`4 z!vOC_xWWIaC=7DC*wALtdbZVYFN!T(3{(Ga|EYHsbU^FiBb4!<8d6%?2z9ms0reta zd1YoaBQ^22(RPG3B#j4^jC1QI#z(Df9JpeT*+X1OI=|q+K zx?{?OvxN{6)z_8Yh>gB| zKN-MD3swkOKpJiDa04_F5cB-y-&j*qLS&11Cy-0A7MLqm@W2h?*SCOX@C)>hpTEwn zU$w$wl{Fk-^6WMO1LMqtfpB}C``DRNA`yQEWP6hpg^Yey2VVb5x~}_e02I2)_2Nr@ z2dyZQ)Lk?_=U42S0+4d!dYgADtlCQ^lriKKBczLf4HVTxW8~KNiG2iDBvL0K+))r( zruS*IgDZufeB1tDP5cCEfUGdI$bsT7;HtVPhP zJ8CV@Ree)-lc0O~$5@P5p)aLPV4cm%kVnMAR4+XZyEN0T4#;!ZbXobsF2KO-Te`Tp z$pF3MJS9Ey`m4@P2UpAf@815y=@C&5^-C!$dqOW-bh<<(@7DFj)zDhE%i0=Eb34KY zs~WbR3uarbg+;CQ0~+F$TX$Ug{_nZ_AFtMFI`qDap|GoM`fzd1m$;wQwk=PmyG*VZ zYz?v)n$CX8@n`t%EDj4ENf&^^0zMTc5Nl$+CU+ekKW1YyzMgyq{GMJOH(zHc zne^gc^gTq`$!GU&Toxc%;=Z|M1|0j*@v`1lc3)J%I*3s>AN8&VC`n9L!Fp8vZ{O2j zC!JLm`a*8$twAMuUmTC*H#o4IKA)a^Fk7B98+<88owiV_9k!gT2u>Badr(kNFaG8b zK%h&55zG;8<(?sNg;eV5mNGFmCctr=^c{a*rbHhk%IJ-2;r*5x{3*P>^WLi51@$d0 zr#~XcddN;m3hHo9bE_YMSm#qxY`n#H7#0l9;_<$L4jm6&f?O2f4@b_+{|2RhFu=sL zs_RkL!hv`N>IyB!tQ32qX<{G#=cc{^86|LPNL)N9km5!98<-i#0 zU~ZNCCS}P5m=L$~M0lpx@*+9Q3NCfm+o{7^6cRpLc<_a{0Gw-meI^Ecs-g*on1oyB z8P%>nL!dp;ZATUb$;IC-X?yl6nh|%ry$vr^P9QCz>u>O#d|_)}{uTM&(-_Oy{`o`f z*{M96N}>dAu0WsD{}R0)MnwKN01lz)Qr_pM)tC93?ZY- z2Yuj&`BU2_Q*%_iZ!GAyQ==mXNBhe<=kZy$5zC42p#Nt}1-H2~dwzGGE*;mtbVh%N z8g<}6CD8$V5mS?TYPS4Z3|FVnEG$nsk-I0Z(B&BJi}jNPs{&L2(8ytNC=}5o>F>5y z0aRb2RIU_Z{E+6(qjX7E{`ewg_01@-E(w|1N`;0RD?>F*KK=#nQ*YAVnf1CUX3I1jE z!cP1L7>VfV&Cqiyj?b7vZX+0s?Np9{#_odndB;@iBfJ5%8D-h%zcqY*F7JxL=^l6q za7K=RjbOD(jwp^-IP?v5NC070uKN?U2BTvD&eHq2Q{!bK)jW> zW!?*Re3Ra)fBu4-@Ap;fR~z{nj=rCvmbI zOyr&A2aHfySjLPu+yIH>@eu@}$vOfPyfQ^|N`++G>U#{v=j3da?fg&o8VK;pW|t|M z%&87o4+orNZC&vrpGi9A?9pHiFp~XS4bv2Lw7-g*Eao*)u8doTFRgq1W9QbvPPiQA zUY?bErWwXeX8w_8H7T#nTg)V*y4;}TIgw%wFgk*U`u5k6g7GuHz`NwF?XX=S4|tN4 zfTg|1iZ5XNAAx}uI6YV^%YpTePye}Myy6*V-2z?`T;c-=t5gyRwG?i>+|o=lYp2}0 zU;jyE_d1W&VZM4QycFQ6BdgQYF+*#pM*p_CT~Pb_v|fBRU)FBzy7(Z{O;rt+f0nbu z>^gaC^PEMcr}(PEO*JN`dkV=q`_I#keia_Nf;HrbCnGigHp@Mocd!^E*k_;={{{V( zy53I~!`S<6E?J-5n>f`xGxVgpl$eFy`}*;+P|*kBXK}X#A=#Qm_o;1CCgCEfGFM&o zyistP24q+Qh=_z^h_#|8@-4t$&sA{1z|EuQ?>Y2yQrxp|?SUYWbI!j7*}Q|%k59-B zRGU!m>L`|bkHfe65BI_T(ZrdC{&GV1WOjYzUJwhk z{3}HJ803X!pH_9VliGkpZiw)*O$JJ>ykrW^s5p9>ul!*w|5I$-#yO;#gpdtL6z{f~ zo9xZJ)_#n&CVXl&{a4FZVi_G-;JK6hJYofIppdL5>3(i|w}D;0ZS%!7m7GO=BydlZ z;Wsh$J`Y=5t)0qOuXDfdoyXrOaanJmJZ!Ez_a0E*KqxXLKo6MyynKh7t$129vA8bN_yx!DYfk>OTh7TddaCqB+qx-{yUthmPs+@LnVPNZ&2zLB7GPm|ehP8gdQiGPY~vy2KQuh)XafZ%f&V#}_|^Y^WW{CC?v z*V4yK?Z4V(jMhezUk*`V1{d4iEtGjfWIFBT=SMq9);c#skJji>i#ysy-&A|!6n^ww zx~YDgOPBE~unoG}0VQhj?Q89Yykniv=f|aOWBtVT8Dit%A)n?~c0CCXBv0h{n9((Q z^h-%8OLJ{T$UNjwr0tN{HzCx|l}D^cI!8sEeb;mhda|%SVY-Dq%gEXRWU#h%sVOwB z80$~XRTs@qI6pZyzpUB|S-WdZ*~n!V?*17P*_~z)jIJup=n56(W~Hfz$ht9r49H>q z)KQ0DtfNTYG4J}UL>)D|ow;9Ag?IIj-tT<*yd&D6vMAskM5Ebz9ToietEdRY@lNhD zEvD{7UiYtm`on8nWLl!1X_Aj=%F?H)^dJqmK3u@5DPOaAR^;H5bgJM>@I?;H#j&lV z^+vwii%yVMEx|r==Lh+T`A4c>Nl>)>zN&E#etdNDVcrkt0s2NzVBnmDt^{fVgZ#7F z#dWy{6z_tv5>1+*_{hXa$e-tiJ=I@onyGX@F|1i^N3>#VNI(sh?&<_OEGnt>6eC<> zaq=vXn^|<9@y5Z5T}MG5*()p{ormj4mnHh$Vr5{K_`b3QZgfb18Kdb}*~$SMl>HPJ zBVpJV_R~R?WM44ls`76O~MI z>l~9ao!=6L|LkGS!~k^{I|z2*je*k(jWWIXsTF4{AUT5FKA<0(vLX@n1b?zV0Eg&) z_`BTs2<3T@?3iwAM^qEMK^d=^ZGqPSiBbsFg+-yanSWpr14) zwlclKUXlYr`}OAOJQ+d+5I(<|T;rp^H%R!@@C*^8#7WCj%u=ja_Iz-9>gFq-Jxx*n z3MW=IU>=F_&T+P=4}93`rU2iKY2LD!%>H8luu(zL5Vr?TV*BBh`pw?GzdV^}sC8v& zagl@NV4c>lApzctNzCNhyBweV8_|A7k4muE--|gld5j-RQofvChW3FAzO|vIFAk!6 zMwzS@1VEm-{N?C!2Qa6EO&tm7u}bpfon_Htd$M&Yv0=`*A*|p@LV#Tx&=(K(X*y#A znMN+SwR^8%oUoJAc>K|T&Q6PkeajbB9P_|L%2B_7>CSj;IX!B2=S%J%mSFa}BThd? zaAIz0K+$td(Hc0M*UrfZ+8!-XIfg}FQI8c2G?A+dAW66tv0@{V1}~zFl;mpmh_*VM zNx#Ms%?joDb-o!4y_6sA$Lu#DKTwxUz@*=b z_Eq8^4jArHPMxhj->_ONZ7rK^Hz|nGjZ!FI;D_j?0|xGdE@@f3n-V_)XI@qgQs3xv zL!Z5s2CeJ=E>pq)-`fl!;|%}*NV@KLs{a3f?Gd8LH7aE9?ONI5T9J8?xU%=&L?m&M zmHja;Ntev*nVlW7vR&ND&dB(^`~L7pf8deFd7pD$ujh<5pEsvkUeUU_`|~$2uXCc- z27HuBlMN(vx*J(yjf|ea0YWoZl8ga^Ve<2{A3t7-G7^G0le8OPzPl%tPg~)n`=JV` zLZduPwTm#6>J?ykplk55Z2+GaP5Js>bw!_rjMwT&RZ*)a8#i&sHGSA=8b{D5hb)Qp zI=F&BnnG^WT93{4-z#2oO&*@-Ifoy3f%=#FG}g9mFs9p@w(n(9iDQnp<>*Tmb)c4` zV^w8B=HfmPvJ#TAMT*>^`S4*Hp~XW)PnDB;i@H`MwHhV6-ob#hv=7F~FuvPn6zW6e zpKiK8#5T1NKx9~`a%$p5orj(3=6)=e(|yBSF28zMJ7}U1^jmr)vnHJ-@UZ1ADtnb` zaAR5;`E_%1^O3@V2xnxdQsI0_%gnf@%x#-U?re48ddZ#fP(3BNfxcQC#P*C8;xXO2 z9~;(1```S}O%TX+y>$Qjm7r=vRN?F9nT>1YkJkp8>SZn)b91Ls*UjxwTcm7ikvBE) z$LU6xmw|0+_o^s-hgn!XM{M2iW{>=4s)BG%m|W;JSZ70X@gq`N!WXVAatM^U*;}zK=V^l3Igy474He zqA7M1a(A(OnjYD#;f^E1%FfH`GYgrN*O%2zcdtOm7?If}bjpUlchyLlX}jq07U?vRQ00xO5QS2J1$Y#nNSP~6!)d^m^f@KyB(Ze)B_;d>CvkFfp1+ zKJLTe%+h^FzO?P?gCW?HR%`^F>N@XthBlrvd&E;WO#^?+UNtS+`%y&M1Bnr7%9bBP znB#J4tf3M*uJJ25OwRJ7hoO2r8#zzrm=W{%5g@>R;!F;PMcswzvWG@F>oTS?+uFoV zGz>2;@|kBpHwswk}PqhT8$Lv;ru8I6x%5#K2-*VC zZn?VH>BjxJciwFl6-}v8A|p`ex!#i~SX^9nY?nhFg2R)U5Dz5&Fv1(N)wS}rfR;>B^{B@AEnRROO?D-uIWrez1Uj!DV?2~5Vkge%*~`rV$FXbX zcpI7dr9WM?-RoTcU8h>Vb_PY#jDubS{(tQT7+;g!_7%^C%BT0=U$v(}mdC=#P~Q(_ zX7pd=sEI2^h$_CNSJS6W1Ys>8{wcX?8mdC34`*d{=iF>AD(#Z?K-~)R^~NRl-Ai1B zdbT%(hlVs{a*Ruik;)Vk*6EMDJFcsG1}5HIjufjuqfpuZpAG#J`AKdak zS2=?|E=q$I_l zmnElT@=w1%n(ie)7js<<4`Zs|f2`7{B~S!AxtWAcjRj_ceM1>$5AwJ(xT5uG2|Ia* z>Z(Q>#{(>;jNY^f=etHTov5>H-r zkB798MtaEIlhR3fZkAONKQ2rGj4AC3v8xTPXy3;sAY>yjS&Ju%<(vAyz!T8=!21NC>|rR<-~-?QVhX$^N4=FDs0l&EkMmHW zVu##NNeYMx1R%=4jJFigkez2T_h)R`yUL^&D}+z_4rkr(457HZTo` zXJP{Z&c8*?v3~s9-v6g@NA}1MR1lN2;u{AV%1E_Z!WUK5X+_6k?N>{eCuU}1f*-xz z-&T9R*?#c(`j9L9z>>A0W|}c-#+2WGS!?$um$c&Z1rS`ve>k^r>iRo2Xe~QY+WimX zVzbZ5fYn(?Uw)8Nqp6?W!0)#Bc}NF0-$4t197(0F*#GZ|anYUI&cR4hbNGLj-Y$2I*>mjm9-q$dAr{|Nc(9G~Qs)^eJfg_}i9WBG0qoB6(Xm zAn#v8m#k+_F@V+^4dTSqxvNGDltBn%Xt+$a6DCL##MfgN4qHaTXP^(#$fFcBx0y9K zvP+)FZw5}&;AFK7F~EzR0x~z#Mbb-nK&ln!95Iqt+P&!XEF1m=9T zvU@Qydt8?iuXY{&=Sohu8pnHEZDy`J(`KY`QwhyznDu<1-qtMLMVNT)g=8;ul9;h+ zMYP?GG3d1QZ__SrX0ejVq2pbfWbgN99nUE4ahO*}+ft@;n0f1b5GnT>iI-m04&L>> zhS4M^8)g3Q)6C$b6=GyCUd}$hrqmtNI(@?MIP2Xr>yP(Nd!_j2bq66H2~w45C{^2j zs6CiNF8UKM`Zsl$;tR@4fKESxp>Vr#^?umT#GxsfH95`4xG9K*ncSo-p6Q1EkEl(P z1rgKn|GH+DSz6wQCPm!nY#fWZce)vMy>z`4-g?b+lc->a5Q1$~n?|k^-?q8!N}Ke1 zsN}N?b7mfNcN9z5T{UT6$gLY##zIQ*Vw^Ky=u{~p-YmT*np$~eWJIL*oP_uwTA4FN zzse?_iwL55iwN2B!@;)3Y(x!hP~{q20mH&{Wpe9XWm5*ec!pejuR>rXWr99f@N&f= zVjK=Phk1mI`sw4xc~N4S@wg_i@IW9u&n2^uTMVfQ5zegZy$MVyy(~7+m?uwCKm}5p zf=tlP_o}sM<$PlqiXx}F^x@5tk`k(Wa>aEcwl!68AcP=}4jCW2lMpi=BXUn&AF586 z1V=Xl{j@V$euhACs2ZKRZP;7y3R+CDvD!>)O;5Wr2BGfa**PE(-_9P(@95pz%>trC zp((nhHM2Q5*aIfXfTvD&cIr&I@X}VpIL{H-6Sxts&Z&;Kcubwd+98%g9~vD-&A!Z0 zX1?`KGDd_r)|!@86#rXqj>@U`eu#U9T%Bykos3)84wF_ax8Fi5|E|cNw64v`2WKn2 zdtA~rakJ**BV{LG4Qo?kG2h#jJJ73x&q8&`OvZ@reJ*k%u3y8;=+n(*$o;@*>N+j@ z@@!L3^L2d-_UVn&}uxBXvLZ_?*^jP z7$n|hB^MMFXmh5(19?#i1tD#Z1x;2pqOHF)KI_j9J2xAuCd8s}L3tUY^tIET%-F-| zyRv>7j?$<%{ow41&00w2FSR`9TVrP6MH0Vyb$VqMF@(cCu`|=WqG9$7M2oHujS&Ow;HD=buSfi}=jzKTnll5il9P}9|(~g&v z>HL&cix+3)G8bGW8Uw+T7S(<8MNfz=iCp8$oiGka(16VsHAVcoV?0x)Ffn#jMfLt$ zXU`Pb4H35^l|ETwAN@GICzMN-#@0*5fJg95Kxmbjy?VVu^TtCRjR@7(1RbBmC62$I zE4tb9svCZ?pZ0sVq4(t1Y|l#7aST^57bJtO(=zKFK}8Z5)MSM3B5Rir zKfZ2rA};h&0KJ@MzQlGv+Mb#MG81~DZNj5K4yd*8@=E~VTozhj|_^vVc zb8G1E$jB{*CHJO^A)-eyz0f{S{`rb2=bS^~xWNrk8V1+e>6|w(<0q4ogzpCIn2w3r zYWT|y%D(MLppD*kg>8P#3~IGD6D5sQwqng5_Uh~w&dJJ~=w?e!CiQ%0r_c zBEN$Bl?If-E2hKIkOB+AQMxzr`}?o=OLnndf%AS1)MY+0#=PX+S65d)yaMQT3lA-oZmo}|bBB$r z4o-&H6DQI9<8U-g&QSgHYrRdeS0k;svrq`)y?iW0@vZmmM|uj!i$Rz;{(_~F!@JWQ z_I8>)kIUU&j2hwACcO#&!-L+=XgdC^2bIzPp=Gf>Be=xj_yr@ibSt`m_Fz!Z?Tvbg ztfeMo%873C`E~`>ngw--n4ZFuGD9db-ZSRsMa|K~$;P3@i|KCyfg(rn4;s6)%_(XK?VM ztqm4e?>>JoW@T%O2jj~N7&Y2AJqXN?nHgZdrLG!xFy>8zqYDlB(LinJyQtnOA};;~ zT5eoUMuMgljbmFnd9rO-G%Nf|EIp{`-piG}ehp}!MXXM?6q7JGJ5_P6efOA|DT+QB z^1J?M$-GhJImxNIu56mFrdk)&Y9Pp^DO7>86U}__0#DVrQi@sx#Wr(X$o;{%+<<(( z8r1p)9u!kA__i991)Lu_)ND6bm{&?M_i3VT07GO}7HaJs=-E`FCY+p{tnscP!|6(9 z3(%hT_VvN&+wOmJ^6m2^j}EEX4$d>@%_z0E?}F)?PEhJ&jKN=^*`s`GG$p-Vws_f_JNfET1{1%y(hewzU1hmjwhD@Bchb>r(bB@-S7feb^{I=t6$sN zCDRVHZ$NOqL76z4ve=8dFCDs`iZ09%T!SyeW9?ov%9s$FW~IvXY34q z&~@ILma~p9Q@aF#xuq|daUwTU&EMyFt9S$oN)FoF+sEI}IMpR5ueeC^q1g!#T>s@T zAiG%D3SA`a&5WTj!*-uM*VIR~&e{@V8!ZGiP0E0r+G%Qk!BA$Hh$}*)$C}m&sm*rP zlO*!H30Mi^m061?02-vpGr6tJ=I}v?q21^0Zo29 z4AZ^M!bqRl)r*Tryn@&bc*hPoExL6hNbc6VF13OGYy0+E+8dU<_V!y^x{&WvK9Go) zrWJ-%_heG}JwFwGsmwe6$y;^r##m1Uwev7ZOB|);Mc*l7xzi(CbtZ^Se-SL`_Zc=n zeC15XyxwiDMU!WEw&{;A12Sh{_a^N@CV=3losWe{iHS*c6MzE`(K_uQ4km(dt8i${ zl+!RMtPGIMkoDa$lu6M=gX<@TTDzwZ7Gi7rrQW5lUTec^(!FGI+p94?BHeZ4pb?I? znFm8aMTVD!6v}s*q{o0KW%yi4SXx2?Ce9pVP5n|ON(HqxjiW@1W4r2ek$tGrC)=vk zQgLi7z`yx?HCP|pVOX#<(g>{e66F@}Odw5Y7c8l)coYTC3 zN+C54Wbz_-j0Qncg&_|e--Zp^;^WMRgrF(+=}S9;*YR--)G90(=|YZ1j+X+_9MP_1 zj+)BPC_3M{noBBNZ55?2>r;D-7&ndZC#2|fT`>?RyUNb&wzrKtAnF${f8S{t{Vh{i zu}AmdgAp$P2+{trBlBQkmV{?*tk zbz&0ZK(k*-jiP6Ii4TK*o|3T=s`$Cd(wH2^@XZNP(5%I?s#^!(hNYH5PZrakeVX=B zE8l8jk-J{i3jeb`sxhS8n=}{bWa1QVg-kEtiwU2e$C{K`O`nHWq?2u#jXvd5C(YV? zO_?8X(EKRzYVgs+ixsQWv;5DZJfab}t1snZOO{N2yK|mDzm&ZeL&}1+MgC#l(8jIR zGn+C%IQaw?ac?TS%vMW-o;`}1Sqcc;NhlvjbnVW0e;8NpC8v`DsXpY zd|)X9c8AhzjLeK5ray#He}S9T%S)Nag>GugDD%|E9q%?q^568;e3r=w4ZnZ=(&w#Khpv?b}R+Fynex zvEDB3_(E8&W?z-4YE|3=w9CA@GCGaQ=k4S5CS#28jYyU_w$4C*D+kxFZ>z4o4ZKIq zf6gX1!>RDz?%uZiRQ%;}4|y_-KIZ`&IqNNXJi4BO`-WP_s`>@`mGoL@_133lhK9_K z^l2Y~HPCJeTxp+u zy*p#Au^Jw=kfu%t6fv(>f?Fx3o&W64C0g(5jP^{{Cy6zwW)pApt(pA>f3a>$dNxI4 zElNm2bx&BeJ{6&2;CJ(eG%9GirhGAoN)a5Dt^1!S`4H$h6n6OQ@a0P1)#<;!2F%jM zjQ>Jdv*{8Qr2h0CV$<}KGq&D^69@j#Xrkssk*^yEfCB{PRZ<$YIv>uWgk%HpEK0VN z^$7<|BuhW`Mp~8F>-_%`ezZIvszUZ96>ta)RdJP;fXxGZbPKzjht(M~F*AOgc6!Ro z5&bSN&&yIMWkh%+tLCNy?W$wQg-eWperzQ-m$y}%5ka-^4FHOKAeAnWKa{jLmZ;6T} zZTq*N5DLYG|FT3NNjdQxIRd=Kv7{6XYunm)7`$DBu_L+h6v~elo*nP8*M&qlHD0W* zC0_UcU0R}GW3|1-`Eb+~YV#;hdO{sP_$*CDR+5tQ`Rxq+(f6{`2@(CzqN#2oHH5=I zML7rVal>4e+U`ob>q9XLND93F5aqqR>a`Ly&G2RK;#I6w$T+owH@fwG-tk8)qfKt!ZLyoF+b}S5K4{Tyw0N8T3-+{TmOC8|f-}KYq@pt9rDF{M zWox8?01<*en=Jd<(GkIV5UM~5h7l5yS71VP?W>q!>exHoU%MnA*wzkmvJp)gxTRlZ z_~F}!JX~X*3C4J`Oq?bV{KLpOfmp>5;mO)SU8890a~*rKkmezN0?FDmX9`XD{)qn zNrPLM)lc*B3jWd7iocpBci}D*aXD|BIOmNVBYcdJ$_rjmbDV`cvZ~I^wb;l-lvnKv z1?^6Cu{ytaI(>~b*%&O>I0#ldufjqs9Fi@A5S6;GR&L$So(}orS3jf6=^dQxzG&Vv zSY0fs-cz;K5c|HCJW{cz3I}Yie|1K+7?5uf1Ke>h9UR5RBTfHoycnq+d#&`G0{>Ls zHu%T-&!6|hE)wtm{e8RrNUPmvRQ@EO;sJyL;uMvG2TA^oy0=RAA+ayIT8J5s^uQ5! z-r6ilakDLCIW~K+M6}Uq7&~EPO|2#Z+4Kq=5k_?xRP__xXJ&kF%%=nmeEI>LSA*?h zJC=cWm4+X-4Vx<46&mwd8KKOz{N<*+>00Pw?`~m6M#h7dZ(o1Lv$A!R?0O5@prOdu z??2W4@{y63mKKqdv)JO(*3ltjZB6A8FmwNg#H_)xGBXD@yrSE>z8%#Xh+x@J50L%! zzwBzpmFD9;`#g7!V|HKl*f@gZfl~EHbtT+!Gz>_7(nz}+KuK^2sM_ZA@KSY;`O5cs zTP}$q9$7;@LFgdztGE0FXThttc^_wRukU$SB!FPW&3M-8#`nhM@!n_&mH5I~5n@up zZwEFhw!sm)NojKo61fG_1N&0Iu?|J%h~f7@ClNi?>))wVXmhk2%V2SZ9zYrD=J)5z z{@U%^;OjQ2q2L|a^%T?^O9VlLfifS-K!hW1=k4z=87YAaoG->95goG5cZUBSfL`1S zsB_|BO@kXkfeQi|kcKp%e#uBe9EyqsG7xE7h5oXHkZS(%_i0x0d#kPP*PV9E4^lLL z$8Ga#i(hxxZ(p#gh-xUyU0%G&Uh?#>-g>8MbUHB|*s{JTANV;)z<(A5P@CNmq#B)Z zYHxUUA_gH$XYS@~5*whV!|f8L-y!fOUYC! zCQ8B6{;{#&&i=b|bwl;85Yq0w<<2OOmm5-LD5{$2a)$;9vZIs{i~bE>zs+&tU4B11 zq(0Y?Y+-fNU24#hk`5k8Z_R&Ts0-*xox1^ z3xgDkWg;+l_|kMs{eCW(@&0=TS%oVkXXa*eu+7(Z3;Qs0c``mJs}3s{W=fJ>F#nh_B3CT8L4C1+EpIo$~e-*?430AFYaKr<% z`i&X6Y=BazRXp~T# zNo;Hc1;lF7H~57@kQq0pF=0f~=Cv|Y3bn_0)7Sjc3i`~ijKm(x9RAdm z>%E)U!!yG{J4`Oqj>I*H4E&qjet98vKj^(7rbLus-j{`M|p?5pAa(?IDa_^gQ)b|%gq~`OH%1-59i6rOe=tPZTcK7 zH?uA;=Fi(w_E`9ta@b_=Fqdu~DCVB^yY5gdvzYGnV5QFdWLvaAYtXkE*=X{)H zAdNPxLx8NSZEbbuyK4HY(3kqOeV&rpL=kXI)|kInuPQ$j+ZgiHd04tqY9}fxN@pn% zX(X6YTnk;(j0~$o)VZ3&S6~3>$<5}ILDf7TO5fkFG8}w^d>$_{C%)qg`CjYzgt;$X zM1?k{0B>?>#;SbkF37*S!Bb}d--b~UlaSck+k^1@l2HvPAz-s%@*6A}`# zy4M-i10rr48-*zbwu(nG2w*4zhN5zLopg%Ka^UfPNk1#`8tNolY5|djdvy7RPl# zN~5bXM9i=M*c_a~`B*t>-@Ki1es_~BmV#k|@ws3U2jT-dw1_q)5mjUk`qE$sJ$FdF#d~Y)6KgVy(&vr->~2al74j9{*rBXj#mIVJ&O7{pz_`wLh?QeW?(z)%hxH-|DK%{C5*y{?NW}W&V2# z4XSj$?~n7B`G2ALx1o^(0|VTo0)3q9&uf1LSQi(X&ZFj!vg?9Eoq`x40Ggmxiv?jH zu(J#)~$^kel08-c2+W7L4s`V5Ka_ZYcNCl;fj*xyt*qQ%wnss4V% z;VbRsCRE(o>wy7!-F%s#^|3GCGcz+8*w`>c^sx?}y&QTwZV4i`6e{aEW^W(y%G?n0 zd)|>hNhQPg*FR9Oqj0o_tufG|r)GypZxG5=XrC9j`R|0FGlvODJ4k!hzq*v&S(i7j z)_O|j*;+ZZNKS)w-%T5&bw^JoZSN&wyw@VKlxRP&Ops79FDnc8-V$oHm&Zv%z@aez zSVwKa$01FEEM9LvHorc{hX1x#Q!f9(Ik#02xlTd4oU7omr1N?4v<$Q%KMB9uZfu-Y zZUdg}Tgvvi>sBR;X-!U~jnrcY=3~&L^rC@(P(u4TuHAEV(f|7EBZc$y<{vgUD)69+ zP_+?*V^oH;;8Yq;z|^^YRAypP9QJsNFU{%EQ+dfTg=et`p_gluPma|m#T^_19y`hl zi;K$yIRTEdr*U<2*u1!we~9;vma+#A8jFr(agk(H1Oqo{Q@z_aHOEpdX5X7?48Z?B zPbB7U-(pMP4Q%1la!<~Z($W-ffn%ss$6u5=N<0uB{pLXd$%20SnpjKW|h zzcs#UIOr@HMaiTd*VDNEISoz(DNa&`Prc{*r5Xd$N0t6d^K!{=m#d5z<1 zoa#1VC}Z`WFqt$?6^lm$A$x&0))_%St?EJFFx50l*#1YU}$nhE_H;T0)O!I=O4#QSAVYqRtHL> zKNZtjkQV8jw0Us&tw(=}6!_jjWBPH5;O4{ABeBu8U#{+dGZVv({>-~^K>Dh?xbH-I z_fm`pjeYvLoco336NEppG*>R`SDm`IlbwuR+pFm zGk^meK_40ut=wkH4|k-bq#XVFl@SSO>PQwA{D~OWH%4;8!jH?RpliqK1dtSA)5#OO zj_RKU1=bjG$oKk`;uENKiy?0uTMR`+m#}yYwZ3zjA*rosW?P$bnW1j!li?Eg`FaDH zH~6`jMPq*L5}CB)|8V3h60a*ipm&{=BBWmkx*~0$j;Ga7wtxD2Nfl2mPS*j zc}2%w*mV%?vaACFZQc)Kd=lFFO}~v=wBRcjn=&#M(obz>Kpdv1Y7n>nlQaVQUo9cE z5x^X)0#5fjtE$BM67%01^S<>a<}R*9KWpKTN$!vF6%D4AeSH;SuR|vD`J2nYPa6vx z-tjw5fzi4|tOe4Jw zDWG~pWa4bb7aIfqG~tU^}UCsBc=>9IE1JTfv+kY8{v_7`E{{M%`A{37sW6oAt zb)H~#wQ=+El;jC&j1x}~+YS9b7kXs*xqV+O|GY-wJVh_$Tr3}B$h&PAlq)w^SQ63V zQ({qmxk!I?wa=9o&<95I?ZlAvb6mxm%{}Fh+&V#>OUobXTQRD$`;XY8Fy`mDFjRwd zpj|wy$Fm`{ufAj#SY~*nRrrsN&yqJ@YL=aNY%3ZwHV4|9sm>BZk)5>y~n|O9;G(P0#LRHO=_gsuda}GSw z27@P;FW2wUy#Bhp>`?^+zoW`?jaUm|A1M|ZN`&_5!b5Pc^Q9dqUOyZDCXJOrfq_*{ zC=DK1)3XZhWb(5X5n4i!sqEdlz3m9muoq2(tMR45OPLa4^ciXH_7OoA(LapJQYSOx zQzK)!TJkYrUk*L|IZ=W{)({Gq=*T+(4mQAPWX{fxr<8(HXYcc)?H~+7kUsiZ{C=u) z3ab`5vcEc)n~K75lIoBDr)MPVLSI)qN8CO1N8M+Uj2U|B0s0Mo7@GCd@88FZ9#zhV z=T4K~vaCPUMf(NAUX1r|4yMgEHva0qG`V#tRy?Fku=982`*%}W<*GJvye6y(Z<-B7 z8XgLVAZwtjd3@f8&BMKTk@t^&%DpLD!Ov3rN5Cwt*<(c7zN+#^(2S+RUonMjO<4p1 z_J(q-mM|R)KIt;yw~XP-_HgXyC7-_RtG?@t`(S?#;ms}VA=4Bbb&J3j2+>Z&rEX4C z-}U!$+7!Clsr;wn*=;%;zJIZQv*m2q9UlDK%#eME{DmLZuCnlE#0}|WZ$$GhTV!Okgyc26@4zRYm!KJcu$m zUAY>!&~2{AJp!hvm=1O&Lqd|YCX-WqiYiq5I#nw21c4~iD^z&QQoT`DT%wYp$SUs z@#E3u4p^;tFr?=`;8K^OJb6d-l$MB9XU=;WG z)e2Z4GPvriP-EBRGvyL`4=q{4g}do$N%1IXpw-apExpTcy~%iy#!$DF|EzpW+^nAv zrbhI3H2-eJ#>fXNTxKdPauTYVn!Z6|jW)>4$@nxMv`VFV~ zpfSm8CO0>Cz*cm_U;rv0tygN0p#C6xxT!)+%7mYmJZ#xKeQIe5K$S(@ge*f^=$)DDotMfhgN&pVXemg%x@qr=DoZX#(aIfy?V?F zl9GO#M_Xn8t)z^{h^y41iE?NdM3bj6)-OpT9DMkR#MJX8MCTfJe&(d+kH|y~L0dsT zeeKU-N`{KABgBIj?G^HBrLH1DM`P>Xw}Do^?k~^uQRj)K82xo`=Z~<^ZkuDg zHFI(SE8`Z90Ma>wJZgW?rUbG6$j@UX4Ww~poXe`tVzQZe;uy! zPF|xy%Q2tpXx#pH^0TW-!*&Z1lK<>YB;obbO?Yo_(8VYOEQR_n{E3MUCexQ~sx3Zu zG+Bd2#5o@J?}cCZw};NVx}WB^Uk3}_4NybeNydYZQE@Cd{|XKK)gk`WVa9D^czVaT z_uVI#dEX25%NOophvR+snpe(oOP;&)OUEh-wcc1=^G|{AZ79}4JVB#>}L=LwK?E1kE9@5f9BA=mn6bT5%uEgs$_S@xh^4F z5>O7s6p@Og$=~$!baWYtNJvO<0RKO=hI;kB^||noA%cJyOX-yA-epg!p>&#ESvF@s2h_W&2J;lulc zFQ(Ol9c zK$ER`Bz|-WBbT2+6?yo%1^FDU7pMTBeN)#7kNJc}iw=uHy&WMo%$^5z=1~C@4$lLau#3wGrM61^H zqvS!JyNoklZCn@`zn|Z~th0-7j?9nnRl^uWDH>RBSrkt=A{A*oA@yy#kUK}h^lWSa z=d)OG7UB8}Q?7~@5|igkR@;_aHhuF!P<{HvdoAlHA@|zCn+BKc|1Hd2(dIZ<+>fh2 z{@@nxl;(KuI-7Z8ln0|>`d$J`pl~tr*($vIIQ#-spRMe+kGT+|a1fixljziMo?Nke za3t*rk&3W7LBU$XazpIi!VRH^*B5WD_Y$qnJMUk-@NYZJHUI3o%|}a3BRylu&))Em zs`qWjMM*+;6jRJoc|R|^dJ1mp(@h<7Tv&H?Zlgg@{V$E}mCgcqm4jiG(nxh@K~-wPiswWx zDs49Yf>LE_LIQ$IM7EC%I3c$Qf5K3yenqpOI2VAuo||z!FRos2$JyE2c{R$>FPvmU zp3T>%-l37mri9Q&iuD@sRL&j&7RfL!Hbj9%R5MfQ)yD^{sq7QI4z?EL0I*y-5g1}q znR~|_A4?J8=}M;Pxo#3yQ>vRM7q)s@mcnIUQWDi6rEZq43VB4{$>78N0x?D8vl6?! zy47w^|2?!DrivoDd9T4cs9G3C@r?9TbV-y=w6}H|psY)^&PDUQKXz>-+xT-M0H`6P zQhHy;$CPiQc+!*kM(>QdJLi+PHy-}eDE(*V-MFe{c}>F{yLI% zt=Ck5SHPT<7*jsKs{f|mve21Qhop0ECR6e8Fuj=^K6<$O?iwD~v){Fo3 z+mE|QZXKWXe$Z|m*jxQ^#{7f#=s&D9h`4Qc_lNH=>X$6k>XSu{@%e%D)PNs6s{*}GcjV>h9mK!7_htCsrYV%j z<)DwDU|Sb+ldKWQ*SoI}SYmOQct5Oe`&GsB6W0Yp-r*ik3ZVDWqtuN+l+Dh9&UCpM zEyG&fTp8bAzvYq0wF<1;kCxe!ORq8thyi)$+1Ibyyt(!KTHrR)DxGLss&x$x2q-ov zGfnT2u5k@^bE87ggk~zP1Qm%$OY3tVadd{U{`6{GZ@LEo4ZmvRB|1!feO&Mj1Fx>f z<;F5D(#awSCLYii7^~r|ae4}v_0ULGnlczC8!HjgA%4(qvKJ~`V#p7KLl@r$ZkM9& zV~mD?N|6SNj7{>o``^o0k{1-z3~YpNb5d9bqFH6i%{^Unr`|BGntZNNn&UXrqpxTj8?V6e)}K^~ zFiFf0+E~U@DXuGtM$q7FDLe!tg`vz)W`=v5_-#L;@8Hn@?ec68CVhauUlm{j{|{1R zFd)y#L(`uN^cIDR;7-iKYXa9|u0hQ7;{0y&$@R6uHTH>K=;*#2;U~hGn6De8O%}^E zP2c@I+SD1amigL}rG`hB*!=6O!_n(sU|Ib$@7})OoBg{DS0OL!XKTRoU*J(CE!hif zLelu0>vswUJXY$+S=e_4(eDb6*iy&1CE++4%6AgbEl_Yz>2t{@(P#&;=H}pdEPuwNdog9%c7qj zne0&xY_uUY{-tg*T#w1&m-hC%8z|C$JW1%z`Z18Ka^F^cv3?L4?UhTW2JudVCyiw= zbmKu@Z}OGSWDPl75o0=QgZgf!bEL6x?hSCY%Zf-l2Ir-UqNaj|DysPT%4#*yEg@|l z(S*;8?X02lf4(DQTi5gdw`aDx^}VDO9q+WH6a5>VeIGxmp!s*mr2WH7`~1^&uIp9r zaA`4FEZQJ`-#AMG^Qsk}>`s{`O9Y|_2$OS%X)-e#8-=dc6vDuG7UH(n+bUbxcsT6d zgd1rFOI!MoJ;x&gb*3=%QSAH0Kr*~IqJ}_W*(9JPyRmgD-c2X5u<61bF?FjseV(eS|5M4WS(v7C5YE6sO~UKfugl6_PZss{_3G)x zCrUBtbT(2e!+Qk=YFRM@4%*D00xxVgWtp1I&VzaIoE02ss2S)N93~JT=t?X2&n`(X zx>m9B>u}{aT#A^ttor#`$n<>->6KNJ`43N8lbKF>{7a3c>yFF0t@;jWjFeAqi#%N) zIKt5(KP)I~?47)JWS7oRuJgFP&_5sTuz8m^s2V1CfbA@pn zP>0!7*Vnp0p!qp+zN=r5i3$&4rE4}2eJ~V$B$ds$(#agi;c_A zUI@9CPrr9AdO)YAt6eJ0tT3rh&%pAE zNJ-7{^pVfT6a8XP<#VwWP-ZIUv-hmb6ufO+&nf9hdcP>graC*Lp^g&>8Zt;sY&;$$ zQ*ed&rJA$;CzYwCC}L;njhD+B9e9%!{zrQ&@UqlxoYvN<39WZ5MQ-N1g@+U2cTS&f z8g1R8=Bq2?poAh^-DfrV=4WY!xZ2ss&P9~8RA zHpDHq^@fDSnpPM)4L4%wWc=&lK2v-W)O4?6{MlU<>hlT{%-H|4N z2*Owx*=$|V-`1QhTB6lIliIJEvf30%_hbTnu<_2((fYu?*iD6fht1I|pOfEnqt&B0gZLn2BWyH=u`!QX9gOc*5 z1OWCig2f9p!R~B#AM>Zl2@yr4+)e>0jN>lpaU4U{36d3SC9O%2ow}@Fux&$L3Iat! zmKk58kNwn1V7BiPKFZb^$y^$ngBD4sh^r#_1OsH!l)y9t0FOU^~_}Njeh*o-jI;H z0$mVouH{U;2rKt{VH+Lt3a129G47ftkz&m_OHa4J(q2yYxf*20VXQbli@;}!y4#Ih zqVzK<#GS%pq*)GA2R)>uoz!0xtuD?@?3~kTGvZd?w{H7v(+NV~n-IDuCE@p*##??4 zPDGk}wQ>%ZnR>D!A*um?qB9=23X@CQ^M$$deZw}!;+A<=lC;`K=)NFbs*d;MjH0yi z7AlCBu8oc!22*u#oMmKeb&u~vU(d`nP)|Ae9vy8LT!fjfy_{9m8%jUGhX1ku5p**D z@T@fd{BQWFP3!n4e#cls2C~@p9Pe z`Z#7K`SS0{?rdxDweR0Qzvi1m|56ctdf9>UaBB|p$Bh3lQ$1oZDgM~C$K+lvemOpS zu(e)$|7DqWJ)#nXm`F>IK~QTBNVcU6I7MQ7ydK~pft-?>P=z3>z7{Y@uJb%5K#@~2 z?m|IH2_Eqdg)1R;7n>hdPc0CdFhD#DD21suVuG$VGBW>Br0+)ufY#aVzvf{VZ-QU&C zGJh8cAu!-lw?Kh2AD@~veVeXWkVs9%LZQU>7H}2_@bTXjPy8or7j5<8js`DGKJ+8A zt0-5v6fFk#3mITRsIyhEd|j;1>Q!bj$B#l3q{)g+509osXHv~t&D_HEH1Cb03;e`J z3N*D^UAuo{ev`X_?QMN0)$ZfHJ$4!%M0}tOf`Ouhtl_ZY!l+|%Th-lwgK+w%*=y zQ3(M*erM>&V_4OB1i$`pOb|_qs>4DR24p~JH0<02FF}%sO4w&RWGjb4<3iBe`{JhZ z-&r;!13!Uh6S#yIncg>s4b)4&TpTeG*X9{KOIsA!GP#is@?AG9klq$$bHJh-hU0Hd*^cXip zs#aIUv0=;0iK@rRR)-iCMBAUJTg;Ima6R~(=$&Hs$)0VVVNPZyl$siRf1dzULJnNC z_6DBM;S3g#E+`Eh4>~HG8E4t(;%I!=w_CeAiS4iAk6Lz}5>2BI57+x59#*AV`2#;{ zs)E1s-$hcJ8&I~@^xquwwq{N=FISF^`yZ#}^qcdl6IO8+tex{4){fO$yCD~wKcaoy z{frVGK)Ve!^xPn8-I*)vBqX* zh1gzLh@T`Kez=`OZUUptQqG-Bfg!!*)M@fqL~%UBCTECH&lHy`HWj9{%siegocVv^%uUi zgO0v_>mZ_v9vaX~NXUyWgZatwISdV9DH9r=>1KxZNfX9q5=HqvYSUHxw z=7>6W%#rUC9o!|UN@!?kgK~U$Pwsv1f(Yr|wFr>08=W}dc)hGBO1 zTPqAa;uvdf?qSYBm@Fiq^{%*8*ze>&&ZL-*@6{D0(ovn^H8ZV;jk)b8b=d~i=x_*3 z^r-D#127=wL4g9ZVWAh*v{7S4+o2uF9=?|N(;NCI}9E%fHx2sv@t zf^7Cb147^qn-NP2n^do>a6x-~3#4?>*|~PA4eJwDu7S6Lmbef{fLW32lUXawTn+iy zrxK1=*r;chmj!Q&_JQ3Vq@gZre+fiK?}74nkjFXKANrIW^q!3)ixz79NAW=xJsgh~ zgdH+Z`6GO{9(suhCR6z!cJ3WzGIBwlC*zuqPH$VAbC(tyO1UN-O@Y}*TVNlPZb>Dbzg0<_;WN~q^AM&Mju*jzIt&Zl? z1#H@g-p4_K?YZ1|AWLqhlRv>&in=euiC)10%ygc6+oJ~F4gnije^{KRKRpME&U~#^ z{G(TpiXUNQ{np_+7;>Ij-z`x@kN;HXJVdoRzO@=N#j%>S`Sb)Om@u=__x8fAhh{Pl zW{em6ROn*$5f?XN==`hqz)K z!M+ZEfM(-CSc>lHRS}=lnH^ALhLyhYWp4pl*d7X71YJA8sT7J&Sv|AY)+WiH;iR9z zg9urU)-w=&iF0IJa^6gy`+?eOS+38(Z10KR2%HQc;s6#Rr|Pt=CCQN({ToS z(BaqlSe)^l;hPQ9V*%zqJTQd*E)XCss?j}8Jgv&lauG-{pCx_C^x z;!y&V$LjWjKZ2YgSI)2l6vndt2v`TQcg@FP!*1YV%HHp6pax8aoEFh8?SdqkPPFJb z7PSsM+3-~?ZqyfTiSG1L@&nTl!T?EynM_(W@9ShctoMRmH_8Qq0xw^_6nkkEPht#NTtdyxjlvL5Vyaj2A)k+=4WkCsm~(RGB}?ir*KVZVbCiP(!M5(mlH| ze`QF1#EGZvl)XGK^?`J#m8Wre!qV(Cnf!sM%o4$VXA#s2lcwn^YpQhDon_6?zy)rC z=8Z|cxThiy_lN#VQ0YfW`I?Bt(GzXmESM}2>=cxuJS@jGS-g^1yIvw;v^v)RO%q*= zzf`TE<=`HMvW(|<_*nd*7an(hc^UTJa+s;eqj~3CMts7OpZ{Gp{}KPe8=bh%#l?yu6Hs>4qw|G(sm;qkZI8Uk3M_keMetDP(&!Nx=M*X=5tq${+ zS0_vaZf43^4y5C}ayKRSk9v`9y?UJYjt3tSf0QLoM)k-9d%ZIudJ_8EY5A`KB9By_bLwz z;FD=6TIq4SrwF3t;|QTj=3@(qpKOyLcjJ)L{bn74o3CYE6d+OV!ULY5Flujt^^>Z63Y)JJoWL`s?5iVmi|RaXC7}G_+XE zmwo^7a^Z4Pcl|xtAF5WSc+>G{p?28eE(s;*rb8xn-};?VD?q{fZ}GVO z|5&` zL1R4+gCH8PY8#0;TOPXICQETYfjUg_k7)choc`@54Pj3y#*|bjt2Md%JAD$LH~Iwy za{Om|y7k_pbv#svae0 zgS2q>EUww8Vg}R?+1wX4DPex4$HS3}bJpid|Hh;5#0SF#;c=oTz1!P626J`#2n@<$ zzMbgj(#wJukDE`y-0ND!$bUJo^?q(x;&$HUJt;UmYm-Bm?ZUBPiJmbWA1&w$*T2sa z(lV+`(#c3Npn<6fE(?YNq=w^L$~azln>OVIeZb^r?C@>&H@$>&-SvHkNx{y%2*;s_ zHcRP8^j_1>Qp!wiOZTS}l+RorUR5nJk#lBO>}zWhKQPfLg-dqF=q^CTUaF7DS&XFv z;KRUjfZZIWfcW+;$w6Alcuk-As)$Xx#$ z(2vf>!nox_20DA%lnQ{pK#7hbT?c~Ppp;~?+k-nx%vlhgS*i7y+Iq6MEY1D~g#=ZI zi7xdDQp1ei5&KJzHH98O5e?q2Xl6SFA(Z};R?%&Qn~|SOE?Z7}HDPpnejI_*Uls3? zw_0rSzvUM1rHg5h;_KcLzx?xd>Ga;5Gi?j6mX=Nm)6@_i zk{ea@gc@Fi=?n0|lfG2b|9M*U3d^?W3`+l~U5lLyg+uYE!()G$OSfxWE9>88q@ z$KG_c_hnmK+en)v;Oi8;4XRv+$U9~`E;4a7zw3P^XG2f4M$^H-7Tpey6XO>V$>`sJ zc20B%0cig=7inl{Xv$c4{rfe9PYYq_dnQ(|2v(Gcv_6=sTCBwF*`8Ornoopg3{ssu`vOpvkvzpJcflpLwEBP-_ez3%_l zS{HVMyNimfqp+aWmI{`5A$as~*?c)9a(MGip89?s{NQ(whM8FQAcQ?mB{endwDq(j ziblR4{B?S9Dy6yp$WdlSh4>tu9+~*Xbee#Rc`&0rC z4;81Jh?a4KGFfrx$j9uO<1Ga=20B^(K zocQ4-QTt@6oJjkrZ**yZ8saViqT>ZR}28l()jUa*%LVb%+E;nv}%6xgrXyp;it@qe}AE2q&jIMt# zFH1r(ls?F7eNZ{5fHUsD(Z%5egR3V`8rNdy@B`Etx*GLJ!)anu%y8)by{HRgHiOs- zppmEIHp5<%E)cH^VV+>-*yu^4>%UryPD{(katMh%78_Rj?t zK`7w@i~;EZb8UaiBmgs<@wa=vN`rtAYjY+r_m#wv(`#U7*g%`E8;qa$G4Ts{V8OIGtZwACQBRqOT?x`Qn%9%< zeIV3d1<<+;qhe=E;7N6)`}2JGn-YaoP}BiW&EC^+PQ22D0NWn?I|PZE`1#&t&abT@ zf%5WNsx71utV&24Lcl$(^#KWDf^!EATip~!H`O5L0ZiDoFP@~Xv6p`KtYz?BW%Lal zy6$pDbFql4@0$6-Pd~cr=dGXlvO>KW-Oo6BGst17ZhunGq4~Iw(!;14q+Y$TZf_ayhet$4*9GMaj6N-?d=WHMH zs(iJ=kf%UN50(Cs)DxF2=vPzjzdQ>#yB(gs2bfhXVLQLASm7-xO0f>>xr8@JR~63B6++sQzE+K`NT&4au+V_pOe zc1OO+ZK*@^Tp~LW$>?v#p~lemMs0@_l=fv2HNVUCzENB8xZY&msDfkm?QaufD_|^A z^3vhHu|tB*ZmZk3WoxVX*;9ya1(4y>O*rKY{ z$mDBpFw@yklYm-}ClI0<%i z7Ba?`4%sl|@Q?U^F$nLe1rK_QXnJBt&O8M(7hf=ocX#%gcU}<0rPz1Zjlhz^#3t^@ z3C41<*l4 z3l#iU`i-g=UceKTHM(5M6Z0{6Dj)6l7uU9OokMJT^!zc-(KYk?gNrZq+5V=z^C>$o z93D|Z`JA&1t+xl689X!Exk&_eExj*jepF1Yur z2rRfRw6s68_|S5N*s`4v!=yqNxYs&9MpuNWCLtM11jCaRQt5~Tos(7xa)RXf1LQ;zMY7B%ZiGJ-Aqdotxz3V4~nG$ zd&?Mk3zJ64cecT6}_dnb&_{rOpl?8A0YVPZjj?O6QOt1&#^0YIngL}V? zO&!j7gK+VJ*KR3T0BmPuAib)QZNa0gZ)O%f>XnT@&JAhXP|dx-!9%GVLHeko(n8VE z+Mv-CyYgz8R2iQj2>edw=%+nGetX+W!(zj|*qlWLk#0qKPl#aLyz8Ee{|t&FBO{f0 zJ$yTODCGju(xf)EzC9QExrCE8sq3@);ujo)6hZKKhk_gOO8`>N?V7U zy2ohs|D^fS2A#Un6je**7C+?uzl;cAkEqZXawr*7u>rzC0dE)7@R!Y2ZR<(D#C7gt zvE!~Jqlc3;YX8*;jYbu-p#aJ#4uwy`1-+`Ul=h?lTn~cCpLFNuJDqnTs9N=43|)kA6+; zAgua4eM3$zq7m()ZGS;dY9+S1?4SuZ5PT}jdtoQZ zO%u?QeRW{*u*%hPueMY+X%oBkn#B!D`(;GQ#|uIY6oWd_P5_uojD29<5Ovk zqr26+plaJ%P$lS_snL&|>sN<%3KW44!w=6{CS@R@z0j>pD>xpk)Rizq9m*S||Ge4* z`CwBvbG<3D)MNo(THcq9sT;g3&^0Qc5J)L}Hzy3#@4*a0rygDs??VkTL+OuQN9D!D zhBS2-_o1M$o?i|8`P0X@)@`jqKc!3XJL(zqk znCFGmnyyAj`wDP7dOkE@Rqt@!EiBFZ%?BxLH#zcc+2s;*B(L1WS)K^^u>c z`2SJh-5J#OLN1?`M~A+{-g2Q~Q^W5MPN+YkNM)qz(-ew=RQRIjEv5bKwg0iplIPhd z7PZed7#(C~aVYkujwNW&@FoS@!st(RQ;NOBZXu_Yex?ea3DvR7V$|~~Q+`Ye6VT7q z2{XA%77M`xStBCT!&MJ1kfW5lB$)Z|ZtfzJKio0qj_MGpJlxER~S! z5n1Sey%a!j__y(QT0?*nKuoml_AWIPHuI^^Y@A=sg4dqaEF1zG>CQ3ks{Ff4t+4F~ zd0(h4=D3_v?z6$BCUN>NRo20+TrmFHQ@+?6`&ZB4yUMo2T)AmSTQ$n&_np0Six>Q; z%aC22N^Laoa~lCv%8qN<&nFlE7E*}?1@~Ot<7dKGARsWtg&b>b{F@22K9+cc8Ulsz zpo1W6o!w&9c_+2s5>O$ojuNn_y#SQH#rNdT>sIxxG3U1|GUOm>*yM3?`=l=T7kBKt z(oxIMmi>Cw-*b&t*pkk(RZprbsR-_!(yH#Krg1x4VLd*M{WT!OsqXGwi*TE}~ z9fU58F3dP+@I@!dH+lJtK_sScAzbxL=p=!%Vz+NiVzyeC8td3Z4JzGyny<1+}tOe0I9`?Ov} zVh7Sv2tm$&nZQ1tHTr_fffqk%W;|73%DQ7DmS{hxBhy3_9C`|{sg@2_!R z_Jt7{Ox>z15O)EIJmd zN%`i9UDJPmH!~anhV^VLom&H<&3yKhTI{J@!oF9cp5(t%m7u7pWA~#CRt?vmzdjM- zt@YN+a4W)w5&}?6zG5;)Kq~Z$Rfh+kiu!yXwZyf0-Jh8HR5q6sm8(z;J3wyN|MX`^ zivj?vLte|g`p?0SEw;lv4;GqG!#*VYd>5fk8k|5l|5$2L@j!ReJh)~k^&Q=qViEUZ zgWa6kLzkNAI4mPjr@Y|>x+Ub9_sOqsJe)3rUJ=ukeaJB=y}yZE z#?_Wky0A8)F7NW4(o31&9YWD2I^4UiOPyF9G~jrcWfX4>CV0%w_pP9Mk%)%ZeYLBv z-2eUoxn^`91bOqC`ToZ-im>S)=JZcY>yns|?0rZ8ZSNLHWgWKM9{f6-Ncz@t+k7Z? zwfs>{>>NaQVRLl`J+DloE16@_DV(T&3aaaJMRGi3PZ{^OWO^bk`Y=XJ(9WI9P8SYg zJ0)x+G^lShI@$%5m_<<9pm{YXO{6qXUgKVe7p_z;Lt<0S=Y^CGE!TzrjB5+kKrQ8% zvE|Qr{a(+80*9D~d9HSjxn8y?q8Fu&&@Ej2*CKhgMT7ibsq0du#K}k(+F2O=Yn({b zPprGgeG$ifeO}8wy7F>w@=;)n#LA_&>CBNr>CRT0bC>=Ln@ui9DW)AI#LX{Xe2x*Z zZ(ZYXXb_29on24nkz?DKq8zKrPY(YcgW2|`=pmQ3$3=zeG^M0i2Ho{_l#91a#YN61 zg72!0D(RDblB5~e62=40wvYiG_pQ^QQ%$$ts=4tpMi3K%Z55cXTsPE|ev`&3w!x7Fi)!+(@1xP)*BT;MCGI-oT5cES`0Z#Q1u^S;KX$JRvYSQ3 zZfE!B$`)Gor6nHL{%N#s(3Lh9@bsO-)%P8&l z%RgwSqB+J)V8q+Q1U(SIn(+ZDA0x*RX(DZk!^6v6;V=v&nT9QJS?6o4)_RXBl7n1E z`*eHw1C^LwduPXgqeP=|m8y>to`~{N+iD!_g#%~9(0QJJB1o)0JdQq5EC1Cbe+`zQnYxrC4+HHphmh104Ua{)sX~V3%|q%29mvY2eHgSvR=Zpd8n^F7Kz)V z!SV;^SS`!Z!v2oA%7^Pis}vd%gFn7g6Sq4BkaJrd@?verY{hTDEOS%<_KIo#J4QN? z$kswjlExWw{6LM_)H{UOFJRWZUz50igU^!p%}&@eukGdBj{yCMB55@f4d8mTyf`AP zWqqNe)!owi5pq+g_TuO?Rx>isU&o;Yc$Gc_{#neqItNN1NnRJ~**6>`cFPuO-A+>z zR){#`X$b?zbWMoo!!)6vg^eECo$P|x=MqV3z-!Rd@W$@)?)A=(5 zjDk+9m*2-KB-U&*g0Vwb1a+_z_T=l_M2QfrN#6x3?$xc->2 ze7IloKfVWphfVy>)xT;kM!!kovGpDE@YkZzNUPFw?-nyI4)3=l&R6n!;(U>}OUNOQ zXWkDX6p`H;mH$Z%6|8YP4c&g}r@rxg8at$!Vzb2vM*B6O*gH>b3ib@vuo6mH_RD?= zfMIKHXKjAk&X~|LpjRRwR;Zz-(UNjc8VN6ceC#A>^_epMq?IWJc>`%FOfwV~6o2{F z{zbzEcR#KhUU0OFjGSrX$E1j#U)L%ez973PvTw&E?!>SNgTiu&HA*mAJhw!a8sI$_ zBt3DD<7~lsba@Qy7f#u*9*4Zg-ggX0?y!V^Yg$=J&@vJVC@G+6TAZw@&B<7ZKr2^i zS)>70tNE*p)@#aXvN`=Qwcms(vG{#Jstb*TD=C_Nmz|!|OY#1vJx^Aj?wKlMqaS@7 zachC(ROUT5fcN-plk}-7Sy0_1nw6#a4?;dA}3swWPOzr}1Gu zK|b5-W(WRF6}bFaOMBzZW&nMT7nMFA6vb&O;`RYr3tSmBlu#$G+h0d)v~N(>G#V{{ z=XDhhHVZ_Og$y%yQjrA%P*LfnKLQ!K@w`sYTECD2L9gnogr#vC10!q>*D&&?&B7t69Fe9zOCqcQv{kT>_2&G+I{=b)(p zL;yR38Bh`0x#i{j;wYt!*wJu|{1nY=pX_k$40VsESo%{zzF0-R!O5oa&Cj)dKeV*? zdS5O&-amU$+S56iKKc8ManhC1*6g>=B;RoB9T1aGsWMQUEZ?17sG|H&@}I2UI7pDo z0BfG@&}&QN@^J2BxeE32o^Ni}r(XfvP9H2C!9y|QpkC}fnJCERMRB!U{bnl%2uv?` zbAVpT=L0-4n1v3Pw2b<9z}_1A^hS}S_T6VM`!>&g{lI53=)r=K0E-z3m)Sn-w-^rr zF5yE!IxD2KuipXE^TG7{WAc-i5Sg}KZW&XK^%n5r^U(?A6+dmr&vK)Dr!_G3q`Q2OP3$j4N)6(xP(-0v_{&KqO z-&9J6`V9o7-w7JBM7NY$%xf=%!q8_2$TUK(+r1HM1&Seja_E_lhC=M-bTigpe1VGz zEQ_WZhje{qR<4(aeq9p=S>%Og@8Kak@Hl1+ zG32`D%X(efER#;@e|$`3JNsL;5_n0Zf4l~-HI#%+A*+j&{1wvmk4~EF^S+? zw0wcW(^n`dgMh*H*_y4^Hl@TRUh+&*E|%k*J>HsBYuQn8xM}xZM1}MUhbb!!Jpq;( z28&lMhu+y9Gy3F1-L_5#qd-S`vn`zmqpL-Q{NIqIQGZ}kx?)?jsmZmk^lOSlM}y9; ztEzX(nq6JZnd7ClzH;D-6;e(aL_6RfCKSJ?hwKlGMlI!{EA; z|DX=P+LMU<`sMT%Uz%Qz-(j)Z<8YEg&)u-vWxP;D#AA<2p^t!@2f?5(tjj_hyp2uF zsQp=aj9Itp*yeL&fbwhrq4oFMzunSdOy=ghel$;~xUIsiq>Ysm+mgDjoK||#r2abU z5Y42Dx-O?+wLH|;Yz4(i8=)OBY6eBIU>xX!GqU*2Fw#>vg&?R+rMSch< znHljN7mOr!Lgh+v79EC4p@%N$XD-CK4h2AND0rU8(*-3|r|PLly~W_T;zC^m{z?x5DpB+{_PT)Wi0$7cWoa#o zWoj+RTFR}oKpGpe<7qN0|6vqu@*ureIi>f{KPus+ru-#lce=hlk8ulB`pQU7s_(=5 z(X&wz&qU}U*1l!rSD;{bF;MNI_|13jX36ljPaU6f@~rE=W-WO~JZ&QP)!iFAka2@_zx$01V}WrrNM?Q2 zV2$peIpVP584{g3)>wSF_Xb(}3lL%$jLo`rHxQYY;1(1F4Xb>nZ4~_ZNn8xU+NKPv zO;laP4r@L#p04dLbML=}CZ<7tGf=53bIU<+-+zT>QHhBOyN5!xg6G{$?1&vSXwViL zzTa+D#Jbxh5cCVMFzZaP5110ML?W(5Dn!nac zJ?1?Qo6B~ypkg}gDIft``?rKzZVpT<&$=RH?4k%0mpSQB(LP!Vnpb&(Bv$A8{MU?L z%UtnS&V;E99vsjDTLy0ul})2-&Gkj^)Z#?1mZcB<4}YZucIPjZFa8ZE zAmlStUK9x6s8{}WYu~i8Nm(Y}rLc1h07)i0z;$2Vbn?5Bq=)G_6Cj%Wt<`N_q@m-i zw-bSE3leN~K$}N)J~_yF*lnMTl^mn0a+LZQyOnz*XyUDokLe+w87{I%@`)MNGfj2F zo*}l)^KxsYWZGqapVryWh`s>Z{Qph$^<4&s+S>yMGX&1h$@FRQ1GMoFhW`z(hTLA# zAslYipeC8$&%j%QJD_kZsd__8O(kZSwD???HH$yA=Jk+2AqBIPTSIOXGZ6}r?5iX+ zpTBAnLLz+oI&`ls+ZY9mbysUSJlWfX(1=rOz`xKEk33R)F#WAGNVWw9;b8KuNIae_ z#VkQ56KCs4CpkoBiioTN`WxmmdS~8zS9ZojFa9sxFH*?6W7FDtTFdA^Ypr;FQr6RO z%IwSc-`Dbgxj!2Xfe~`RM0Ka*pjTkgW>2+#!tHo+1;NHV>P{mN2fut{nT1Ybbm60w zH8(fxiZQdRlcRehzj;|3>IWk-QTKw`C9vIc7m9YlglPOWZ@vigzpDfx_!!Di%oN(8 z#^P2(%G*uWUY#d!DEzhQ6QYPVVxRG&qeA@7pL9CdA=^iyoXS-!Q$_Ei2uJde#<(g(+bwICOY34#%2%@?zFdOr^U4(!8+ z52l@t{8@b#Rk7WPO(rU!BT15`;@!b`_f4AS3+8RAkwA!X06MSa2!yAt%XVWTuSc%u z`71%p^tFapR6Gby!7sD#TC+N_Oac@bIIbIpeKxyB^RAVjuplNdIS_#AP z4>(u4f|3z-U zSnT4E(vxHs3to(+rx7%rFy&lz`Gbkrs|s?W`4jFx@190zSF#ic!&n9j4?Q{MA*i8k zD3Dx=8Vi3=m9T?c#+neuu%LG=DYBqqw{re1p(1kDIJ%eQoE|GPHfg&re+l6|>a z(R7_{>&$?e3#iog)}K0|jFF;UI`fsQa61KCYPc@toKq0D27+*HL6sJj7KI3aKck)O zl&6mWOys%ERb!z2a_eA#{(5M&q{Qm+Q&iGUNScZ^sJ5#Kin(Fa^xS;SG(V|8n0{0PMUzfZIo7 z$JaI?7+@hDv@(+zBy)K?za3^QQpQ=y9<>wI2Qus}L{5fvR=eSw; zRTGD+R$G)P?OJp{2q$U8$?vd_>6z#Dt6^dVbU8Rv@h@&jZ-Y3b1#^bKeE6Xm#*u}j-m z)6ZYuOR8nM8E{_pffznB!E(b;od0r83?9KOYFflY3{QA8EtQBnd&VGcAS6v%E-`>` zMyus6MLLY&5eqHR1aSTQFQ)#*A}xqKnjeB5ef|C{jQVyOOjtjwdG>i0FQa-9v-P7U zK`bUP2DcR8Sd2uHh|00pmm^v; z@d-Wy=207m51S6?FihcN#tJb*&QUR4M^wTdzv>2{Wj(&{!V=nIjZKLS?^W#oR}aSD z8{+u(4QsdVuWU-0;)3el8{k)zb(M*D)7V8ud6|8Y-xIFD~$~13ARoD_D-OELuKm8_Y%pUoUGD|Rjk<}@dTfI)-z?Y;G{2_m+iARu=oL)MGN_Kp z{{<74$*@zWV@5|ehVV<9D0Jx>W-zw9QOFv6P;KUR&ArRV?^8?GjBp4Duhfj-YKPQ} zzINh8b$2*YWOr`Wl4YQ4gThUe7&-)H(y>YEWj z+|+UDT3VdzwW1EG)26Z0BE?HyoVl^==W}YN7D&Y@P1236Qpli}l0qh*3@#y!{EucN zkkOdf@RY=_ZbF%HmMuf{+y>syIN^>o?1TG3QTIfgcPZ<8^VFPex%~tYZqmrfj`poI z0;S%e0*tjNg0HIhHH3Fky?gounoT7SQop`?z9%8`ZTqbu+W2u==}#Rizy+cp98_wjrmfAKK>!l5Je_d=LSfYBQS4=3G)m zGv|*HpCaK)4hQp}ktj+ajQlR|K9XJwSw@z=P(;@O{DoI^D?E9CpYakSstayRqXxH zkJwJ64RxnUpz>;N>DXP#q*xq_WXct1SyjdkcX>x~e>< zruQ)UF^&$0g*_Czzh9@Z>S=|~Gq0xa!{v(JsjIg}xH% z6gS-IKRYqd|K<>z4UolZV1w>|Oo2?{=q8J!YZ8~6xB2kgV3R^t>A>60F&n#z5&1*b z+{Aiwqf?%VEoLNuwt`)rYYUbttkveybch}WBkAVh+Z>j-w{YQmn`#`SF7uh3`C;{k zs4(3R!Pt&A;?-{wCvSm>6KXBiyWuQH37Q>#SK6^$5A4|*Ed^vKK81He|Oc&Q*VHZ44hSKl9W&zA0z> z#|hVa2?>ct0$|-JzQDrWq(G~BCSS1^&L=Uwa`7?GN0TB|HrNT6M(qh}zF=w;T_ra26f}jU0taTwJ~Enq}xGzQwU~DGYt& zXl)q4`tYCyaLzr$u>acv6t~RRIYuRa9kqQ(L!KCw-T(Pk_xQ1i)_SY_{X%u|&@s1x z`t6%nMNhJ!V!qZsC9{^ylFNXStE~fMZ#bTzlzThf)+jpmAKO3T=Mid|!hY;YC()78 zyzIIEHNNn$@cV0$#$?optxWG^1I#KR}Ji^{p)E)_6f zLZLGYHr{%LH0Z>8+4iBq)8|ZiS^nqzho{-)<{wg;JyAcbANRCg4)EFkPSVIOfB+)a zzy75&+jk@W-?;z#CVB|^1d$6%_Y2629HfL*4+?tTTuR9F+=+Lc--=&PTuiNreyHM% z6f{7xvuJ!RFNv>Pv01<92`cSAdXS4GXCQX+WLo6IuYeEEECs8*4t^(V)YOz>8x8zw z+Tui7JUV8tgo%Sj5w&|c$Oix8LXpMqCMWt#xV6UNGOU^)xmvcXD1e97NZ_ zi#PXc>z5n@^>$Mg`FuLYC2pvaS?r!c{FlgGPn|Zm%*3IPH@dG`{BFRSS$W*VCFJIe z-f5P(LmYrQ-A#gkzG;~=<;F^RXD&xoEb#6Zo|6i2G4LAEs zQf}JdX3N!Mqo(7=W{JyTWP@?Elm464X*~z;I>Rs?6iXUFB7;6Jw0=1IcO?48WWMR6 z(uT2>`KSn=)6sl~E^o$l=<<}-Q#!`$zS{qysIQU_HXN~rO-P9Cl=8?ragMRAdGtH- zNW_2J_t3N4Oy?C87?C2RI8`7Mx=AjorekG%TBQ_9wdpfLsK%MQ!L+O_Ug8w-M6dg$ zOv6X)MNNviKlh#yU`$mL(LKzTBWKn8uR8Gp+<0*H9gW!__>NcW|1}(-?bu}qco8eK z>$*hIpwb1SV;PBEw0&G+Kw!UO>!v72+1{7?KUw*QBs1s={)&xv-}nDmI_tP5-}mj` z8v{lOBLoQoE{M4&0;>9)+eMPMaJL~g)x{4Jdu%8aCv*HRF)wpO(%oQx2kYeuj2axg zX1;BTy}_5Ra@=drxoXOxqyM)?4`i{&CQUJ!q4gMO*#%`Ea^(Tt?64}UD3|EkaCYSz z(_mPz{p~`@j)_QYP}&N%f-?91rSaLfFY1*Y+hNNm1yYyU-aJ7-xx81@$URNsl~IG8 z!Gp<ii+cmD@7N zolG!LCT#Rbdwb0Px&8y3E)nx#8ek=}R<5J&^F5DsB3PDM&%&k5r!(Ws^owgS(eZa} zK;rq=!3J1dpnqkA?Ns2~ixJS2=<)MHLD~ea(wFiuLX}cJd!r98c*TM}9LiKKKp`MQ zdG9Uv;NQ(p!gha^DG&+q(0j~GqjT03Q({fs_FuIif`(geM&F$>@3rMxn-6`80oefmnF z6Et(x#cchzyS!3fhcgd228zFO6!OVkOKcHPUu5f9{FN)LiwxTDVZ5AZ`FZjz)*n2j zr<6brT)>pHMK?Q?1*|Z)M$${W93H3B(A*0h^sGtG*WLe@Wr5(=AbiFm(xdfA*qvyc z%P-4-%3nT;z@|J=sKjfpksJ+C^7oI9zT=H8rmMilbsUK|82Po$(AGBUoaZbaF1Jd4 zl1UUEd>X&3G+;0Y4ut&&1q1fq5e85Mv;MY$Lf+rwa6x9-t(`!;D9lQTko^^4S1 zI=FE-_#t)t?C!I|k%viO<$}uEvqlPs;;8q@GEImfgVo5>Rwxh1rOhhI6^yzAGs_}YeS+f{^b6#Fc z1fH}F0+&K$aPuojGQ8ECR!^H$$5(VEj{e#M4>SWO( zw%4x1yQH0WRsPa1cWcIHW4CtgtmRa1nF`i*iqe-l40yC^Xstq-$a;H-mZ`NQY>3T*_ zYVtH_WgbgYt^8Y=Pbe6`WQt1}-HE4P_k7W>-@L}fAv7$o>fXgsR(Ok#Sa!o?OI#da zPuTh<6JqPVb-2Y4{gA zC98zDxgU&9wyTikubH+@m-)lPbCJx1(>##s`|Qa)SDaQP%CAuNU?fLd>Ud=Nrn%)S zMA=&HhrEA8xX6f>5~+59H7O$lDU{-K>zDd+!!Ry(X+0b+FCm_s{cpyXabU0T&!Z)U zsSjdxB}FOZ{?j}^)yWmB(bZFz7d}_Mdk+{d)}$^ADq`=1{n=1a;YJsUsnZ^aqCW!= zs56yG`s(${{!v)$9pboH>Jfj~uqATfM7md}R+UD+Solp3f{D*SeGP`57K;(6X&pnTYw`a)W)}V)WkQD8Pb&K5lh$ zC{f}dEf_~N_qumTN_|K*!!BSY*i2(9tb_jaZ9=im0~vocx>>(t5A3!BZ;Lz*4Wd<= zk6f5r(4vrLrSstRMyMjfuzK>%_sT79izG|%j>XT%eRL|NMIv>zfN8Fvldqor8{=YI zQUpVyqwaK*CDh>{vvV$n=Q?h+u^SY7*rptc0d+WgZie%)hTFun-s5f7Kk8#gyh`E7 z$4I?S8k0hr%)fLJzK5fo`(NcP zUdXVY^*YWAyZ!quXOjJEgjy!xYYLHONz$qyRjy-*i9iwX z5r(>z>kBy7-@BqgnjvDLB(0&z588a(6C}oBGW2RYrrhw?7g7OFiK|*=8j`>}e zuHjUE-bNN7NE&wnzmqEb#}7m7NyAfbaGCa-!3+>`pv6qO_vBJBNJ#Eq=b6jif&AwG znR2pC4-~<-9!a3w2A||GFiXQpC6D)?kxG^)>17vQq%L_=D5rl^0W_XF5O}%PzdBzA zT|e7T>^%p*FIT*-%2})#tx=feU1VW~bOC_v17}~8&H15P>#NYnWcFy(n&npYiHJV) zZQ%Ii#Y!OyeHXvoKs76C$UV%PP4A=ZO21u!objutGjuG8(--<3dTF|H!$nhPpr!*7 zF0PutALcy{PTF)9L?rxP^`ag2Jy#9a_K0R1UDJRpdOYUZwVJ-dsaQXGI zAA#0$h8aWxvqO1IzRgWD+VAv0s_nE?a_1g3;P0TeZ+o9C6Jq9nySjCB6(?7nYev>$ zhZ!%Avi+4fLX|NN4Msw__ESMH)abwF3at$4G`nm)TMmd4H+uH+UT00ruk$Bh$wf;` zi?QDZL$->wq3uZEZBvR{q|n2|$!eFN2W*&3I(m9DnIZ!mSO!f1TT$U?hzf7`tyA`< zF$_V!qgh5JpBo-%dtr>da3PgC&Whe&HINq4d_hvR`-Pt3C7A&|e%b`*0W6AHvn?Np>eLj#0$B(b2}@eQV^fli{3G zCQ>kVjlEMuc$$*QzsBU8S8Wo(#eYR2J@%QWduXoL^bO?L-R(3>@}}hF?N}bpRc(72 zLD&2rT=wVRZLNNDL@ZN7BKv-wI_OJX({(r48?v$v`{hYx`flvnes zn>k!R$FgG|%dTdSz;iRpe`8oO;7tff(*(y(VV<5XVf^-k7_o^`3(fBgEuzcZ3T)$n zVtORH=?-%_lM)>rvuqKB#-Db?t`%y1b)_&|M16;$Tu$A@Y;MGYzbW{>jyYP3fIO?T zblG5ZOZHn86AMNyq@R2&=dEC_J6%BV=-pTTeewqq8wY#d8_Q3zn4;?H*6jyGRq$y( zUGno~&Bu0mxDi(FOEvJ?85P`_;bM|)-&-H4vtfNlN(jaY2u9(m1%FkF2p7Ubd^#tP zm0Pjzr@PO)EAFo9m2HWDbjuy$`!_W$XWa2B`9e_(e*hJDeG_KD$thm>^yQYwsGOaK zAfNHSkP~_ma}x5871SXzO`5ISvEowK+a2xZ+jlJ?R7hskj=ba{aq$gSLwI*57SWs^E5ElbAdoRgJ|}(T(aT3VH1rk$mCIM1Pj+v#^c5AmRZ)f?n<4dp zA@8xb@mHQ(=N5gvnQSfYne~1tZFqazFM`g82b-gL*u_Uy5(8&EStaJ2-~hNW@{4f+ zW=_WFbvP7vd&_t|N9yNk>7fl>7IDLV5uR)9|5L)joU0U1)(Iu|M{uMB;)K0CA&JS} z*^8PFEC{LFzT%oH6bnQ-%(h}6%ExQkdjETx-qPNw_kK^D#AbJHG zk+mBT{R z=dv_8ncyva)dN28N}o+_E!5^IU>oe{TP1o=4^nqGQs#P}0s~W+ka-Aosx$xgj$2we zldRtJWX!W(&;O;*CF8J+P|gVVk!fReoDTSQ%6|_`oxlG!>&j2}0FxZ+Sjh*CUwpnR zLB4uG-r7uCn!8L~WXV z$qXv|MPT9G$egk3)STNAkM^}JP^hM4g4C@aI#G&N5CQhY%p7V4Y`f6>BmtcYdU0PB z*q4)4+`?IcYFgL{bhX5g7Ws+qAu7po-1A+N!S2rPZOK{t*?-@};zXj<{~|8;Aya64GZE(RKeemuP=Ry*bLkLsZpFN($@@k>oAdqZ#G7XiRIZ|b zpWlm!83(H~Wb6y?CO-RC#bMN1Z?19YIc46|t_w!uXu>w|%-uq__dUWDzijb2~ z^hoWsTpGdZ7`U%|*}84`A&d)v(onR-QUvD}XsSIgdZ%s=1LAQf=3LKvOA-z~eIBY^ zQ8mWX9r5QDHh=qZCMkTN52B=vFZ+x)2d!y3 zX$#2cQF6U5KQdHWfLX6)4_n1&Fh0r!>r)0wO{n1WQ0Ce0v+`Fb|zvgGXN$D@;Z%E15t&61?+c`lbNA#1P360RqCd!4Xx zhY_6{LK5*FUFXu1vGaNMqpik6`|cF&8VV~`jq2*c6UpV~GdqxVmWNV%Ygurd&w-xS z06$FYeGt~YWjJs?xr`0+eu^D$z~=64*4aO|&E62awU^V0htuTJEFRqiDaPgeqoXg^ z9QjJwyx`rxO3U)F#1bowj$jj*{B?uK8Jo<5y^2BfDW>3GZbdInQ{+NzaHa19!-7O%6Mb&@?4he=(h@C9m z1|{UC4%MvKL)rwYXdq-J^)t31WNglguUpc@dGo_zGa$XP>AJ=5dcg0O;ok1ww=a)H zt7V>TK$`Qi3ty;+1Nh^_PcN>0gH*_5U;!?=j(gcz?cfCgUE4YVji7-gB`phPsI9UG<0c~=J&8c(!MZI^E=Jl|4 zyEkU8U5}n&$KEa~$Uj19%Zak4byX=`DMOltFsQ+M`Lo~7oUfbD%P+Vle^e;Xv!6#y zTutjK2ZkdJO0*zvgcJXA;wi@F)CJz*p!=?iBfiz0OX1@W1yVlP+Y%jK5! z#cimaMY{^0vpDRz-ClFg3^LOoaqN-b_rj&+(LbL`I1|v;c#0iIn``TQ3UhYTIP@*YuoBig zem~u6*}aVLX!Gi6PEIQq$k>^Et8qh+-YSdR3FTigsXopc$a$pLKW1TWhmeDCKK?F@ z!hm-;jm?^h=P(u+6~Xm|@pZE4TRXe|urjlo2Bi!n`bTE9s8ifQNJCW?s+7p}-qzWO z<>~eFDXG+Gz@YSMCB%2{VF{bRONB?%=FZ!lNNGSt5SE@i3q92DxblJ?*0(I{4O*tj z&s4q9WBwP3_mKZ!Kmj-ZUA1VeZA?vZ>(?1Ei}Lq0c${(|hfN@uqFraPr4GG8{L= zAFxMldlJN@U+;&Wl74a*>g=k;8MwkD7KKKBx?9@(3h~036hll&)?@qXK5ujEHW^Z1 zbr;tv3@7pacS_TcZyQ>Xw`_m>;ZOmApjkbU)xqE5mqL|3CvmN7_#nIFMUkM|&KO7F zi^RYdi$f0`JT)>Xaq5MBoP@xuNUnGxJXlRO9hIWTyB~G!4bO4>fyo-jMR+XWeUg2( z*R&U)%EL(aj9E|g+q*hFY#)($CH2$n+6|Ktu14C_k}Qz{+^z0yq@mX3&B#p~cUL;E z74&_PWAljKOX<7pAG}Jo=(zeY5atxrb~8X0GC6QNmxCv>|3K=nCnTDUW2-|WV35m? z3MOYx;xD2?00fyqAXRYdaZ*M}r^BUAX;HWtp0lA0<@(GQf}qieAG{CAD2u`ufcKPI zUd>{67@r=8_+cBKU7fAds}ZPLX7?h=tX-m4Rh6jih9+u{%7%~za?-tEQ;XAesbmZG zoD}>1CW8v*f0vBbgKb`VUU}d}-eTY&(5G|C@RSw@s^Kh+U|q))8bM6PeQ*L`k!x$L zkyUSA6Z3}2bo`5!hrI%lW##JZUzvb}EvaK1M=wlVTaQO_fg5GWu-g|y#HdPY;<3L1 zn->hyX@lE-(b?0}*XhqJUm;63Ak#Fx+v4TnfD!Yj-d1=4JbHZnHwU{nuU9Edr>FD2 z-g4t4Fs|>ek4xEN1oi%|fqiSze`l&!LfUq-Iwiz^quJ*M2YW+$t5~gb-HGWip2?RX z(_nqymy2C^;?fJNd^|%yTr^oNhW|!{pFaYBHNkNSVuUV5nd_jk7xST%R=4 zi$+e-Jt7*x3-$`p7sILTuGqG-*qA$4DiLU9L)4$=L87rblsbp@)Ib&M__^;Z4#&l+ z)ds)qp3lPX0Odmxn3Zx*R%0F$xv7jPfb;%vbirlgO1*_yv*1yL?PPRpuWrUuF$sFj zGG^lEb62P!->$wC3NHjL6rSI$r+^=YF7xv8Te<4M;`^;Q=s!Ve0|0Sn#r9ZCqXprm zV@7;*C(!q3=_)JiGBh-#jx{nLE+dG@FE?%}{!%8H7fubCCGdZ~k1;+!Z0PW*+_*wN z=kKD_`2XKP{G&8wXdIw_(V+bmNAlByCplcnBaF8(#))s=0dV8?jpl8!3N5f>r(>hc*yl20 zz@Gxk7fX>#rhMwuk2-fAmY07N2u zdftcW8#d2lHAFPAU|YJ#9ewsgp@3VvZuyZKDFb3_XS?|KAllNoNaJVY zyC2+7ejG=ELQ6J-zMM3K@B8n5JSU@vbCw!LDQIe8Zu<;sG57ry_4G5wGq;ve#`R{CjCyGv}8>FQA4`6 zrV`BE<$$om;<;DHS<90jo^7S31#*R!f9h26-c|IYfHE`0-vqWCj<9OAR~9>XRC|S8g6!8n7pE z+WYqi=hsDw499?6HLG(3kPk+IEA$tJV!R4PKcc9r`I*V}FTQ~6;u7oZAq#)k!y-^YIi7;pC-FKMT|{W-(Noivz}w$WY~lGtNX z;w;H!Z7%J8uv}<5JI+@@soI2mNkI3_r@R0rfTuPg$Hs!zUb?*Wr0WTzq7rQ=eR!8`g+J=A<+fJ zqgq`dDZd@;o9rQia`5FwY}cCgWvlmhobJ{9Q zd{mzFBT-tfSpbc&pfl;L-SKCES)#fM!Uk2q7f*Yp{Bx68XlW)ReI$8H$gA=)6tMid0)?|m=`8%=;d}-hQkT=;9+UA$=1!ZRQ5U}MvQM5ZQbF%6eZU;S z2?%8VJe=a;xBe;yt#x(t&^Pbz@9&=YQ=>aY6JjchZ}hK>7-Hk?4O$HymfLqwjy@xH zMn{V+Mkm9<%QFJ+XPO-?AzZAw$uM|fp3N%fnWXpT?|Z!Z>{>WQ21S|i4~homQ40&T zE{cv_)&X3T&I3PR`D~w$O8Nc?2)qApbJvYBpShlxTAxhh|1K%2cM4NB|8%c^dEGJ^ zlb~Szb`G}}dsTw<`Zu3I>a!m-_peQ*q`xbhUG>X7e-y(9gNe$a-^f+IUkQ_w?d?ZB z1?JXxmoZ2pJsAc0cX(csXQ{+$9+r1u?f`%zDf%<1lYXqxDs zKK}TMn%?J66V6&+=MZH+J5)W^J>!j*(^ilb^TsCw6=}+lXFbneIDhN8n*m?$p6rm` zX@)C{gMbu=?3{%T$K_R=D@>LoogxCT2}y(Jy%1FX^E1Hd{__I)t%oiKV4RHSg;l1= z0T!(A<~E+MH@~to{_lPFl2oS<;6WLr}2#~4IZN>_R8qAMxP!<1!4j3JxP?s_U&bf=;rxB>D3|;)DY7W zw)j9kK7Zz~a{V4?NI1~1vb4M`B`C2Z#ixVPDw_ew=$ENsGL+KNHGx2WG9FpIL1Iqj z1J3d|Pf22i?Re?~a{;10qq-10_^P{G1<%5w&j+Od$9_|Y%|R?)&Vs-baOC0 z2wyIhh-W^wv+J8tah9$D3Lxu5ZZ#85L{k;MSCRi+s6rcfe3nIR$^yM>r4?MGbjq_h zxvtW8!jJcUXA#7h8W|E4%sKvanlr2vb^kpPLBSSxUzA@&P()ZPG^v+BSvV14N0k?0 zu2G1l_)L@V>6jRykgj0QW)Ztp!2i1MPsji+fv`HNSitO4thi8i6wRVecsQJ3O{8X4 zvvh_*zX617gCl&F$4=YFz5bZb61>U*xOb5(2&6nSpTp?eOUmDuEG24}{rr?Fzix4e zum@w{^_aMJ78VJIP1IOIG>5+}CZ2C)eXr5!A%oW~c~LX>5$9Y_Ti>ATT&#HRiDVL__-S66xgrAF{p^ znMRI$Q4}9Y&-pZWA^s$3Hj+KOJGP?2aoG=h8-)M#_<4ux*$>AdO9{-_w3pyI5S5nU zGTu8Rwmm^v%MBwY6VPP#+A{FN4sJGeRhvD8_rxcWO9N`YJS9|%l&%^*$F+GE>1Y<- zlb;LWkq7jOsAK!(;<#-6U+quB&O}-M{9Si4jDjG+ zG92N!PqBSop(^W2Wa>nprkj|_$rA{IX12BM7aCv8760zrpU?}SzC(ZexdYpw1y}}h z50QK05>1T^jF{j%9w??xNTtew&#+fr+tnvX5XO!kc}K;RU#)<^Q%`fWW2C)zGXok@ zZo6(9e%#D*Z$EJ+&&^o;^kkUz`di@~NB2q2006uqF=^H0dGlx>mVuG;uHryH7r?&r zG27kMp16c%7zd$`T!=`m!_d~G!SZ1Ss36<*&u6~dKOKGB_O36$7VGeN8l5y6tElf0 z{=%^#ecvwemrrk`&IGWQHyOz=6OzPY+yryks=EnD;C!)Rms>udQ5PbPd2QdH<|U`f z?+@bZi^G*zEflD;O6_96jS!7{U9HR93v+G`vjAIrN+!Tgs=?Xt`>-2WeX>5gnz!m= zjkz!Je*YXyb`Uq3)l6S}w+J&VNI>O;Kn0x+0@U)$MPw=@FjT8Z;vh3+ zFR{-jjvYJ69zQ;8j2OFbZ@nq|*di8VX^a499>iNnAh}w_ahXc~@kKN|M&eWj9u*v- zu67yA6jJClRFWPHamT@l@_wA&|5>pLihDh*c+ysQ`LN3;%I-!G781ng z-7dD?b%J0ydij@9xAT(5SiR+@TITxNHYD?GZ(<~i;im>lgk8&`xG$)=T@GQ7cV&4B z63>Z@G^60KmCi{4a866w&<7cPUaK3UN=Ulh*XC-*#>SsWtBzmrS3{(G;vM(??HD5H z)*A1V=~@k(M5EQ|uiAxYp_>)iRECnA6)mfO%t}0OmzoN~z_X{ZLy!L6*4}HlUWuHH z%^`WhbAa{p+5s6J-lgH(kGc=vc7KTPwXU6he%EUHJp~0{X~_-;qMr9cne&AR8}KyQ z7VU^_z%^lY?OL3~>9I|9p+p=2i3rf)f;>P5#`~9nvK7u++Cp!Bbz0>aB7o!)sjU=#dRNCN5YZ?D^L-}l zA&gZtx}OH3%I_m7WlequnNLaJ@4mzySzcYZd5Bu!gaUGM;f1!5hlPBR%osNzQ?$Xy z-39CW=chj4jSk0ZZc;9M7738Q7(Z_F^i^b5IYbs9mztfv#bF2Iu6tYjM%De@NX0n} zHZ>!-;{ydA%z$>Q3--qc=-Gx$)wqV zEzjpD6qTKJr4d*he=oUOi;Hu;R|`-Q&9>}hc}NHIe?Zrg{;z*bAFR)A{_{hsIbZU- z$Diqj-HX}8mlBWp`1r*5X+!g-R1nqv+s(?PIDu$-`Gi@qNalf)6xViJT)>Im93_B& zls}%;F-HkK&TWis`8c)(GBDb%K~|byAE)rU#p!P|e>J#al}0DTY~+@omeTtwxigVb zubyF21WMY%9W<0`fd&tE$&*omj>EE!)9J*Qx7Sy`@80Z1vgGGN z2H$@mlrnVc2SP~zvXWp@KHx3xN#P5tD!6Iy+FD;gK)~dI^YX;Jrzrf_V4$n3n^w=@ z2pV6;gv44vcK}nRAsfj(Fk1b%Vw;&`#GpmtD`%aYdSMc)hC1b2pLB9S&b96?V#NNJ z2!c8;UnFM?FL?^2dYlgSkYz?l!mtv2LT zsB8|zS6j|(&CD75{kS~Yo~txo&c^8CshQv|DZFW0*lIxA4>RE?0MPf>-dOG3Chfnn zE)YjbL=|BdZ4_YtOLn&9<6KH?IUX7M--OhV2Q|+54u1QQqw%#ydmD-EdJOms(Mdqz zDYH;Ir8y{t8gX4L(39QlEZ^L8`CR#31a)|ha5r>Xd>E#@Ws}TR5(vjdAYvZzRl~K- z#qXzeB~-7juJRFwg(;%Iqlj(VFX+BcpWP?nIw0%lmG9z7oFGk<3rHQJ< zF+B*NE)7Q>$zlaQYdng~H&+A$wcj6EYMZ0NJ+m7?NVMLXdg0ynX%p%(amX{~%?Gk9 z7v+vD-6H(&D!#PX%|vE@u4r5AJPR=u6qJw<6!hM`zHr&VfN%$JTa5)-b08}Y2Gr%?-gfGPe`#ONWElydN3$}(QyVK{)yApD(?v7h0n`kf}5 zW#2xTc~;532v^a86E1!E70+7_koF_FcY^`>KT@~ZXRTk_+E52}HJfXHoYT7-bD2_f zeQ5}Ss|iYF?0H$bOc?Y zMFwCp1VpxV;%vOgdF0tFnL`p-TEJ&zDhx^=C=vb%b_3*u*sv)6rK1vPDhBh%n9uEd zV|%`HEe(~%D2Tm9a#^lB{sXlV#-ld@rvBe>3V7+o@|U7>q17aoG%T=Y;A5qaEBPK7 zj5Yq(=HPq34stwG+ki7hu=OVoRn}7WMY~E}2x4hv+8KH+MG^i*)0$~7PlE@Nt0nY< zw)!7C+G?hhq)gke#e)>5${(32CAI%-&vT&ZXv3U>*HWId$oEik@55YN>$5r7lf;j2 zrY88vOey-nqlzhB*r}W$jN-UslSI|(A>_nP1MxL^sTROn0T+%^w@XKOXlIEP7Ahk^ zE?)YRpm|-tjao&q1Kq4m6|q9V1L{_n?jrjLsDCv&0c{MfuJ>B@Zht0os?qCHY&Y-w zm5pblpfCBQpSra6$x7ucdY|rc(s~#kz)Tc)JL$#HBNY$6Gyhl$*&5-Qrki?%F(_M< zQrxECWI0dcFI%%w9?WU3Tym? zipsrx6nQst=P!G8>h(mO?CWO1pD8692ky=wc@cF+Ig1g{(xb%FZ4Tgca?LKe@%&Yd-{Ini){Drm;$Fj{GdDv`roq+8B`D>c0?oD?$sY%FpuuwK ziT?5Z#6KYcW-;n|Z{=rcL0eZD*s6H3>T$cTzvpYG{j>*Q&NZ0nv?bc&Ny)IkTG3hG zUT$4JsP$I~t`68t^!W5=E{nl07XW4mDfz{F4RO2lq zqh<-y0s`d2S&L1NY#uh@55<0|d)F(-lO`~xS6!mMm3Nmn$VjwaONo^^`Y> zX~F~&09nX~vtF?w#9_b0gPq==jE{ZSb`8>jK2#F$b0YG$2-3-wVW_$I5$DbFc`Qu? z50EGP)RJ>IV3U*XTsY@_;u{BRr$^xiHrQ6?7Sr@?nLI9FuL!m>C9t;OYRelz=j+tm zG=k!d8ab*#9ZRc{95^f;n_GmS9Jc>m&ib55ot`I3UG_*Fev`U+9da5i8x9lXkw9LW z?P`T{`4f^vvEl-EsVMrSf5%fzRP;bcbA{xg8bm{&tWDe>9`j43NrNkO#Pk(GkVXkT zOokrpe*%Y|%e5lp=MjNgq|Gvimbt>S8o)NXB0Tt&a+ zVj~vO-LsG6D>keQHm)Pq&V07Bt+PMay1LFE8Q^9*4a!kj=f{_>)cqRZ){7Bo5Z6FF z4lH|v)|-svIzlxp9fDMF3dh^*Cw#!eU5j8j5nm^Hd^i$zKK>w1fE;JCjx!1OhmP`k$CR*4YgXd#|~uBEa^ddGMTgdEe((HL1^Ls$=_K zy_i=qF$GE#PTVTzHQ&*?ERj2#^Np!v1k9fl&c)C3+@w1W9l=OwO+IIb_ey!3s11DPLI$bG>3W_}kGj!4<6peSbbWn26b-qi1L!R+8@2X}K(!kp&A_ z@MNms<(*5eJtTSotgwDuwap9j-TsFWxR320j9ig2U^o0OzZeK}ej2|1^1myu*O2}v zhS1=O4FV$13au@1(x=Bib1whx-Td6UUBQ>q8MNNYHfdE-aU&Ay?pn?JlKnxL|KItC z0ZhDmbTzB+j)Ox`P>_Rz1DM2hqW5rj%a-&ry7H|+t%xiRQIWm%v|%S7^-3T16cw$} z?Zkak0yCx2GoP(66c7~T>NcgZbXh=e&oW54XNHu`Y;A7ZSX+M)VucqNJYD|z^Zr1w z0nbG{V<=O?qu)oSG)%wFJ*%6B*%5tC|2||tevE*aXfSFfOzE-7imALsFfpTq1dPl7 z0w>~*>3DvH_tVB-N?cBu$4P+Ddf#f+wI+t05ZhNIK`Lt6h%X=74rF!8PTqzGBKzgo z>61$7W-G5S*o(GhaL=$ogo~eNKp;6cbjhRILHF5Oiiyx)!}I+3g3Wk-!{ShY;L$H~ z{9l~`H=vXOBa=U?38Sgl0*0|qOFHih6ca}X$(c-{ z>Shi0It{*VEjVPbyge~A=abC6%rtCoDS-n)FLU)eh|iQF4n!u7^f{GJTb4db(X-_h zh9cFHg?aQP_oj{X8X4Ij0;oD6^m9{l)V9}yf&y?^f<5X5uV0{d>#F~WZz@nF&1@-~ zdJlgBkd*xK`HAC4-({K9DuYM*GMah-M>tpMZNg*B$Zr>1oQav1?y>V{&z^~sZ-AG6wq8!?%p^=9mlC4H`w0!qTJ{Q+S=MSFgZ8?vjZ_3T?ksJ?<^|% z7U2)bv&5Ruxyc19iB<%C4aLLH@wprdyWi+&?-}3QO!$g}%skqO7Mh`vc$$D`qGlVc zc84=3wxa3{eeNHBD#gSYd*QXcw%bLpcGSmX>7exYl`b;%#MVmy7-qKcV^Q~{=L#d0 zx}f~fP6l~tzZ>;)e#1yy1sI?AVZr7RFb4g!>cck$*;lDQ zJ}^PkCO=|M9m=#TMdzM)9!`S=Hi!XC&D2_v29xoMimvDj$^oUX=sTrM0&y`h`sU6H znH!DF^qM%!XX0XuAStVW{ZXn)x~hVBv%#ZE;||mJ@4ssDfF;MZWt<#d7Uc&%9h#5c zqME%v7F0W-vTTKTJK&V~{}IRQ`p7!;%o`Umtr(J9+C#uos!6 z(TyjsvbJW(;IMpT=;%2>+I};u|E_)%`t6gGWk8d4HxgRNB@N$aTt2s&sd8V^nt3hD z0;`*Cyq_Fp&h|1}>8H|LOZ_e!+|Gkmwa-s*$OSmx(tgnuW6tYkpO~HfV{4db+WEna zu0e27Zc7=PYLQy9EfxTwDOq%sI-h6?U}7C$CPXSjPt}2<6mVhY#x!foOs55Qol!A} z;w~jU&BDvzq|A_wD)N^?(flQRy+}u z*aYEdzsrTxDf^t3e#}t4DO0iAFO)E1))3|w7S7&&Gn_2ju7*e+Ugv|Sm&LlDbyPWm z==5-ISqh3a8Q{ec-r4kpCZrrd$EE?K27Z`IV?p1v>L9^6bi zqarDoQ${Soy!)djm#>ikPG)+8}v4ibA~$D2d*rZfql#yzNb_TKNi2u=sL<9QWP_d z2y8T`t>WmE(c1y-Co2?d0dHc7Ml&YrHn|f=;4l%_{r@bS?t(OQuzc2mX|NJ3d|87$zRM}K>ezDD4gM->!$L{i*)K%>bKB-k} zbU2N9WqC96|DGW=sttZ@Ozot#T7GV4l+y}FQGrWwN{(eW!#R**;d|8JyG;b{0bhR%=Z)k58jCnolp2BNk)_3ghd4BXMeskdlos*4`m_G2Ybsvw$B?qd38#<#F0Ot zilGG68YtVMB&|_Zx!8grB$C+_kzg+@E-bt&cE6EkAao&+Y0vmJk_c(DnfgPWw%hnq>yyoARr_{b1Zt2{oV{R;PcDmHU7-^a@*^^ z=VqnU!4t_JocVl|5YxL><;KFrVmAWrQsXWoZE_nY$s|L5rqNQ)fYIJ$=-AyU`a zaW=G|bq5rvgpNh4jwf!$!^jE5;3c^y$ zWsHuUKzkgZSHkyIK|N{A!Uq#WmaG*n$S+o!#FMPz;IJb@b4p6=>grBRO0rHAVv7z8 z&(0wzsA_BT0X?e;@l`sHPEU`u${>h`A=(T(ReZJb@@6C&hS>JvHlFa;^q#e+d83gx za>dGRk<+aCNZX=c>>7HAe9N-%Pw@ak*~W+rX8khjcG#ShLEL-u-$Jb^8RcO}bQuJg zw}G1ITl;8`8Y>`3PEu`VtL?M3N0{Pb)cUqE3%3$A3VNQ+{af%k11&W{Qpcy`v9T5e z%%(RPWwD05NlIcI-0s6~@TQtp&%ZB!P_?dAW_t1f`siTmh0jxA8};t{WY+gaBcwdr zWmoe8ZhOM+h^Zi$9}q=>sK1NUhR?mGaQR>Z205#%*fI$zDKX)Sy6j$Ot8Y83P&=Q2 zx0v-rp{IzlQ0nYlRCxW^J)8{u~RxRFfu}7 zeD9UblAy3y&7iNS3Ehz-YNfCDe#y<#4$%sRCsYb50xca*YHz6YHTb?|#g#-@MLB=2jv+ls8qjQn0 z)Zub>b4%e|=Oc!^fB#;I!h=+@ug;RxQUp$kI zBF3-S3F23CdfN3e@{<6De_*yb*33>fm@Fh_uPM^Ne01_b*x82mvx^~)S2#arm| znLu_A#g3f)Pv`B=EpX(N#hnOh$B-GPkMtojMZ=>~`!^mPXKX*wE?2V#PqoW%-h{28 ze;F`Gy|rjFAcsbW?ypxaZ(9>G(Y+$MFG!6{TP6OC-c0LNPa8Ssl{i~E-%NXko^o^B zWg=46epf1PdXItXUXTh(ua+lL)bznoV>^YcMfH!Y9}XeJ0H1$`$A;l-WTMNd#e?!O zsAm4ADt4ka>_zqtorTphhNV@Hmc@kjr5qy3LODvI@$aIY)|o{D2?5ck(Y;rVxn6II zZwj48sOZYjx90=c-|5!b+gtytLk|egUh-;s_;GCT^4Pc)3Ga>T(*M-o6?^f&Fk;Cz z>KoUBZit&cLXew<>M1@Z5fW-)ArW0e0`Y?lX?#edNrHr19GTsZ;Y2 zVV2U6_1lN**4Da<7^ebG&@>Q_H}+JgI05MfkmcYXILS$3X-w>LRgJkcOA`V9nH#A6 zR3s{o2`p*D|AwxqRWQ9oNs6&3oOp=vUdhc)p5%voLN7vt=aYQ9D=7otB3s1hH<8Ob zX8@kKR0DK^J;yI*9T)lD^oe5$#3am+NJqqZJnPWzC8E&!VD-zHxNzxt*i?%H6FBtO z4ZqFG@x?Qrb3J!J>Woy4cP`I)+{)CAIt7dflz>7Tv-M*538e%WE0MEn;zO83!O2j( z5<4uogaD>vs6tvkUu6Z9GDf8DcavT(Y_OSfaCkS9ahsFzNZ)NwWZaNoR7!x10}3ym zd47D9VC?41+#X%2>&4HY&qkFr-11mXBTE-7hoMV94WM2j3y?PmdV84B>)Z*InNSkF zzcaW$2WHXJ5h?fuVf_Coq~O-LnM(?+C@sAU&=>&nrE6cNb$4bVb{p;8>Uqmy@4o@W z+*j*-Jly@0vi_}8UUpCVFaED?t08g_`d2U!TcOcxZ=zlBk|omV!Uu@OzF%tqFmy>h zWPo{@sJXc*kPXnEauWA_2f80!_U4#Sxz?jll>{EsTsE9Q@t|nre3Y1o$i&L{d(PmP zIs{^zErzBK(80g#g{i2q)yiu>pDsbL;GvZxUtq%3CeBeo*f!tjQQ6kP?6Ms1R48f0 zJok2DyMRaaXWrR@qeqn`9p&rcMIAH(8dLTHh+-q=Gi|s&_Sh9Q9{%)tqJjT96>!AN zF1gN**ey0U3kO>7Scrg>%dtqUd<;01k$1mqcxc3Yo5sfRL0`L7>u*|U5B~T+v=_&- z@)OULz*wiul1L+Q`RPy+G(xCAz=jb96Nm^a+TAm+(6uDNz*yZsw7mppGaP#wEoCwF zD0KQI$Kkb8oIXXdX?^_dKZ>tO=f9Qe{8n|->z@P1R5WH~KV_MREk zzxyJ0CA;>DbxG zQn&S6u^S4)Ff_C9Q`hwCiXcPVmjT||F*IU1B3bgGEONOCpR=2cdwzCww z?+hOV8@EOs-W*d}TcTDP>19JoRCb)cz@bwInP8(eoWF`mzkG?~;E?@zg~;Ri@%JwE zrN#R!A+vR8`XF0Covwfcfur&}w$BiaGNv$I%O$NSAIW?6CBFH+l`r642FB&8{NxeNT`Hsw zWM(3T%`MR=OV_U;#n*-yM1k~LOKelyui!5R^nZKOp-xTi=!jCs8`7FH83$;Au=JsB z7pW8%$z5#K*+`y1M^n)EnN9Kh?}{N_PeE8IT8b{G?`F%6MkI&@nd7&}>b&%rzeIE0 zroFix&3Zi1Y+D$gXAbCn(50DUC|VXfb%kk1ZYk+@e4$SbcglR&*Wcmr6Vd`gk{YF2N3-p;L||ZXU~&6o zgpeRaLfWQYl|9r*i#_@D+DAucaks{TIReL1RR5{7u3*eN8tPfs(&>^Til~1}>9`k& z7c3FRtCymqXaT@)boFxdoA5qVKW^W?EY%BM!>hwv!&~7w2JGp<0_XkDexbK=k=oriIJHlLX5eXs16o?Ymg{5Y8=>ECw5j%PcUFh z?zFBl8{887ad>`om-B3)+55@Q>RRIc5kp*uKWeKRqatrxzr?(sV^WCB6$EJog{`w$ z+uMsdpIi-)^x-yUMCZc4QUr2-ujqsg!kDl=C+^Q&TwK5hVNYXCtvj#r!fCLKJF6^~ zpTFDT3<|`;Em$`re$ZK6s9}~XMnbt42TQbw{JWSiMG*b$|nk**Ux}!y8amZ)ABK6 zDc4mGm(K7sZH1r18xzooWxk#j6X6zz@1l2jXC@Dt%d;EtA_h#Whin!9K!wtW-Q3&~ z6bjfPa@l~Aea5L#S78tDx6|v<)9U#QtZoE@DyZF-6}Gd_*{_A`7JL3KJ)nu{uVDOu zP)tmohpp||P&mmG0y?PEj`C`wvjno6MFW<(?FqcIiEtsqu(BlMh{?P#?z)EkTCK;e zEJmW}=;#XF#y$1=)rU`6_l=nq6=ZsLFJ^UOK6rgsC&9)HdAs`K5yepi(i5mQ`c)p< zKG9a@XVNuvH^=yew6E7Wvog_~TB&^!qm}Ia#eQTzkSD&du<$&GO?m)-;#D2}x#w6B z0R+cvc`va-LGArEQByT1oICb$Cv|H<+3*RR#&RSCZSLn>(fmhh0c>{`#P zDLc?U#3!Vfrb|7wdVBPuk&@>dEaz{~A%8J10dk|MLWeW)VbkF8z{cSACggnoW9voQ zk@w`~;Zmc`^v^`7Izra`wUW(OGsnUQ7%}R5b_Hfo=L~T!Jvff{H>qcfo{d+tOsR;F zzL4MDRr?x%hCjov)M_;Tq4iv|OTARnv(^94)v*t^!SiI0p`oF;aLj&Fua=3etud&7 z%L&lS1xn`d3cN`=BaFiYArxN~yCq@O5TVHZp4&cpP-+STqr`K>pbvK_uU<2T5*{qp z+h?~u{+WL{RF)_SUUd%m3RqJ=@j{;GJ_SlY=GL_m32-VIh-+Ido@tw0yhP%>7tksn zKf6Wz(>u1%+8O5%cZ1fwKfbLa&A5way6Qt+dhBOh7_E{6Q)}92&s_+K-p;PA zro%B;_+ZWes!4TF&bLgaNwso<9Dmch#{1AOmtT6sqn?5{=1?KZ#6B?IcS{;9FWO;e zBv@MEEjJ~l8kLqNxJ>vi^yLMz_yp~I$4jI4gKd7NEB01>D)7{lWMN@{cGmIgl4edU z!hXr8p1bc?BG0(0Gn5*F8jdHx0UUZq{xU7<8Ow<;(%;yWy68|WWG30M;m%U`V)*$z zj2c)z>Y|&N%Ql0+eZAu`rQe_eK!DTYi#WZ0e|!h*ud3&Lxe#;|3~?+yD(lhO@`J|SY8p|u zT-9Z-vEO?eVCi%&)lC+GV~+^aFyWgy0>M&lg&$9J2%cAJ0e)<#Xz zspeODW}N%GoC{(qQm^|w>1_cZCuupg@Zb6;=WjyV#MO^Qq;!mPE3!55K&!p1&!?VF zb_9W7pvQ+{@6hLFD8Z6c2ZCSuf8X7+W{sG4{dU`SZb^eq zHCtZb%oI<)Z5-Q7$FAnv=?Jm-Dji|9FC)BW1h^4lMqt-+WSHw@=D64+=bAr;0C8sZBr& z3m^lCUbmlDAD^|yjczLXc$(gGpLz=Rb@uku)p)`Oh%=qX|ITe*-C?ZfZuUk4j?Euz z@65xWBo3B|4z7L4AQK&`@rUPH_@rt{$(iB|nsEf}SUzbVFn5NW>})8BUQg8kMRgsQ z&ZU0KWm3BRO0)Y>5*IZQ3O0TKChwR_HzfS;pPgsh8~Q1k9T8y={3Yc)tY|4x4AKC$ zALf1EzXH>U#Jrvf#N@V2WPx#kZE8m}hQoQKYP?*|+YnadUp; zdD*=3a8X~Wjch6}&qjfm7x2a~_BodGN016s=3{8&wWs#KLF)!xirn?5KKLD&OUPj} zfCSY=Ggp5xy#$eTu;eosQJV6t?9A-|GQmvxO|S3lsGQR1g#wk)NSj$ZK;pXb8$lvy zJRD`O${CJ@w&)|!TvAU#af~b%hsY;lgFkxhbhd;-Nxq6l?Z2jzDp*V1zI+CCfyr_7 zNlByPZ+F1ByYu*S|au>yNweJ_v zhbyTsB6r0~zR$A;SDgljfjeXJUkH}TEl`pKN{-3twmlJ7tC7_lZk0FCDyJzt5(*E9 zvMzbf0^lR#9!t2cD$ z>V1xJ@vH93wlfxWSjO}4_a#O8MkT8xId5FEfH_7_HZa=uoGm@{nEmoN=Q^ubiabdu z!BkNU3oDVfF8P41)PiG+_qY7Xv)7I{wRxgMiZpW{+OLd9x#X*Wl>$fDQ6szwmb#qfdp~G;T%8qEVtIFnV4zZUYe157@@6AF4p8YUxDK+r*=( zflhS#?D)5+vcFut-7Bq~4|?X!NtC76h*xgg8J|5~_36!iTRs9_{r{1-rnt%R>4EVx zN&qrt(41%-O*vntukT@F5Xf-WY|TZk{IJ@<7hJ0HzN{$M0br*a(Etz$FB|0X^modl zeJy3EE!xtn$JPKTL*qKReEK}2`=2Ax09$4SX$K@E$rvd|$IysS$+{UjI7qu|wkkut z4|Vy=V$}Qe#4O%rm||p)4RIx7=O+%E%EdYBmQ3&U%N;MxzuX?l687F2nwy&&TWg=2 zV~efs4bzh4FgontCe~&se}(muT49HrXvnw4@A@4$1I`Hq@p@QVZk8n4->e)p0##+h zX=3G{-96*Fc2pdn)o7Ev1mnSq4H?JZmWBMbg_$4NvB=kU0a%ZaSB3~-K-G9L*M>TB zy;Y0ai;|Ys?<(g>O)8#_u-ybF&4*daM5dJoew$g_g&*uulH@AR1>Uc!i={cy7Ik|^ zuLu@z)PvgtQQ^txQv1)}hRLONRV$2M zbt>?mLr1nsY>E4%K}H}L5ovhm$-mV_#StGKg~X9~8ryrrH9@~q!7iwiuO>_36|n99 zKm65O!54*ho)+7HFx-^R1H7CH}0Q|FpU$ z8BXwpWcpDvBpVylE?_A^i*OXRA)f**n|9~6xouz8r77wnXs-U?S z94!)%m>OluH$kAIS&jwaP8yu12liIP@zm7Y{R#+Geho!Z?JcbFQxT$p?UN)fBUHn& z(;vKerPB(OLEB$wX{o7pkB+FRsYL-HZLu0>A~4IcgV3N8MC1|GqGh9?YaznplfgFD z*_cN2CN4=R?aH*HM6t*aF3pm}i@LfauP$o89a=FLAjj{*&8^A^T{&Tgg<0Z*kP*Sq z65m-?R3i#Y75eBiq2noM9=2JFY;)|9B%fKPVO3HPL$H$Ac{i|x#GU8-nK0&N8&ZRd zoa=`CFx#K;(aE6+k>8S~{jvUAz({oFo1U!Z+k(KX`5%r_FW2;7LOM>#1+~UqxPO>P z%~hYrm>3lc6O}>uA9Eq7^2IxF+Z+&OKx^zVYo_-v#m6FP=768WRrH|eDMiurFCFs2 z&*HvUkf@q+aexuy73I_)TR(DWIgtL%Psza@4(WN%iOGv6_qPx@hJbW=d}jPpNz&>U zc5JP{)e(9LJuqFTO0TAXt)*Pg6 zK)ec&#GS~Y`Ej+bytJmYurM?(zfQ4q{lV%TRrX~ZJD?X6vft*1LC zYhL8?EaL++>?KqB@^Q+SfNfg#nP>jj!D8(^TXG-l_D)DZKGyjbmkzph&K`0ek9YFm zDTEgUlaQnTb`b1Z>iraCsj}nGz9SFDAX2fGpKU18HkZJYi^G1YMUb`X|95WS1H)(O zP>FX+ob9-TPBqP-&%84Vjp$)M!z054z#SI-jog}_fZ z$w{EsLI7{NAj~pgk>1=~T3=t7m)CW?G!jD~%TZhaeieEu?xd2{Z$2|YPxO7Q?=``n zUgl2$^l=rllq4V`LllTI2$&-|4N7R?z3uWX1mbl7Q=HSED+KUB2=Van@bOdUV2-4H z2#7SWRgXwGVmBgIIgj*Cm2VO5D1^j2Go;&m70^{r_QXeElHw>#P>-vj&tJrhkQ_w?bXODL zSnJG=x4S6CJ=<7eSU-WuvHR6p&Pv650Zce)3l~;Ezl^{~-8Sdh3b@g4p@^(ZCO(xn zR4^ijV)Yi$H_Q*GJ#`Y$#uGRy3AjCtl#<7~;za=3oF)MQ-D|fSpVb|Y*AZpg79=^W z?J}=s&KS~Mqyk??{{Ve;6K5(*FmRki?l)J16vcmJO!!B0ag&Z=74TktTfP3q8}qVw z@ShO{)#12(>1|nXGR&W%a){>I(`;;-O8}wz6ja8~iOR+z5VH?ZV$F##4cnc4eYbCJ z2x#A^vL}m<4sJSSZf{l3XJ*nd?d|R!TH24AIRnzDM%}uWv7}*BfV3w7r0G;w>|goy zy3!WZs^%wX!1A$T^S74E^wLuZ09@micoe6WaX=8qKlb?_ z45JtELrje)ApeVP|A)0MLS(&rHCAlev7^VX+wJ`Mm08lLNvX>^g8x7luf+f)W!K{j zn2Uhk!#s64zV>TY{oZxCH$tO$g6ouSA=e8R_h5UIG8tnOKO1%lMqE6B^q_WzyORe? z#9IF*CdIJ>fqQ`C@mY!y@Q}Y>$$t$^*?XD>TD$c_Q)GQk6~3Ea^kL$6?~wu|Q?;~; zx=%o$SBrzwh%&+<_aiCnmy0-dKZFhNz2$_~^^GTP#Jlmg(VEw0Qvo+D>N9BoxlR87*b zaTQyh6saf^IVMhK+U#48{^~6JNdxi?@kOzC{}CKux;azh-{JT}Cip1Tc&)pdp#ZE# zoJE06iF-@%4eD~D#vT>OI}sym9QI1FIkvs{8VogCl(q$HlA_bA!L>=8X`CoO>b0Gi z3F^hq2CSi`+%Q_{U#j6AEy~=*0LAb7`CtreS51-?iNvplcmd-0g!Ruj-n)ue)dqwZ2R$k?5$Dc zPbYs-!>4W_JvdnKq#(Ge(n@ofQ0&Ycf{ap>xEu)3{c0)Ns?x-s~iymuw z`4km32&RCdz*34*NQZ_6T=Uc47*Bo8V7iCv34>zYB(j*XIG>emHZApNXkGQ(?-!6q z|CsHEKp7ITLbp1q#*t{tz1T0!-%B6J60Gp4Hoy3tYc|XEQxyg+jKY!VrQ5Nt7qQ~+ zj>J^PyUo(qapZ`SdV3F>FBj9;2Gs4@+POVZa>9aCe7c6cy)`TFAent8H7<>a&rLTllD@?>re_E)%1udhiVGIo=Eog5 zQHnMcF(fh+Ck~!i)^GTDIr3w%>}~ryfBg#QPbff`ga1w2oeN7mf3Gfq?DXAe<-8x7 z#cr+i61L&s!hG7Kjds>4q}}Sj73LJV?M#YHQNRSwzE>tB&SO7kOSa5+#Z~=;L^5il z;TO)^A31ZRGRlN0EbBt%hQ-cN+A6R;HY>?TB=iw~G_dC)? zGjw_k&NyRQ~-KkW@PqzZ~ETkq3;c`rO=I=dr%eqNc=)o~}4*$i(7x zR(xXf?}7)CGYQHF67>kGy;60HFc=6=9~UOlf% z3`zOe<~f=d){n{eW|>MPEXZ_Kl8Pw~yr%x>nH0eH@ue$&~30$eQGt5nEc^ z$Dh~yU%RKU&3th0t0_{npZzk zSttZ)g*ZxyhecUF$r5upStJyL_{B&tgJaNH!P~D!>a@w%KH311MSjPG@MR$EpI_G2-4}kHg()nN=G6miOv>$dl39 zxv}nNH&s~+KjxXCh?9c?kE>t1{GPjR{p2hqg_y}rIPr;Pij`m?X}o5Q+=yzH&!c7Q zWYMp8>fxF_ay5*}>`A)BLc#h4boilu=d%C;K(D`^0TQb)?*G5(gg&u=ShR|jCyRO- zF29s4V}u+{A2|Zf)u%GGvhwg#bH#7tT=1@)@Q2;~!ZaTJP^Th%+RP#dtesM&{lxKg z?njpPXETFdH~~%$j{~QdB4_kwPMD;O#X@JYD8Wo$6UMDEZYsmmrub;W^NIs%M@e$&uL?Op zy0;9adrN7B?An6R*C_mVKi^&)4Q`$)aDbGbm)|A+>-QTd8}@#BKSZi4)vGu4B)_is zW~a#dIhjhVPWeoXVR~|^9=cC*9Ju{PfR3pqDn<8-i-K3bypuY*sBrhG?4;ORteF(3 zfFq6lV$)`G+jjGj$22XFwdMO}UkB5qtW+J`^LjOMzE@?(BF&VW2jWIbkD^E{ocYG# zET=CM{eZfc=~zJiq+OfzXENn=e1JUqi(arKck=aKa%-*sD(*MCU?@&FIE=jc-Mhfl zupLX`pw=$y{2g06Pf95=}kv1x?D=41YbZ0{#*184#kY!LbHOOiS6PrGQ8H}#c z>ArM>^F$m4N@4LNVL|#c=G74pC`i%zSGw<9_dq;uWh^13w&5LFave3#x=6`E=w!hN zx=uCM8tO4fKfy?M?ECU_vIYt&Iyr0rBrep7mDPqtd(RP8!L@^LDPT6B?WBDyx$_q? zDsLqj?JEEA>-B-wDabB9yOUIH`s}dAC320E+A`1CA{4^rlyfoUoLLXPVePdg`4bt| z-q{IL*iY*lr=-SEr~ZeoGq_#0Z@5_4*S~C<1qPmVo7M6#Tn4DZC-D(yiEas}JI<*S z8)=SjOb>N?y75*GR;Vj8xC=5skuCBtH#r&^uab>DW~@h6l+*)4zS6ZNx~UvRLst%H zwEY)^x_Y%RD}F1S5>EQje!rIIzWh|Fb>(M1 zv+s*gmMUtEN~=K+91wbgytl=5K=9CWmneFsY|2`;QMSi!2hYb2sY0bJ5(aV7St&KR z6$+DT6T-uDB1z-%<%oZg^@r~>c-2K<*h}Gdma-+UN&J1Gmo5@+M`_w}}0qg%Xb#iE>i zvbU9%|0=#I?!C?~io6Q0x~Alch8KDv<{KEd=tMAcYE<2+>|qA^n8=;@Bo~Lzm|7-b z2^*G>U>qzJSk#LlT~6|v#-$NjVSV4*K?BFd!NVV??^iFW+mynbeyx)XQdn@~+wv00 z5r9!dT95ikMq3P3P}nl^FTcDzL6B$rXMEi{)-6=!ykBg)dyCZlX?7>S?YVxdt2ix* zk}+0Ql2A$wB#Bqsr4P--q?!u_1~~LO*V)hO@-(wV77YmXc>wG(X-HkF3$=8}Gyj{$ z&f=FFn-LoNQUrCLF!Na@J*~6Hs8Sq^FfMtYMf};Lg=z^No&JTWVTIP6som3OcLfII zD6_7#v;2-xy!>+es5h5$fxda_LN|PoD`MP^8=20nTqbr$(yp{4hrzLCtK0u$k%j*_ zBt8fnRvG7+XwgbD`+!uHW9zil&M83$^kONVfMMC*YlHfzVIxMfb-nJK!y635fv`oS zq=tp<1Ue4PskxO;qrXohk_j}YqDS}m8@`?8^9y6sf)dOy9@kO4)%bHs-ojOj9jzEUc#|dS&K5Y_t%80@NWQrg?UY0!K>-6-M2mOd=46a&d~m<6nCK z5j`|$Dgt|KF z_nrObzT|YuRq4IF?{>75mTf+0yb(sKZGy~`2ngfeykcUI(UN)`7}N@|7yUc;E50xh zxC+&@D)E+RuqOd5d$CK2N(!z7!QtxV+WjT7tA8x~gGcRQ0V_(G+GjoQ%9Uzg*|R5O1e&zP}5LH!Whc|HzR3LA+j8GMO^xoGN{ z6_+kLLx-so`KaOAMS99l06XOI?vlsvMf^Y<#ITr*U5QKj@&AVMs?F%>=TRNE&>Cnl zmO zih@)W46NlK{HWsSpje{`Q;;Ob>eY$mD^Y|OPrQc9lbX!vTOjjBUsa200LUGFA zh_TtU=kZ9LnkT&dar74iLzIQG>CW zJwqueYBdV{$E^Wp9%R;)3~-5*)5i8JIah%|Pi>G+pt11^tf5a2$sXxJ{Oc(}rmQHo zMYKIEJrqQqrbp@*SA{Hn>ipA>t1srnlV2hG7H;{a;Gj@UnT z0WbhhM(D-3f{1IGAP`PK4){U`qym!wT*J>5R2Z`S0U;uw{#rCRFVO^uYc0zCHt4v9 z=mGIgdi-lXp4z!+pLeVxK)q6@o0a}1KXj!}D6!Cq0?d_7;NLI7edC?I<9nxBkJ}r4 zF*lj};GUV0`lO{`&Isdjn(~a;<8^$VX0yqe@}K8SQKsDAO0b7=!W1?qeqLPcU+8Z_ zC~az0K|i_)Ui3zk5)p;R-Hb$b3ch5CsO4QO(0*Qw0mWqo=qzG-LBYg`4C2xCn>-=)TScT$X#d+ zVoTIME3$ZRL<@SRDzanA3FH5|v1)rEf4j=ZIML*4F-ko1gwl-BDmB4NJue zrXUxfN5818A@_=*fr+FA0?pBQF8|FjR+8I$o5Nrw)tPu%$?~@a_qK}?3q27Y-k1AB zS+VNJwGMR-%{1tHT)%?`7xrmNGrv~WAsWHlyk^iWv&i>biEur9Rw z$D3<^Nm3a2Yt)fWUDQhvBck_!vuomEkJmpfj0dK^!PJgWNImu8Bk)A8!( z@_mEKQ#=-IHd)5mDLO%^ue{^LYZRq}KHQdDjlPu}cTBQ7l6>-EQjov-cW}IxyF@oz zgj}p71X7?*BW*+}qheadPI@A^>T!>p)%8B-*R)@4_1zw!)fU@$uCVkQ@`nu-g!xyG zb$*N=B94Zq*NG3`8__Zaq4IVH<%-9u_FxfYK+nJR(LI@!lAbZnJ!Ov%WkYQF{4DU$ zD!?u#W+%exQqD1bbvJ48MpTIJ_bqn~bD$O{y@TF?Qk?ZqXS&Zd*xw_>Ta_t!5*NLj z4E$zkiK^%U2XyX2S@*z|&%_%NYMa2lpX#(W%_s8JTsakDsYY<-HTLHbbySzQRpR_% zDXs-iGezAOaGr^}P2tG&-YopmoM`46dqR!ZR2sf@?q=oes!eoeN6dO|ENd=)o3S5 zQL6AjoCaN;CB_Ed5jfH`=%xMD%d*Q{!=d%Y3EjE`G%X)g>TC*H$A?ONpeV|TQ*kGtTToPpuI?95fg2EXf;bFcy z-k0gSFtE9v-<$lZKS6QZoa?z82S{~xICc&ek+8Aj(|hZmb+-@jL$Zd=6lq?EM{K_O z@?*&R=3PB~1ZV%b;A+Fi$zFN^`9sS|{OF;xq6J+6#O~pr2DhV3&K!(vA%_^MS1Kn@ zLg`=qvBIIEK}Bnq4{8!eJ`DjxvH%XKql4jKhb@UWF6S4KYSqW(GB)UCaf2M1z1ZH7qcxic!JDSgHS>$@wQ`_10$nN|TYR{daOwGoqsKFr**< z6b?V63Iz5!$h3cWcy^zYHT>SNlKt~@XM?-pDk@}2C%3M^sMW7i3a=6FozsS)gEnaT zOKyAb3fuhn|8Cg>NPL|6@NL?WJV;kFFPFZEbdZ@uhJI;svgNWww0^~DFjLff2JlWg z_W^9a_oL3>cDN%MALwNjR=`(D^roRzkx!vvU-Qf00hIK-f7_*$XhdB@farMR34(WT z;MSBahvx?0f2LvOV&TmHX}g=;rt|%6wCcRU1}De&4&sgQ(k%Ddq;(JJ#mNTctch5A zFb|9^O^>fE&kGR4f__%BK_C&ib6RL1dE^$b%_Qz)iFj9_`eMfN09XPE#o%HAV!3(B z#pUH;(?KJZTYb{t(Ic_B&jl5@&NI2>+|aN*xu;~pkzG~c39K&KX|03rLwU<`kIn6 zfb>ZjmpD6J44%i zL)lnGnSYB&Q9%-r%Z+cxK4e(^uN_vy=uzCo)e1sjM@n)m*bO>{1uK>r1|4S{otEkk2RV6tjoJ? zJWPAj=WL{g-oLMQV{;_>2DFm$!>Dn;#6+Qr%UV1Eb|u2lS35%b-m-l=S}y=-*r~CK zU9g+UpRw!H1xI0FZ_XkG%52q6f%rj=@-DWCm<_MiweIEAp_c+62@r+~Xx4s-vX_W{ z`g4K9ZYguBq-ye=;ztm;n_;fmw{N;xkZ6{tn|lU71w;vEyc-w0y8j$X;CkUASQw!= zN)=0CqY?+a+y8?JAMVsiUxVU_UmL)-=P_u9plzh^U-DViw!W)h^0b77ira8OMwFB` zzN@PZMqQ;xO&e)8l|W9Cj#pEYmyqzkES5J$TnXC6-&0OfbGdmQi7c^RurToaE~0p! z;k|QQ_`El8gCWo?WPptl+bqIO<$gQTL}b#@hlYCfJPO;H>S*nO;>~?=8!K26raB;~ zJn7CAj7bjxgJb!9mZJsro$t>#HgT*%F_Z)1z3N_3KM^PC6A$Vv)wlq*(5^*Cwk*l+ z_Zenbf!S)sltBMK|1{(mB2U)&+DEf+c6^!<$xru>{Y=6)lfMI-WJ=^5D zO^pgybh3G+;!9&;j5x_%c=>-+vw{oa4dU~fztSNOJPv5|HBJGrkgD$=R@8JfwNe^& zxkDw2NCCm2tgO5H>`IGwW!pp3a`AU$?XK=FO(Gi(z?TBBu>I6-ly!BLQ!nn3V#;~?1fO?t-)Sr;Q? zBH~z5ry11HN~kg=#=F+^(Zb!GG6Zmh0}O_+?wN|cT~W`QDWGtOMgpSdGvC!w)47Ok z&tO{tEJhT(JP*Jbw)s;X;&VPdEAnt90n%qj8XX%wpv$eV$uk*Fn+8<6t1aHM{F(-U=BXqGxby2! zQP~3Ksx=bOv5U;{FsQFT&JY z$WND133zFVa6pj_6&q0ND*Ls{#}l4j*^osiDiVw^{DRb;uKI8p)}7QIt-RW%gH4{J zT|Mm>{OpqEcE^vztn~RVq_P|u6?0X7dZZe(v#z@C`TkWkrGT!&+rtzZ(=qB%U|if> z*sR8hkB?7e^d{?QcUM3F7|Ny(_XiyOv2@pScW|)0zrjbmkO1T15irD=an~XlB#}>7 zE>4nHokwR0pS}4FwU%tRpnHu2v$plQ>(%+#aCq)>?RGiN!|v%y^fH+! z@ducAp!Hao^51(!P4~;6>KwvCLTcj;mjkGgav3)yIGxQc zAr87yZ6xJr`{!(DY!A8rZvmfPKLatQ>Dg43C!#XBxxJaS*E(t#nVU=5Xi_)^z(#X++xStaCK^-Z#}PztR9tIyz6X$!7ot!>{S98Sb#dPa=mOOKE}(!&>76vuTW#&`|<~RZqc$(^a|e1E+_SX=~q~ z_4hv7MJba`S+lsLdlXt)w>r)xz68*?(6D~nc#p#^s7IfpbT=HxEd6kw%(GN~T3qmQpMinI z`BQpE9G@A*TZej(VH`C)($CyT(%7ia(b}FVFl+{HZc`rKDaSscz+T=Yv;b_SYk=b? z>V09m(%^W1<0@QdKe|;7XgH@08pyQ?ven9;&;+Bd(T|wi+J+l0^JU*#AqeoFRACr` zFnE>mvzGnO*2YN?X63#TC04ytdnOL2+VQ{n1>FZ~y;LXLMMnmWvhX zNCScy?N~?4A1 z1BA;k7jUZZ*Drh6rE4||rop@NdJ=QuslF+y*hz32KK|VqeeC2R#E;(Qv*YX}2RB@d z$l*GlbGjIFnZE6NZIsQqzn!c->p!!-Vf!iW<$WaQottCb_NcBf2*n~h7?3&4tXFuJ zHQ?6zkLb*+VV>nW21KqO>#^W{Dz|yOJHO)bf81R{x2mH&3-*r5h<&jr0E9Jnq{pyy}Se(z=~AVI3w=^3E* z&g5IUbPjz{wb`HsOc`Kvxw*TUJJ0i%YLsiYc-_X>0ri}Av)em+C3{WaW6_NEHKEyB zOI08Y-nP!urmpvBl6!82G7?RZ<>R)RV`8BzR!O57XIwLf6>lZ(Z&gATRTJAREhCWw zn2~?NWx%83js?pxg#vht>bMyC@gFB<1)tQ&M~r zzm|bff+Ak#N@fSIACXb<*3n_vKr;fPqxs&40jnV*06p~lylh7gVQq$li#o0cZ9qji zydryy3yACL>ltD;^p zB@)_=ZxCYXMYYv*bzSl5LN42JD`{Ufl*KnCea;R*4KcWV^xL@JHt63xg_Qsxmey5c zR=Y@U6nH@-;b2+bYRUGfcBrf}`B^WnE$dIkC#OS&@wGQ`7Q3fLTlT%L-_LRgCvf4T z{7VL&BxmU6jX-I?6s`a2uBVyunVwDaIizzY0t9KS*l*%SV=G{x$$(+gWPQDU%cR1x z4AP}f5f_DCJ;&m#kwzgIYrTs9CP3etO2qr~ieN^h!gX_#ZCir;jPsmg>{!7b-MK3TAzd z;)b@iwOX8;r;YSQ@VcR-`R4~Dzjo%>UIIX?nCNIEk_@y4gmg?R6y((a9}XS zE3TS3E#IW&j7WQ2JN$J2vhhcI9MBcF{vc|HO<$Dx-ww}K?mCnQm^n;I&u@T8guaNA zuU+~-61M*B;i*L;sciDtoM_$kcewEHmHvaWCM`sUpwVO1_%843--@o@0}#q1H$7P! zR;)k-0s)L8@qsJvjjxx3!_FLnBrh+Iv8xMa?PKd=OeFLXjkzHccLEg8^wwcwXl{y) zfZ89sv?D*h@w+BOba|Qf8!d2R0nO;n!NKm~u73STJwO=@)P&BBw!pW@ojwVRkmD$* z(l8-QFF}1B1o}ytR|_Bm&*Gq$j_azUGdXP-S2y=x=tNx?hPU{x6-lX#2Fz-VD2J&b zMmF2lNVpr4oW%FACd>11fOYZ2)10Fd^ltp1+!TdP9M7e%)F7RsHWcAVGrO>ReC)gg zlIy*6J6MPvG|PojQd19a*C=qTo`$Ib-pfYkZKCnZdjLfWjlt;X{@z?zs9OB{^qMW= zW+ZFWv<40Bb5|D-Ok{^1z#E2%iTSo?9f&94OSMI)O(_H$LEq@R&?>=l*`8~b(9+V1 zU9ViW{@Jyl^ZXtA(qfb}vPTtx9^fZnq4>vwkM`R)&7&4CfV{4^yxhCM8zr*(CD)R# zSYK#Zu_B_oS3+l|`Cw~b)2)e99F)&JBk+V?d;s9K4z%4)e(~3}36)NmfFYY>!`1)E z==C0CXClglEgab<5|2^&-tG*N1w!G|4}%A30C5@-asKXsBZcc2LkD?{3HiK{-it#g3`tR)6IAr<=(4kwz1 z4AO8bCUMB9`^(g*BhOOJ@)1CSVP}`BSXW-|J6$rpKU2PMX+O9PG?5fI7^{~j*BKj@ zedCa9hlw%WzZy3MTYuiRi$#KSQP^IbofC>(Oih|>wnMb@VblI_(`ZGs4vLjf3sTQ+ zsxBRy7|U8<5ZwKA)NzCDE?wzvx^+8q_?jvM|IhHX-svlwd7N&%O5ZN`)#JTcKBinD zHmGw(XTp&aOefUr501VrcgwMga2Dl&_Z(!p|o{EUjW2mFGGdu>vrN zWdUzgQx{vEEph_@rm&am&5|V<7tgJ%v>NwB#IwE!PCObwECc>AXFYdYTNhIowOE|4 z{>zuZd612vJHAr{tD&@?{`eIetI)^?*tshbPJ@5U2*nRTEA~BC&dQCQ*4N4u4SVmO z>^DjcOv=TAzUD%Uyo?Q8_lJcA&agg9QP>+V_MTy0O=z}0`~!A56`%$bS#H@G)}fj7 zI%eG`1(~NkQvA}j8Q7+fLcovj=Cc@Uf3?U$t_W03fK8M#7oY_vkG9`Vtqsvm|hiyVDd^3;$ewO6p zjqID@q!_A( z9(w5c#^-zA^PF|o`7>*NP}Z7z=Dx3M?|to^n5b2=V$B`91n!dmIEjrS9>uShmzTB; zGiWrZaII_rKO_{Y%C-1SrIIGiXNsB|;c9FCACvDfDk(`PTFj;ojNJbkLQEOod&5n% zLhL<{>$T3k60q;VjRA@YBX7#N6QAZMQxdF#&)9r7Zxgm?L9g5WmK+PqMX@j3|0{61 zul^>PAd(NC`%tF4dNLaqzl)ry5i zlfJwETwP8f2f`o#Eew1ZFu+88s&PsE)p`O)!6PU#aq%neMlO$XTqR2_qcpf|Jh-vA3 zMDido3#~dy5x{uH;bW7b(gs22;*dAFu+!%DTZ& z7PAAOo0xw|x^-7`evJ7sx5*-pta&)4GcXOXU=kVy6?87zGJoQcOe;^Al&mSm;uH>^NTBujOLw~=#O|6 z(bT(7ZrG&jYn85lk=lJGU5QJ=zd9^i|DvsfFT47oEnAqYT#0pNnxzz`JQF$+wvvU zaMx;M4*WeS3F!ftlbBunZeZ8EeIsbNdiedGVzk&$Y;8TthnRCg5gJ^h5+AiV{@^|# zT&Sur6nL`o)W-ef8k_(8x3DWX0KRka#lNFmm?GS5qr>T<(TVW2C`m=Lm-#U#mS$7* zv@`&dbo6B@ki_GH`@p!_Egp3XSG#d&QyRoE_qqY6GV!=|b9M7`BR=x?QX_jzHph05 zR_kRG&AWH$7{a!3z0oxiFXU*Ow$1w|9icbPjVX;0_A3@a(t*!rh77xAl+E2Gah2&y zzs8D&E$is_R^pSQ{yL58U>bECk7;&)(&$B2XPcsmSA+Jo)XMJFu(RGnx1Fdr61{0u zx-RTlnX|8*-G~s?bLO(wS5~RHa!rHQl*m}Bx&OC z*`3789A}zoD!Eu!mL|k@Wo0&p#<}blg)(YqTNYvLALM!^Am>i%tZK#7m*7-=i_YGQ zy%F!F7A}P5%el3VS+n=)YwasVf%9`7>(}1CD_$#@auUC>0*=So$%_1yd2r*| zSGBJ%p&_q&OoiZ{v%4r?Zo!`!)aAgawHWpHQ;wk{1n6#h&L4i|LMiwe%5 zo7?k$PTPcED$bb`o?80jBikH(j0kp|fP`wBhsTA}D}v$hwWHe6IcNII_HmZ0?(LAm`o>J}GHTirbh`=mZ$vwS9E zZ(MphdR?XR=!84(d=vo!A5+HVQ}oy$Dipsue=KOR@LCu%4rpL@I|T_;BQV2?L%#@8 z8j28j->gR74u<~Z@X!OWZ<@Ub4C)-y#kt28!f2xE-0C)R4=wBTuFbtr6GlOnS9n$5 z06VCW;o&NT7g&?mO-s4`{POuu<dLQ;zz!YR%MLur z_O8#7i+^eM@_kjH#9_uU=5GggA($!dBpIxbQ~OC`MnQ+vQM~aE=hC1|@R2FAOwjtd zRh}>3usT%DRq1ZMz@0YT=NqRYS?gl?Q2+N>8jjedk)WfxuDv*d!!KOFi?#bVII;0l zw%ip4b^u^1;-r~Xg+ z-+%pE#-Xtd4OS~lWiXgT?W4GsXj9aZ2@zR|o!<{C6+Z|#DW)cO8l!RE8+76GJdcE3 zWFzNG;owmZoKCOUw%=msk~Xmarq}N}G}~t$aU^H$y*|Y4gQOLWJU9mD3A#yoZ*S^3 zS5Y-+*iv$;@sjt2fPat|!ge)EIpwjYVc9$tZ^}nuGP(?{upahjVTzQnJg5&r2*UN@chZB6PXrpvVJ{h-|oGgG^U9-BHm32Vj9s% z+jY3bb{>o1Pgv#y6e4px8wk5oTzBB9O(p&doo8HDVS(}#pHf9=LIb<mF;r91uVk_V&7O={+(;7(}Z0m3$v`VsobbQ47j|hU-N8JO$|4LvtCX6 zq|F$iMMO-WLPd3wji^72t`j?#v_W-rC4ngx9tFOB$;WS{xndUI zqIF>&#Is2OgvhdHjeM)x5z*g@r4Um!paimDx$1_^g^CQ-$_#a$Uj*?bLg=9JcMhLZ zcC(oOnCkd{c;6i)W)H29(M3O9^?0ckqDM`K4B3PtTu@6 zI?ElnI>89gJ*nZ{TLvhc2?aCJ8G2Z{X{&tSbp#=S;MOo6lls-CH|211>Ssb5<3%og z@Y?=U%I+*GqVBSfhL4yQ1z5zlmvnOIgUfyU<9NPH={PK|T#ReTUmSsn(=KJQ^fJhs zv7O-CIy@&{0860#{aKqt;qO9>m9h6h4EIQtXJmw?=B6xzVZwL6MIL{@M@)41Y@j-B z{{!+;F~n3OU|UrFgn?l5XLA}(B^k`vD;XF6>}_p=JoYo$bpW6!Q``C!dznQzWJ$aw zuL)C+7v^aEo@l=>WB-T#2}K^!Z?`EUOk3?>A3G4t6B}EoYha?0s$Z!+w10m7-3KLU z{sWd~anjoxQphtdru@~jZ5TGLFN!MSQL}B38A%W(zsfqeFGJJ!_s7$qQF2b+NxM=L zyCz!eqH%uEus@oA=qEePxLEG{Z1wS1cFzDXG^27qFSuSf$F1<*)*}ri7AXYNyieUN zk;J7fX6S9#)*L%=%-Ocrt@qSiz=^}qAX(h(NW)KG_OByyhX*DEdBWc{H|&zW><~2v z#HaZ!%k)JK6~em2p~0#Vadw&K>wq}DaBn#wKEnpLeUKt)I^BQTG%7(xq%dQ_!8|)! zwIbx-Mf4|eBt~Ks>g9W5?!p;{2L~Z3!NR`^`K(!os9NngDYF62GcWMyJ-!_q(4gXA z_{L%`a6yY-%t34hVEbJO);|7#aX(oTrx zfySGH`Yb9O02_^mR{lkIR?wf@PJVvc>9@v+=n^ycZ*HqIWeK2|leu>+-h&$`iO+{y z`o`B4-O4Nqo;uS{`01doGdLH^CkEi zEg8^U-!20ZIjJ2F1)slp%TLpnLCy4`((*&GMHzN>OYE74-VO5>XwJUaFi}b{%slvW zf(k16qg!5daE;OK&XXU}6v&`naXTy*sl<^a=_nWMH7{FxLjjOeZ+S5zA;0aox@viC61a} zo&5T`s=t}V9rf|&r3?_W!zw~l~7}2+t+SDm7D3%13ARz^$K!u^T zSauGz#)P%l`r#=^3pm4-zBU{lim5Mit>MY@{9zGcmjyhraMjdLNZ3fNjOvnp$Jo|k z8dnX%d%BG!_q1H*}eI_DYmnN(jp#=j7xL;+fB^Xtu8u7#)N ziP;(Mf>MgkY_MPTMzROf+fAGY*PBfvO+PP+j&$xEztqT=>eBM&G}R)@VDeYDm@`WL z;mD!D@FD5-k}wDKHa$hDvG*l+(D^_to7#oNw-FYOuO09Y^xQT)IX1STiBd&o>tocpH9o`2kzb0lOCdp$a#j zylcu=LH(1yT`m7eT&S=d>_F1$A@oPRC1t_jet-X+x6PfMD42nX30Qx|*F;1_6!KJn zDRuXX_hh_KHee>@p>Kf2pHP7Yx6TKm=JCu|qriil?_jZ`JT%wzb}KD@J7ys64zbPh z1d_x1X9ThL4yJ8>ulpt)2@yMU0ryUCb!Mk)Nnwc=`<`P4`ggl>F{jt33HbHOornbTckce4Z-odB;*(itN`hIMxuQ znhOB*E}SX{HMBv;I)Q6UIoaM|QIuG`y8 zjN_;G=6Btl@5eEKBdCdTMe7zN-I99s$)4wE`%ZA3axESfqcL{X2?!Ank#ROzVk$Nl|){Z)=V)mq}bN=JZtX zd~PIQ&zud9=A~&$0PCpUAypCK=f1C7Y@ZOiP9h>qvkxizXC|E(Ld!vET8#f)P=NxXVe|ZtKLpVnhj&GKw<8M(|`ziN* zg94H9Mjv!*+j>XS0kD2zbOJB`xf>oLQeh5YF$1e6Ff330s3wU`%6oNi%sl$3^MTmu zpfZC08B+-h8Wv0{q~O^1Fvu)f{%}qmY^SETg&yP?k{U{=-%f})*o*+|1tFdd<0(M! z{`bBlg^4Fi+-zqPBbO5z9eY33Lq%DyW!)sz8{ep;(s>AocbQixn~G5Q^=*FL^7{9x z&%cEw6c8fKgNm%g0|>KuMXpi|#3A^mo6^2}$1YAk*1w;8%G9-M-&c$!*&+cUlzaE? zDN@)u1dTSKCO16F($m2nrIx>LQ7E>s$Mgka3Or`1nY459@xlVl>8=d!t{h}xL`?pj zzXgU|ElXZ^ykO-9aRX&dzOqj=gK79%Q1!|NftYBwTM*m8&fhqEW54T`NPrDqiS4Zt zjRNKNGrgo<+IFnbkZ6MG+hnopck6N7z+~qO{Q=kzO~6J|zyN~qH!76J6JwIj|I4`5 zj2L1%6^`d6dPg73i}`o?cV|%k`ts?_si#_*W7Ga_QV>5yC}>g_$8Ksq7|%C0xYp}l zR7wNr2T$~x=U?>xg91eFZ6G!8h=kEdki|efubH-lG6Xv`@A5sP0pf+#NORnrejqNE z1TVLvT&<7>v>sO0!shNL(;Vg^9c=-aIT+T_;R(jz z8fE6hi48BoGu1Nn{c!F3E0lE6gRrffqANN<86*nQ9gOzo3QXTF2if5#{-e^r9J_G2 zINLiRO6)t$J<&+97tZ0T^n-(TXbEG27kfcgoYhrS6ou+XiU0R0|9ik2 zM7rE>X_^s7G!hw~&Kz4?>d(TK{?s6^1zBh?o@8;4Q^~fo8PjGWz$%g?mkecc*{1h2un!y#fuoW zeF2P4_7h8QFp#9?Or{-ATjiY!f~|sv>@J)}$K`JUT#9&KFD%%r4)&0Z&~b8%|_bPKs^9I$KB@bWM%x!nUDfb6ham-z z@d?s=;b30kZzOkqPcS+wO{0Y6Xn8hm}H#FK>^zXim zTD;pA<-|iQ)EGCKS6qhw+!X6K7STWSU2?2*-Eh04O~cO{RX&=lJYz1uDbB&XeUxiN z(Fbw&$4eROjODjon)xw`cUk5xIN&t5KIDmi?Ug(`V9=1k_hU1&uI9mAVd9D-CbaLK z&4!CtB?vsgaEg$BC87>}zS!ygw|4-v;cCe;=8ZQuq~Iv0IHM|sng>4_B$`I@x4ua; zZO*>G)mfEjX3q|2JPCdqPsYjhA=@Ui@6zdFU66osI=6M&Qe-C;*;#Rs>G#G;wXx() zIXs+dj*@*ZiiFmCK2<7?jOY(D!FOGr94)1i)Gow){;u$A4I|AjN&!JpMn+|3E%O{; z23$5{T01bCKQ_yBj2|K`t*}zRuZjp{tmK$9SKm+p)hTME(G{~>hv1VBC>mQ)geoAW z@GGwi>cw;1utto^AaqZ!_-W1A(^Fh^>Cc~H$0h^DJDiWc7V`YX&W80A@T()4mksTP zVdaLG%9_N%9s*@kOK`JzX1m9C=BM z(?R%MePh;wBa+2quWKsdExk8j>Us!qykbQ7{-^nZ#o)>AgE_pb2gfSeVxPoyqUO{D zTiSim@BHrCFiCv2nD}_IO^&W7ff*4~K@dzerjE$Hn80@KY*GvQ zC`o<)OIxqwn_2l8)qWTa4Da*-H)LsaAQM~|s_CS=Y$JxzI zr9|!q!gFNs(YZ|9tN7XFM|9gx1dZ8F(118US%UAbeJw*EAF(O!l7(e$433+N}|3U)ue=@hur)0U)? zjTz^2FduT+T=@-{|5O*_+l+`$1~Xd;nEc}yf+6T&qwMZN*M38K`eg-y*Yw~_rEoUpdPWaOB$lrxKcbv7~*~M=VjNKU)KQKMAz`S?SrptA|Asurn7WR z#;@M)QRHzDRapI@E`2V)+fthEyB$Hj@tggN@XG53e%#|!Y>~^aTi6bAV&^;k*`9X+ z^BnW>6eI=gSpGDOaPrZF(16Vk(9V*y;(?Pua2JOhz~m#F<%)e9O#wEk^RA0O1oPd6 zhZ}}>PFpJ7lB=xAczK^=JPnGB1&8SA{#sh);x%DpYLd7VVWh4mwU0~l_6sKNzbaQd-}KQAPal_x=KXg^oBL3kuT?nBsZ3% z7_?OOG8MgHSFooEM>C@8#LZ;0oelQ3A+pIvmM@ageZjjQXy&Ua2G2?jq`j0o)i+U!O zy#Mz?83Y5H7y^h`>Cy5Tw|T^?pDsROBT9kZ92+Wq4rZIfKGiyKhKQFjw@ z+w=#q|47$)4`Ih#9u*#bWqts1H2U8!=6~+Ia~+twb!;L~M`RcBU_zO$i9v4mdujI_ zs80c3y}252B&SpFG1$Vfnsqc;SFRz82Mf5fgOfu?%lpnf%?hSsUosm=RdI?yBbIbi zl!*PYIMtuBmx7l^QDFR^`~i!VaR75JCD&~$7e*e}H4kUPUs6t8^J1z_^9Kx11e>^RnwTOOwGxEy> z6Ch?#2~Vu!Z$cI8wRU#qS+wA*W;Z0Uu%*2{R}X9G!0zd(FX&CbKj&w{NXqe5^GC!4 z63&Us;l|~}EjRN}Ddl8m5AEed*UB&`QgFl;()3l&Z!n~YlQCacE^ELAe&Z(B2$6X-TFaWMn+=xsioI1i;n;JQI_Ir0SoU&WD9nOw5BvZC|KIbw8x+t5( zFNFvD1kqhAW3mvIMPnaq4qIh;Tj&e5K@J4mgwnpcq<>W-?J#cYa0WGd=1)JATm+hs z^1bK*HA!zO|KwQd$w^4C4QPI_RQtAWGrkC=TZW@&tSbc#bt@;=k-zji1j08D$$j!} z*YXcRg-7<`F)1I%GO1ITBF1JIC^@3L{b&^opWwdfXHTTshB%{)Hksd6zxPQDjz&vr zyakgw3WJ%Z3o?}O^8o|5X)zyRLc~aMTd{>qnzdn(F@{{|?8LiKqLt#4+4<8g!mj*< zk`}@Z*@3s0-(7U{C8W);If-|BUGv?CQyLM5wv)tCVk!Kl4hK3~XYN|iGPIz;5u?g1 zyQDX-n)9Wa877wVf9;Qlud{5HJZgWOYuY$;{CpE)DhRHsWcy>kW~$NnHd*M_j%w=% z51XM$iBBn%bRGK-VU=EO`GM!V-17LA-gnE~8dSVQ(E&Dngt*o`NU#Dj&28`<{#Dtg zPV{$rYwA+Lih%7Com&BWZlFK4UErTY;@Bp-B?F(7v|MmQ3ccRTH83P>T( zDRRT;k*#*jYu`Fr5{a}=Qb{x@=y@Jw9qvvfDad+HR_0*GpzCO(iz8Zb=7|-XM&fkC z3R3roh7LS}{`|cq=?)|MM4uyUlo{$~=Wwko6&rDzO$A(K{j6fN3;k^o4kaiUc+FQF z*|)g&Qws)B)`Z)Iu|HYjN-Q)+ls16*PqiT2+|CpHvBA^RF;EERtzl6%yVVHg9M&>_ z+JRYhmkxf1m86@njp5!~X+Q(%Ov)fFJj4D%QLV@$;*yR|CG{e>d-Ua@gyJX~zSj+O zf4rBS;t$%7Z+BjN1{R9?Z9hF2;v<7=N0}i=nZ4*tN_U;~u+h=4uZNDR4h8Lk6DoD` z_KTl;kqsU%Vw3bnWvSt=8(Z({r8O9KWsnn^RWAJ8kx| zX}*^M9ao9iAGIF!MC49}2#m}7`Ib76^uWh0Y!bA!0*CWYzH}WhT`pyBcXj1WKxVP3 z-s;fcxJT5$0)p#qr#N>8BAY)yWrv~%wQ7bMSCzNgMId0&9`RnwiP1{)xY z$`~0%dsAOel5IfPr!?*jeAO*6E-ua|PZXKTRfE=U%~9jU1=LN;1f15Rea*~jwAHn> zwe>5}-@nJ=U zgGHFZw3`Ow=qV(v^zAsms0ENdG_CZ)NZh!B1abDZcY8zP72OQC*){TBhfyo#4Wa|r@ReJsFG>D`mo3O_nA+qr-&@kyLR$` zpy4Y{DOj-BBAO#G1hcs2#{AlPJ^0CZsIuKntxPB$SgiwRvbfm->iIobx!v6MQ$>Lt zu@-uK6U8Nh*j(U9f)mKdQ~Ua(r=D_Y)Dyw2sDOM= zMGRRn^v0#sZ1FvS$9`=|b^g!+4Ni!C3)k|ius!7@CKe{EwCt$!T*L?By#n{Az>M<& z`TNY&pOj{3=myjU8p0TM^eMU>d47`6)oQWODVdyk%jy2= z<#wo-Ib6zRx!g)hs9vFJv>Hc)_~hQ}hfh(;dYM{kSet<{^Z117F`oPTwiMmoz{M{f zhVIBEX=38aD#PRauCun0fJJK54cAN#4rt$(&cw(9C* ztIN$PF!m>E{T# z{$`6+*UoXN`~{fxS)*B|tN8hRFj%3wzoC;f;n~WlP1Y%iX=MgqHA6|}R*`rJ}hWmMM7Gr+cc9jb%@6>iGLuBcC1!05(NY+>}$pfnrgVex^T z`yGyW?bUoY6pUm~1*`d~P{GFvEO?U6(N34h$Ase~v|z;(B#e&b+UcE7-(Sajw;U%1 z+y;QW_E+CYZeTHcgR`BNES-pa{eSUDifPcM~>?-ZDty) z?#NwqFw7LV&?arFBsF#REZ*-TWOVJ7apzS?tWVmyMywJr+ch`{5d%bkCGJR4z!dw;Ruy7JqE&u(WF30>EScWSCF?B9d#}2j(GzxumPML~sSqCZC`~At z8hYbFxrZjB8MI!%l7A@v!9(aL8~unwZq=V_?qBvgkT6tBx)z05Wx}#IoYfVTTNe-F zU=O5YIR%HbcwdM3ReEGvh}PBRxLFx}utc{^Pq!?Z!sX{JQk zvb*`^^#R!|Oel%>`n2F>M%U$ijey1X>@@eT-JM}L&HsOZljf@s)2y@YmVG>hKljnj zS^09?hzChkp(z@3d8mCCsP+p0W|CT}*Tm*t=#*bNRW2(^VZ)P)c|jErHR!_q*jVnc zHg@TRgj$PgBg81xJUXkc*1&@KHb4SU#0PURxfJ}hGKiwR&nD;Y9_Xm;I-#71Nvp%y z;%Gv2v?!2#7x{egRN_mGk7-Od3LLp=wE_QE0cIOEKi<_CrAHO1U%q;s$3v6O=aRO?R_@<4kTE!eN6%zsIUTGn`UX6ZC*c#_?J^=EB`gjMHX zyRPlUrq0}89|i2k*vx5vk2z(Oljx9#`Y?nm@|Ugnjl6`d7 zRaUIecFxfLoD|D|uz9>2X5%nt|Iim4&2(^dMClu(TNJ!9W(0{C?Q%NJR4$O1Ycncl zo zTvIo*HI-z^5Cqn)D|Z&l5JO2!6%+fS2!jR1kK_Q4F*@cw<9}~T8O;${0fZZ(RmZ?K;A|0mzcE`21oLpz0567 zX^PPQVFS)hg-qiQ&jNUhlY4#gOZ1?Sj5%&PNnD>$d>Wi0^rc|F+EVk4^oWT5MdxIT z_vpXBYKlsYeO~ju26+PNP-i&I+QIhl1DcGkVJv5tC$>QxFs+~@&ahlNKR-Y9B?tP1 zCR7*zD}N!F5O}f%E`Q}oTzZH=r2j-e;e}XPWJ*?M&AKJYc#mkceFS72!Y&Oo0MF>1 zE?dRt=6_pVsCvRlaWX*7i2UJZ)A?}M?m4v}Qq_MKVsB+EsoFNP(b`dZcw{-m+#HK< z^^|dLxp4FZBzG_xW^&m>D|gU+L9svqPb{?BLEiBmy{e`2g7gyF?Y2CDH@hstQ5LR(kRmxLwO>Bb#zn-!mVJHQ{ z(^K7?o|TWeqj>LZfO;@vyQbM;%hZy790=peo~{Q=8iE}@x+V}vc6HR3vlf0@>`UpbH2@(fQ#PdlhD6*FZ|Flca1GaW;EUU{_PJ9+jjI|5A?yUFEuLJ)*jguKt*ThSQnar0+c zrzJbuIk%-b&A;r3vD;7RHr#@S-1Y{rJq+H&rUehK6^o;Nu?=V+P~KWmmt~w0+{-g} zuF2a^nx#mE>SK>q{00NF4tQi`cE4if&KHx;7uhZs-A3f5HV#917!B=Ly?!?PqV7O| zBN%UB!YUi^rL{VvfKN2BQ2(M5=MBbXp9M1aN1&qz;dqqM%~Nt<=PNUHIt_7z!m#hf zXipfTbC2rL3QaRVz*wk6Gy5f;*cE z!Akl*3Jisj(^7H#n{4dw?|)vJh9!0Ux;paXho`hGC%u+<_V7CFAr)P}3u2ZwbW}=x zC4y0r%I+qhyId=i6*42ULm6rcXXQkQy(I#;($Fwe52Fu#>=~|eA2UlU9J-*wQ(%9V zL1Nl1(>-!IWpYkV*kQ9V&(N~abM5)x^7X$J(3NqOp*xQbFx248O)Gyr8gtek5plNN zhAZ#fyEhMJQvUWlupadK`KaIEutD&`@6ThrQuH8^3-<%%Jq1X#bo80Q%(Rl1xq4AS z(bJ&4ngO}Xn8*LLU7*G;|G~_Y;&3Pntoc;XfkZ``E_h?gr>O94G!P%`vyy7V zI&jZuuk6n;fG`_l|Le0$3CiyC^PCl#n^)cSa%5b3r6pKt+TFCM?Bcazg-g=k8xBT8P3T2@u1*3TfRir?^dvG^#X`LetZfs=m$8cifaB^}IUPfxB zJ4t(&_)eG*Rh`%skY#eS1F0i>tBxTUVlvgZ3)eGc8w0H!U$b`T72;1I0*bjf?|n~= z4rgRZ2fq0_Cl<*I&HC?Lwq?zFPy(G_)`6*he3z*H-fbyz`Q0@6i;*s@3F8d0Ip)ma zd*%N}e)y7j2RtvTW!v8#42eA>xyA}MKhl_^Bic^v(5=AzM{ad)z*kcJU)*xLF{Sx| zkpKj1ND;Z(-sU~LrT@sgj}oABL4HxP($KZ`nv%r_?A+^gJsGU27*+gU9#)-Aq>gD@ zr&7(CSkmP!8w#b!Kj>ToFyy(nVR7j5U_EG3=5er zJcFQ{;m;%2s)_OYTb`PXpxhIT(VkU%z*iDs9Zwnz#)W7K;hx`;bsf1mFk2a%w6t-4 zMe8BQM#aLwkJf*9$*7{d(^(+E0_3fckR z-Ay-e1pfP)+iwgzO`i}=E*>lgeD;Ew@Io3)3I2wNH*Ujo62#6n3Uoy5<`q1#`jvm% z+5Rd+`%R88_%3V*4lA-VNM#=Lgx80%tX+}VS<}G-WH>S>d~>fR+fCS&Okh5MCp)8Z z+iy>FiJmys4DTL1Hs+xz)m)Ji(nqio`7`Q(~s_VD}XO?L5; zzAJ;E0gAh3n0P{)I`mY0%J9p=V>w9Gjauh+&R!=E8`_XW_N`lN$zwt zE&pQfUa2t=P?r9Rh|akuW>At7qCM%r-lgeU$H!p6YnC{1A@S6&P6hpyq8ql-C1z`r zzCaTwRzqc;m54d267-uVaq>-T$a7hY34akRB-O*-PDLC{Qzv6L(o_WJd`$C#f(uJr zR;VzUx^jNvrpoHoYQ*_vY6CqjL{qGF5h$-NTKqPH78+?*G!|WW435nW>Q!pr8>*dW z`|9#@J#C0O(~_(#g?iL3&k5WT$HZ=jH6NWsqw{@A%x z5}=sz72TUGC_roqGB`>VQF|$8xK;-jIn!>5k>ODj@_5(x4<%N}!w#G-gD%l*K`TXr z@HF}YE#euEwf{28%@068^GJwJucqAR)3?_9h=q=-!l0w4fHRh5xl#e}y!n5)I_s#Y z;&mDJcm7k#!s60~U;X&wjtp`#hg#UqNxdWu>6GtN8YezU&p8l$Z*7SC9^9&}ebb zmi4km;_j;e;xn@Ks(<6yr`a2v*-=MXVWXN5E+C62+vqjh4z_Wd|5kFx3n?;98=AGF zA`yyrP-Ij7&tw~fd&#VoEFPfrn8N8~bTa=|Jh%7tE)uSkdvqt|N_di6;qU+<9xb^Ny;lskb6Bikh$azeA;QuDmmnm=An6FU z)r6gWtSm#%eIs_OSodNhu{K0CC(~;_W#1F)TA8b2;)xS(9@Y!Cb>;F=m*kvjHnuXV zPDW3518!_Ciljr_Th)oN5w(HpiB7a-LZI^Nc)2Cg_6J!<`>JDdMDG&WMTL(p}H(-g8eB^TtTH$_;B6Y|ITWx*{jF?0Ga7ZyAu-&_7Z5wUJe`sO@V zx^!D-bjS2LrIv4&BaBBE{^5n}9*8w{H4?XNUL%W4ep|nJ-piia_%^6tsS5X$q&P3% zuTwna*u8&blWXKc2Tau4+uO!H(zm%?^ifR1AFBQ=^i0@>Y-&4p!x$sNW6$s@dIA5b zbT7&WWvJD+ty(A+dy5y_6teNX@C_?BYv4MtEb#w1Zd;nnKo}k6 zdXo;luOcSQ=Sjqi#>Ii8uv|33f@FR54?b9U0LFz%N!wH5 z$JJ|wf1GR^N@=!U34~b-ZpTAb&iCzOp&8UqB(DRDoS{7aLGhSs;6V>?XaoKyI)LbX z01Cy%SN++e+1l9j`0`J)gbR#}oZA~G-=X;f9Prl=^xT)P?LXppHNaBTEf~t0Y7%j-*(0URq|nRIU2YwP(F4{kRnM-iw^0nr4>E@KJ1NLj-jyEMrFCIB;{ug zw1YMj(xnlZ+R@}24hRFgPU|N#zzUZ7>!))ai$_;^V>wCXhP-z)9xiOGS>xg?!qHc4 z;mucCYQ4A{6k8%~cphFn@&rs_X6a5?8!k;n($a2F`RGe}oRc$CN=3^CI8TpOe2+C)2=5I%PAhd?DYLqaRE2*!Q>R7(adMZOjQVl6oe3CLz=7 zWuutpv$8qm1NZ-xA{jM+yyoYe;5zn?&}UJW4k#aOc&gKfRAkEEnYN$7>sT^gWlb>@ zM5x5?@T-NY-pTozA>@^+e#<;ey^&ey=@o+>Eo$Z&`X&+gYK(`w-|k9%)kh#ywZD{6 z-R=Vo)>+HG!RXyxjdz#vta8YQxQq%;Zjz?ra?#G98{Ma^ea|5B(GQq)r(X5dyptfX z<;?Y$_IWe-IwC1Nk}VK^&E*&IxQyJiJ!ShVHqg#g>)WfU;BsoH+tOi9G%Fi@arhQV zn@`)&@FjXJD7~P{#|LUvWX!D_hAZGA?M3sw@KgSWHTTP#p8VuhwWZRMWq$Aslc`>|_r(BAC)pW|b(2PFvZ7p$J{yQGsO z1wq7Mp?unhDAtJWEJGt^-m9MwrRUnfjj$Q@#-@^RjWmySfYo=-ll<&9kL9bC%!d&gjUle`t>k{!d>v|D0r(;!b7U}q%9gr`I z&`C5{p59v{H-0J_HLc zYK=ZPdlyLi0CZNe@v8;oRD77F{>!9i|2(EbmvQxS>E7q-TERp+ z4r|k<=YbTtwBBPX@CHJu$qRgfdOEu4%##76RoaP*zTpr91Rb6>OqphTV+tM4hizG( z%zg<&`owitU!|KtyP&4F;tm07V%UFve201_NOtSrKI;$Z%@%#@E;YN#JJ8}HVetz0 zX!`;|+2IN#QFx|$0z{1!{dX4U+S@~lN3ybQEiB%l(WRnXF9c^$7xIff@X6?DX+c^G zU#4D)=mwkiyxi;;C=PQ%Dv?SsfF9z7HwPN`tk(~?tw6zVLm9LM*dzh`56Rq2dFl@M zA6)h=sUimBV5BrzVF%=9QRMmq$Me?Te@)9CekH5@iQL(bxE`4s@A!u7e0z%;Qpm~* z_rAnUs=stS(7peNx%v0S*Qz1wM`Q#7zb*a$|Fb=BCCuZnB>NU{YBwdrQLEV5En zJV%ykTa^~%x-kTfTnNH(yYwm3c9-~|7#+J=N`{ii zQherJNZcjL;V`*(T24-m4yN~tTQ+&^`4~SShvsR6Mk~c`gSDeLG&*$pDp>MILts?- z&uo9YUj1NG%7JT`Imz3`H0CGrW54E*8tTSDG31+Na_rSyo)t#y_&W-{c!! z-78>XrMEYsOIHfNGQgGohDdH>CX!o^jFQe|R;Rztcc5N}eKF85sR`WoSDhFf?a%tE^*vh$@H#3{k1{ zbW$yzm6hcQUWqI7BLh-M_^c)yg;73LwftOpsO?me_1tdu4(=K&ZRJl;vm7b0O^oH|HH#eH6*H3A%&^%|;q~F{! zl)1{AAjqgil9Ivg7HWd20lt(}-bIJwR zceg!U5p^$xuUn%rR(o|I;Rw4&oZVh1F4DI^sMA2Ede@(-HM1oO^vufR0UM@AL8g(L zvO2CyAxv$bbArELI)AeIl4QQP;Fc*ScgZS3$j!Z`RFA`atZ9W~?8nsrl9~AR<}7`- z=4Pk;3eN;Xyyu;J(g{f#PuGu_n^w{5%KnFna%MaTERYC;KVr-bhAwq|D3hgr zHF7Ja!@u!5jHxNW2qsaOBfG$a#WY9*Ehqil1I5ozLQioMmUg9YUrz!g9uD!%F?knm z2cOkFu$wRS!wcmQcvz!YP%klNPf@am zmm5e5Dn&|J@|2$irIpD#xgzKx&y$I;laZFAu(0B)G8<39%fg=trqsd0)OHFJ4vp*U z!Bb)4#-CWp_fJAAsb{{Dj})XR-uJrKR0sEDWPY0ATKW0Ayq({Jo6@U;A@nxGkK3PG zIiZ#EOP43#N5!q)g8a7^?-pf?-?6yVcE{K7RudxV3j%)D)wn}jp(zY!`xEDM^IKLr zL(>9pHTE&w^i?`U|A;*zqC1h1M91CxGNN~;Ifi(cUfaBiZ(RG1d$8!oURC4XZDm}o$n1NSyZ0tFCL3_@upV7+cuP71UsAEggx zY;I_yG68S=JA9Bk!voG{{&*iEMF-LuupC>UZWhN{N#L^fl&`8)BT^ULvO9Ueje>eue%i z@$^47-_V6C+*TXr?=n0;F>tV7a0OV6oC?-4({DUVL+jY}hSDo-EQ_1X@|j>aaddCR zH>e}x4)~uAs}PTzV(w0^L6rdB_vAdTC1Jkg#4R}1ho%#jQ+4eXCGm0fgge4I-KF1o z7JmjU zcglNVXCpwsiu6ouap9b+1H|o}Xb@Q3Q-7IPy2jEI#Mp-Nsk-gt7fx?R|JZr7>WGrV zoqHN>l_KzKul8uF<)HWE$Q?f3oaRebHb{^QZX;MJd*_UR%Hdi-*w|)@c)dwSB|8vX z?U0S`A8u4rxRu*03|okMTN%0;=u-=5w|Tv_a6#ywhN3&{i^m>cwS1v54a+t8T=)52 zzWaCH1nB`lqp(w4^lbnoUEb`}&oOrlT>r5SG&cZ4Zv8rmi8WV(AN-SU9gDd%utm6a zA$~5jrKPTVp=o#=gxOh)*ug9KpWWGTdlKY zDPPu=0-IQoX*Zk|h!E`T8C6OjNTw-d37o3eL+4VmAAjc^y*H2e_bV5;gRR7W{qjL) zAay;iXO7h$mYMb7aA6YAueLuZs2%>QZHn^F;L6|4i%4W?n&=B9^0Ytc{~3+YH#T%L zVi%66I^Vk`8prK`55~jZNDXWm*NFDZi|cDd6Mw>oI2?$ zKqUYd5MT*iMO7&R@Y)BT8@-h(YjwHcM04;W!wli)w>mjirC;id_96U9qiRLc5XlI&7}|7~=_F_sx`-$Nl(S@w58&MD^;yWnU^E=+C+~W%rKRPbRcP2zWz6&9@m!6>18QPQ> zBi;{vI!!~hjx{!BfE&8Km=&Z;ts+=-D2>fn%78F*JV)t$6-?1F4<(1TP*ke11h??RQnUNU zN-n?_T$pFp`%+Ufu3M3-*AM_|(ARx%fij@hCIYHs0aOU5LM~3um%;-VGTwW9l@-l4YXeFcXJ}nWA^vC4g}<3~VHfmZW8P5@?`2c>2#MK+4-LO7%6}wpWBH zyMXV4-8R-oSi_=p5N+^`kI#)4e)ls^`b;E6)kEwMWokY>C7k}K}(V1sl2sD@;($C zeCyq!<9P4(Jjd(xUC4jfX@=&7XNyABc#Ku5r8g;NkiShQ` zdZluA^4Y)?c}hV+;SHCQy|f_P`-(G(<{vbyHj2WzJen3T08(>L(`4Q$fkG50s3TJG z!pmd|J8yfTSM!(=Q_?edx)D1gz-WbnPm*P|C}Ga)n5K`DNV~zk??Tk`1{ZyM_p! z)IDG0c}H*hoVdBpPuxsOPf{j&?WKBT$`BIULks|J;mm-AubWGGJ8|Ur6mrlscIm?1 z$!V|O;@(e=WJjj5Yg`+V#T~G-(X6Cf2nTHO_h40{+^gG!fEH5o!Me(m32%OFxY3_) z(sYz+S&MO*i4MyqjXKe{O$pBffJL)Aczx<{1(0P|UGFbdcKd7*VZDmW04Mny9EL)=RG_^iSU@% zjw{y#{($ZrBWcsts71fv7i}!u(e@AAMDmb1iv7mRR=GoZRqRk?s@2z=@AzP98m$)| zv?&{{x90y2b?BbCBo;3Tz06vF@OwjNsAc^)$kyZg0U-DNdkTXO z>skJm-$m`RbkAF1%k;<*DdM(DY81WCNQh*@tTujzTmiSQNbyxN%xZ)>KA z-t1hFJ6uxe3=TaZ{|m*820YvB;+j)5v%mN26p^Wm?tcwptGF72`Yq?-a#RggSYtf$ zIRx$jA0zhuzJ8(~(7Y!DrrpKwcuBt0ByU@tqM*>rzm=6FKHuNGU&Yd@BvVJ1Iu{|L zd9g76t-esxHZjm~v7}Tit|A>O!IWlXcznE{>I9URLDu@Xi-p@}pVPH)HH9oj;K{~+8y-Wba z5oQ{EFsbS49U8VoRh z6w&Za&(OcRp~o%9he03DMhXc%aNsW9vD{3M!mIpyWB@ufFHr3=HXxtz zMXQqeT{f3<(9`u2Zw(e%T(s*~SZ{CpgW^Os%~l~RN$07&_jHWB@U^`Q+^X4@)IeZ7PTKz;dL#DB0FJJ1uw2fd*fj~S$t15 zS-P|L=D1GU0**VTPhO}!>aTW7&@o~s2De|Nm$giBDVi5)@ThkBH+(d+UpG}KV);?V z>?_ZKuR9ZkJ#U)$u>9yV?}N!9uB#V6c~9}UAmx5xM=gAWU^y60R*W1`DL*xpjTKEE zQ8P`;EJHynwdI>SVieO%3$#hzI5ZgtzkNkst+uJjmuyX*^c%(YI~z)l2%M=)J)uAS!)4nu4!ftQVF+`pE~_iC27*#SlrE(r4ILRbVsh9 z-Y3BEAbE$Xx^g>e1|vF)H1%tX5M+r^A&riXMkc0>zb0SdJ9CZuF-w;P2!`QchQobI zy&!(*1gBKLs81k_CecHWuJGV2+VcNdXt;sHAIIGx70Dlm!Z;;U>oQ-OeD)>ofgFV( zoz6GCx`Y!e;t}%!b$+oy&l!l~ic;L6+R5a=aVzt2aoTpV5ueCIcsf$fkx57FhrjR1 z{7=qFbS%Y9|08k|{Tkyz!}@xFeLwF=LDWQ9CKhWnvGZ@#Tg9^jhn1^pIhtrW8b_2Pb2Ssp z{A%7?Wh%^kicolL&W@u&e`JG%j=o}L#E|T&%8OZ)h_FjZQFP0;Eju90$CL~e9!bl z>u1-#!B+rw9+tN2k4jb0mq?HO{mZ1ExPVeUU{v3Joz+a6>C*T$;)97_NF_mPU&!^{31AVIWgm}a z`(;^_!b=m#mtr*m*F<=|;J(+t%hN;jOhy)DmF2yrU$b%Ym80MIb|| z*ONk93podWA}MR3=8M^zM0s4Qv1jnIWy=<@%6*98R}P)up5DHi?5VQw-~9BS;lpNGEH_~eU+m3yWNw+y=R z%AcRPUtXaVz2`vu-P-C#tbMBMX%Kfb_@FpX3`QVL+I~1#eJ`Idz3LQlgU#mo;Sanr z_d=7g6cFW2l=)s2kE?9cCd#9~_+aSg&-3EOEstzBd#L+EZ8v|MuCA5AoCQpbI*Xf;yi_4c9zP;37e=j* z(+`wJAL|~q6*uW?ML)uvc7&ku)Yu2#d(-^}_WG%bq*$-3}AU zVKA_vr%>k+XRD=oJ&^#-+=je7LDehmZ1WzLI{aET>vyK*vbHE3F~YVSdi`gwhHr?B05y$V375 zfNE+dOU31vaXbmpz}T1*guMY{0(h>7_Yw@a2c?Allx z&u8w}=|CQ-AEpA9V)CWwCT`d&e(E;a+e!`~o^#X&LG7|uw(EM5^NO=b=3#q2ZGf@P z05I1qk~T}^(Xn7`d#Lig4{if}ryI`Q|HXIo{7+xz8aB8h&8j3q=QGCKQ?;JVJK#*m zGMgQ?-g3ApaNkT!M%{ia5-jTN% z-cPq`+8x}1JQA`0**2~8cK#SWt~WjZ=k!V~ZvqS8abP&48S|dtf|gO+T3OzYukgH9 zJ=_rSe73wQe)q=T!JAd`{eapJ*AwD)S)k_@&KYWmpusno6)SabbJ?owMv<}(He%Ec z3-3N#njG8KHhNT=qBwg?Fo$eI2S4gkCSh;laAvnAok%zNF4pWs3qo}%Sa;(WL;f$) zOb%iNWueO!w9hHSE3arq5|X$1p{N-nb`V)R{Ch5dul`8uS;5$&E1(a~0t`V5*Xf0x zOVzIYx?0N;j<{NKK@ie-U*bb}YjJTlJTOa63vid441TQoHyG1NI^_;^S2Y3s;}ll} z^tF-+m|om4j*2jtI|vxRD?n|7z|lH6Kapuj*^)hqfl}+cjyWmD8};2y|KspxRw_W` zw<=kdD|FwqVefaA9{~3Cdl_QFDVoBS&bjpj_SDszsEKt_ zlj#$fN4xcE0ne9ZyZ_~cFqgN<41X;j%~Xofc~dip5mkEX6&M)!ySI7eItVAyg5FxI z;wUXyzF$@w8Z82+C?IbzYT%WGw3TnA8Ea=5O6D7nFUsm8uWI=&eZK%)W&qt7ydn>R z!#)GoM^|^b=M5ww7Gu@~WA!h&cWOFs4HL#r07e;l`dbJRGVwSn&2veSLLQdmRfI3& zQpcmcMdFufnfa|OY;FYYEVjL%bs_yyWi>Eb{KoU5J5gTC5h?G88wU`JHaV{eYcd+l zQLuRper`GTkA<-zvI0d98(GuV0*wy;pRw4M?W`x&!`H-%1D758Cns0H(TKn4k+ z&@w`CbuBH818*sR*j&$Dwd22}8{~Qbla9|}2Mbwh5@D>u<4e)tikR-V%nzKN)Rdot zy{O$E`ZUm8V1=4K7KIcWKe4y>-kX{2_;TmcIn_yzTGoujh}fdD9O``e{ep_5L?O}~ z8ztmGZFl*oULPv5TGlkdNGrg4yni7Tas*EQ0429eFmcE|ql8fuS9)MPT@>DVz>51f< z;9L$%VTBksc6Y+jqvf%%UOKSj9^9=Z`J!A~sVD zfv{(wk5m2_0rFz!Kq|o_^myh&2WTbeH8eDi%92!521t~tx4*x2RhBP&X`l;eTEEfr zSvV(v82kn79F&$70pTW1TQZM&i_flLMETi1P^<8jOspvu7Y&DHnQ~>X5PrdNiUCCU zGEcwG$KlhtgBHc9bAW9=%9}tV9BowhBs5>@t`W!f7LhroL#pEe;xx4Ce=u3^1J=S! zB9|iN2!ZB?Zd$T1CgfGamxkIloPji1?dl9P4c!QDU(%}hD*<>iA2sb(x{$EY&FH7Y zAH-a)bM;X@h)~<_&i;qcdaf%FEX{QD71ssz*RECIX#fA2mwBD$9x`9xdaq^4Z+#N_QTAD)e z9&iLez!DEwYYr5xFe)w6N4n|G-5JK;0@q6%kRp{pfucmAGjHMyN{h#h1C3BBVaHu> zYz{$g@Z|Rwfw12{-^f_i*`Y~Ru~JMSCN`~b2kx_`-#%}bfp#JJoff9<^H4se`6)G4 zxWly{Jn1+Ijz^_nQ@gz?@d~&?LmIIm8+OGb?=mxiBC6+vt|Cxgw)-}`_|;+b<5bFV zxd6rcp#xS(FMm#ghbh{dUEBUax>FX(n_ZaoW=Y*cCzKI8BDc2^h0fU2@@6j$P_|gM zS2odrpxq#A%jG68{uLJ&GkJT2X2}zO`xnIuYB?hdlSlKx`?(h18Jd?b4dLl*U?H$P zWdn;g{;E0v@D=RZTGUyhTtuE8IG=>rSkena-7^dZEgI1aaq?#6VxUFHfl@Mo5HRgWwo_E}0mTf(fXXUJ*W^Q}SB&T6EbElC_zVAea5f0E~)^OqA)8p74;m`g)lYiZGC zt4IQC+r74*`8NSi)XD}>2x;$TsRphrR^)dDMz=!9QG&9hSl|{>WjRMylw=oK^vv`c zM%pm{dx4H38_e3{liV+i=q8b>HJT*>IoBQh(XXC0HR3r#Ooxzg@<&-Dn7ZIX`7_B7 zfXM;OXs*N>^FFx3-BDz&OIA-;cQUGg(ej4RJ{UGuz(bBeAke{?u>qa;1EVbA&`&ERsIMMARc z{WLA&4avn5pw52`b(A`???h9_icpwo(NuRm^!VF+OXR))Yq-FElE9Gy@LPiW@kU z6mwZBC4{UnI7Q%ISH0}mT0IoOSc(DRuyj(OpV_@j*+>a&E=dpPv zc(}u3cCobZ-0d^m=P?%PE`$dTkE_Ov4Ss4HJ$h1d3u{NnTWP<9Q=p-}y&9Fm{YA1Geszx4Peb=v3 zr4eqdhg0I>;|}29_6f}_T`4G=kArn!AGaz#|G9Vh=X+I2F$#6W{c~b+_`F2O5e%(p z$*qU7Ft^-^RsT;Bp|vEexdFiD0#s;@#L{j9nf*%=3wS&UKCB%ret49hI(6p_3*a@R z7sN_LLR#uu3;BuvvMU89t=vAu0r2Hy5Fbzf-Z;fik?y3g?#;%F=!U;ymsq>SO}q5t z!wZo9PN&w8XboanDIi{BZ4F{$Iu}m+H}`eug1`wdh0Ym%yk>3{JumsS?&F8)G)VRu z{n1}Exy|jv#Qq`I>XP9{mLH;V6qE1CSdFOG^OYO78Db_QsqKX8JR(G*8<7*dZ-FJ= z5Dih(8R^CZFV*T_Ke==PvN=x^#J6C2>T{Xee)-@+-MTybkdeMm)k_T#u;~uxb(5tt zVXwccgIEO=D4-hGgFj3#V3a54nTe;Lw&vS7NPN zkF9F1d@_p}1(DC4-=j7vN8=gt93n+{u|6GJZ~S5OfR)~C9EzN)>JW{T)4GOlV0o_$ z6B4em4~m>&eRM^qyFO8qM&mu`O{155A(*I%@9&SYHz^(kC{mTrf+j3o6$}xI@vLJy z=VU~g@xgaH6NNgYI^DKeYtB8I`~-c(vOzBHC{ZXe-!<0^6kiYzz^i0a-m6qTy6l=x z3AunHDXz}0iS6y2)fJx8klw!%0cL5wPbdPaqEfopUb7Rd8J2ge32B@xAclb=C+J*^ z7eQFpPPl&uNIRQy1r>#l82u_hY;$qdp*FqTP{G4Z@w81{u0(rl!Cm(?{|%F~5)@t~ z{Dj*Rd=$D~5VhBz_NbFBAGHHOJ^!K%28hnar)2S)Pl%4nn|*Kg67|ga zNm@@6iSFBe)0Wpe7daW|Cp`HYPpIu&uB%b}Q|04KABj)~(dL$2`K)lhWA}UtZ+(3T z-z;lVKw3&K6^mkc5}^@{?Ma0ER81*KCAFreI$5=_zZ1gwdd4ssK^hy z)ubjT`%v4-YSJ*Z2Agv_@#%t{RS_&`^4z!)M{Q+C!3jZWdDeFtBwV&EiG5c2g}Q7U&R$)Ld*;MBoe+s>Pn|FDGn8YPuUJf$+uBt z_~AwNze>(Fv0Vl{u{`7FESdl$%$##ik^^VN5*aHI#ni5@-_;e%L?_ApuTnM=XsMFW5D zqJLv=OnP(&0C`32R;lVn8x+pUa-e86yp9j|#_Gp(a^+x56f=DpRU>i{yu%6F5v+H& z9Sus;${u_ooR|6P?1MW?UkLlG`0yz7^#}4D-474kYMC$b*R2jd>68OPAL8P4_lK4Y z-kYBxrWvq^dVSJGel|_Y4jO;)I_E82ctLR6zdhVk`+qz^o{QOb^C8QXj%HGry?{nT ztCi3j;Qi2MA+x2YDrCi5-#{o|^u10lzqmI*#m@cRNV>_$$oS>;bWOF_%#RNp7zvLn z%bn zv*gtpOuC-t#7V8uO0{mS%x5w%kDg~zn$I8V4t$@dCoB}{diD7QJCx968bZ=loj3WC znAC4Ppl!xf>3k(oN!`&ckpms-`PRwR=qdGUD11gAkbCV=R3Em9%9b40!%v4V#(gsQ zS*jUE$FSV$2B}|DuCBn#ndxs-C85~@ZTGAa8HKnnyrg7>r%i^uR3Pbo%O@WADwf%-tWi_&70(1cJp(YrCB+-HXr0c?mgsbj>x*{M1E7$BfWRLqpO)Hpl`$A@MrI z9kxHG0yUz7bW+F2*pcbdihqrl;?%&dSeju5o*7KhPKCUu&%@a96e8BC7}<@ox7iuk z)gNsgw9L2&xo|flbx6{qq0~j8;D%n6z;Tkm-dOg@-pH;W2emPsbvmEYoq!jvnKiDN zY~ij4Y#JvJ1mHzL29nc4ToiP5j}0lj&7t=B;+j*fs8Pj-K;V3i7{pQe)pWk{j5;vI zelUnddz&e7fje9;KQH-7O`xUV$Ss7kv%j_AGwC>FdI zlkNrC`wV4Z8`s>pauIM>Y$B*SjTX?1RHlV&a(EPmP90B?B({Z@$BVk?=VrN_y22`eLpkzA6HWkd>$w+TfjE zt_?WH1@ofsX!Co6iS~u>P5WyDRiU*Abfzl?0+;BWr3mqD-g(9l7ZFCIy-=e_+ z{YaOKH>8(AGq>GmM3HEPDoPG&_?v6DvD~t&7CJ*4w$UT5c6JmYMMvMNJW%f#e0L57 zE{XrTDjK7dAT~)c){Lsqx4XOvP5O!FViJ!1kqyUCsT#EG_5(#^PdOo<-dZ=vQ;YfJ$88mh4f~=Sk3E*Tz_sri+49 zM!M*(B7L60oTC0iNop>u$E_^P)Fw{ttl(i%@|C8J?!MRAkZ(^Lrg0y=1{&4ey~3@l zzOl;1--I^G;_r{|?K>6bvv=4_Me((~noeBV!M`RKr82Nc={CrL=7dV=Qa@t`&~~Mf zJ0!9FaD}(|D;U^+qRwrqGy6`U=j7f(+knX zGTP)@%HoPjY$fyk?nrWciSv2YjVLX}VDi$P3sW`UG)ec4jg8I9iZRE&74^PY2qz)0 zek%f#k0<@5Ema@@Xd8KT0)Pyt-vyNc!ZJlHBHcduseYW{pgI^9y}XP77tr&s+qVpq z_;z4|aQO*0qW^(=OgiV8J{n@gr0|;W_L-R7R@ehdEf$D#2AsQ2mTXU!ZTItSB{8nm z&W~s}`5ICQca{FjdI205SUJlyD+IDkEJ1DeIXUd7`LjI`IhaZ6i0Mg5jxRE(%!<`R zphgjZ!Vnc{EzOjjbJ%&Zry? z*vFLC2(Jt>*Gel%BqvM6Rf!tE*BcT(6)z}ZEUdw*{#-Cz^8-Uv5qar!M&L&uJU?OU zwcmPwrUN!44et4=YM!<4E07K3SL~l5!j=!-#-4>Et_nh024FsllPs+~c%Dpf?g(5= z^kx=_pLkt%5)zbllmVNW;d~uK?&F(JCQ}OfW;Wh_E1aW~Dsj;LF=Qiitt*EPKFR?} zsA-=lC}!YGF!+YI4~5O>auRIQ(la8~jScV?29idVI2JJ-qkc#CgeNpt?{ICO z{5=?;B`l(x|Hxfjr}MgD+bMl&{nRfbVZG(Xd&RWcpW&s!8(qh|K}rP|Y@0UXc)7wtL`wLT94-;Syyr3% zJk9*DEiEVCQ|4>jJ*Vr@Bqg!&>1j{S{zs}gTEoA;^IT}CJ~%iqwSKLWyZ6{Ewd?UU zj^v4lL(-hLvqqqxMjDQTbWDS+CqG^v z<+dEvp8V;z4EXxw5n88{<;CTo+?VuXpPBz2fUMsxG!qfgTQ!H@<_nX_R=nHkMXa*z z-XuNqAp0MQhZ^KvWQDjvi1b6aF<|tpF?uiqUf&KlETThF+<0%?<*U7pP zNN}r1tSG=?J1!1_|OpAyNv2x-|x zi5v3BO^U$nfPH&ZewBL z@h0Y>=usU+RDeJEZD25>_rU7_zdGynsFa@g@`7v$szoz4_N&q|7i&U#^NZHo6B15e ziyr0GrkI5zB4?ZBYn>9d+t!utt%Fdljft%lati0NA zhxe?5>iF4dJ3Un1lZ}nx>fXv!hAGv7`$#-UV3%N0dPE0&axoZ!#8oujzoyn@dn{pD z0cYs|byp$Teg-zz)|#_BrE6?Ff7@GhOO9abcYr^){w#|t_1mgS0C1i-Z?Y^lc(}MU z!mqeboEKhKDIHWQPIwjft_F~CGOAE)on`pG`{uR8z3=y5TaT47++&Jwg9NGMgg((8 zT;;P1($RxjPBI3-%J|pxVJYB$ram-E@9YZ~ilq9=R-Sl%E9p*;r=WCT+;Z>l?UOi( zQwjqoX3bfQRthphi=>FGlj~L|PG2~Hudemf9vf;y}upmiA)re-XxhGuM z*5gY<;@&Lf>3f&F1kvlNTsdKVk1;go&)Z1CI{A1qe=_SZeL}jNdzTv~v!Fp>R=Lu0EJ!d)k4sx*-cCy2 z@e^lCGcPI6SC+1>7Qm?m3Wf_! z!X-+pq%HeM28B7&+D~j;SFyBFxA+_Y=t(jSXe8~Gob7q+yB~PiR8PM^|J`=ATd8zX z-%DLLjtOX>K3DpfB{AdqbIF7+mVsH*O@a5>6Cpt#e?{2Z>Z)X?bZ=zI>saAEftw~4 zBc|BemTY6b*R`lB#J!JcZWHG_6?r_T>b$Wf273+8Lyz*2L)N61!Y&0}4{g}H=10r0 z`{T>*6FglkM`z6gvg#VPcTz@mI+W!NC>~)9&X!y>=bxOtw8!8W%VhCL4@w?SkfACABO?&bKn=G&}8tHrk%V zWgE)H16K3NUPr%-W@;|Pj{n&Dk>*^*fy(^y19Y9=dOi$L+;*FCwT=wH{UkejIE@Q#NLxIq}NY)c>rP}qTEc1=Ty|Rf~57vB^93Q-eNZiTZ z!?s%yRHW@LfRLG(;5|c2jH#4BVu-Z4SLN$fCnmD{oPE>@7HXi-*R~~es@6Tri5Tp{ zB;Pe7cHUVB(U*oEIv^hPeUircnJt-eqVK?#nJe)wIn)eqUgB()RT z96mvG%HK!nsLjC>)F3{0`Xqeu{dCp+-6IPD0e|daj9A&z!K`hOL*lymG6LH7uf6!! z4h`CpmQQxcC-rK@ELSvF1itFO&0dt4(0y1p%sJ|pV;(EVw#>+sn5rgRmKz2=*th4V zy1)_UYJqU*&BLJ#-@oB|kR{5VH6q!Iu@*x1L_)|eyM`#)w<(pe zr82gxktGb-vnyNnY-1n0!C>rToq6xR@AE##_xHTdarmRZbi3~By6*G*oS)_V0=aPR zA%Rd-2+~^|CaW*;jriQOt-Sc1e)RwpPpT(6c??@%;pQT_m1>emYGJ1@>A%>O#H?dF zAal89o}tTP68bKevV7!*yY=#Db?GkFqhVK5{@h^$feVewvpGsQb`N8pq==-aZ3e#=jN`Su3>ypcb47_#i2dQkd*K|a~+F(@hHvVpUCQ(YMa1gCk=Kw%3jCf9g#$z^GQ@{{IOES zdj_NyuR;!kQFly#dhOxSsGrJSf88xvH4iA%-yZeoE(OgebxnRchGf>{fnXwi=qiZj z=ZCJlFq)X4rGUsgk?+NM7GV2nq^1LuXPh1mqB^_ve8=&Np5W|(_3ZKaEa@C!^t(ty zZ##`|H%BxxmTOQ+Dx6~YvuRRh9G3xSWqA?8S-3mkvwiZHI&URZu8njZ_* z2H;ibAPJ#Q&ANT9_GO?`^zKjb2N3q&jWqSP5}bx83!t{m<&?_p@DhFN%M}0t>TxaH zvfo6sm^GO-yR5@C{32WQ1w(tAz`AAg>ST$^!43=jxf}eMb?XnBGM>Wu>V|jh zWL!Cwl<;=@3@7sptp0Etdwt2UMF76av{wnkIr}qI{YQM2b6A*4PdR9#n!p*S6?A%P z^h1&RgYclcoP#}R?pp0p@4dW^llSzv6j3nhu51SK!)L3?aKi7W*Yh)j7a9kYy`5k( zy@n75IGG&~(Q=ff-OjZv-#`P{G=?r_5|<3lTf@%cJZGDXO%nzaBU}0Iy%`>#Y@|}1 zJwey-0Q;LB!|z*vg&o+QOUx2BhAH4DR&>zdCANneELweeJ3rXBGOoa-Dkv6y4|ol^Xpc;-uc!t#)5-ei1Bbi1*?uQBZj9!JTx{Iw`uK;2OL{b)fDZZ#!yB~jnmE(zvKpbiF@hiniFa>y(N!e~XJTpX#M2z$UGCYH zq8>gj1kiA5L{|hgRqfN?jx3h-m}>GpuX8tNxPj8k9_i)8rpcNj@wpV$Zk)BV*Xh0Y zRC2G_hp=gbqC_&9e&IK^lgjL4$~EVOkhsny*1W+_{@~0@732BG`x)( zbSdu4IqB*wRXQJWSiDzjvV2h$LSCUZcWE$~;t{=MrOS#2jp-KECjo(B=@d_F zoIP(OE$s&1=833&042*ZRWO_!|Aq4=hTtq`1A)IaH7{UaR%%@`&p~@puTtE6PNuYX zKLqYCoqpA+L@Y2)dhp$0YcJEq9?0`nQw~6YAffVS%njvK^i-P6rt`e~0^i`_%J*+f zEf$L0my;&;VE0Y3Kx#(U{IsmHS;JQ5@iZ{So3_d`KY@{BII0lxjehTkV|*T zN44eeaS5y*M6Qzcg*v|MxV}Z~wP)iLH1wcMpi!*YaxtoVC}_Y!-Z~ngX~+@)we1~C z*53ZSRBf=d)AJPfo95?j&(a!6yBqJ`r%Y8}rK4;uZwOuNK>If&OBm^*Cl&p++&w*2 zz&MuDW@6n?R3z(yyK=M$)Efc4-d`@>2ClnFqQ{g~?aN#bF4!f;n=5;Dg;hh~nb829 zUsj4o^($5<&2{fV8VF!OW#HKkEY35DV>MUs*7d*Tc}Pay&s6no^WD^8{mdbUAhDvA z_q6OE*F_M%gEkegkcs_8UX>kD(z=fViC&iU;I)6hNcfdC0sKB2s2qHt$U42bZ9K_w zrJ^%@gjqfIK<;;8u=l?pi0Ae+cRDP@v~`0sohBF+-!Z(YGTrxXAefk3J+7-OG1gUM zo)i~+K4p5OW}eutW8ntqATOUyGH7&oV}6Or``R`UPm1K9Pd0GUMV!_LX)zmK#KdV` zM*)Uw6agtU)?CJEefj90EIGHegtF(o{Bda1yca_EMs~G_B0d2PZ5QtWyD}X&sT8_( zg@L^;$CnE^Hi;KqF9GxSoQTCcy^+O|xw6uVFp$ze<`bRwz56qhTP~owbnJ;~5HV;8 zvRyjPb}O-(*iqit#4EZK)n(}Ci{#yxzX=Y&y1`u!&O6a)cC3{N>uG#rVaepJqrb<` zKQPp2A1p2o#v52Q`Z@){9&u?jO7&RQN?l**)P>jQ>@U@C3YLz+0aRU}G}LkaAMdN%Ag_+PyKmWqF+|kf6{8 zd^G>ZT1n9S^_yfJnnAx*Hc&UD%`2gk4ebzqeagin(Hn|7_!I)KqHK2DkIsfWDAJgi zykigu-%5i`$>mmJe8CZSk-dGHj=DzVYq@*i+X)?(iG^xCk)?Yw8qT1OYrNm(O5TEr z&>W_VxUSU>bbpcibztUa+Z4KY=vb|ojy?GJ8XHY1_xpDVOdWyd)UumP{!Be0{za~* z+zR&VLBGOxuVg*JY%+~rCzC#`_q#%vq66LFE=`P9P6D_LFHGQp&>lby^T`cngfN ziAqXiWjIW|AaoxrWM@f(VcYeYA)%F3jo!;pykdKCl||i!reELu$NU#VSAXucqilmS ze6Z!$UrRR;$PNx9+G-%TY(seQo!-jwn5HG{*^g5mC2nA@Tvm1dac$RWRb^UrF`wk( zyGwVKb5%0=8v{zP;n@AJch8R(?>8H0RIK#|*(Qlyrje5T#kB;P)v~yB!CP}?QT z>Y`8e`Qy2rBWHyhe>DxceGLE6_2WgKRS8{XBbsYL)EX>m40`-&{D#!5fdeO%y5I9W zP~IAj={S3eM;%VobblhV%be5V5D;V}&0#(qMfFJci`NB~wIYxzrIQBPctm=;oUK@TT z)4C=#X_Q@p6TaagQ}M=lWAv?(_u27m>@?qSyPj&n!+U~=U9^3SiAeyyot{g6`8q40 zI(A%FQVIPPXWQmn|s`tB=v1_!F(Ax%KY_#T^%2fpqy02=FgNDt=s`@8buJZ}N z$8U^ha+$ULt?J`{rJqp?Q*6yI^{}lkKNxo~^L811*2V-^t7qzCJau=hEuk9f_e06+ zJwSQs(Q)!IxL(LSI+#7mB=*l9_syQ^KY9iW;sP9~qacjJSD!#i$U)pdTfw>pzoZ&J z;2{^uwhL@iWNIj3PkcPVzGS%nv@!o~&E9I8?TA9q>jCW0Vs=kxa}p_S=k;!da+b!7PShuy$3&jNb!Bh%A~FAQ&6imJt#9u>r*3y`L5;tzl0 zDwryaI3Hbj^D%2}^<8OGu(xMe>8873-{eDT9gFLLXs%#I%8PzZ>C8TOGCQKy!}39K zf>thAj)K1#0hQ6G{#vr0tMS?B9f%sVaXO@LDwHNjJ}qYtSF4IiRo6obY^j9bKWLHD z6__|j?f5IC@@3-{LdJVvM|N7~bU8Aa>VA)+W(2`bZ}4P^?(+9M;0-VHl}d$(KH_9V z1l;BsaQ3!GceF7?8fCbE!h>|hQ|tO|Rnx_Uow@F#t7AO%QYCwxHDVVS;W?f$J@S5= z7(xT@%%7BE)cZt@tF`Z0i;Af~v-oF}EeKcKhEF$u& zK1{>^6a8z0v*mzvcF^X*YGFO-gs@ZyWV#XdWC4qVt02AD#5y8C+Jn=>VdZQ`H(H zGWzC;sUZ2fs;cUS!kwSbdaYRAA(^S$b!5UQ=(5xtP~+X}rb6#E;b5>e zu4>FP?EFMujpAJ^>Ngnm7C@b$;~v5)X^?LWy1a-Clq*TF+bN`rj>3PRH-_kn;%u+grVN(rsrj))SQOug!CVsESYk_Sr}vi6zWp=6CBs;=9pdnM=9fV(&b3 z?3R*Gf0!$7$rW!eQ?&bBgF9Ahc#XU2GBj7i8quAMLfurwG*&ziF-LXtkLflNPhxjm z|A>TawzGBQe5yD*N=SFX?EFJ?zSDY9LtmT5*nuMpZF;`rba`L1TsgN-I&KHcr6K1+ zVY~UEpxdf{(-X&l=dpa>eFm>ECJgzzqSwty|LQG_FG_-JYswn58Q2C#1Meh2`{sY^ zY7F;`gY+~1ET8R~{$Sc7?eTeS3+k`JQ4DYXMvaK(*(Na;PpzvY zalF!Xtb|&e&fPc>dSur((i&@7ZyUDR0eL1Z}RvcUH3|STyuCKkS=9l`u<0@>H zY@as3W@6Gs$TkiCHMi22iaAX-GD+kICaX~+GV~GZj3|6v|34>v{KnSh^Nuo@AEKBFhh_A$#4vSQKy&^8hmGeC+PUj&TY+4iQt9B*wF{=uB~3(bPos0KDc!9hzhaL8Yv#SWgF zJR07G3utAe_-XjL1<+s6TYS}Y1WwgHh3y&;yTDG#?KYS+8=kk>rlsJ$<#o9%v-sa< zzA+@Vq`H**vecIM3F%jSeA9;5@8M@azn`pstsgAbT>J{OL*+OmY?9OPyu;x&`5|n5 z;^y0H?`m=TXPorK(Gc`J@kQ@P5JzJA^q4-n0{^@?xGf$o5C437fkJ=dv$gFd0U!C|bTY zG_BZtpgz0( z8jCqt>i17}3%A==ZR8!*qHl=4bV%joU+RQlP_y4LegxDHuMH5V2hQ=N2-5r_FX^2| z8umiA#dCSSXDI(;n{6vwAN1nwwZ_>@?N{r8uRRf4m-VlCL2>=H_1)(R^vX{NL2K&t zyq^iD1&0w-;>zoOU8W~{EklVr1Dv3Q~GVp!|oH$ zmlJVq^bL%2&xt-FzHYrcX9VXgdH@`SOSVXHVzw{3e$fieN#J*$M~gYQX9&G-)I6GI zYdSlrv_5)rMU$+c(8x_TWlIc|=3V1C8?b)W;2*$q`Y6#9*7(qYHYI{7A;(@JorA*8 zN=9F#9)gI}5xqhgelO9fzjp0aUL_fj<3sl;l5x~khKc9Q;zB{mGu{T%XL6}xC{WbM ztETZTAmfw)v;N6@%t0MUC19R#8Qa@Czez}AykVcGIn40M$U+2^QI?@2sy&=p^vC*N z8_$E4ENjo@9;yvvo zEy;sY-mwm(&&~JKMp8dZ;x-S?|1$*GbOioZkWPIUUiOFu#1;z^S4c-HA3AeDo9BUS zt=JR)e#ES>$f@sh-0Io$WE{>xqtzuA@~w`r5h2c`Y5~Khxw92JEFZYVLXG4AAo8=R zD8!e7X6{(!s5{ou?>Sz-rqpH>s-Ms7@M^evFnQipCbQ3vaM(x>GWZL7xr`pqrQL9L z%tIEJX}EcM@&XF@p2BI)IHCCa{Drv5Ac^E9nC+>BxrvWSpR>2~2a&HvzfQ$_jCB#W zGY4Ys6vSWoHQcC~6WjD)?ANfXJD#xcEb3+$9|{!tG(rPb!_PbJR36(-=Mso^ound`ET_k+IDwY9;S5dqMA^s5B1Z{_t8Ilk(RJ` z((%Hma6?@r!ad^#oU4Xl8@ODBtML`u^BtrFRfK+vJ4Yw6Tqf2iA5E}=@Wd-0j2K&w zRe5kZusk0^ANo$oMe0;O@6O)020v}uIfED?5q+3wbaHYqwyWggoD(|)x?mY+BPr+^ zD%1p=--+J%9Oyd10XG4z8dJcth0zw7E)BF_fg9N@Ig{h(x9>JB?Zu@qQ+~5xbLU2< zs89fMpSPXN`&_(%YaqKfg5z4=fd*7hDWU!T5t8^!`lircJ+l@CNKyHR&egT1)A9M>C0Z2KonaWu819t~Rmm zL{`d0{;72`RE$byEbU2UG=wh93=F(F`Ki1^zrHWKFXc0xVql}l$g!0{_Uchrxef62%kNpI2GL84DUv*CgAExP=IX`DRlOXkU_X(J9lpLm=rJt0fPVT z+e*wNZ3JuCVJU(95HXvUJr(w=z0}+}^nAnv(n{$$fiy$fN;j}pzee#mE170hGKb|W z6}bzyw^c6jxw2UK9X<+vrWXUuDpf2@);HG>Q4ps1w+R-o8-h9-uYZuAgKnB(JME>_ z=>tWgZAiqu?;7_7A`!K?x^mH}Y7<>)Z|gDZF&g6wfKhgy+d3$C66<6~U08QC_XXjm z6P^n%=^|Q>gICJ+++)DOA~|55XzrlF%gku32gXISQy4zV?!yeUy}4vhz4GJB`i@Xn zn_sJ`A}??|gtavKrCj=R4^IQZ_{z&F5Qah5tH$e-CBhGMAFy7hw6cWgKx!JKMzqA* zMYLx5G>c4}k;B_Ykzg!93CY*6e-&DD>djEh-Z?K6&l~03RuC?(!APoqPgQ8s_d50 zxpzSidr4U07SQ;yW#=mdz}l8d$g4Kn;t5ihsxfa)Hz6&JiSq~M>Zd;&+gBX{K6JhPBHii&KM4z@HeeH%!^Z!#E89%#+cZ9qdTPx=spW9 zc8-gaRsA2>Cq@=n+6*(oTYBF%#ye^p##J>`|%qHUwy zH)u}#{P-KgtclmO7s!=8XIqa4s`{11ag(i~XW@szJc&P-4P%EvtdATX!3yZOlC|ja z0n$)L=JxVG#5TXMvDqU}(n(DSu7>lmDVKBN#eN^v5psAv>Ca47u1`0q04Ldc-;weT zJ0q*8zz4dY+rO5NzumVDo(GMYLvuInZ7yGlmdo8AQN+A(Aw0axKHU%?-6Q32Q`#{5 zm+Ng?@o(?_rBeQxu!EgYIa*I}Slm1A?%~lZg;=NsY^vI0$`s^29NS5b&&+sCRf|8^ zxgKBW1V%#LV*L8M8Yd7*3-o0(W{Q99mhpg9c10_-uQo~)MWygM0zdNh4Rv+bkl9B3 zwUJCw*}suGVoouPmA$=fs&A9@hzhyk%5j0im0oKiF^d5cUo7weiJZ_U0@ZW zN)P;JDS3c2{E?H__sU&<<3Gi+GSThE1~#W+Do07AU3%hN1wOdLwQ4%d3e`{mr@<(* zt`Gn~gKK{a(sF{;uZPR(D89U1CaL%6yVF>Kw4X}9#apq}R z@@FHHA@{!iz7EX&F)IKE0YKJ&t*aMtF^Szye@mOTD1Hhb{UhJNDkH(faI5~NHjn{3`)Xgbf+G2n$x=>s_MvdyT z0u}H`H*zv=(8BE|@n@%DqB|M)zP@M=@YxsKzH{7)#bjpaXA2bO-;|SN*8D=W~7;O zAssg#f$HR)*`pE94jpuDws<8}SrBu!bm9c?v@&XoT^n(FsQy3qs!|~Un9BP096d04 z-bT?TLV?;pojtu2c2IhjpmLhujPHVtC4V=UX`O%MwmQYZK6i=MB$exGqPC2=W(pmwQZQi?8gp z6JM3TWa&sx7XxV*6yWs6Na5{Vd`P(h?B!ha3DOBH|#c5jd*6vxcWz$al+U^?+NbQ zwz_Z`3jAmi@StP94lvxo8tI8>2^#5o>uMEEul-Iz4bJTpnRVtoHFvfQS0YFB{LNtS zzcCZV^>@LL9H9G%=+iBbfgF8Qqf;SCs1O%bc9=QRs552a)y*TKHWJsjB+suv2bP~Y_u%O}Np3;VAd;AjJ=W8FZ|5{TXd%&yXOW=l|%b$XDv!=cBW##$4F#Lt?pmeYn-->gg zu>VXAs;wDB7$wCtF0p^<;Mlu4yE_lw5IHJG;VM5XD*EUy-dlXl<~F-^32@&4wsj9F z%)Bp?0elQC#rk1Kxf0F3d+6dFISDm>%IVrbr!c9fZ%bgP7Zz)p!>+k06RwRFjTOx` zH8u4$6^)zWLMsevV4-SK<^$f-_bi((>?x2Ney?*zb5nc9FPNE!@VfkT9C7(xQLq1!c1T|Mt#=Y6N;u^+y98r zhCrEf4+M%o+;3Cf0OtIwWF-867SW%fE(C25lt0@TINLbjlCLWfiyBfGt4{AJ*Q}6C z0pln1BGbHIl3AtjaCZPM5TJoNomitfK(xZD;ChqRiuSIo{&7EX=^j~lrw3O zkj7-9Z^{_S^xlliqm=RuYL!z;PBk8WMrW3V@8 ziTSagC+jT9E~D>&66tv+l~U|Avt-CA9BiWm3Z?? z^mWI4^&KuaTH&o7iuKd5PvqXOe7U>=_IUU3w&a*WCiNcf^591i>MkzDgitqeFL<7- zckfmINe}i#Df0LsU9orYUX7kQHeEzkf*PgHH%Pi1_>$fiDYkH8pb<^fOpu zak_pLF@DF~m>;TLabFd`Txrcw1&m6C-Y1w+i^bBh?ddob580z#yf)22fnydXt2*n)D zXP1yr06^%X-gxRpe+9T5radn$jXTPVYaVCaE^i_z`?fhi&hmB&o|xu>l*G|dzu?mv zQf(YD1%K`kc1AwCt*k-)x_Glol|T*H$Mm`v-{-z$0k|d;1fy}Kv&5mKW-0ty*JT1B?FB<(OUdqMz}`4vXTdHJ5eAsU&8S4Bj|njVcw7=oGJiyU@iqI%d!}0% z?*osKnY={4-(1IWZSD-L0Hg5R0Z=c;dmKTU!zvSZ%xs%&MiPuBvy?eEn(n#2Bs}cg zRT@pT_2c1h{Z18C{&E5v20&WWcQ6uB7KcBKKl4Cz4gOV!+i-CLHC(ObFK2xTEY(?w zvyo8i5;iA6Y$u&boM8==LlaEfW6lr)3bXul*8l*Bfl~ZXOl=N4KS-_dwXF{Tz4;b* z+mfF^g>$Q(JIX*ST?A(kvMrgTYVBOkZL$zx3Q`rKj83C=u!B=t)E9Ih)(M`pg+&aQ-;G;8Y zM;TH4*A1Ba7+(H6_fU`=(0P_=ip-G3T84(}<|1Q~J$E<(?!%`~(iJ-jkmYC#jl*?2N}#=`5wE9QT2UoI>Zma*I1P%!(pP1n6}AA-An5xA^P%3>sgNUM-02F}w- zYxuJ%gW!cj%yaXxTLaf`pf0tm+v|F!t!?~QQT?BKkiH|TdGM?0Lv1wYLk;8?U&FCc zV*{lFTjFT*2^;Y@>8wkr1Qi<_$n)14yxP02@k7B}3^tqN6IkiY&x!X4v}o!Lz-ij< zN0nUoXUx)m>57aNH6Ow+S-Jx#xB<+CUj-)(27-w9j?m^x4)qd3Lt>6l1Ky~f-pOwX(*OCrFA9-+)K82)-{SaNwb7?<4x|#5wS4YFzy1b|LCe<_Bqr}gw+Dw<+&0~5 z=pBy!K3y2ZfiiRC`2dYNBh^vb-A@|2_=01UHVq-dXIPS8kx=Z9&z1Qt>;x1wuwRbQ zOaaN(^InqNmswkXC}m}M9^NNY&N|pT@#T=yrG6NvWdH&4!{(m4kxes6i>PWuZzq`9 z98ZKDf~Rrde419Gfx%up+DIWAd065s+2(EF=tmB0UxJwY|CVkps5737TbCvoF;t%! zM9^-=pAr``NwmEFq|D;$^4SA%YQZN z5e*!OM5H7?d8EIhSJLv#DMnTc8(?a(>?%{(Q{GQogsigL&bngO)leBF4XUvY+I)__ zEXSrX4rD%r1RgB$YDl zBJkwx^{3xIe`jz&^gp3QM5YZ^QcyBL)X4D0I&1w~&!>F!hka3*{dpE^$O2lo@@;DY zKvIjj;?JgZe8uydO%s(jOTOJn_l95Ao!JyU_L z_s@rqJ_Wp7y;a8#FES)2GfQ)z7!A2l5V6!d4&}Y(Fzp<}#5-8`6}<$GqJ*q;_X|=x z+pg35$tIPI3JdpQMWN*4IcW7?IMKYjAyyeS6lXuk0ZRMvnM`6&T-cdV=!uZ+QMZ{* z^Ksx^c6J~465JPUUm0IGD#0OOY=uT+gTFOM#06g z5O}pmmE7(rQs=SajMi(3!sW^U4J{N-VUXq$pD0S?6I3ww)%IoipE4jifOAGMmOXrT zyAanqncbUS5s<+aV3X*Sf_+!^@T*)FE8?v9$Row!&8Kx$^$gazPhOwhjP;0ih4v7j zvu^hOMt%?{hW>sWrp?3lxVuM%$gHw6tgEa%XU! z26=2=wZI&*c#7OB!K*o@<lfDFg z7o!ThAuYmx{eS;>jOETf5{y`#`Zl)nHR%gG6B+qgMHq3f`J|}%gtmE`7HpM!z^QlT z5G<7R732SqJY60tb2%hlp|N1qe*Z|~Px9Jtpv8%DpN+L;rnSzOzQZ!94r7gE9KX<1 z8hwI!u!&9DG~UUAz^VGKZOj9J*KF}OJe$+%^wpDW!Q#^q#-y)2-ehow@QR19gQL+- zZS^d zxcA|n$JfJ_PD#^*urUxSRmAKdhoh2ZvleauW@Vl#cC48@#KPAk(LZJ;*fG1@%FX=Ai34E4x#zyQbJ?mB>VO0pZ5~25(&SHD&=5nj2*wdNZ zP{M;R)z<#c66-GT6PMqYnr4l^OJ)_c5E&OdRTI)FPx=%WI!m~FzCS!E?kT_syD>^p z{EGLF^_ke6w-uLz7^fe=k13O5P3tcdEgpGUZ_f8vbQZf@>H*PEp%XKJ@d@MvS&XLh z!u4eL+yxs_-OoqJTvBV6b6EdyWwFKg91zPQxf&K^?*eLH?! zw21OkI$nxU+5Z}M4nirz5lwDEu%aJdc%CngL{J0UT{MJx4Ke~HfD!(ItykL8+d1a} zwzc+4A&L)0cK-Q%fe2ZC=z>t_E?69nbhzeJp<}4wdfL22lTQ>^1+2&Z>u&!4VBH*A zLS(c&IRoQ0&YVwtAP@QpQWJ{d3~QASZj}#R1OfQLxX@K{-UbCu@i;6-^N63Sg;JJO zeD8&PE6aQ_L$D8BI<7L<@d+mOhvw|vhhfPy27_qr&J0k3MY9B*F75WK?O%6rsAhYCVMeXrxlBoKN;JkqS zjp;>5$(Gge2T#(W%0WJH#4oth56ly+kD;s|Ov+VjIbBVAt=$7ZP;QUv%;dINt(m)E zE8ArKzN_~6NawdI&|u*R)DcCK)|p9LV_+*4Z8)E|z%1Hge6v9gyLlae4=C1cDKNF| zk;9qCs-mTq6g;zhC8)1RaFJOt06()@w#s8`Zi&Lle=cFY8S75j{O@7|pA&lgOXSId zgdr^fJZqYUuWqd<(e>u{GWHz=>j$ea!{G!<)z#P#qu)pdu z#-Om}Jj+)}oaF*ngy zH#b#w`(}6Vg7+`)Q3pL(kj^S|qMzmReu0k`TBR}xU($m&R=dvfZ1}dFs*OK~ei{@N zB-1cD`fX-=wj6fe(|p!LY90vL3c!aGe%*l;vzE#%sC%au5Oi|!D;8e6XNPtGTpf(= z;r1f@7ryzQ)u6fx6mQt2q`cMR0BtnosMSPA!`cEK98?Xf(7$ zF2R4$oiB68WSt02*HB5HV8i>nfQ%<%k00C&JL+{&ITZs72hz#xSt^@t;9AE4F8tTu ziQqep*BOlYnLg8JE3+#E?;`!>chmpp-SVI9K=5d1`to;3)$|LQ1|0@#j;^vseD&bU zIqa|L7H3!fAmyDJRtd zzl@$l$X(nzK3-{>oa>yN8-Sa*INS5{YgB@8YVv)N+*nY($fSqV+}ZMLKArGu#5shm z41^AY4+`pBEsDl2U-q{j^qM_6Oq5qpI&%p=`Z+MriN5KuDJ45tFqakeJz19P|bo8`wYlLgXho$TNn-9xtaPFR)p1Lu?Q z4qi}&og@kJKW-i@1xk|amG#jATl4-IHj~urqWo{COxd0dC0WdJ z!pg;^(eFTai6>Vr$G6{fPR<9Z=k}jZ1ldTOkfl>e7XHjqJ>b*$$7})) zRL-{W!6$@J{BS0*@t;~w-gD-$F8w+fz;??U+MaA5Q<*Js5tyrSFP`a}93+QpbCXZg zi}qOlP*#4#l6QI(%p=p?H^l&0(PeG^zm?1nJ+0zoHEO*`mTXzM+|{WMDXGUw1ABf= zxHW7>2;m>h8MY@Mwg)<}5=P<;oKsBlci!};eqNuNq6D}DNU18N@AB;hJdhg{(+xgY zM4Elg| zmbW!FwyXe-KqPZ8B&aohp#ra!|L>0f-{;(G#X<3N-ra{Q_u#&Rl^(`s2h8B`8y_^g z7YjZDeefLlKmCcH^7;N^++F8bQ)+0L+6ai)nO`hsK||l##Z0jKg%H97>Th(iN8awS z5@5JR5BV!)LXS|Tr`e)3VJY53= zJW{@UeS}jIycqwFM+6J_;t0x!%qh1rX8t@R0Md*dQ3t_tW(eys3z!tH#Hx08+8f^# zc(5*w4_vV%{i*rKbg*39IXpC-FV}-vy?fs~4L&0exnzZruCQupbEu0{N-+2tJJ|=h z2FDV?^WX>ntVbdQwb@YCVPTeNlIYZ{*UR_biBYm*(Db1e_1j)C_o(1GlkzVr0S;9E zHVvp{{9o`C6&H{u;^(to6l{56-*bB(?1b+Db8}er<&+3wbTVk+nS+cH;a6L+iedaH zG8impjHoycel+$gV$jupxw{{f%=Qx+2_bPRz6Wu^tF8Fp^{Zwj8zbSbC&nR~NTk#2>7^uOO`HfI~yjONhu6Sn4~T=4Z*NHm{GLC3-&*`iUOQxXO> z(h$NRiqN6cJIM~{Kt*z$wvt{nepq{vlgjg9?}fu5i1^N8hJfWtlXd$u$gNu|bKcxR zTeybD=DjuiB|jZ`-}fYPggo{+|2OOiA)Ly+2@URPX}KeWNN7iQg>P*#Ej3-x5(-gV zbjv(QlZQvBDUoRkbgHE2q0_@2nSgUrIiA?r<9@%Uf1777g8*)L-k-jk>!ay6-+#L& z2E575a21f!MLoU8Z|c8mw>r10|AY_0Toj1KLH0_g4NLm9@}yEH90u%1pMS_J>Q?^$0i*dslnObsFK0N z$y`x>V79>B(1S^4v)R22m*ySv*`uW@A)9c{gO$iqW|C#LCGv3V@(O?fe8 z_^(4Wy1&0a2z-D4u5`r=e~=ugv;P+Io8?2?qyb;%Cx^@MrQ z*{OByU3><)MaZf+T`}^uiXSv&6J{F(8q+rIFH4XP!Onsfe04>(q!ULeF!G>6>Pl>p z_H_UOWlgxQ+n3#Sk;@*N=Cr`D@Oj9aG5S|z&CowF87SbJ`VP}>Cf{bL3Z|?GRx(VtARfrfu6QN5e zGZ?&uPeJzkhSdZI-TE&(04Q3LYKc;OrKTEu%l_~A)`p+TxPjmJa{c%hu2ghu!o!dr^ohuxz}``g5+g|$L#!NQj_ED*=0qW zWZB=4QqS)6NYU=NEOMExg<13jHVpzQph}@ZJJEQ+Aiz+#|+>69Vu<0O7an_rI!9s6MG<=Khc@(q-1Vl7)5p z8Pzk~I10KAhlb0?huAaq170HDOtwV|e? z1NkK{;9zv@@bJ(kcn5SI4m}2m5o4ay{yRkypy&}7?acQ~3COA%wCLhE64=m)p$l}- z;9;m5aehI$O9`mnW=FZO0%^cBNPE6#yAky37a<&KDd6C)yY{&Ri|KDxPByKWIFbLK zpQ`;$C6KW6)6zE(Jt-M2CE%8uoPw~A(GbxnP3SN0WtV2B0~r-{6;&Zl4OOyW$jD~o z4*JL%9~=mp4^mc@fp!xd4wZ5iIc1bUrCQbHGB>x^zT5z}_~wFjE+(qKi37Cqx5~3x z`XojHkm*qLy34+A({d0P(xnNY0$2)kMk-?o^WZjiQcevyD3a(~sQ||qCq3Zd9BU>X z_wb+QH&v$maIN>7KY+3{!u}UouRQ)hw666WFio&Vquu z`|Di{b5T33K;$(+U8HlgWV$|>2lG$}RE%2IJa63X7OMe|K>mO9FpwJI{X~n{QEZ{{pDk-f}f7WbJRCFs!N{ zG|hMz>oOX8GW|R1oBZT)R321wR#I8Pd339(nc~OAPMmmJsV0e+ZXxwYTCYs=CDndZX`oRpa^j*z@V}%#G&5N!ajOFO^ElL!sf2cRxkT z>@;#?wMKN!VcdXg&e#BWjGn-%w776%_wpTOjw@>;=)Wn+M_gO@6eq>oYx2iM&oVgg z&2ZEo&1rf9t#v+6OtPg&RoB3sGEvpMcR}OAs%^*xOI1jqq?77wgVM5Q7aWWUY}K3| zJCE|7yCl?7pAJsb1J+RA0BcJ2+s&76WlAy5E&EKfZrqj|B_rcqfz1*i!S~*aL{Jz$ z#M8SJL;afHdhr&gE#6qX>l0-Hl=UuL2WU%FDe&iC+P3PSH}1@ZJTWp)2F5LNz!vFo z;h(+2!nA*O>OJLP;JSEew)*fW_>1rE21&t9{16%0IsRuurI5 zYJPI?HZ%|1Bqg}Il0D1-)))`o&>mBwxl1nmx*A$An1}A;rFU{2PrL(VVz``!B7RZs zrE;m__7;C#>HnS|$l{dDGpI`1m^-~>scV?{uuc66Pp`P80W^a(xKzKh3JudrGh$bJ z)&HIWthlbKA%~ECG^U}~p(l~Hzk1CR`Rs>fz^oW&d)(gg($SLa!pqo}IeWT7fQJe2 zVkG#4EvD5|NyhC4ALx+5gI0x;6k6DiM!Lzhf|tW$A=#`z;NhV&|S^p zk*;T`DU8+Ob9}yrOYqsZvQ~4DNCOffjrb=ZE#w9C8UkZ<0Q$>tf2NUmk1mV5gkxxv zaQ^sdm6p|Gs~v_HINoQepK6|>9Bj$|)=;^QPRpooPK8e+R=;JD>{4k*zbf5j{(q=? z^KhuY_>Fr+b{R_=TI@1RGDx<3Ws9-ISTd85Z7hw6$QDW=`#$!i8pb}#GRhidU$c!6 zl4UA|s6^p8{jT5hT)*evuFKLmpYu8Ae&6@&_Lj}BMPs>;u`pXH=^THOEoa7#1AJ%@ zX`rcih4b3QSC%SCsw-BBbQ86X6bU%K>H_LBZ0YVZ$h9#|aEUMO#BKSc*d6B?8qz=5 zuhm}bdHv?}y6B-1s+g2!@ux*JaJUPWSc4{TG2X**qE$5APAK`)ub>U8KmYWz-T(Am zsZ8M{wMg0jM5j-^^TOtpcvf8yFCI_&d>sf1pJ+f)`j&rtsLRt}=i9wV?VAP#Z^n;Y zb68oT^!qOv8UGfI+4kzEEBZZL4(nA)EEBANE~Tp3ojw9Fkr^8sNKotQtlY#sl|ju5 zoI*1lc~UfY^=m!-I{VqlZ$IW}-rlDc9v8eyd3DR}{fIgdjhh0lJYe7J&bqS%8=n?@ z^3%u~1&_k!ktFi#|MU-{oDw=sD-*bdUGUM1^}Y8ly<}=Us& z8~V`C{Ma#Zqn0*d2YE{A_~#%k2ZRtNGk9alPez$5-w0l!PscKGAXy=s^O}rhM~S2w zE6^%z=t?&&zHnFES$|@t>fya}6Nf|b>IW%&HN;uO!0;K=zN$C)=woR2UOn>;oNU=i z3r3@e^cvy;V!FyC?%+?rZ5U+ZANg{ zT}VpY5egK7U)8stv7Y+*#4{H$kj-Ib|d#{+InW8c-jrQ#(S>1~Yy#I=P7?JGi^?>b9 z*IE}pA6z;$+X4%ZdbxM$RtuzWU!VrLr=Rarb6M}dyXTz+3x6dqybcWfSlEt}OT+#C zrMs}s3{m4bZvR%10eCAV)$*yG>hmNtQYN)uU6(0M>Jpzo>X;mbgcm0M4pL({bH{hR z#>#&V*d@j6Foz>&U;-{u64c{l6g+mWGmadA)_9dPN2Te*yP*I-6+0Jg_e*ui*=%Z> zG=3?GSS$v+I1|g8pJNq=EKyd`naT>hIC?m}u2sr2I&iX*kujpJS*>=%D8hZH#d}L* z<8#~a>4Zxb8N~CC8>#PJLpN;R-7UZLTn_Xa3X{)mj_2=}x75h`uC zpnt)4z&B!0M}K%4e_KmgLZnFg?G3^s&S%CYlaizNao68~_q)YwfD19k4 zCWvcpYIa6Ue{IU!0JAms5>N^_=t*p<#WhYk*#P6*`yxH4d!$IRq|c_)rp_H*sW-l(4oF& zFT+*31KQ-l(@Av=bMY%9 zlkQDNT_!>tW9O)K|Hz5<-#3ms$003!yXpRO{zQx|+`xrfnLs7!Qb>bZgN{@l@m~9j zu!;HTYC=3&P>(=OR5eT!8@5t%-b@!HwaPHn3y#)t(b75t3G>Nt(hpfXR};)p5E7$MwL)Q73+0`x<~^UJCi zrAkgjI8jI+pbwnZHb5SU{8y4>PR-cqz=mLJI<7m`1)gW>%uAME7dKZsJl4K)=6?AI z<6bNHaQ|nNa$+n7LztHMFXfFMM8eJa`~5llLSln`M)NKBwH|KqAV@u3j&d?U05RN9~xu`y|(eB96ZJ`tvSJZ8<&ifi7kC0ZlR)t72KUjCoC|K{ce ziqClIYH#GdgvE0vQ)ARVm^{}e>Ycf*K5(C~O<&3yqcwb!%8=$VwXuPG2z$}mTELI2 zOTDlFnTDVk8i-={Zo$Cri)RU>N5c8r<(;YH16hIy6}k{F=ayZ zPIv!J*V<0}bYr+kbv4oqBD$@0o%hsYD^yxvuxA=EKzcnCWbL8KAISNig|-LvdUWG3 zz8fadKv!c_RNMq1HJlC2m521Y7BSm!Jxno9J`=5R`^Y2k_PzGVhu!w1rBodqjDHh! zZ6{`6MY9c26s=FS-PZ_`yFo50d_xQ_mjqi8%is+@vF=a#b@ZFmy1@%(5$xELIUQe` z0L@h2Dr05Hdj%bbi!`vE^f?NYM zr-g-NBneNmLv;2O3hM$IaZ~kjo*p|u)-3diAQo0yvpAW^l!t6}B#}I5C~j(=4fCdW zStpKDDwNNG3G$1s8W!RTY0vvhwlPc6;QvNVuBFRG5st+wj#7dyf(4DSywRC$OZ6Rm z@>3(ERV6zfX*?mH&dTRWguGLq^*_yrS#wUPEYQirH%3+rzHcx~0B=1*kK7XQXW*wu zxx9x7w2ai6!fgbj&{+1J+IZOqwacQ#-8eWYjF)hlj7DiP;>eC4N<8Dp;m5g*RK+zn zs5NlDaMw%r3FSb=b7sF>m^^X7`NxPbJv$ivro*-N{H->)Gz9oj>#|9*9@N$oV?}Ac z?!n^v2qTw7rUWEz%D5(zZs-**y?0~C+vL;{6MjqIVg4F$Bdn+Qb1NuuuURM2H7K&p zOj}?gFT3LgqbfvH zs_>&XPU9+A0%QUk{UQ3z;=zIbk1bb|%R)hY{@{j*NI=Gjp-*DvF%#P@DIXmP)T-bZ zJHe<4ZgvD8=L0FY^fue2#69)?m}$lueR^6_OWSI`phpP15Unvjj6 z;8+qGRr_b_tmSW~RZtA-#peMTti|)sVC4@$OB9c|`7doTUEDQrH0`Wy`wde463%Hp zuRKNUAB5^GlM+;Qx136UwQ+s0-$Tkeunr?+c6mv}Ve^|lwt;K^d z>=ga0fczi~FqG%)=V3)>#G5k+Fr~!dHu9N2n;$&@XT9sBOuxl5U-xRpX zO}uEs8R_5qxN*Mt)oJxUr_`Xu5HF!H5r;U5BERA2Cq5XXibSSI!-2sLb_{1zdN#?< zwF7#b+2o^Eq=lfg#F4TFqN6lFu=&SvKrJ{*dz%J>H*lFYHj^quhNeDZpr6e<0p1TF zJESNtWrbWriJ968XW--3_Zg;{y#Xon9@uQd@xk*)4F;>~*JqCL2~hN70kxMcVlh;k z4MM56J0efz=R?NF-L@1Y&MFAS>#LmN#y`Yz{5l)E1nyFJ0=kV>z1C93O(97!e+*-U zrR3oV(gWTJqk~d5je}A#y80&gIDy)>&b1iSy3y9&k(AEbz)P<$7J_}wdeovWxG``x| zS{P^y<=r#*&Bq2Q{mm?vfAieK(!`tE0WEfHEk9YMy@&iXvxXHO|ENyZvcSxNq#Un< z@Hs*(((S^P>fKI6r8&F!fRg?SIQ&#rpguJqOUS*m4Fk2N_TEDA2DqtzZ_8T5IF^T1 z52Kj_fOuF!BDkgAA|`g(=?Lz-23}{8kCSj`?c-9ivJ`|^*i3qsG5IsRB4FAJjsa=uvK8Y@1@}iZrUdOKAJr_ee8_JDk%Ju47l1I1#qg`S@DQ z#`4ncU!DDj2Xj4w9~%qboV)=--_dzCs^C59$SUUW2sCs%9}~YxnG&t(J}MeXysDGu z+@;HVg&J-NXAU1$C(J1wvz@UcR7tb4N=XnA%+OSSbI+l%5KC$2Ec^`28YNF9)eiIg zqYm7n8c^yGCIe%k^%%PUyoP&KhM5)tXH>q{wCHilYD-%X|2g7(Wq-)CdWC+p{AuTu z7HzrXEm!oj@Y<2Jva3U6L_e6|BRfB|UZht^TI<}QD+qD95LZt@NCut>1tSb4qbN=_ z7=lFQYks1b8n>2KL}5>GLP;t#ca=hxhQm{0h|;RW5C9cPabm-tql4arbu4pFhVB z^LOs;RzE(e{a2GYsBh=zCM10P1Xk-a?m|>T002T1cOQMd^Rm&mwe+4av&y6_w?|S3+Vz+1S6k1= zKV))_L4SRj#(iTe#fP2PgQ)<3PW2TCwS(D@0;aLF>R4*+7#3E%REwm%LRXwot8GAek7 z{D`|(1dS()q0GkPZY8^F(4UnOuM>z85;BXEt_pgQ(_W9*gB?EH#1sy>z1B62wY~KS z>|t;7i;W-_F?YD-ej5l>Li8zrz&-Ayw{&`o><3 zRTs!sw?(eru^K*4k>dUU)%`~m@uX5}pUd`FgGy+2?;v+Z98D(jxvxm((5+hfa&Uv) zAQdRxz1raxZvVr~GpmcSOmpB@57*xwRwqm0W02h)Pgq%9>UXqDvhkp51iRLEr>T_| z1wral{JA?|RQysqjG{PjN+J{AnE4aB`|9T_y{o)gP$L3Wb@28B(PF-cojY2E1SoM3 z_r<|mRP=eyY87L%dt~?dkqqro?dj+zf+B>@@tZ4*n|#NVYQtq=!1Rw@F;Eb%lG<8p zI_8k_oS3`XKb%CD>2$20o}d18xomj`JHU--D64HAhYP^7dK;*`4BAt)e-U1GeS`F>4{37X0Txhzkmt%tfN4sB}< zD7n#5CWX#C^}yO=icYOcJSN^7lr<5tk;(c$i~xgPkZ*KJkUG6wuQY1}rpnI>scq-3r}b6kEH=Da_mIUq2JfaNZLR0q94J6EiUgm*HGrwP>AEX8DdrH`@b^+_v*Y)&40Pnd)MT>*jdk9H|nCKr@#1D)uAJhypmKu zSB#8yhrTiCVW*;+beigf* z>+3gNVtPdY@$~CE{5d<*VxH+YMs|upl<*0 zRh0WNf0DYbb>fMFeyDisAfpa3a0LV_HE7*ObjDT4@+s)80yei%zDyKeKZ>2+WKahI z83JZmsxl+8UQ+A#w3lrr<;jtau-OJsOW|iWevFm5hC@L2egb@?u6nIr^>vW_Zf-C{ zVW5Jj38BvfZPX!7M3Lv_<}$cN1`5M}-;edYTgX7KN-38j!KTTup9;1-oxxx3FDmfs zl?ulX$^eH52443RsnK0eXU>=|^e|u~GnuVJ;*;;{xK;-LR`+$(v(5oRve z?gW86WWEqP?gDo>43EL)O*ScfwG-M)sj@+p|IE$ePwXkG-sWE$aC-^k z?8f()0SJA%4=}6fwwZXK0po@EqTg7uYfJMR{0FMMr@_C^2E^RdnolE!5ej%6gs!<4 zASht+bb82{Bpsl)qsI)f7XJ;G?La@yn0m!`A(_XAoYkcq zdaW+DOyNj)&=ZCK{M9yT-G~r~0Fv5fBrDI-9=-cUmQ93^{d@Dqro$KdHSs5cnK6@< zT86>4XX!>UKZ-wGR71R${3w^kcIof*ig5j?Apq&2B$1w!pyMLX3BiP}`zWKoYs*N92HaXc&UTjB!zG{Aoed+GxiNu^sltH8)3VX# zL2x2WFtGf(@vZS+yTxnWSaD7lDV7_8Q3JL6efMTnYj=+8ZWf$SIPlU=*Ltyhhb`ji zPQitSZ!@5E31G?PtcROQGDO}><+u9xDy&JPv z`ORXQ1K6@6cbsc_x8Gyr$wv$Bjqu6^ z_7)vAhSK!kNXk02x`>T|uN%8j5eK@obkA4Yb=18}T1JX@e`bS57G3VQL zk4M}gY9fibB}_Wyat_bz$la~93&Y!?+K;pF;w!iPaj{!ZNGn~itrlqDQpoA$?nD#dh zem*@TB~AS(7xq=iJetX!$I=$X^+W>Ot&{hQ((|XZ^HNVuhuoTR^w9Gkd!t8mxSfKv zBM}37o940_kZuuby)7%BOAJchvMpJhGx3V$l&FFME!)X~o^CI455zByz2 ztbc8`+6HBuc^WD`$rFzHT>Z@%#A0IU+G5APG;M41P5RH#PAl`WGJikim76L1^mX+b zYs3T7%l^0?7*hvKxr%LjH$ga#Y{zJDi9-?WzyYD=#9X@~Dq*XY_)t+q3U3-W${1O<|f4&Ft;(Fq7+l(it^?%Gin|`D_ zmAZ!EI)J(8vD0&ZCMD8u?kn?A!J7#TDgn5+{JDOEI%A+i7qog zOF925E+-=(c7}()yTe?zcXU6pL8kEzNYrhb8VAhC*AB-klOE>swZARvnkz;E1?H|m zhik}O1^}7LH4W*Txl!nuMlY>1-!?WTf9eSNVj8-!QfGAQTQJBiuwBEE5l5Pw{E5|h7Pr^%Xu&k+Cj~lG?V!YuH%w?$d z(KAmGKFKdl;PK7Qi{nj2VpDW11?B0_uCNdi%LMvwwyK(I=<1|R)cOhY4Xk&%?TK1a z1!hs@$L*kPeX(Z7BL0oc&+p}ZVUIXERf>~7_E|^S)tEz#{aE%VeTdInr{AGp)l)3#=x$(M?5Lc{}|ob;v1mN2JcX1NSz^L$ZO z3MWrsG6ATIK#gm8Sr-!hfO5ws4i8iY#|Y?aFO31f>iN{l)((r9c*QE(X*h*#o_OI@ z%@$GOsj)ERz72D^1Y>VVMy_Jc=w=VOZAG4l?;0M?=yK-Mt*W@G{rEZo_D-YJC+7nj z1jWG=PwrI;++H<&-fN0McWCoTgl9lH|Q3Z;bUYmpAotj*&q3c4qp19LG&N64p# zdz4B%9K42ZJ*VLKdf0KwBwvv)58hUej|NqOqv>Whbhpe2pU)^2Ml}u}9g2=O3V7`#)hPsC&(26o2 zSSx={1tgM5WAt2_NOv4_1j-WN~xbU zcYU*{82MI(3l4!`Z^4at!Z-eUs5I4uurYx}8ujzZvH9yREgQvOpL*tk37zLvc;jG~ zAil`4#tv$g@V%x-bPB6%IqT2vQ9o>N2*ac$u#UeLh z=-o1e*Lse5Q9&C;mBOM7y%PAUUvsu}$N-oo^+#jlYXIMwK*c*&(hQ&Se;ypaD=B9H zH+Bfdec^<3rZBo{M##ll6m>?+4jzMbr_ud!(a-@v)=FTiN0*VZ| zf&L40#P#LiAD0VvJ=DB}64|3T;^CH9rR(*!CL4>Yei&}??ra9eKKJwMnWSs)P{i3d zH9J(fY=4R84}SId^WM-Yr`z0i-j3bb^w%D`N9`x)w&a{fuxkjv_%8bcIkGD#uIyqp zuXvFe&Mu{o8$Bc2~^s19c%sK4E`V4F~!f_Y0LrPqOJr!%TdhIj&|9p z{N0QMZ;e8AlSzT|GT9H}-@bYkz1{u>df0yHx4PC}$OrUBCFq?<%pNIkzWZ*BU z=`f$XTQ8QNE0m!%(sf8MGsvi~W=}A>Bk%n1dHsM>`B{ayP>q7)2I3A+;gl85(?x!@ z2JJBw*C$VO!(69AGzwONOK9duxb@E`z}9AvTAJ<5E^(S2GXA*}miV%G@a{%D*%W@k z%*uvR{Hb0QY2fzK_vcOaj%dYGJbDRuOoN#~s1Kbff+o|o+^jI->+HExeK?R`^XRhvcYl;MDT@9`*cpWd9-)k}y6Hh!9z$JjZr8Eus6K_~2^w33Mvr?^ znITz>PMuCnW+^FVw|}xS*uk_!x$o*!#N`C>^KpwlFlVXN4o5MLnMKF9DpYWrc}i^l z@Fu8brTG|N*Ag9%L5((gDdlv+zBK(p!d!5fQ$NM{{Iw&YkBTf4;N2bt8J9Cw)a6vV zIiV+yUBRwt1_tCf5n5<3UOYxilq`Bk9ajoYeSzC~H`y~&{Y_(p+y9aU3dmMx2e$4i z2JM47iCUHJgcmPDH@Caq|8Xq9rHw;p8!FeZG2G1_;VxnfbltbRvIPobB z3kRDt#P0sXi~3y)Fv+85(45lWUxaSVTxj&1S@a1n(`8S?q&!Q(WZc*}LP5%CVS$GKm>uy5eH%(=2t%RbKPBq^Ij% z*8a7z6;Yc$b!ED8Rp8orEi0D4w|*RqynXtLEk6C?v-|hYGlK0tKHlC{*W%YalNEde zWZWGg?YuL)qKY77bz|mE(R+O^-8-0&nE_-i3+J4{yC!GbR{;>{bu;7UyBpF@R|mR- z3r$)#P-9Z!71hMV8r-b-YY;(jRhkA&chg^dw?jfgMiDJLfZ!f^B`26bFiE=)PMKSK zK59~c>>Ec`q_1LxLR0!p%D45xyU4=HSCHK$o(2y_v0ULDR{)>T5@jGR7niL*r2=O6 zWfp{`uuX9xym(UY(s#Ar(2rX+fY)qeD=mG51^(pDurZJGlz*_L8%DKYNZMsV#mr$D zm`v{6q@Jrpc}f>v;h*V8JtL`N1=2kL;-NvxC)YH-sL6$1|}1<;SYakRLC(lrK#WV2a%#O0V8 zSxzd?LnS)NK%8TvR<>!F5v(oZjn664N0n=xS7MAQsUVz9W8?;usXWL@|8Mr0CwAA@ ztKWyLzMrhT1~ej7H#L!=RX=NXSyRxd2rWOZAeZ!)4| ze*T`rGTIgtNC{ZZ)}t!H{k8_jVA4T&Q~lagWTT*+N*lx9=I$zj{sK_sbQd=Tr@+#| zjcXb$!SLGbz&9fAD&>&ypwBn6i#eQJKQI_^_iX@`yS~H|Em5A}z6$Os)oW5aE$9+9 za&bly@(8mWX>ZD3<5e|*Mey_9qNAOlL7hh~D9^j)?Tr*boZB|dRDaqdOG!}O?vm)v z#+&zF1&~iqzhN8ya|W8P$)I95cqQXma?kY+HkZVm2g3ceU=-12g^l&j;w{WBpw0gw zkuT8k!qxp~X@#A6ff^rN0`j3~=7j}q7(q9In_E6zAlMl<#q``FEVsl-f@+p}f_&Ci z5WO(~<~NCcwhb*TmG-T74OpL8)Rc??vLfR?ek=_=xEi>%ZiS-vq3m7BBG6_vD8#e(*GP(kqL;-59{TLmKt z_m$xg@xehiWq4@=UE!#o#qNj73b`$^XWpn!wtFKQb+Ulb_%6O%>fs~6#yS@LFe{Gy zyc?W-jyF0&*g?1Kxu<>-zQKm1iVB&M-OxhDZYVOV`7JifW-_3_>$4(KOpcmR1Kx4! zm4Y{m_KdwdTK{YQMToADi)QKW>Jmc1;)1mAK?LNDC$C9kK59)M=t#|b0kWz-YV6`5!jv<@ zfT#X--+Wqya$;yQ0JW-!raI$M1a#&boL?#{?k4timGbhf%mYutTPv|fmhY(Y?6JhP zDLj|%=3>EY^U`;_bxZUTVd<6DNM4C>au3`<=GgpM3438?lW4)EL@3!lTbApZ%}TyW zY~;0cM&nMwMyTl#$qUT!#c8@n2a%lx1;l9pJbjOCe(_>0|73Cx2%_@4XW`3fIYX<2 zG+v0RSso*bh3l)Wi$(e};}MSbZ)y(L;{@ zQ+H6feRur? zE=tv$MGu-aukre3_D~5yS{rg_7#V>EWhX8PwNKB-2B(sk{#9l^vCr%R8zrzAI)RY~ z26cKfR9log8lbe_%^$QG38QW*KXObou>U8iMzN>&O*AI(i+=<=vHNzT{yE|}XwSyz ze46<+L+hEXHqkfC%O)#H$vhsB*{x$E!+Dp*M@NBXx6gI7oE`>UsbCyhEnD#F-4|oBzB*6%|qw z>oddZ^54q_PCcxQ{6p*7G|Fwfj+?M~MC(+z!JHtn`aPnb# z?Oc-*mW6& zY^zS5^KHdrz}S9T5gym#ncJoN`Ys8V^>ci^$@bR*v_^KTi=3KvR$V4i> zJQA5$qhuRm+o4u>Ku!2ZxJ>f@KW5j`II9c2cUZGpsnUN` zQqCpe?c1n(V)Gj0k%uw5zcV?tEPJj$V9{YQuICcJ{QKqWoeb-O$bS|N*)L5pGLG59 zCBu!?#DQAS4P}c0Wfo-XDQ1u^B2>~mg?N-*b7mK2T^usMV_lq4kQDV7&CQyR&sQ{& zNyP`3U(WCkn3s;1cv4uut3TT zh|ke)p0d}G3@#LyhfT$joo8ug<6cB>O9_{A0^ioZVco$*E8m|!-Qn14YBnzR!BH)R zM|7hZXcp$B%9J0@$P=RSkQAvkR}FZ-1b}+}YU2WeKOG^$mt_i%-N`IoR?dp;OfP;h zRc%vydHI^N+Uvzf6zV*BWF89|(q~oqzkspxTOo)uH?XF=BqB|#>eCzqbzzC!VDHTV z2(xW%Fng};6JX}lv2GW89vKU!WVKo)FIV5bATq+PP5E1rVsv8N#)XlIhAyLVPOPA2 zM#cH5m@EHhBL%*p&J$TG=@OtY`Tl&p1SkcbbwQ?idc{bqzw&Hl3ZYcm39>)s1P_!Q99A$~S zPVR+vpt;RNPqZJuzLG5=f!e#Vn0DmO?a0Fam3RMl{j1KLY>K5eFYskeFR{h^q9FYL z@#m$94QR%bhHtD9PN>&1H#FW2V4ADbxfLKWtE(k%D1V=MWI%7vHULfI-IY!d$c(cj z2}gNJa)eOp*?nIS-4lcp4b%JdRRV;zk1G2UL9rL8Q||?}BSKbatT)k{`VjK?1jz9Y zrZ9p*-Spb%C1HRy}_BWxw)|YN9^#0F>e1C=6*@c1aP!$n4 z1@LF@$Ulf;7rDDpS;6`5;2aHm^hR9ZBmsnTTXy_$0^bkj+Zl_`PTRtbHoms!)}W9e7@D&ApHJO$;6b}IIXOmY7*}tvzJHjqEh6CMeCJXRc823-P>IP$xhdEnfZpQFvu>GV+Tt8mbq!6IA9$(LKg zA&!VztmEd}+1ZIB3(6#<2uM9`Yi^d$C_u?SwdQ!h!j#v5?6$yW9ywt!+K@;C#UzAp zca%;iJraN9RvjB<)3JXZ7Bg|#o!sXnRKp!PF2WX zdxk2$0ON+KMFJJCbqABlH%h(!0yH3G*bctmt(N7Csvu&)*8A^mL6Mz3X7a+?aq(y@ z*8^k;@%qBU=3+7l=Rl3|ujpa_Kl^8x&Z4Bc1fBJ%_ETWPOGbX;%&36YGoK*5OEg?;iN4A^ zIz2zaIwB zEL;-?E(y{5_hh#d62utq7cJZ|tM<{*j*{}*C-4|up|hPITI9DrT8N%GbEahnuAeU| zH(9wV%IxdP-gek9Yz&i#s`|!-XxH5PyQ`cp>ecR&C-!f>7N56w0&9oqY~}*#*}Y%e zpLyThx18r%i2CmeQj;nj`D9{y|G$(27ybkJu%6~>zQ#f9#M`YEo1$B0v2%Epy= z)(!^ry)~TDz$G`Hj!8CdT_C7@Tty@gZHy%5l z)Ov!4&$xN|R&#YCG7=g`ek(DPO>#z8RIlxU63bN#y^#*93%RR%3@;j3v+$p`wg@%w zPnb>SusX4RrwtJ&F~a-P>Ba0)bzal44)2Pg{kgfPAQoYjpdCqao@yXVKeEVNakHWU z)p>IKR`h%3PV)={%c-AFs|SwkCBq}_GAD2CSZ5w588#ieBA@0w7*9sT9s8U!+*R~3 z$cmxn(--+MJ14NDO`W_c)>j!ttAo>OVPeg8eY=fmxJ0`=nL>c*?X-9JDiTKn(N zr2f_y5o4Lh|J?^I)H{DlJQcNc*#iT24*%}+3VK)77+?-I4iCH?1bKORo9E_Xl?GGM zB1cepLo!`Ly=x^PQej|QumMer7GgazaQN zN4=n@^)8^9Mh^nv;`{qQPt(#Z1f$JDy8ysE6I^%R+C0)tgquKc|yD2ct{aM5!~P~lNb zcG)17O7lByuA5?eQddXIVXEP=b{N>=^gW4aQ?pqw)ssaR;y#d{pZK9bqes7KLsXLZ zm%~Z?rmhT){C1FmZ3~(}cX@ zYzrwTsQKrqH$|J+xKWQt3qvo3PVdtZ+Z!~M`Otf7H%xaA%(lkx;78XMV^3$c=)>WV05(*Kmk4ycjsYcK#d7hkT`}pO5-H@$aD@1DWl3XX8est~m8@ z*BFWaCJB-btZz7v^WJ}G!A9D(MiDD2{0agc2psczZ>^;9iCvno0N6dD(GIo^JvZu= z%sN%=NUafle%*|EAqsj{g=}LfXl8Yinpg5`cqMqAMwQB7n2uhZL9eFj_VTy-z>U(Fwc1lPxP^HUh$e9U$(06nm zrAG>QwIH8bTlL{+d9n8WUGAZ`*<>1Kw&BmdL;aa$` z{nBbxaq+acFJ3uB?Nn3sxdF5BiRZnQ6?;EEKlK!OH$GXyDGt*sgHbG@XpyEH`Qp}>YNG0F;#yNvxfLoo`Ow-X7H&1=kK((nE&k|6Td1LP z_&UCz*fUpaZDljB1isZG*WciY`h-*Uk9jH=2bE0v)vK9JNwklXtv?qpKF}8c@FRlJUBZBXNsQnMi(x63?5 zD8*~oNIvH#b%u7>-h_&OA*1_UleU;}xu?j+B|+Uq=Sy0l>l$aj5XAq3?R2nJAP$3?;hqab#_MH%y#F;q{IWmJ^s?#IX6*&_ zZm~?RPg@F~x1XIb#jL}EWbk1uv#nTytp5ioC!@B`#eZ!`s_;~sS8H%;&F?L+#{n??CwV7I3V3cr4cy)Miy%1d;2KlW?HJflMs&2P!^H$Iok) z-U$5dl(9vN9e#!3;|8ht-FvStc9RVFoS)|;B1;7GO1hK$3s*3i7yS1JvTo=v@BDZ8 z)&49tMD)pRhbe!w9Lq+8_m{m_hkp+L{+wC}Y9r2GHg_NhJWNJ<5*uc}EiFmje1lgt zf!`Hv2%N3{raB8ArdnCBc>|u#UP#@X*dTNBZyU_?;Okq%wV^5ZM(RL26`}5CT{-Us zeSZxGwZ?32*&u0uOE8^(rRkV+5werqTZi}qzWlh0$E;AQY6n|>p9j&=p-0dq4!Z^u zu)a{>XMkD8{F++0f0lb#u=S0n9U|_OfzQ*pak7fP+jsr^wL7l};A)AIUT&(#5%$ap zps_;pTBgCc3UO+FVUC8&n?{T%Npr$^iBliZhkv(6=H`Ta^Rs1xEz3v=qyD)2yDKNo zjnxy~sN0|#ijLi>#mS6peZVE`Yroj3_FG>+IHul7mabFND*v(<{)b=sYVuVZn>aN1 zbfQ$HzwUE(b~^5t3RU?FhIk7e>|n z+mz6R!q$hLkooaw2P?8$a=I~3dHT=J%8ilzdyjwJyT28}xOI5+tLVSwo$Z*Aq+S1a zcg@s)f29CYwQjWj9;P7v_W!Bu%fq35zrUv_ls(I2jS`Bi*%?_PM7BhBvNV>$WF3T% zElRSlS+b0s>_YY>>y%-}mcgX4WSJSv^X}8H&+mGk@AZ4G=lO^Gnt9(d*WCBH&v~75 zpYu9AmsvEQuA-^b%i4RuH8{|FnPek?aVvlQa&7mzne8%f06}Z{vhdp-5eV15_+;C` zUfyWzToZi=)rE`M6b-C~I8b2P3CSW)66>g!_zHgnJ@Hhon$=>sDN3ngj$tX&Yc)5n~ zjviwgw?gI_q!)d{rfF~pjc~*IoelO2`HebX(#y|UV(~RP) zbDzk&5VCiOuNZ#_`PkQRCGmYwP|z&?pk!(_^MU|?eZ*qa;X<)pQK3Tq0a4XaE=?TougvdJ$@qRCYy2BG%u7Q;S5lX=`D9Bnr{_o zW#NSej_tSMKwkUK_jG)hKF@)zQXa+7n2O~>V!6OyTL9(lKdXF(H(3a;jz1S@!}j9l z)geiN2_#Sivj8`B-zQl`^NpR?AWgyW?B)(?)TY3_^m3+FYNFBLY&nn(Q%c_!b}uP; zN67!Wg+?;~$oTVt&W17kz0&V%9l9-n3}pJJKGAW!F?$x!r(r-4bO!zi)U$Kx-NF=A zC=+Cj>1DJxcM3Jr@!k_L4Hijg`pmDrQssSmMrAoS38B54wBIlCogFWpX$hND(m29N zHD?yb8W^od;c=<xxB=(23K7l;d5m;jTWCX=qTG%fJ%L}1{S<0^vEH3UB2N~9YEEB&pJ@) z1^6+1_{KZ>f6TdE!`>79)ERIih8Y!4JrNp@NFJ(AC?xTAoO ze{4aySi-HJcsx@#-86f}&558Wr^`PrHS&m(dW#M2RXFpMGj%>yn-$5|V)79Ted)AT zhRIgQe7DW{dG2x{ZE<&e!;D3yxT-$(NeN)3fz(QS+DGQV#jg6tj+U`lem@4Sl2RGc zv5Ftn89tT*Se8`p*WnWa)<%7N>39*K4E1PinZGcqXIoOp@;h@Qpb_PduXBNpcr-#$ z`pE#<^w2vo3*0Cy>KM2!au~@|i2(12KwI`bTIfEpYo5|I`=A14W4*E?g3CnV56IiN zkfYs01|CWo|N0r=lO^vDu+DY_ zN}G{R?CsY?hK4to1yD5L4&<5L{zuk83LqcQ8fdPZ;>o_IAMLz+I$m;$TNiV7pH}9R z;S?U1y>zLUN>^Wanu^*LP~B)n%Cotz@5ih^SiYZSLDmtbj`x&79iQA|kq>+OiYEB9 zYrn&W^HqM>JS~Taux6^{S2|6JONGq&+_uAenlEq^5$p_66oVhtj##q3NW)K=%=NDd zMKD+uIKc8m3QOu`db{p7%*^baI>T0evkGo!xHlbA0TlNc#4h;*Z-P;*h zynV0w#5Et!9VPNX2YDJ>nljA`ARklH6f9tZWv_|WS;fxXEus0$sJ`{cT1i=@moXVA zn|o}WcNI!&7#p3^@S)8+e;PZMlU^io+aj69SSK04mF*v(hBc_oxN!+TpDf=SpQAsT zVlA_={D93eis=UrNTM3*0q6GoM_IY}fnB%8z_91B@hhd*l1uvfw3vXZ-57{a;ZEyU zx6)*Loe%CU%O!UhJ?T{B@C_(;D_M!i1uNP%^NaTP?wCVOe^>Dd-D_nloMRlNXV>Mf zUqm%*19JSWVdSKWy~0$7gEZ5t>ZX;DZ&RAYeySy>TAX<{r-MeU4nTF*fE&zzV&xqF zxmRfzfOlL58QCSz zl)jV78-)Ip=zjF$fYIU8MKzuA)DvgzWp#NIjmt3n=j)kYPjTE|9^cUYdXIfvSeUX? zsDG8Kj6N%!y~>r*GZ8Dlv}~9HuFtWJy-F7WzR6{)z`VrFuauMShGYXS%>cB8e+!6W|xzP$BGibJRl}$xGb*k%z zQ_FnA-Rt}kJ<0hDw~{ArD`NLa%7ZzJ54zwHETUUCTw6x`L@o;-%r=0Sm0Y-Oby z=e(i~U(UMy)?!5;=RIZ{;)U;LOjzs!p(^9kg(j#e;7a$|B-dkXkdu4LHx)|AZRjr` zqBJQpv$1?D@n^HlnBlrigL{UNhTZdqilG`G`hhDPM*08`YH|aLE`K5m7k87fNMl8B zQkoyWPSfAfV~t>=GUljx%||K6%ouu3A^>RLDK=QMX>sKSFT9n@@J0!I6(|TR7m5LT zuOjJAn^c)qXzqxpThh_45rJH_Z+54aJNPKa!>kAhTue<5;Z72JTYfS??$pBqWZ+p$ z(Sduf?byS&ZpTp@p&u5$mSFQ221xcxqg|kJGtK?pa81KTjR`K2dynS5srHSzN>=}2|+ehD|D@@ z+S8!wdG|Cv3l-k8W~MuxxqSK1>34LPl;mO3WNq`^_7;U7mY~9PE9v# zGLGBD0c{{VFwXvn1Oj-*;#JpU3wBL(gH;?&%MXBqNvcAVGcc}KCGvsGg}iX^(fGhP z^+f;qpJ)QEK z)FEd4GaO8NgxzkB#_&gkAu*fZOoY&t=!dadDX&X$&DXB;F4VGunE|rEYiGZg?TF~Y zNAI-Hdt@|>-&SNnDOfZY(}UtX@bLO?3ywN3A7`*loB+xq|A2?*-TIKjnLuXiN}@~M z@PpV1ryP}epeS18T{4RfsnwFI!a>Q?7mp1BIs!Q?pUIwJyk4pzY$n?PkPIN@N4@nBXr_9u=UHEa`>x1^@z*RTjLdrO$RZZ|m}|mV z?glS10qY5M_i#tn18uen@Rl)8J1`$8C#BxAR8*rji(E4rTxBxu@{+R6MQ7#BkT)G2 z%O!+0c$N73#{+eQzgFBerUDEC;Gu`d&qzcT_h}Wf_yo7^!ZjbeGyZUlOAzLBB(HBX zIl-yhz2}QclXcL6g;Vp-P|vJTGUz?lq-hC?n{XT`ni%OYY{4opHUNjR?Vq()nkz}Gx6Iu!|WBMln zCCXnkyHwAqfRm{{{bCZ;;X~-raVTQgdVo~)4L1NuY9#P+U>(nBYehjKbBUKat;3ne zPTRSzuRIFy;O1E|7JpB&t$6Qv#&c>Q`8&1}^=354=(aT^i_mI`M*Br^OP0AR=N(`6 z0TpGnkpVZ|^F2kb=j8dj7g#qGWKUU7=)N)QSpK}9`F2DHNjOsc2?eTip9?BU^G5k$ zj9tgQz4vje_D39zp_@T&cG+yV!NHaF&o?t3UEJ5&Rbu$mT~loyxpw)z+`|lR(c`uTfyvuL<}O zJr8&{@`cp4WQGQ7${B#?iT58(H&l2d$_IZ-3wyldQbcH3k;fC4ZSnv?0;W z9TFNDk52q<@~U+#BUsc>65EncKHi8_E(f}RtC7(r3&Z}f znUy$oC#jMXXa8qkc}wrUD5jY{DxNpa{+_Yf3q#-(+uf!=t zrvAR=rLNf-RbMvMD6cLF2kL%vuDn^g7v23}IZJjXS2o#bmP>QnVB)~WGE9HM7QNCN z0?@kZhwxkrq6^3V@-&X=yEeP}i=sG3DSkZUsaA-Zx@eIcj-ya>wN?;gEVOY%S}PnY zKAGR2c!(v%MeiaFuC;B6-N_RVxBQCs%i)%+q!3R!R#$v32=XR?YBx^wi|{Q~jJdvZ zNEBHX-`=y+2b7gOQ88HfbztMoz?h%gzVZCv9A@lFcYQQ7)ZLtgx)yE1$s~}!(S52~ z9Zo0&8}2oTT%Wq4FL9Q4=_&T4M^$zaTtPX0=i`N9K3+b6szu}8rW_Q-Mz5+v1GzN3 z`oMf(NOqC2x9JAJLrAbaUS@Ehy;i6+_VedYwXIK7qWSe~hAt3$9I9cH)z&RC%l1mZ z(!;tGaQ@A+@9KN)eJ{FB&g}E4>fFk<{><2z0eCm~3#_eCd6k+#njfQ@IZ!{xKW1po z-y2HooB4XT{w3ei2Y+lBgG3KYlFE(lB2VNqZ8snfD|KQXo4Pa}`np z$g3DGy$}xmq%YB1Bh=>YHwaE83cX=fj{vi9F{~hj-liAb)^|_&F84rHn#06nv64sE zIp|F;S&!SMFv8_JG%lcCZJHq%^KN9q<>-m(6Cy!mEVqU@VyT!G=T);E^32HK5S{i| zaJ8Q9QY_%9dyio{aldMQ!#hX5@DjV4zKMwQq;54+@Y}wAmbcKz3sNf zbSZI6dmnK=)a>efY!j7`m;lCVB`qr%>u*F;)KOg|GB_VhwaxV)2e<-5r9{7p>@qMu z&}>no?M?NQ?y}eM&yE5+(3VtA^@S-OxHaA!^1@Tp$neoXEP0v4I7H^2^Z z$_Z;DIJOsFjZy0H1)+IPqWOfyU>~bkm2V!hLICq7!S+k>`ve*uLiGk)f}+w=WuilF za;I!3Z?M>YSRLM>^8p$r+R)a5=ob27p&oT@NW@5TN2yi@it4rCRGYG}eNk2b7Pv;E z`hqP=ro{a0x4T!Qk+AR;W zUVsp*JRa&Bl*rBc6Nv!n{~%a*s^85AFT0ZaSeTrd?Z0@X3vmT)`lY_$ zDF;GtYxnwdiC%ezq2TC2RSB~S;2eY}1g4ma+Lya$Tm@K({FAWJ&{upw*EKdbKK1mo zQ9LCXvDe(5$dpUK?dBQP<8DLcf1X+n0&O77Q|Qc*kIWaBv=?EBp&Co*^CLrKQ!=AY z@&fwOG%66XQqe^m+x_uX%^^UZ1_7O+sjme+?@ zqs*=VlytA4UDwCXe*oBg)vffWS))8PSWj==L|A>8JX*c=mFYu1_1zD+v(sN}?d{*q zf|Z4qLgu%`i*T7T9E5TgiqlqG-9uK%*3q!r^HIMdRp1i-4u_Ay^(783ILTMjH61@< z77x%ZHRblSiFPkGPlg#HLdzrHNhQBlW3Key1;O8e#$)+ma4)OT)`p5Hae1VTj z)zO*BUq^dyN`Bd2`}B=57kb5e?6|#s=3qkKPd3F$wZF^1U{Ph7RUZ;pLfhZT8}6R( zQel5Y=ndL?V8oncTtK!Aj5NASeM+nA`bJaa0;v-YHPYeJV5#P?-WC>kWYPhn+vF(R zY71W^4Ij?*W!hqoYIEJk^U*V9>ghdy#-rSw_w~;Z*JGBXp&Qw)I^@Nl z$AOb%%`%Rc%f&O~iqZRO)Oi%D)J<3O>+j!z-l0M5Z^NBK1#gE0G-xVBZOkEGvP_`)1m)VKGWhg=+wr1`b$ZPX$Jw!g( zDg)u&2Re=pifp(2tXs7C!G;O5%9!0O)K==st6r#Ym5i5Hxq6%*z8w@N!5_+~wgze) z7`zA_iR(x&k&=8+nSF~{(!+A_q1HO8IPxaj&8ACr4BkJA(+L%B`9^Ic6`IN5x}zU5 z8`b(3RHm?0#VoB1=;fT4d>53==aOMrQ(JXf3#dbUy$m%17qWE|PN<6Ucr(%fVE(Dw(!@k|u(O!O8Mqp+bcd}ip`{i!A*FG*_W;7qsmiJ=+x!y>^@(w@8EEW{cpm3C9#>fGBXZ_ zraXlA$4OpmZxO@K-ph{i9-(?ff@Xb)t2}7=T@4e-6N@`rviqX@Hg&*Q#bn650!3}G z7!Z)@6U_k)mq7zp-$@n;xURscn+{`CqH2?agN}V>k`f#JeJ~*lX(mA&=Xy>wat6Lt zy!kajSCmRrrZ|Lz6Pq^7KGS|8MA5w%dV4S}X>WT)9ZCQS`AmmeIPLyS| zJE(Nj0iD6)#V$jLF^JbkL4i*7R4bCpSUr;K#FtZ-B7(Hd1n4dzp32dINsZUyRR##n zWcVtl^K3wf=2`2ufKhSY+ZoOZRSu*rOZrZn9KM~rA@4!Rc-kebMa@^4|LsgfVwoHT z@7{ihNPv{Qgyyl)ct(m zH8)6&n$S6PqrwiLt~C2DqaJFFM1~w!>Zj=-_u`$inecm$LZ45)m2&RH_ik%z4#<>LAld#@MOs1CY!mlZ8GikJc=#1)J$H8>u`yw z17erQ3SEs%GiG>TWhm?0s=sw*dGS6hY*NEB&Tx;?@#lWWl5WS1%H3mFT{f(>jmVRn zrs#PZ0gff_1`sYVe3nN^W#y=RdpohCg;i7(j8A)2fHp3nkioWQKkR(GdVufWe-&b_ zCJRL8UO8EF$Y)RXzaVipHxwY=c$gU+oQ5%|SbiRfHH%Jrz#cS8^{h>SbM6Ou+?(Lq zcJ#7uU~V(5{P=?3k@o?179)mGicI$Km}-% ztC0!^j@R_13m8xFaXCaJ(#)_uJN3^4d_Ew63KW>aC)+O?0UAjM2cK#qSLg5+QV1IV zdZp)U^X3;Bx2adV*S&PM84z&q$Qa_r7I{d?JO6Ioku*B2O1>K1&|#&e9IH?bIo14Ehqgayo$l2$gD#J ztqI!dCX01;&jm};cMa`LXy8QPYHXbUL1y4&`^>2Maco*2mafNc%$?Elju@F^Ip_$h z`AZH)yzoNfc%+4-xAP@%Z& znYY%=x)jmR9QH^y{w;AvBX7U&jg=z}mT{tb6Q$n{u8dOO>v&2f)b^-+c%I2MJ%O_f zIXZIUO7ebXa6^{Pq>us6RSQ2E*ONhT7~__8Jni*YY{ z2i%TpYxc`tN!rZh$3hlKK89nmZO^><&j+@b3vMxShmJQdOHKr5=r^9sT}eG4dfPZR z%cA6toh~9Lb_2+e_EA}w)KY3H$#?D7t8(_dFt-8g_4fFA1G&>mc8de7C9%f|0oD-tQFgnOLWWhIN=^2V(u4JyY)_;73EzWD zIwtAbApxaWATb$)FnzahIU5vr{ffj+x8&zN!KrO-*ztKw=b5|7X1`7*S(dL@>FkgN zwY<{JF&4o;ch}>`4ldK&G$`zY6e7r}I7^)Wh(({O)5%dj+NV+K)oM_Vbz#=^fcEu5 zMcr!y8u3{xgd)xMJBXcT2YS#S0q3RuyUanCQYrPQn}i^7-NEwN8o~lPH^93(a35d` zWJx!i*#B$aexID*yWs9_GR9^niMVOuxhpFVDsGJ$cwGAw(iVf9!M=ZTSp-$@F9VLD^|O)9w1Dy8}_r= zu6In1#=`G5IK@`XhduI0X_4TL^8%pIH@Z%k^@0Tw=@oK}i)CHV( zfE~MjztC>*&c{YFpgqT&TsiH@`{9Vz`@ar9LHoB!`2A~FC_XV>QlmB(s-59~pxyT3 za|7TPdk~JhZv1&a;_qs%oFzo_sbamFzb|3r4h~A+;tmX^J#||~BbK!gd4}S4kRa){ zkbOvBHwWf&P@8w=@OE46ECs$y>W#{T8{r9kXKX;3*YiG=`}k+gcbYtoPd*UcU6E-X zNBtRn1m->|hn8<-E`}kSkQql}k<@TCmE8)*^6SJfc zR~+gz^5LlBK4YLAA*=E}|FGihDQ)54HUk2PirRSH2yiy)ajG^3f`0R3PvbnclMfVq zA&mRv*T}}g`l#KXeb4YF>2EA~`(K@%OC?e?E`Wkm4LszLhB!0%_I&-S@_qSsuO$iG zv2`vlH7R5OhsvMr_V3fTXBX*coDA4^NCQCcZ-MWp3j4cP|LXYv9N@t z|FTgX3wG-eEk$>o*k4XaQj6qG2t##+aA96G3a(d!0 zB56esq>2aDhRX5t@+}jNvNQ0)gY^kC-uo5Gzwd2Hvg4FDuauZ|OGT3J+ZDYFV|9Y_ zYp5kaN47rT!&~6GuP6|Q)(3K$FsMngwc+0nhC07KX?^}Q@HtF%Mw!Rab$NGx_OdP) zRua?tv9tU2HfypO-QOi5sQx|U|DNIh95`|ZX|U!>V_`RPp^By{&BJqVcBdu4q_*=@ z;T@_F5qGBnjonAnFlLWK{}Ezat+n&d;FiSzmSt3q6Wp>4Ay`|da2D#1-}(ti1z~+K z4(Wfl!~dMsLx0$D5NWS?im&}XcX&0XMY?TMp0wD^_DLFmSGX%8V__}Cqq`FH7{%t% z;d&novi-!;nOoOcPb5u`(|dEzG39=rttdY0#kVLQkP3h0T7S_xg1qa2i)ijgmO3OB zt>r-1-N)t`bWti76?r`B^}8hBmE*=R%P`~gRdGHm*GXSd+?Xk{`eIJ1h!@WQNAK<| zMsWo${(mZf#D{@4Wds1E^$b+z%eBHK~ zX9!KCe^*OJ75!Orl2Sm93Q;KU5IQXYtlpbB{M~ZFvJXK|2v0xm!@}nhD8wyZrP)rW zx=akQ0HCO{_NvuJeVw;x^Qdv$E)C8$kuy!p0hv}m#d?v{W;~B?MqX%&?)}kS>^lF2 zJ@53oY!KD_qnXBx=E7K|E}z2ZUGGL27G)rR%;SIRhW~tJNL52a!|;sD!29b~&dDd+ z5u!=*mENLlyLX84@cyTPgs1h0?Bttu-fC?}IryCOQv#`hw=Ju)Y7Vy{zNF#`RMpW* zT41o~rt;3IJc_C*-wsQxLc!jvKC%4CwFfr3VnKR=ML)1FHz30mu|YTIzjIbJR;82D zOA-BF#|(-K#A^4+CNOct_$V}?@%r?gL}`b>nJI|WSfw{U7BB%exMNb0z!goVaNadg zqAX#l)+43(<@*Kd~P+Aah%cS+KdCcJ#bH;U$xzKY7^Vo$b#(yRLxK(2JgC z1H@3aCQ0zr3FzNZ5UiGIj!;Mt@z9NQM`Vq|327# z;lJ>o3cu2+OP>22#fL{G3Ya%*L>n{g;hb#IHw0vJ*J?=ah=2{&bgHnau6w3Wvg}1b z-UJ0d-+k(eOqxb;LTwHZirLsePxRJqK80$EM0#O|YI~=qtT#%c6jG z=*6fv$_Kc%+cVpah?~5{79m9?3{{;^+e>hkPlGG0%v1)3cTEt#9B(5y9Y~Aa)7^lE zO!;wPsGmDmdK`v+dOXx9XsAU9Q-3<;-Ej^oVc{q+_8lHT_9QF$>yqCTj!gQrZIW|r zxX|08nXMH6Z8<-N_5IFZ`J;CKTe+;LU*bmx{<~C{{-R(n60JPQ4aJ>NoOjN{D@CQ8;y;O(VM*_bu4k4s9u&MAYYud2CbA9@(JxmE z-s6T9B^Uj&x0>{ds35zCO33ViS%_g)nmMkz6)WkA#$nzw*j4~L%~mbNhgCkz!I*#p z;Tu7i=IW>CjaN69E$s5(!0Cg1CKKI zUh$^>6_@{K+v_N-S2!S!k$nx@ZlKW6-m1r|9u z-q8tSyn1qqRGbV&IW~?%D##Nk~w0e#Reo*jUsCIJqK}hx2GNTSNu8jVg^u| zneBre59KPG@2a(JG0366GQ@616;?1r1np6NJ@ZFT{JTzb^ul{u_w-I*{pWSZ>iZ;Q zYH$+G{?FRs9m+pWMk2$C?%(HED7GI{Gu{jV(6}ixO8rLI76AdobN!Rf&}k4>+4tk_ zm$WK)Xwk0Uv47j|^->HvV$_(r9P)My`o8^eqE{X}@BXU0nz-%Iw`=ih4=Fh+&+9o? zqEcDkQG!2KsC*M6Z1KcQ*5>F{G1|55qQ zAN}^&idJOh*$4fgEGe5RW_K3M1Vxn9hWG#IRtAB$=i}Qc7C}pc; z3eTe#6ZkWnB%mFHfRE+_A4fZ%M+y#JkAQz5NpVRjQ3(lAad{I783k!^1#$W7;=o^V kwd!Zl|Jww2Pe)fL|NnV{)Q?eVU;^llroIM3%{J_R0sIlm`~Uy| literal 0 HcmV?d00001 diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 00000000..8d81c02e --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 00000000..250f5665 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..997797f2 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,152 @@ +html[data-theme="light"] .highlight pre { line-height: 125%; } +html[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight .hll { background-color: #7971292e } +html[data-theme="light"] .highlight { background: #fefefe; color: #545454 } +html[data-theme="light"] .highlight .c { color: #797129 } /* Comment */ +html[data-theme="light"] .highlight .err { color: #d91e18 } /* Error */ +html[data-theme="light"] .highlight .k { color: #7928a1 } /* Keyword */ +html[data-theme="light"] .highlight .l { color: #797129 } /* Literal */ +html[data-theme="light"] .highlight .n { color: #545454 } /* Name */ +html[data-theme="light"] .highlight .o { color: #008000 } /* Operator */ +html[data-theme="light"] .highlight .p { color: #545454 } /* Punctuation */ +html[data-theme="light"] .highlight .ch { color: #797129 } /* Comment.Hashbang */ +html[data-theme="light"] .highlight .cm { color: #797129 } /* Comment.Multiline */ +html[data-theme="light"] .highlight .cp { color: #797129 } /* Comment.Preproc */ +html[data-theme="light"] .highlight .cpf { color: #797129 } /* Comment.PreprocFile */ +html[data-theme="light"] .highlight .c1 { color: #797129 } /* Comment.Single */ +html[data-theme="light"] .highlight .cs { color: #797129 } /* Comment.Special */ +html[data-theme="light"] .highlight .gd { color: #007faa } /* Generic.Deleted */ +html[data-theme="light"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="light"] .highlight .gh { color: #007faa } /* Generic.Heading */ +html[data-theme="light"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="light"] .highlight .gu { color: #007faa } /* Generic.Subheading */ +html[data-theme="light"] .highlight .kc { color: #7928a1 } /* Keyword.Constant */ +html[data-theme="light"] .highlight .kd { color: #7928a1 } /* Keyword.Declaration */ +html[data-theme="light"] .highlight .kn { color: #7928a1 } /* Keyword.Namespace */ +html[data-theme="light"] .highlight .kp { color: #7928a1 } /* Keyword.Pseudo */ +html[data-theme="light"] .highlight .kr { color: #7928a1 } /* Keyword.Reserved */ +html[data-theme="light"] .highlight .kt { color: #797129 } /* Keyword.Type */ +html[data-theme="light"] .highlight .ld { color: #797129 } /* Literal.Date */ +html[data-theme="light"] .highlight .m { color: #797129 } /* Literal.Number */ +html[data-theme="light"] .highlight .s { color: #008000 } /* Literal.String */ +html[data-theme="light"] .highlight .na { color: #797129 } /* Name.Attribute */ +html[data-theme="light"] .highlight .nb { color: #797129 } /* Name.Builtin */ +html[data-theme="light"] .highlight .nc { color: #007faa } /* Name.Class */ +html[data-theme="light"] .highlight .no { color: #007faa } /* Name.Constant */ +html[data-theme="light"] .highlight .nd { color: #797129 } /* Name.Decorator */ +html[data-theme="light"] .highlight .ni { color: #008000 } /* Name.Entity */ +html[data-theme="light"] .highlight .ne { color: #7928a1 } /* Name.Exception */ +html[data-theme="light"] .highlight .nf { color: #007faa } /* Name.Function */ +html[data-theme="light"] .highlight .nl { color: #797129 } /* Name.Label */ +html[data-theme="light"] .highlight .nn { color: #545454 } /* Name.Namespace */ +html[data-theme="light"] .highlight .nx { color: #545454 } /* Name.Other */ +html[data-theme="light"] .highlight .py { color: #007faa } /* Name.Property */ +html[data-theme="light"] .highlight .nt { color: #007faa } /* Name.Tag */ +html[data-theme="light"] .highlight .nv { color: #d91e18 } /* Name.Variable */ +html[data-theme="light"] .highlight .ow { color: #7928a1 } /* Operator.Word */ +html[data-theme="light"] .highlight .pm { color: #545454 } /* Punctuation.Marker */ +html[data-theme="light"] .highlight .w { color: #545454 } /* Text.Whitespace */ +html[data-theme="light"] .highlight .mb { color: #797129 } /* Literal.Number.Bin */ +html[data-theme="light"] .highlight .mf { color: #797129 } /* Literal.Number.Float */ +html[data-theme="light"] .highlight .mh { color: #797129 } /* Literal.Number.Hex */ +html[data-theme="light"] .highlight .mi { color: #797129 } /* Literal.Number.Integer */ +html[data-theme="light"] .highlight .mo { color: #797129 } /* Literal.Number.Oct */ +html[data-theme="light"] .highlight .sa { color: #008000 } /* Literal.String.Affix */ +html[data-theme="light"] .highlight .sb { color: #008000 } /* Literal.String.Backtick */ +html[data-theme="light"] .highlight .sc { color: #008000 } /* Literal.String.Char */ +html[data-theme="light"] .highlight .dl { color: #008000 } /* Literal.String.Delimiter */ +html[data-theme="light"] .highlight .sd { color: #008000 } /* Literal.String.Doc */ +html[data-theme="light"] .highlight .s2 { color: #008000 } /* Literal.String.Double */ +html[data-theme="light"] .highlight .se { color: #008000 } /* Literal.String.Escape */ +html[data-theme="light"] .highlight .sh { color: #008000 } /* Literal.String.Heredoc */ +html[data-theme="light"] .highlight .si { color: #008000 } /* Literal.String.Interpol */ +html[data-theme="light"] .highlight .sx { color: #008000 } /* Literal.String.Other */ +html[data-theme="light"] .highlight .sr { color: #d91e18 } /* Literal.String.Regex */ +html[data-theme="light"] .highlight .s1 { color: #008000 } /* Literal.String.Single */ +html[data-theme="light"] .highlight .ss { color: #007faa } /* Literal.String.Symbol */ +html[data-theme="light"] .highlight .bp { color: #797129 } /* Name.Builtin.Pseudo */ +html[data-theme="light"] .highlight .fm { color: #007faa } /* Name.Function.Magic */ +html[data-theme="light"] .highlight .vc { color: #d91e18 } /* Name.Variable.Class */ +html[data-theme="light"] .highlight .vg { color: #d91e18 } /* Name.Variable.Global */ +html[data-theme="light"] .highlight .vi { color: #d91e18 } /* Name.Variable.Instance */ +html[data-theme="light"] .highlight .vm { color: #797129 } /* Name.Variable.Magic */ +html[data-theme="light"] .highlight .il { color: #797129 } /* Literal.Number.Integer.Long */ +html[data-theme="dark"] .highlight pre { line-height: 125%; } +html[data-theme="dark"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight .hll { background-color: #ffd9002e } +html[data-theme="dark"] .highlight { background: #2b2b2b; color: #f8f8f2 } +html[data-theme="dark"] .highlight .c { color: #ffd900 } /* Comment */ +html[data-theme="dark"] .highlight .err { color: #ffa07a } /* Error */ +html[data-theme="dark"] .highlight .k { color: #dcc6e0 } /* Keyword */ +html[data-theme="dark"] .highlight .l { color: #ffd900 } /* Literal */ +html[data-theme="dark"] .highlight .n { color: #f8f8f2 } /* Name */ +html[data-theme="dark"] .highlight .o { color: #abe338 } /* Operator */ +html[data-theme="dark"] .highlight .p { color: #f8f8f2 } /* Punctuation */ +html[data-theme="dark"] .highlight .ch { color: #ffd900 } /* Comment.Hashbang */ +html[data-theme="dark"] .highlight .cm { color: #ffd900 } /* Comment.Multiline */ +html[data-theme="dark"] .highlight .cp { color: #ffd900 } /* Comment.Preproc */ +html[data-theme="dark"] .highlight .cpf { color: #ffd900 } /* Comment.PreprocFile */ +html[data-theme="dark"] .highlight .c1 { color: #ffd900 } /* Comment.Single */ +html[data-theme="dark"] .highlight .cs { color: #ffd900 } /* Comment.Special */ +html[data-theme="dark"] .highlight .gd { color: #00e0e0 } /* Generic.Deleted */ +html[data-theme="dark"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="dark"] .highlight .gh { color: #00e0e0 } /* Generic.Heading */ +html[data-theme="dark"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="dark"] .highlight .gu { color: #00e0e0 } /* Generic.Subheading */ +html[data-theme="dark"] .highlight .kc { color: #dcc6e0 } /* Keyword.Constant */ +html[data-theme="dark"] .highlight .kd { color: #dcc6e0 } /* Keyword.Declaration */ +html[data-theme="dark"] .highlight .kn { color: #dcc6e0 } /* Keyword.Namespace */ +html[data-theme="dark"] .highlight .kp { color: #dcc6e0 } /* Keyword.Pseudo */ +html[data-theme="dark"] .highlight .kr { color: #dcc6e0 } /* Keyword.Reserved */ +html[data-theme="dark"] .highlight .kt { color: #ffd900 } /* Keyword.Type */ +html[data-theme="dark"] .highlight .ld { color: #ffd900 } /* Literal.Date */ +html[data-theme="dark"] .highlight .m { color: #ffd900 } /* Literal.Number */ +html[data-theme="dark"] .highlight .s { color: #abe338 } /* Literal.String */ +html[data-theme="dark"] .highlight .na { color: #ffd900 } /* Name.Attribute */ +html[data-theme="dark"] .highlight .nb { color: #ffd900 } /* Name.Builtin */ +html[data-theme="dark"] .highlight .nc { color: #00e0e0 } /* Name.Class */ +html[data-theme="dark"] .highlight .no { color: #00e0e0 } /* Name.Constant */ +html[data-theme="dark"] .highlight .nd { color: #ffd900 } /* Name.Decorator */ +html[data-theme="dark"] .highlight .ni { color: #abe338 } /* Name.Entity */ +html[data-theme="dark"] .highlight .ne { color: #dcc6e0 } /* Name.Exception */ +html[data-theme="dark"] .highlight .nf { color: #00e0e0 } /* Name.Function */ +html[data-theme="dark"] .highlight .nl { color: #ffd900 } /* Name.Label */ +html[data-theme="dark"] .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +html[data-theme="dark"] .highlight .nx { color: #f8f8f2 } /* Name.Other */ +html[data-theme="dark"] .highlight .py { color: #00e0e0 } /* Name.Property */ +html[data-theme="dark"] .highlight .nt { color: #00e0e0 } /* Name.Tag */ +html[data-theme="dark"] .highlight .nv { color: #ffa07a } /* Name.Variable */ +html[data-theme="dark"] .highlight .ow { color: #dcc6e0 } /* Operator.Word */ +html[data-theme="dark"] .highlight .pm { color: #f8f8f2 } /* Punctuation.Marker */ +html[data-theme="dark"] .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +html[data-theme="dark"] .highlight .mb { color: #ffd900 } /* Literal.Number.Bin */ +html[data-theme="dark"] .highlight .mf { color: #ffd900 } /* Literal.Number.Float */ +html[data-theme="dark"] .highlight .mh { color: #ffd900 } /* Literal.Number.Hex */ +html[data-theme="dark"] .highlight .mi { color: #ffd900 } /* Literal.Number.Integer */ +html[data-theme="dark"] .highlight .mo { color: #ffd900 } /* Literal.Number.Oct */ +html[data-theme="dark"] .highlight .sa { color: #abe338 } /* Literal.String.Affix */ +html[data-theme="dark"] .highlight .sb { color: #abe338 } /* Literal.String.Backtick */ +html[data-theme="dark"] .highlight .sc { color: #abe338 } /* Literal.String.Char */ +html[data-theme="dark"] .highlight .dl { color: #abe338 } /* Literal.String.Delimiter */ +html[data-theme="dark"] .highlight .sd { color: #abe338 } /* Literal.String.Doc */ +html[data-theme="dark"] .highlight .s2 { color: #abe338 } /* Literal.String.Double */ +html[data-theme="dark"] .highlight .se { color: #abe338 } /* Literal.String.Escape */ +html[data-theme="dark"] .highlight .sh { color: #abe338 } /* Literal.String.Heredoc */ +html[data-theme="dark"] .highlight .si { color: #abe338 } /* Literal.String.Interpol */ +html[data-theme="dark"] .highlight .sx { color: #abe338 } /* Literal.String.Other */ +html[data-theme="dark"] .highlight .sr { color: #ffa07a } /* Literal.String.Regex */ +html[data-theme="dark"] .highlight .s1 { color: #abe338 } /* Literal.String.Single */ +html[data-theme="dark"] .highlight .ss { color: #00e0e0 } /* Literal.String.Symbol */ +html[data-theme="dark"] .highlight .bp { color: #ffd900 } /* Name.Builtin.Pseudo */ +html[data-theme="dark"] .highlight .fm { color: #00e0e0 } /* Name.Function.Magic */ +html[data-theme="dark"] .highlight .vc { color: #ffa07a } /* Name.Variable.Class */ +html[data-theme="dark"] .highlight .vg { color: #ffa07a } /* Name.Variable.Global */ +html[data-theme="dark"] .highlight .vi { color: #ffa07a } /* Name.Variable.Instance */ +html[data-theme="dark"] .highlight .vm { color: #ffd900 } /* Name.Variable.Magic */ +html[data-theme="dark"] .highlight .il { color: #ffd900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/scripts/bootstrap.js b/_static/scripts/bootstrap.js new file mode 100644 index 00000000..4e209b0e --- /dev/null +++ b/_static/scripts/bootstrap.js @@ -0,0 +1,3 @@ +/*! For license information please see bootstrap.js.LICENSE.txt */ +(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{afterMain:()=>E,afterRead:()=>v,afterWrite:()=>C,applyStyles:()=>$,arrow:()=>J,auto:()=>a,basePlacements:()=>l,beforeMain:()=>y,beforeRead:()=>_,beforeWrite:()=>A,bottom:()=>s,clippingParents:()=>d,computeStyles:()=>it,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>h,eventListeners:()=>st,flip:()=>bt,hide:()=>wt,left:()=>r,main:()=>w,modifierPhases:()=>O,offset:()=>Et,placements:()=>g,popper:()=>f,popperGenerator:()=>Lt,popperOffsets:()=>At,preventOverflow:()=>Tt,read:()=>b,reference:()=>p,right:()=>o,start:()=>c,top:()=>n,variationPlacements:()=>m,viewport:()=>u,write:()=>T});var i={};t.r(i),t.d(i,{Alert:()=>Oe,Button:()=>ke,Carousel:()=>ri,Collapse:()=>yi,Dropdown:()=>Vi,Modal:()=>xn,Offcanvas:()=>Vn,Popover:()=>fs,ScrollSpy:()=>Ts,Tab:()=>Ks,Toast:()=>lo,Tooltip:()=>hs});var n="top",s="bottom",o="right",r="left",a="auto",l=[n,s,o,r],c="start",h="end",d="clippingParents",u="viewport",f="popper",p="reference",m=l.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+h])}),[]),g=[].concat(l,[a]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+h])}),[]),_="beforeRead",b="read",v="afterRead",y="beforeMain",w="main",E="afterMain",A="beforeWrite",T="write",C="afterWrite",O=[_,b,v,y,w,E,A,T,C];function x(t){return t?(t.nodeName||"").toLowerCase():null}function k(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof k(t).Element||t instanceof Element}function S(t){return t instanceof k(t).HTMLElement||t instanceof HTMLElement}function D(t){return"undefined"!=typeof ShadowRoot&&(t instanceof k(t).ShadowRoot||t instanceof ShadowRoot)}const $={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];S(s)&&x(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});S(n)&&x(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function I(t){return t.split("-")[0]}var N=Math.max,P=Math.min,M=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(j())}function H(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&S(t)&&(s=t.offsetWidth>0&&M(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&M(n.height)/t.offsetHeight||1);var r=(L(t)?k(t):window).visualViewport,a=!F()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function B(t){var e=H(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&D(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function z(t){return k(t).getComputedStyle(t)}function R(t){return["table","td","th"].indexOf(x(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function V(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(D(t)?t.host:null)||q(t)}function Y(t){return S(t)&&"fixed"!==z(t).position?t.offsetParent:null}function K(t){for(var e=k(t),i=Y(t);i&&R(i)&&"static"===z(i).position;)i=Y(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===z(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&S(t)&&"fixed"===z(t).position)return null;var i=V(t);for(D(i)&&(i=i.host);S(i)&&["html","body"].indexOf(x(i))<0;){var n=z(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return N(t,P(e,i))}function U(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const J={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,c=t.options,h=i.elements.arrow,d=i.modifiersData.popperOffsets,u=I(i.placement),f=Q(u),p=[r,o].indexOf(u)>=0?"height":"width";if(h&&d){var m=function(t,e){return U("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,l))}(c.padding,i),g=B(h),_="y"===f?n:r,b="y"===f?s:o,v=i.rects.reference[p]+i.rects.reference[f]-d[f]-i.rects.popper[p],y=d[f]-i.rects.reference[f],w=K(h),E=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,A=v/2-y/2,T=m[_],C=E-g[p]-m[b],O=E/2-g[p]/2+A,x=X(T,O,C),k=f;i.modifiersData[a]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,l=t.placement,c=t.variation,d=t.offsets,u=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=d.x,b=void 0===_?0:_,v=d.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var E=d.hasOwnProperty("x"),A=d.hasOwnProperty("y"),T=r,C=n,O=window;if(p){var x=K(i),L="clientHeight",S="clientWidth";x===k(i)&&"static"!==z(x=q(i)).position&&"absolute"===u&&(L="scrollHeight",S="scrollWidth"),(l===n||(l===r||l===o)&&c===h)&&(C=s,y-=(g&&x===O&&O.visualViewport?O.visualViewport.height:x[L])-a.height,y*=f?1:-1),l!==r&&(l!==n&&l!==s||c!==h)||(T=o,b-=(g&&x===O&&O.visualViewport?O.visualViewport.width:x[S])-a.width,b*=f?1:-1)}var D,$=Object.assign({position:u},p&&tt),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:M(i*s)/s||0,y:M(n*s)/s||0}}({x:b,y},k(i)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=A?"0":"",D[T]=E?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=A?y+"px":"",e[T]=E?b+"px":"",e.transform="",e))}const it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:I(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var nt={passive:!0};const st={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=k(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),a&&l.addEventListener("resize",i.update,nt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),a&&l.removeEventListener("resize",i.update,nt)}},data:{}};var ot={left:"right",right:"left",bottom:"top",top:"bottom"};function rt(t){return t.replace(/left|right|bottom|top/g,(function(t){return ot[t]}))}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function ct(t){var e=k(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ht(t){return H(q(t)).left+ct(t).scrollLeft}function dt(t){var e=z(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:S(t)&&dt(t)?t:ut(V(t))}function ft(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=k(n),r=s?[o].concat(o.visualViewport||[],dt(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ft(V(r)))}function pt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function mt(t,e,i){return e===u?pt(function(t,e){var i=k(t),n=q(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=F();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ht(t),y:l}}(t,i)):L(e)?function(t,e){var i=H(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):pt(function(t){var e,i=q(t),n=ct(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=N(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=N(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ht(t),l=-n.scrollTop;return"rtl"===z(s||i).direction&&(a+=N(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(q(t)))}function gt(t){var e,i=t.reference,a=t.element,l=t.placement,d=l?I(l):null,u=l?Z(l):null,f=i.x+i.width/2-a.width/2,p=i.y+i.height/2-a.height/2;switch(d){case n:e={x:f,y:i.y-a.height};break;case s:e={x:f,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:p};break;case r:e={x:i.x-a.width,y:p};break;default:e={x:i.x,y:i.y}}var m=d?Q(d):null;if(null!=m){var g="y"===m?"height":"width";switch(u){case c:e[m]=e[m]-(i[g]/2-a[g]/2);break;case h:e[m]=e[m]+(i[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var i=e,r=i.placement,a=void 0===r?t.placement:r,c=i.strategy,h=void 0===c?t.strategy:c,m=i.boundary,g=void 0===m?d:m,_=i.rootBoundary,b=void 0===_?u:_,v=i.elementContext,y=void 0===v?f:v,w=i.altBoundary,E=void 0!==w&&w,A=i.padding,T=void 0===A?0:A,C=U("number"!=typeof T?T:G(T,l)),O=y===f?p:f,k=t.rects.popper,D=t.elements[E?O:y],$=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ft(V(t)),i=["absolute","fixed"].indexOf(z(t).position)>=0&&S(t)?K(t):t;return L(i)?e.filter((function(t){return L(t)&&W(t,i)&&"body"!==x(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=mt(t,i,n);return e.top=N(s.top,e.top),e.right=P(s.right,e.right),e.bottom=P(s.bottom,e.bottom),e.left=N(s.left,e.left),e}),mt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(D)?D:D.contextElement||q(t.elements.popper),g,b,h),I=H(t.elements.reference),M=gt({reference:I,element:k,strategy:"absolute",placement:a}),j=pt(Object.assign({},k,M)),F=y===f?j:I,B={top:$.top-F.top+C.top,bottom:F.bottom-$.bottom+C.bottom,left:$.left-F.left+C.left,right:F.right-$.right+C.right},R=t.modifiersData.offset;if(y===f&&R){var Y=R[a];Object.keys(B).forEach((function(t){var e=[o,s].indexOf(t)>=0?1:-1,i=[n,s].indexOf(t)>=0?"y":"x";B[t]+=Y[i]*e}))}return B}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var d=i.mainAxis,u=void 0===d||d,f=i.altAxis,p=void 0===f||f,_=i.fallbackPlacements,b=i.padding,v=i.boundary,y=i.rootBoundary,w=i.altBoundary,E=i.flipVariations,A=void 0===E||E,T=i.allowedAutoPlacements,C=e.options.placement,O=I(C),x=_||(O!==C&&A?function(t){if(I(t)===a)return[];var e=rt(t);return[lt(t),e,lt(e)]}(C):[rt(C)]),k=[C].concat(x).reduce((function(t,i){return t.concat(I(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?g:c,d=Z(n),u=d?a?m:m.filter((function(t){return Z(t)===d})):l,f=u.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=u);var p=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[I(i)],e}),{});return Object.keys(p).sort((function(t,e){return p[t]-p[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:A,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,$=!0,N=k[0],P=0;P=0,B=H?"width":"height",W=_t(e,{placement:M,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?o:r:F?s:n;L[B]>S[B]&&(z=rt(z));var R=rt(z),q=[];if(u&&q.push(W[j]<=0),p&&q.push(W[z]<=0,W[R]<=0),q.every((function(t){return t}))){N=M,$=!1;break}D.set(M,q)}if($)for(var V=function(t){var e=k.find((function(e){var i=D.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return N=e,"break"},Y=A?3:1;Y>0&&"break"!==V(Y);Y--);e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[n,o,s,r].some((function(e){return t[e]>=0}))}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),d=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Et={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,s=t.name,a=i.offset,l=void 0===a?[0,0]:a,c=g.reduce((function(t,i){return t[i]=function(t,e,i){var s=I(t),a=[r,n].indexOf(s)>=0?-1:1,l="function"==typeof i?i(Object.assign({},e,{placement:t})):i,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[r,o].indexOf(s)>=0?{x:h,y:c}:{x:c,y:h}}(i,e.rects,l),t}),{}),h=c[e.placement],d=h.x,u=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=d,e.modifiersData.popperOffsets.y+=u),e.modifiersData[s]=c}},At={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Tt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,a=t.name,l=i.mainAxis,h=void 0===l||l,d=i.altAxis,u=void 0!==d&&d,f=i.boundary,p=i.rootBoundary,m=i.altBoundary,g=i.padding,_=i.tether,b=void 0===_||_,v=i.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),E=I(e.placement),A=Z(e.placement),T=!A,C=Q(E),O="x"===C?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),$=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,M={x:0,y:0};if(x){if(h){var j,F="y"===C?n:r,H="y"===C?s:o,W="y"===C?"height":"width",z=x[C],R=z+w[F],q=z-w[H],V=b?-L[W]/2:0,Y=A===c?k[W]:L[W],U=A===c?-L[W]:-k[W],G=e.elements.arrow,J=b&&G?B(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[H],nt=X(0,k[W],J[W]),st=T?k[W]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=T?-k[W]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(j=null==$?void 0:$[C])?j:0,ct=z+ot-lt,ht=X(b?P(R,z+st-lt-at):R,z,b?N(q,ct):q);x[C]=ht,M[C]=ht-z}if(u){var dt,ut="x"===C?n:r,ft="x"===C?s:o,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[ut],bt=pt-w[ft],vt=-1!==[n,r].indexOf(E),yt=null!=(dt=null==$?void 0:$[O])?dt:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,Et=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,At=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,Et):X(b?wt:gt,pt,b?Et:bt);x[O]=At,M[O]=At-pt}e.modifiersData[a]=M}},requiresIfExists:["offset"]};function Ct(t,e,i){void 0===i&&(i=!1);var n,s,o=S(e),r=S(e)&&function(t){var e=t.getBoundingClientRect(),i=M(e.width)/t.offsetWidth||1,n=M(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=q(e),l=H(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==x(e)||dt(a))&&(c=(n=e)!==k(n)&&S(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:ct(n)),S(e)?((h=H(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ht(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",Mt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),jt=t=>{t.dispatchEvent(new Event(Pt))},Ft=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Ht=t=>Ft(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(Mt(t)):null,Bt=t=>{if(!Ft(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},Wt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),zt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?zt(t.parentNode):null},Rt=()=>{},qt=t=>{t.offsetHeight},Vt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Yt=[],Kt=()=>"rtl"===document.documentElement.dir,Qt=t=>{var e;e=()=>{const e=Vt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Yt.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of Yt)t()})),Yt.push(e)):e()},Xt=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Ut=(t,e,i=!0)=>{if(!i)return void Xt(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Xt(t))};e.addEventListener(Pt,o),setTimeout((()=>{s||jt(e)}),n)},Gt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Jt=/[^.]*(?=\..*)\.|.*/,Zt=/\..*/,te=/::\d+$/,ee={};let ie=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},se=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function oe(t,e){return e&&`${e}::${ie++}`||t.uidEvent||ie++}function re(t){const e=oe(t);return t.uidEvent=e,ee[e]=ee[e]||{},ee[e]}function ae(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function le(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=ue(t);return se.has(o)||(o=t),[n,s,o]}function ce(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=le(e,i,n);if(e in ne){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=re(t),c=l[a]||(l[a]={}),h=ae(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=oe(r,e.replace(Jt,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return pe(s,{delegateTarget:r}),n.oneOff&&fe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return pe(n,{delegateTarget:t}),i.oneOff&&fe.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function he(t,e,i,n,s){const o=ae(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function de(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&he(t,e,i,r.callable,r.delegationSelector)}function ue(t){return t=t.replace(Zt,""),ne[t]||t}const fe={on(t,e,i,n){ce(t,e,i,n,!1)},one(t,e,i,n){ce(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=le(e,i,n),a=r!==e,l=re(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))de(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(te,"");a&&!e.includes(s)||he(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;he(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Vt();let s=null,o=!0,r=!0,a=!1;e!==ue(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=pe(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function pe(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function me(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function ge(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const _e={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${ge(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${ge(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=me(t.dataset[n])}return e},getDataAttribute:(t,e)=>me(t.getAttribute(`data-bs-${ge(e)}`))};class be{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ft(e)?_e.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ft(e)?_e.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],o=Ft(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${o}" but expected type "${s}".`)}var i}}class ve extends be{constructor(t,e){super(),(t=Ht(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),fe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Ut(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Ht(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.2"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const ye=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?Mt(i.trim()):null}return e},we={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!Wt(t)&&Bt(t)))},getSelectorFromElement(t){const e=ye(t);return e&&we.findOne(e)?e:null},getElementFromSelector(t){const e=ye(t);return e?we.findOne(e):null},getMultipleElementsFromSelector(t){const e=ye(t);return e?we.find(e):[]}},Ee=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;fe.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),Wt(this))return;const s=we.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},Ae=".bs.alert",Te=`close${Ae}`,Ce=`closed${Ae}`;class Oe extends ve{static get NAME(){return"alert"}close(){if(fe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),fe.trigger(this._element,Ce),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Oe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ee(Oe,"close"),Qt(Oe);const xe='[data-bs-toggle="button"]';class ke extends ve{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ke.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}fe.on(document,"click.bs.button.data-api",xe,(t=>{t.preventDefault();const e=t.target.closest(xe);ke.getOrCreateInstance(e).toggle()})),Qt(ke);const Le=".bs.swipe",Se=`touchstart${Le}`,De=`touchmove${Le}`,$e=`touchend${Le}`,Ie=`pointerdown${Le}`,Ne=`pointerup${Le}`,Pe={endCallback:null,leftCallback:null,rightCallback:null},Me={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class je extends be{constructor(t,e){super(),this._element=t,t&&je.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pe}static get DefaultType(){return Me}static get NAME(){return"swipe"}dispose(){fe.off(this._element,Le)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Xt(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Xt(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(fe.on(this._element,Ie,(t=>this._start(t))),fe.on(this._element,Ne,(t=>this._end(t))),this._element.classList.add("pointer-event")):(fe.on(this._element,Se,(t=>this._start(t))),fe.on(this._element,De,(t=>this._move(t))),fe.on(this._element,$e,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Fe=".bs.carousel",He=".data-api",Be="next",We="prev",ze="left",Re="right",qe=`slide${Fe}`,Ve=`slid${Fe}`,Ye=`keydown${Fe}`,Ke=`mouseenter${Fe}`,Qe=`mouseleave${Fe}`,Xe=`dragstart${Fe}`,Ue=`load${Fe}${He}`,Ge=`click${Fe}${He}`,Je="carousel",Ze="active",ti=".active",ei=".carousel-item",ii=ti+ei,ni={ArrowLeft:Re,ArrowRight:ze},si={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},oi={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class ri extends ve{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=we.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===Je&&this.cycle()}static get Default(){return si}static get DefaultType(){return oi}static get NAME(){return"carousel"}next(){this._slide(Be)}nextWhenVisible(){!document.hidden&&Bt(this._element)&&this.next()}prev(){this._slide(We)}pause(){this._isSliding&&jt(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?fe.one(this._element,Ve,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void fe.one(this._element,Ve,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?Be:We;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&fe.on(this._element,Ye,(t=>this._keydown(t))),"hover"===this._config.pause&&(fe.on(this._element,Ke,(()=>this.pause())),fe.on(this._element,Qe,(()=>this._maybeEnableCycle()))),this._config.touch&&je.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of we.find(".carousel-item img",this._element))fe.on(t,Xe,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ze)),rightCallback:()=>this._slide(this._directionToOrder(Re)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new je(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=ni[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=we.findOne(ti,this._indicatorsElement);e.classList.remove(Ze),e.removeAttribute("aria-current");const i=we.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(Ze),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===Be,s=e||Gt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>fe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(qe).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),qt(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(Ze),i.classList.remove(Ze,c,l),this._isSliding=!1,r(Ve)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return we.findOne(ii,this._element)}_getItems(){return we.find(ei,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Kt()?t===ze?We:Be:t===ze?Be:We}_orderToDirection(t){return Kt()?t===We?ze:Re:t===We?Re:ze}static jQueryInterface(t){return this.each((function(){const e=ri.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}fe.on(document,Ge,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=we.getElementFromSelector(this);if(!e||!e.classList.contains(Je))return;t.preventDefault();const i=ri.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===_e.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),fe.on(window,Ue,(()=>{const t=we.find('[data-bs-ride="carousel"]');for(const e of t)ri.getOrCreateInstance(e)})),Qt(ri);const ai=".bs.collapse",li=`show${ai}`,ci=`shown${ai}`,hi=`hide${ai}`,di=`hidden${ai}`,ui=`click${ai}.data-api`,fi="show",pi="collapse",mi="collapsing",gi=`:scope .${pi} .${pi}`,_i='[data-bs-toggle="collapse"]',bi={parent:null,toggle:!0},vi={parent:"(null|element)",toggle:"boolean"};class yi extends ve{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=we.find(_i);for(const t of i){const e=we.getSelectorFromElement(t),i=we.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return bi}static get DefaultType(){return vi}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>yi.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(fe.trigger(this._element,li).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(pi),this._element.classList.add(mi),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(mi),this._element.classList.add(pi,fi),this._element.style[e]="",fe.trigger(this._element,ci)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(fe.trigger(this._element,hi).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,qt(this._element),this._element.classList.add(mi),this._element.classList.remove(pi,fi);for(const t of this._triggerArray){const e=we.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(mi),this._element.classList.add(pi),fe.trigger(this._element,di)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(fi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Ht(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(_i);for(const e of t){const t=we.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=we.find(gi,this._config.parent);return we.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=yi.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}fe.on(document,ui,_i,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of we.getMultipleElementsFromSelector(this))yi.getOrCreateInstance(t,{toggle:!1}).toggle()})),Qt(yi);const wi="dropdown",Ei=".bs.dropdown",Ai=".data-api",Ti="ArrowUp",Ci="ArrowDown",Oi=`hide${Ei}`,xi=`hidden${Ei}`,ki=`show${Ei}`,Li=`shown${Ei}`,Si=`click${Ei}${Ai}`,Di=`keydown${Ei}${Ai}`,$i=`keyup${Ei}${Ai}`,Ii="show",Ni='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Pi=`${Ni}.${Ii}`,Mi=".dropdown-menu",ji=Kt()?"top-end":"top-start",Fi=Kt()?"top-start":"top-end",Hi=Kt()?"bottom-end":"bottom-start",Bi=Kt()?"bottom-start":"bottom-end",Wi=Kt()?"left-start":"right-start",zi=Kt()?"right-start":"left-start",Ri={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},qi={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Vi extends ve{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=we.next(this._element,Mi)[0]||we.prev(this._element,Mi)[0]||we.findOne(Mi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Ri}static get DefaultType(){return qi}static get NAME(){return wi}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Wt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!fe.trigger(this._element,ki,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Ii),this._element.classList.add(Ii),fe.trigger(this._element,Li,t)}}hide(){if(Wt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!fe.trigger(this._element,Oi,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Ii),this._element.classList.remove(Ii),this._element.setAttribute("aria-expanded","false"),_e.removeDataAttribute(this._menu,"popper"),fe.trigger(this._element,xi,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ft(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${wi.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===e)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:Ft(this._config.reference)?t=Ht(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const i=this._getPopperConfig();this._popper=Dt(t,this._menu,i)}_isShown(){return this._menu.classList.contains(Ii)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Wi;if(t.classList.contains("dropstart"))return zi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Fi:ji:e?Bi:Hi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_e.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Xt(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=we.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>Bt(t)));i.length&&Gt(i,e,t===Ci,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Vi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=we.find(Pi);for(const i of e){const e=Vi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ti,Ci].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ni)?this:we.prev(this,Ni)[0]||we.next(this,Ni)[0]||we.findOne(Ni,t.delegateTarget.parentNode),o=Vi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}fe.on(document,Di,Ni,Vi.dataApiKeydownHandler),fe.on(document,Di,Mi,Vi.dataApiKeydownHandler),fe.on(document,Si,Vi.clearMenus),fe.on(document,$i,Vi.clearMenus),fe.on(document,Si,Ni,(function(t){t.preventDefault(),Vi.getOrCreateInstance(this).toggle()})),Qt(Vi);const Yi="backdrop",Ki="show",Qi=`mousedown.bs.${Yi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ui={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Gi extends be{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Ui}static get NAME(){return Yi}show(t){if(!this._config.isVisible)return void Xt(t);this._append();const e=this._getElement();this._config.isAnimated&&qt(e),e.classList.add(Ki),this._emulateAnimation((()=>{Xt(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),Xt(t)}))):Xt(t)}dispose(){this._isAppended&&(fe.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Ht(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),fe.on(t,Qi,(()=>{Xt(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){Ut(t,this._getElement(),this._config.isAnimated)}}const Ji=".bs.focustrap",Zi=`focusin${Ji}`,tn=`keydown.tab${Ji}`,en="backward",nn={autofocus:!0,trapElement:null},sn={autofocus:"boolean",trapElement:"element"};class on extends be{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return nn}static get DefaultType(){return sn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),fe.off(document,Ji),fe.on(document,Zi,(t=>this._handleFocusin(t))),fe.on(document,tn,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,fe.off(document,Ji))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=we.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===en?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?en:"forward")}}const rn=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",an=".sticky-top",ln="padding-right",cn="margin-right";class hn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,ln,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e+t)),this._setElementAttributes(an,cn,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,ln),this._resetElementAttributes(rn,ln),this._resetElementAttributes(an,cn)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&_e.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=_e.getDataAttribute(t,e);null!==i?(_e.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(Ft(t))e(t);else for(const i of we.find(t,this._element))e(i)}}const dn=".bs.modal",un=`hide${dn}`,fn=`hidePrevented${dn}`,pn=`hidden${dn}`,mn=`show${dn}`,gn=`shown${dn}`,_n=`resize${dn}`,bn=`click.dismiss${dn}`,vn=`mousedown.dismiss${dn}`,yn=`keydown.dismiss${dn}`,wn=`click${dn}.data-api`,En="modal-open",An="show",Tn="modal-static",Cn={backdrop:!0,focus:!0,keyboard:!0},On={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class xn extends ve{constructor(t,e){super(t,e),this._dialog=we.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new hn,this._addEventListeners()}static get Default(){return Cn}static get DefaultType(){return On}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||fe.trigger(this._element,mn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(En),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(fe.trigger(this._element,un).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){fe.off(window,dn),fe.off(this._dialog,dn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Gi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new on({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=we.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),qt(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,fe.trigger(this._element,gn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){fe.on(this._element,yn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),fe.on(window,_n,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),fe.on(this._element,vn,(t=>{fe.one(this._element,bn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(En),this._resetAdjustments(),this._scrollBar.reset(),fe.trigger(this._element,pn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(fe.trigger(this._element,fn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Tn)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Tn),this._queueCallback((()=>{this._element.classList.remove(Tn),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Kt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Kt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=xn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}fe.on(document,wn,'[data-bs-toggle="modal"]',(function(t){const e=we.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),fe.one(e,mn,(t=>{t.defaultPrevented||fe.one(e,pn,(()=>{Bt(this)&&this.focus()}))}));const i=we.findOne(".modal.show");i&&xn.getInstance(i).hide(),xn.getOrCreateInstance(e).toggle(this)})),Ee(xn),Qt(xn);const kn=".bs.offcanvas",Ln=".data-api",Sn=`load${kn}${Ln}`,Dn="show",$n="showing",In="hiding",Nn=".offcanvas.show",Pn=`show${kn}`,Mn=`shown${kn}`,jn=`hide${kn}`,Fn=`hidePrevented${kn}`,Hn=`hidden${kn}`,Bn=`resize${kn}`,Wn=`click${kn}${Ln}`,zn=`keydown.dismiss${kn}`,Rn={backdrop:!0,keyboard:!0,scroll:!1},qn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Vn extends ve{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Rn}static get DefaultType(){return qn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||fe.trigger(this._element,Pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new hn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($n),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Dn),this._element.classList.remove($n),fe.trigger(this._element,Mn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(fe.trigger(this._element,jn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(In),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Dn,In),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new hn).reset(),fe.trigger(this._element,Hn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Gi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():fe.trigger(this._element,Fn)}:null})}_initializeFocusTrap(){return new on({trapElement:this._element})}_addEventListeners(){fe.on(this._element,zn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():fe.trigger(this._element,Fn))}))}static jQueryInterface(t){return this.each((function(){const e=Vn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}fe.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=we.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this))return;fe.one(e,Hn,(()=>{Bt(this)&&this.focus()}));const i=we.findOne(Nn);i&&i!==e&&Vn.getInstance(i).hide(),Vn.getOrCreateInstance(e).toggle(this)})),fe.on(window,Sn,(()=>{for(const t of we.find(Nn))Vn.getOrCreateInstance(t).show()})),fe.on(window,Bn,(()=>{for(const t of we.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Vn.getOrCreateInstance(t).hide()})),Ee(Vn),Qt(Vn);const Yn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Un={allowList:Yn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Gn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Jn={entry:"(string|element|function|null)",selector:"(string|element)"};class Zn extends be{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Un}static get DefaultType(){return Gn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Jn)}_setContent(t,e,i){const n=we.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ft(e)?this._putElementInTemplate(Ht(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Xt(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const ts=new Set(["sanitize","allowList","sanitizeFn"]),es="fade",is="show",ns=".modal",ss="hide.bs.modal",os="hover",rs="focus",as={AUTO:"auto",TOP:"top",RIGHT:Kt()?"left":"right",BOTTOM:"bottom",LEFT:Kt()?"right":"left"},ls={allowList:Yn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},cs={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class hs extends ve{constructor(t,i){if(void 0===e)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,i),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return ls}static get DefaultType(){return cs}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),fe.off(this._element.closest(ns),ss,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=fe.trigger(this._element,this.constructor.eventName("show")),e=(zt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),fe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(is),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._queueCallback((()=>{fe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!fe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(is),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._activeTrigger.click=!1,this._activeTrigger[rs]=!1,this._activeTrigger[os]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),fe.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(es,is),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(es),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Zn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(es)}_isShown(){return this.tip&&this.tip.classList.contains(is)}_createPopper(t){const e=Xt(this._config.placement,[this,t,this._element]),i=as[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Xt(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Xt(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)fe.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===os?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===os?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");fe.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?rs:os]=!0,e._enter()})),fe.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?rs:os]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},fe.on(this._element.closest(ns),ss,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=_e.getDataAttributes(this._element);for(const t of Object.keys(e))ts.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Ht(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=hs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(hs);const ds={...hs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},us={...hs.DefaultType,content:"(null|string|element|function)"};class fs extends hs{static get Default(){return ds}static get DefaultType(){return us}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(fs);const ps=".bs.scrollspy",ms=`activate${ps}`,gs=`click${ps}`,_s=`load${ps}.data-api`,bs="active",vs="[href]",ys=".nav-link",ws=`${ys}, .nav-item > ${ys}, .list-group-item`,Es={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ts extends ve{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Es}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Ht(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(fe.off(this._config.target,gs),fe.on(this._config.target,gs,vs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=we.find(vs,this._config.target);for(const e of t){if(!e.hash||Wt(e))continue;const t=we.findOne(decodeURI(e.hash),this._element);Bt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(bs),this._activateParents(t),fe.trigger(this._element,ms,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))we.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(bs);else for(const e of we.parents(t,".nav, .list-group"))for(const t of we.prev(e,ws))t.classList.add(bs)}_clearActiveClass(t){t.classList.remove(bs);const e=we.find(`${vs}.${bs}`,t);for(const t of e)t.classList.remove(bs)}static jQueryInterface(t){return this.each((function(){const e=Ts.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(window,_s,(()=>{for(const t of we.find('[data-bs-spy="scroll"]'))Ts.getOrCreateInstance(t)})),Qt(Ts);const Cs=".bs.tab",Os=`hide${Cs}`,xs=`hidden${Cs}`,ks=`show${Cs}`,Ls=`shown${Cs}`,Ss=`click${Cs}`,Ds=`keydown${Cs}`,$s=`load${Cs}`,Is="ArrowLeft",Ns="ArrowRight",Ps="ArrowUp",Ms="ArrowDown",js="Home",Fs="End",Hs="active",Bs="fade",Ws="show",zs=".dropdown-toggle",Rs=`:not(${zs})`,qs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Vs=`.nav-link${Rs}, .list-group-item${Rs}, [role="tab"]${Rs}, ${qs}`,Ys=`.${Hs}[data-bs-toggle="tab"], .${Hs}[data-bs-toggle="pill"], .${Hs}[data-bs-toggle="list"]`;class Ks extends ve{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),fe.on(this._element,Ds,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?fe.trigger(e,Os,{relatedTarget:t}):null;fe.trigger(t,ks,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Hs),this._activate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),fe.trigger(t,Ls,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Bs)))}_deactivate(t,e){t&&(t.classList.remove(Hs),t.blur(),this._deactivate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),fe.trigger(t,xs,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Bs)))}_keydown(t){if(![Is,Ns,Ps,Ms,js,Fs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!Wt(t)));let i;if([js,Fs].includes(t.key))i=e[t.key===js?0:e.length-1];else{const n=[Ns,Ms].includes(t.key);i=Gt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Ks.getOrCreateInstance(i).show())}_getChildren(){return we.find(Vs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=we.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=we.findOne(t,i);s&&s.classList.toggle(n,e)};n(zs,Hs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Hs)}_getInnerElement(t){return t.matches(Vs)?t:we.findOne(Vs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Ks.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(document,Ss,qs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this)||Ks.getOrCreateInstance(this).show()})),fe.on(window,$s,(()=>{for(const t of we.find(Ys))Ks.getOrCreateInstance(t)})),Qt(Ks);const Qs=".bs.toast",Xs=`mouseover${Qs}`,Us=`mouseout${Qs}`,Gs=`focusin${Qs}`,Js=`focusout${Qs}`,Zs=`hide${Qs}`,to=`hidden${Qs}`,eo=`show${Qs}`,io=`shown${Qs}`,no="hide",so="show",oo="showing",ro={animation:"boolean",autohide:"boolean",delay:"number"},ao={animation:!0,autohide:!0,delay:5e3};class lo extends ve{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return ao}static get DefaultType(){return ro}static get NAME(){return"toast"}show(){fe.trigger(this._element,eo).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(no),qt(this._element),this._element.classList.add(so,oo),this._queueCallback((()=>{this._element.classList.remove(oo),fe.trigger(this._element,io),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(fe.trigger(this._element,Zs).defaultPrevented||(this._element.classList.add(oo),this._queueCallback((()=>{this._element.classList.add(no),this._element.classList.remove(oo,so),fe.trigger(this._element,to)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(so),super.dispose()}isShown(){return this._element.classList.contains(so)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){fe.on(this._element,Xs,(t=>this._onInteraction(t,!0))),fe.on(this._element,Us,(t=>this._onInteraction(t,!1))),fe.on(this._element,Gs,(t=>this._onInteraction(t,!0))),fe.on(this._element,Js,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=lo.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}function co(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}Ee(lo),Qt(lo),co((function(){[].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')).map((function(t){return new hs(t,{delay:{show:500,hide:100}})}))})),co((function(){document.getElementById("pst-back-to-top").addEventListener("click",(function(){document.body.scrollTop=0,document.documentElement.scrollTop=0}))})),co((function(){var t=document.getElementById("pst-back-to-top"),e=document.getElementsByClassName("bd-header")[0].getBoundingClientRect();window.addEventListener("scroll",(function(){this.oldScroll>this.scrollY&&this.scrollY>e.bottom?t.style.display="block":t.style.display="none",this.oldScroll=this.scrollY}))})),window.bootstrap=i})(); +//# sourceMappingURL=bootstrap.js.map \ No newline at end of file diff --git a/_static/scripts/bootstrap.js.LICENSE.txt b/_static/scripts/bootstrap.js.LICENSE.txt new file mode 100644 index 00000000..10f979d0 --- /dev/null +++ b/_static/scripts/bootstrap.js.LICENSE.txt @@ -0,0 +1,5 @@ +/*! + * Bootstrap v5.3.2 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ diff --git a/_static/scripts/bootstrap.js.map b/_static/scripts/bootstrap.js.map new file mode 100644 index 00000000..64e212b1 --- /dev/null +++ b/_static/scripts/bootstrap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/bootstrap.js","mappings":"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","sources":["webpack://pydata_sphinx_theme/webpack/bootstrap","webpack://pydata_sphinx_theme/webpack/runtime/define property getters","webpack://pydata_sphinx_theme/webpack/runtime/hasOwnProperty shorthand","webpack://pydata_sphinx_theme/webpack/runtime/make namespace object","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/enums.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/applyStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getBasePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/math.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/userAgent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/contains.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/within.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/expandToHashMap.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/arrow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getVariation.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/computeStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/eventListeners.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/rectToClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/detectOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/flip.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/hide.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/offset.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getAltAxis.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/orderModifiers.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/createPopper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/debounce.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergeByName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper-lite.js","webpack://pydata_sphinx_theme/./node_modules/bootstrap/dist/js/bootstrap.esm.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/mixin.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/bootstrap.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","/*!\n * Bootstrap v5.3.2 (https://getbootstrap.com/)\n * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\nimport * as Popper from '@popperjs/core';\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map();\nconst Data = {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map());\n }\n const instanceMap = elementMap.get(element);\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n return;\n }\n instanceMap.set(key, instance);\n },\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null;\n }\n return null;\n },\n remove(element, key) {\n if (!elementMap.has(element)) {\n return;\n }\n const instanceMap = elementMap.get(element);\n instanceMap.delete(key);\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element);\n }\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1000000;\nconst MILLISECONDS_MULTIPLIER = 1000;\nconst TRANSITION_END = 'transitionend';\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`);\n }\n return selector;\n};\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`;\n }\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n};\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID);\n } while (document.getElementById(prefix));\n return prefix;\n};\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0;\n }\n\n // Get transition-duration of the element\n let {\n transitionDuration,\n transitionDelay\n } = window.getComputedStyle(element);\n const floatTransitionDuration = Number.parseFloat(transitionDuration);\n const floatTransitionDelay = Number.parseFloat(transitionDelay);\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0;\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0];\n transitionDelay = transitionDelay.split(',')[0];\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n};\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END));\n};\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false;\n }\n if (typeof object.jquery !== 'undefined') {\n object = object[0];\n }\n return typeof object.nodeType !== 'undefined';\n};\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object;\n }\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object));\n }\n return null;\n};\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n if (!closedDetails) {\n return elementIsVisible;\n }\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n if (summary === null) {\n return false;\n }\n }\n return elementIsVisible;\n};\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n if (element.classList.contains('disabled')) {\n return true;\n }\n if (typeof element.disabled !== 'undefined') {\n return element.disabled;\n }\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n};\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null;\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode();\n return root instanceof ShadowRoot ? root : null;\n }\n if (element instanceof ShadowRoot) {\n return element;\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null;\n }\n return findShadowRoot(element.parentNode);\n};\nconst noop = () => {};\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight; // eslint-disable-line no-unused-expressions\n};\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery;\n }\n return null;\n};\nconst DOMContentLoadedCallbacks = [];\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback();\n }\n });\n }\n DOMContentLoadedCallbacks.push(callback);\n } else {\n callback();\n }\n};\nconst isRTL = () => document.documentElement.dir === 'rtl';\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery();\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME;\n const JQUERY_NO_CONFLICT = $.fn[name];\n $.fn[name] = plugin.jQueryInterface;\n $.fn[name].Constructor = plugin;\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT;\n return plugin.jQueryInterface;\n };\n }\n });\n};\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;\n};\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback);\n return;\n }\n const durationPadding = 5;\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n let called = false;\n const handler = ({\n target\n }) => {\n if (target !== transitionElement) {\n return;\n }\n called = true;\n transitionElement.removeEventListener(TRANSITION_END, handler);\n execute(callback);\n };\n transitionElement.addEventListener(TRANSITION_END, handler);\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement);\n }\n }, emulatedDuration);\n};\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length;\n let index = list.indexOf(activeElement);\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n }\n index += shouldGetNext ? 1 : -1;\n if (isCycleAllowed) {\n index = (index + listLength) % listLength;\n }\n return list[Math.max(0, Math.min(index, listLength - 1))];\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\nconst stripNameRegex = /\\..*/;\nconst stripUidRegex = /::\\d+$/;\nconst eventRegistry = {}; // Events storage\nlet uidEvent = 1;\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n};\nconst nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n}\nfunction getElementEvents(element) {\n const uid = makeEventUid(element);\n element.uidEvent = uid;\n eventRegistry[uid] = eventRegistry[uid] || {};\n return eventRegistry[uid];\n}\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, {\n delegateTarget: element\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn);\n }\n return fn.apply(element, [event]);\n };\n}\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector);\n for (let {\n target\n } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue;\n }\n hydrateObj(event, {\n delegateTarget: target\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn);\n }\n return fn.apply(target, [event]);\n }\n }\n };\n}\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n}\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string';\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n let typeEvent = getTypeEvent(originalTypeEvent);\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent;\n }\n return [isDelegated, callable, typeEvent];\n}\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n return fn.call(this, event);\n }\n };\n };\n callable = wrapFunction(callable);\n }\n const events = getElementEvents(element);\n const handlers = events[typeEvent] || (events[typeEvent] = {});\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff;\n return;\n }\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n fn.delegationSelector = isDelegated ? handler : null;\n fn.callable = callable;\n fn.oneOff = oneOff;\n fn.uidEvent = uid;\n handlers[uid] = fn;\n element.addEventListener(typeEvent, fn, isDelegated);\n}\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector);\n if (!fn) {\n return;\n }\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n delete events[typeEvent][fn.uidEvent];\n}\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {};\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n}\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '');\n return customEvents[event] || event;\n}\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false);\n },\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true);\n },\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n const inNamespace = typeEvent !== originalTypeEvent;\n const events = getElementEvents(element);\n const storeElementEvent = events[typeEvent] || {};\n const isNamespace = originalTypeEvent.startsWith('.');\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return;\n }\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n return;\n }\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n }\n }\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '');\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n },\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null;\n }\n const $ = getjQuery();\n const typeEvent = getTypeEvent(event);\n const inNamespace = event !== typeEvent;\n let jQueryEvent = null;\n let bubbles = true;\n let nativeDispatch = true;\n let defaultPrevented = false;\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args);\n $(element).trigger(jQueryEvent);\n bubbles = !jQueryEvent.isPropagationStopped();\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n defaultPrevented = jQueryEvent.isDefaultPrevented();\n }\n const evt = hydrateObj(new Event(event, {\n bubbles,\n cancelable: true\n }), args);\n if (defaultPrevented) {\n evt.preventDefault();\n }\n if (nativeDispatch) {\n element.dispatchEvent(evt);\n }\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault();\n }\n return evt;\n }\n};\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value;\n } catch (_unused) {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value;\n }\n });\n }\n }\n return obj;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === Number(value).toString()) {\n return Number(value);\n }\n if (value === '' || value === 'null') {\n return null;\n }\n if (typeof value !== 'string') {\n return value;\n }\n try {\n return JSON.parse(decodeURIComponent(value));\n } catch (_unused) {\n return value;\n }\n}\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n}\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n },\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n },\n getDataAttributes(element) {\n if (!element) {\n return {};\n }\n const attributes = {};\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '');\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n attributes[pureKey] = normalizeData(element.dataset[key]);\n }\n return attributes;\n },\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {};\n }\n static get DefaultType() {\n return {};\n }\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!');\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n return config;\n }\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n };\n }\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property];\n const valueType = isElement(value) ? 'element' : toType(value);\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n }\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.2';\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super();\n element = getElement(element);\n if (!element) {\n return;\n }\n this._element = element;\n this._config = this._getConfig(config);\n Data.set(this._element, this.constructor.DATA_KEY, this);\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY);\n EventHandler.off(this._element, this.constructor.EVENT_KEY);\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null;\n }\n }\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated);\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY);\n }\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n }\n static get VERSION() {\n return VERSION;\n }\n static get DATA_KEY() {\n return `bs.${this.NAME}`;\n }\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`;\n }\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target');\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href');\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n return null;\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n }\n selector = hrefAttribute && hrefAttribute !== '#' ? parseSelector(hrefAttribute.trim()) : null;\n }\n return selector;\n};\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n },\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector);\n },\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector));\n },\n parents(element, selector) {\n const parents = [];\n let ancestor = element.parentNode.closest(selector);\n while (ancestor) {\n parents.push(ancestor);\n ancestor = ancestor.parentNode.closest(selector);\n }\n return parents;\n },\n prev(element, selector) {\n let previous = element.previousElementSibling;\n while (previous) {\n if (previous.matches(selector)) {\n return [previous];\n }\n previous = previous.previousElementSibling;\n }\n return [];\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling;\n while (next) {\n if (next.matches(selector)) {\n return [next];\n }\n next = next.nextElementSibling;\n }\n return [];\n },\n focusableChildren(element) {\n const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n },\n getSelectorFromElement(element) {\n const selector = getSelector(element);\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null;\n }\n return null;\n },\n getElementFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.findOne(selector) : null;\n },\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.find(selector) : [];\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n const name = component.NAME;\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);\n const instance = component.getOrCreateInstance(target);\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]();\n });\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$f = 'alert';\nconst DATA_KEY$a = 'bs.alert';\nconst EVENT_KEY$b = `.${DATA_KEY$a}`;\nconst EVENT_CLOSE = `close${EVENT_KEY$b}`;\nconst EVENT_CLOSED = `closed${EVENT_KEY$b}`;\nconst CLASS_NAME_FADE$5 = 'fade';\nconst CLASS_NAME_SHOW$8 = 'show';\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$f;\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n if (closeEvent.defaultPrevented) {\n return;\n }\n this._element.classList.remove(CLASS_NAME_SHOW$8);\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n }\n\n // Private\n _destroyElement() {\n this._element.remove();\n EventHandler.trigger(this._element, EVENT_CLOSED);\n this.dispose();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close');\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$e = 'button';\nconst DATA_KEY$9 = 'bs.button';\nconst EVENT_KEY$a = `.${DATA_KEY$9}`;\nconst DATA_API_KEY$6 = '.data-api';\nconst CLASS_NAME_ACTIVE$3 = 'active';\nconst SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\nconst EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$e;\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this);\n if (config === 'toggle') {\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n event.preventDefault();\n const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n const data = Button.getOrCreateInstance(button);\n data.toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$d = 'swipe';\nconst EVENT_KEY$9 = '.bs.swipe';\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\nconst POINTER_TYPE_TOUCH = 'touch';\nconst POINTER_TYPE_PEN = 'pen';\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event';\nconst SWIPE_THRESHOLD = 40;\nconst Default$c = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n};\nconst DefaultType$c = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n};\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super();\n this._element = element;\n if (!element || !Swipe.isSupported()) {\n return;\n }\n this._config = this._getConfig(config);\n this._deltaX = 0;\n this._supportPointerEvents = Boolean(window.PointerEvent);\n this._initEvents();\n }\n\n // Getters\n static get Default() {\n return Default$c;\n }\n static get DefaultType() {\n return DefaultType$c;\n }\n static get NAME() {\n return NAME$d;\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY$9);\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX;\n return;\n }\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX;\n }\n }\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX;\n }\n this._handleSwipe();\n execute(this._config.endCallback);\n }\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n }\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX);\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return;\n }\n const direction = absDeltaX / this._deltaX;\n this._deltaX = 0;\n if (!direction) {\n return;\n }\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n }\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n }\n }\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$c = 'carousel';\nconst DATA_KEY$8 = 'bs.carousel';\nconst EVENT_KEY$8 = `.${DATA_KEY$8}`;\nconst DATA_API_KEY$5 = '.data-api';\nconst ARROW_LEFT_KEY$1 = 'ArrowLeft';\nconst ARROW_RIGHT_KEY$1 = 'ArrowRight';\nconst TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next';\nconst ORDER_PREV = 'prev';\nconst DIRECTION_LEFT = 'left';\nconst DIRECTION_RIGHT = 'right';\nconst EVENT_SLIDE = `slide${EVENT_KEY$8}`;\nconst EVENT_SLID = `slid${EVENT_KEY$8}`;\nconst EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\nconst EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\nconst EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\nconst EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst CLASS_NAME_CAROUSEL = 'carousel';\nconst CLASS_NAME_ACTIVE$2 = 'active';\nconst CLASS_NAME_SLIDE = 'slide';\nconst CLASS_NAME_END = 'carousel-item-end';\nconst CLASS_NAME_START = 'carousel-item-start';\nconst CLASS_NAME_NEXT = 'carousel-item-next';\nconst CLASS_NAME_PREV = 'carousel-item-prev';\nconst SELECTOR_ACTIVE = '.active';\nconst SELECTOR_ITEM = '.carousel-item';\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\nconst SELECTOR_ITEM_IMG = '.carousel-item img';\nconst SELECTOR_INDICATORS = '.carousel-indicators';\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n};\nconst Default$b = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n};\nconst DefaultType$b = {\n interval: '(number|boolean)',\n // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._interval = null;\n this._activeElement = null;\n this._isSliding = false;\n this.touchTimeout = null;\n this._swipeHelper = null;\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n this._addEventListeners();\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$b;\n }\n static get DefaultType() {\n return DefaultType$b;\n }\n static get NAME() {\n return NAME$c;\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT);\n }\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next();\n }\n }\n prev() {\n this._slide(ORDER_PREV);\n }\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element);\n }\n this._clearInterval();\n }\n cycle() {\n this._clearInterval();\n this._updateInterval();\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n }\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n return;\n }\n this.cycle();\n }\n to(index) {\n const items = this._getItems();\n if (index > items.length - 1 || index < 0) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n return;\n }\n const activeIndex = this._getItemIndex(this._getActive());\n if (activeIndex === index) {\n return;\n }\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n this._slide(order, items[index]);\n }\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose();\n }\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval;\n return config;\n }\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n }\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n }\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners();\n }\n }\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n }\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return;\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause();\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout);\n }\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n };\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n };\n this._swipeHelper = new Swipe(this._element, swipeConfig);\n }\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return;\n }\n const direction = KEY_TO_DIRECTION[event.key];\n if (direction) {\n event.preventDefault();\n this._slide(this._directionToOrder(direction));\n }\n }\n _getItemIndex(element) {\n return this._getItems().indexOf(element);\n }\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return;\n }\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n activeIndicator.removeAttribute('aria-current');\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n newActiveIndicator.setAttribute('aria-current', 'true');\n }\n }\n _updateInterval() {\n const element = this._activeElement || this._getActive();\n if (!element) {\n return;\n }\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n this._config.interval = elementInterval || this._config.defaultInterval;\n }\n _slide(order, element = null) {\n if (this._isSliding) {\n return;\n }\n const activeElement = this._getActive();\n const isNext = order === ORDER_NEXT;\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n if (nextElement === activeElement) {\n return;\n }\n const nextElementIndex = this._getItemIndex(nextElement);\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n });\n };\n const slideEvent = triggerEvent(EVENT_SLIDE);\n if (slideEvent.defaultPrevented) {\n return;\n }\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return;\n }\n const isCycling = Boolean(this._interval);\n this.pause();\n this._isSliding = true;\n this._setActiveIndicatorElement(nextElementIndex);\n this._activeElement = nextElement;\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n nextElement.classList.add(orderClassName);\n reflow(nextElement);\n activeElement.classList.add(directionalClassName);\n nextElement.classList.add(directionalClassName);\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName);\n nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n this._isSliding = false;\n triggerEvent(EVENT_SLID);\n };\n this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n if (isCycling) {\n this.cycle();\n }\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE);\n }\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n }\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element);\n }\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n }\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n }\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n }\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config);\n if (typeof config === 'number') {\n data.to(config);\n return;\n }\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return;\n }\n event.preventDefault();\n const carousel = Carousel.getOrCreateInstance(target);\n const slideIndex = this.getAttribute('data-bs-slide-to');\n if (slideIndex) {\n carousel.to(slideIndex);\n carousel._maybeEnableCycle();\n return;\n }\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next();\n carousel._maybeEnableCycle();\n return;\n }\n carousel.prev();\n carousel._maybeEnableCycle();\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel);\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$b = 'collapse';\nconst DATA_KEY$7 = 'bs.collapse';\nconst EVENT_KEY$7 = `.${DATA_KEY$7}`;\nconst DATA_API_KEY$4 = '.data-api';\nconst EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\nconst EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\nconst EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\nconst EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\nconst EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\nconst CLASS_NAME_SHOW$7 = 'show';\nconst CLASS_NAME_COLLAPSE = 'collapse';\nconst CLASS_NAME_COLLAPSING = 'collapsing';\nconst CLASS_NAME_COLLAPSED = 'collapsed';\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\nconst WIDTH = 'width';\nconst HEIGHT = 'height';\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\nconst SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\nconst Default$a = {\n parent: null,\n toggle: true\n};\nconst DefaultType$a = {\n parent: '(null|element)',\n toggle: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isTransitioning = false;\n this._triggerArray = [];\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem);\n const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem);\n }\n }\n this._initializeChildren();\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n }\n if (this._config.toggle) {\n this.toggle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$a;\n }\n static get DefaultType() {\n return DefaultType$a;\n }\n static get NAME() {\n return NAME$b;\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide();\n } else {\n this.show();\n }\n }\n show() {\n if (this._isTransitioning || this._isShown()) {\n return;\n }\n let activeChildren = [];\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n toggle: false\n }));\n }\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n for (const activeInstance of activeChildren) {\n activeInstance.hide();\n }\n const dimension = this._getDimension();\n this._element.classList.remove(CLASS_NAME_COLLAPSE);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.style[dimension] = 0;\n this._addAriaAndCollapsedClass(this._triggerArray, true);\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n this._element.style[dimension] = '';\n EventHandler.trigger(this._element, EVENT_SHOWN$6);\n };\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n const scrollSize = `scroll${capitalizedDimension}`;\n this._queueCallback(complete, this._element, true);\n this._element.style[dimension] = `${this._element[scrollSize]}px`;\n }\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n const dimension = this._getDimension();\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger);\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false);\n }\n }\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE);\n EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n };\n this._element.style[dimension] = '';\n this._queueCallback(complete, this._element, true);\n }\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW$7);\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle); // Coerce string values\n config.parent = getElement(config.parent);\n return config;\n }\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n }\n _initializeChildren() {\n if (!this._config.parent) {\n return;\n }\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element);\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected));\n }\n }\n }\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n }\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return;\n }\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n element.setAttribute('aria-expanded', isOpen);\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {};\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false;\n }\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config);\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n event.preventDefault();\n }\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, {\n toggle: false\n }).toggle();\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$a = 'dropdown';\nconst DATA_KEY$6 = 'bs.dropdown';\nconst EVENT_KEY$6 = `.${DATA_KEY$6}`;\nconst DATA_API_KEY$3 = '.data-api';\nconst ESCAPE_KEY$2 = 'Escape';\nconst TAB_KEY$1 = 'Tab';\nconst ARROW_UP_KEY$1 = 'ArrowUp';\nconst ARROW_DOWN_KEY$1 = 'ArrowDown';\nconst RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\nconst EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\nconst EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\nconst EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\nconst EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst CLASS_NAME_SHOW$6 = 'show';\nconst CLASS_NAME_DROPUP = 'dropup';\nconst CLASS_NAME_DROPEND = 'dropend';\nconst CLASS_NAME_DROPSTART = 'dropstart';\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center';\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\nconst SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\nconst SELECTOR_MENU = '.dropdown-menu';\nconst SELECTOR_NAVBAR = '.navbar';\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav';\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\nconst PLACEMENT_TOPCENTER = 'top';\nconst PLACEMENT_BOTTOMCENTER = 'bottom';\nconst Default$9 = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n};\nconst DefaultType$9 = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n};\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._popper = null;\n this._parent = this._element.parentNode; // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n this._inNavbar = this._detectNavbar();\n }\n\n // Getters\n static get Default() {\n return Default$9;\n }\n static get DefaultType() {\n return DefaultType$9;\n }\n static get NAME() {\n return NAME$a;\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show();\n }\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n if (showEvent.defaultPrevented) {\n return;\n }\n this._createPopper();\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n this._element.focus();\n this._element.setAttribute('aria-expanded', true);\n this._menu.classList.add(CLASS_NAME_SHOW$6);\n this._element.classList.add(CLASS_NAME_SHOW$6);\n EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n }\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n this._completeHide(relatedTarget);\n }\n dispose() {\n if (this._popper) {\n this._popper.destroy();\n }\n super.dispose();\n }\n update() {\n this._inNavbar = this._detectNavbar();\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n if (this._popper) {\n this._popper.destroy();\n }\n this._menu.classList.remove(CLASS_NAME_SHOW$6);\n this._element.classList.remove(CLASS_NAME_SHOW$6);\n this._element.setAttribute('aria-expanded', 'false');\n Manipulator.removeDataAttribute(this._menu, 'popper');\n EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n }\n _getConfig(config) {\n config = super._getConfig(config);\n if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n }\n return config;\n }\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n }\n let referenceElement = this._element;\n if (this._config.reference === 'parent') {\n referenceElement = this._parent;\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference);\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference;\n }\n const popperConfig = this._getPopperConfig();\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig);\n }\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n }\n _getPlacement() {\n const parentDropdown = this._parent;\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER;\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n }\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n }\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null;\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n };\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }];\n }\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _selectMenuItem({\n key,\n target\n }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n if (!items.length) {\n return;\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n return;\n }\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle);\n if (!context || context._config.autoClose === false) {\n continue;\n }\n const composedPath = event.composedPath();\n const isMenuTarget = composedPath.includes(context._menu);\n if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n continue;\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue;\n }\n const relatedTarget = {\n relatedTarget: context._element\n };\n if (event.type === 'click') {\n relatedTarget.clickEvent = event;\n }\n context._completeHide(relatedTarget);\n }\n }\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName);\n const isEscapeEvent = event.key === ESCAPE_KEY$2;\n const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return;\n }\n if (isInput && !isEscapeEvent) {\n return;\n }\n event.preventDefault();\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n const instance = Dropdown.getOrCreateInstance(getToggleButton);\n if (isUpOrDownEvent) {\n event.stopPropagation();\n instance.show();\n instance._selectMenuItem(event);\n return;\n }\n if (instance._isShown()) {\n // else is escape and we check if it is shown\n event.stopPropagation();\n instance.hide();\n getToggleButton.focus();\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n event.preventDefault();\n Dropdown.getOrCreateInstance(this).toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$9 = 'backdrop';\nconst CLASS_NAME_FADE$4 = 'fade';\nconst CLASS_NAME_SHOW$5 = 'show';\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\nconst Default$8 = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true,\n // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n};\n\nconst DefaultType$8 = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n};\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isAppended = false;\n this._element = null;\n }\n\n // Getters\n static get Default() {\n return Default$8;\n }\n static get DefaultType() {\n return DefaultType$8;\n }\n static get NAME() {\n return NAME$9;\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._append();\n const element = this._getElement();\n if (this._config.isAnimated) {\n reflow(element);\n }\n element.classList.add(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n execute(callback);\n });\n }\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n this.dispose();\n execute(callback);\n });\n }\n dispose() {\n if (!this._isAppended) {\n return;\n }\n EventHandler.off(this._element, EVENT_MOUSEDOWN);\n this._element.remove();\n this._isAppended = false;\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div');\n backdrop.className = this._config.className;\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE$4);\n }\n this._element = backdrop;\n }\n return this._element;\n }\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement);\n return config;\n }\n _append() {\n if (this._isAppended) {\n return;\n }\n const element = this._getElement();\n this._config.rootElement.append(element);\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback);\n });\n this._isAppended = true;\n }\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$8 = 'focustrap';\nconst DATA_KEY$5 = 'bs.focustrap';\nconst EVENT_KEY$5 = `.${DATA_KEY$5}`;\nconst EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\nconst TAB_KEY = 'Tab';\nconst TAB_NAV_FORWARD = 'forward';\nconst TAB_NAV_BACKWARD = 'backward';\nconst Default$7 = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n};\n\nconst DefaultType$7 = {\n autofocus: 'boolean',\n trapElement: 'element'\n};\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isActive = false;\n this._lastTabNavDirection = null;\n }\n\n // Getters\n static get Default() {\n return Default$7;\n }\n static get DefaultType() {\n return DefaultType$7;\n }\n static get NAME() {\n return NAME$8;\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return;\n }\n if (this._config.autofocus) {\n this._config.trapElement.focus();\n }\n EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n this._isActive = true;\n }\n deactivate() {\n if (!this._isActive) {\n return;\n }\n this._isActive = false;\n EventHandler.off(document, EVENT_KEY$5);\n }\n\n // Private\n _handleFocusin(event) {\n const {\n trapElement\n } = this._config;\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return;\n }\n const elements = SelectorEngine.focusableChildren(trapElement);\n if (elements.length === 0) {\n trapElement.focus();\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus();\n } else {\n elements[0].focus();\n }\n }\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return;\n }\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\nconst SELECTOR_STICKY_CONTENT = '.sticky-top';\nconst PROPERTY_PADDING = 'padding-right';\nconst PROPERTY_MARGIN = 'margin-right';\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body;\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth;\n return Math.abs(window.innerWidth - documentWidth);\n }\n hide() {\n const width = this.getWidth();\n this._disableOverFlow();\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n }\n reset() {\n this._resetElementAttributes(this._element, 'overflow');\n this._resetElementAttributes(this._element, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n }\n isOverflowing() {\n return this.getWidth() > 0;\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow');\n this._element.style.overflow = 'hidden';\n }\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth();\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return;\n }\n this._saveInitialAttribute(element, styleProperty);\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty);\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue);\n }\n }\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty);\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty);\n return;\n }\n Manipulator.removeDataAttribute(element, styleProperty);\n element.style.setProperty(styleProperty, value);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector);\n return;\n }\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel);\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$7 = 'modal';\nconst DATA_KEY$4 = 'bs.modal';\nconst EVENT_KEY$4 = `.${DATA_KEY$4}`;\nconst DATA_API_KEY$2 = '.data-api';\nconst ESCAPE_KEY$1 = 'Escape';\nconst EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\nconst EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\nconst EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\nconst EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\nconst EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\nconst EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\nconst EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\nconst EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\nconst CLASS_NAME_OPEN = 'modal-open';\nconst CLASS_NAME_FADE$3 = 'fade';\nconst CLASS_NAME_SHOW$4 = 'show';\nconst CLASS_NAME_STATIC = 'modal-static';\nconst OPEN_SELECTOR$1 = '.modal.show';\nconst SELECTOR_DIALOG = '.modal-dialog';\nconst SELECTOR_MODAL_BODY = '.modal-body';\nconst SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\nconst Default$6 = {\n backdrop: true,\n focus: true,\n keyboard: true\n};\nconst DefaultType$6 = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._isShown = false;\n this._isTransitioning = false;\n this._scrollBar = new ScrollBarHelper();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$6;\n }\n static get DefaultType() {\n return DefaultType$6;\n }\n static get NAME() {\n return NAME$7;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._isTransitioning = true;\n this._scrollBar.hide();\n document.body.classList.add(CLASS_NAME_OPEN);\n this._adjustDialog();\n this._backdrop.show(() => this._showElement(relatedTarget));\n }\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._isShown = false;\n this._isTransitioning = true;\n this._focustrap.deactivate();\n this._element.classList.remove(CLASS_NAME_SHOW$4);\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n }\n dispose() {\n EventHandler.off(window, EVENT_KEY$4);\n EventHandler.off(this._dialog, EVENT_KEY$4);\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n handleUpdate() {\n this._adjustDialog();\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop),\n // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element);\n }\n this._element.style.display = 'block';\n this._element.removeAttribute('aria-hidden');\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.scrollTop = 0;\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n if (modalBody) {\n modalBody.scrollTop = 0;\n }\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_SHOW$4);\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate();\n }\n this._isTransitioning = false;\n EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n relatedTarget\n });\n };\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n if (event.key !== ESCAPE_KEY$1) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n this._triggerBackdropTransition();\n });\n EventHandler.on(window, EVENT_RESIZE$1, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog();\n }\n });\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return;\n }\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition();\n return;\n }\n if (this._config.backdrop) {\n this.hide();\n }\n });\n });\n }\n _hideModal() {\n this._element.style.display = 'none';\n this._element.setAttribute('aria-hidden', true);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n this._isTransitioning = false;\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN);\n this._resetAdjustments();\n this._scrollBar.reset();\n EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n });\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE$3);\n }\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n if (hideEvent.defaultPrevented) {\n return;\n }\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const initialOverflowY = this._element.style.overflowY;\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return;\n }\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden';\n }\n this._element.classList.add(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY;\n }, this._dialog);\n }, this._dialog);\n this._element.focus();\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const scrollbarWidth = this._scrollBar.getWidth();\n const isBodyOverflowing = scrollbarWidth > 0;\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n }\n _resetAdjustments() {\n this._element.style.paddingLeft = '';\n this._element.style.paddingRight = '';\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](relatedTarget);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$4, () => {\n if (isVisible(this)) {\n this.focus();\n }\n });\n });\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide();\n }\n const data = Modal.getOrCreateInstance(target);\n data.toggle(this);\n});\nenableDismissTrigger(Modal);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$6 = 'offcanvas';\nconst DATA_KEY$3 = 'bs.offcanvas';\nconst EVENT_KEY$3 = `.${DATA_KEY$3}`;\nconst DATA_API_KEY$1 = '.data-api';\nconst EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst ESCAPE_KEY = 'Escape';\nconst CLASS_NAME_SHOW$3 = 'show';\nconst CLASS_NAME_SHOWING$1 = 'showing';\nconst CLASS_NAME_HIDING = 'hiding';\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\nconst OPEN_SELECTOR = '.offcanvas.show';\nconst EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\nconst EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\nconst EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\nconst EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\nconst EVENT_RESIZE = `resize${EVENT_KEY$3}`;\nconst EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\nconst SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\nconst Default$5 = {\n backdrop: true,\n keyboard: true,\n scroll: false\n};\nconst DefaultType$5 = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isShown = false;\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$5;\n }\n static get DefaultType() {\n return DefaultType$5;\n }\n static get NAME() {\n return NAME$6;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._backdrop.show();\n if (!this._config.scroll) {\n new ScrollBarHelper().hide();\n }\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.classList.add(CLASS_NAME_SHOWING$1);\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate();\n }\n this._element.classList.add(CLASS_NAME_SHOW$3);\n this._element.classList.remove(CLASS_NAME_SHOWING$1);\n EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n relatedTarget\n });\n };\n this._queueCallback(completeCallBack, this._element, true);\n }\n hide() {\n if (!this._isShown) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._focustrap.deactivate();\n this._element.blur();\n this._isShown = false;\n this._element.classList.add(CLASS_NAME_HIDING);\n this._backdrop.hide();\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n if (!this._config.scroll) {\n new ScrollBarHelper().reset();\n }\n EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n };\n this._queueCallback(completeCallback, this._element, true);\n }\n dispose() {\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n return;\n }\n this.hide();\n };\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop);\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n });\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$3, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus();\n }\n });\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide();\n }\n const data = Offcanvas.getOrCreateInstance(target);\n data.toggle(this);\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show();\n }\n});\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide();\n }\n }\n});\nenableDismissTrigger(Offcanvas);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\nconst DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n};\n// js-docs-end allow-list\n\nconst uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase();\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));\n }\n return true;\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n};\nfunction sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml;\n }\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml);\n }\n const domParser = new window.DOMParser();\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase();\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove();\n continue;\n }\n const attributeList = [].concat(...element.attributes);\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName);\n }\n }\n }\n return createdDocument.body.innerHTML;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$5 = 'TemplateFactory';\nconst Default$4 = {\n allowList: DefaultAllowlist,\n content: {},\n // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n};\nconst DefaultType$4 = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n};\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n};\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n }\n\n // Getters\n static get Default() {\n return Default$4;\n }\n static get DefaultType() {\n return DefaultType$4;\n }\n static get NAME() {\n return NAME$5;\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n }\n hasContent() {\n return this.getContent().length > 0;\n }\n changeContent(content) {\n this._checkContent(content);\n this._config.content = {\n ...this._config.content,\n ...content\n };\n return this;\n }\n toHtml() {\n const templateWrapper = document.createElement('div');\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector);\n }\n const template = templateWrapper.children[0];\n const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n if (extraClass) {\n template.classList.add(...extraClass.split(' '));\n }\n return template;\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config);\n this._checkContent(config.content);\n }\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({\n selector,\n entry: content\n }, DefaultContentType);\n }\n }\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template);\n if (!templateElement) {\n return;\n }\n content = this._resolvePossibleFunction(content);\n if (!content) {\n templateElement.remove();\n return;\n }\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement);\n return;\n }\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content);\n return;\n }\n templateElement.textContent = content;\n }\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this]);\n }\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = '';\n templateElement.append(element);\n return;\n }\n templateElement.textContent = element.textContent;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$4 = 'tooltip';\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\nconst CLASS_NAME_FADE$2 = 'fade';\nconst CLASS_NAME_MODAL = 'modal';\nconst CLASS_NAME_SHOW$2 = 'show';\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\nconst EVENT_MODAL_HIDE = 'hide.bs.modal';\nconst TRIGGER_HOVER = 'hover';\nconst TRIGGER_FOCUS = 'focus';\nconst TRIGGER_CLICK = 'click';\nconst TRIGGER_MANUAL = 'manual';\nconst EVENT_HIDE$2 = 'hide';\nconst EVENT_HIDDEN$2 = 'hidden';\nconst EVENT_SHOW$2 = 'show';\nconst EVENT_SHOWN$2 = 'shown';\nconst EVENT_INSERTED = 'inserted';\nconst EVENT_CLICK$1 = 'click';\nconst EVENT_FOCUSIN$1 = 'focusin';\nconst EVENT_FOCUSOUT$1 = 'focusout';\nconst EVENT_MOUSEENTER = 'mouseenter';\nconst EVENT_MOUSELEAVE = 'mouseleave';\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n};\nconst Default$3 = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' + '
' + '
' + '
',\n title: '',\n trigger: 'hover focus'\n};\nconst DefaultType$3 = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n};\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n }\n super(element, config);\n\n // Private\n this._isEnabled = true;\n this._timeout = 0;\n this._isHovered = null;\n this._activeTrigger = {};\n this._popper = null;\n this._templateFactory = null;\n this._newContent = null;\n\n // Protected\n this.tip = null;\n this._setListeners();\n if (!this._config.selector) {\n this._fixTitle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$3;\n }\n static get DefaultType() {\n return DefaultType$3;\n }\n static get NAME() {\n return NAME$4;\n }\n\n // Public\n enable() {\n this._isEnabled = true;\n }\n disable() {\n this._isEnabled = false;\n }\n toggleEnabled() {\n this._isEnabled = !this._isEnabled;\n }\n toggle() {\n if (!this._isEnabled) {\n return;\n }\n this._activeTrigger.click = !this._activeTrigger.click;\n if (this._isShown()) {\n this._leave();\n return;\n }\n this._enter();\n }\n dispose() {\n clearTimeout(this._timeout);\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n }\n this._disposePopper();\n super.dispose();\n }\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements');\n }\n if (!(this._isWithContent() && this._isEnabled)) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n const shadowRoot = findShadowRoot(this._element);\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n if (showEvent.defaultPrevented || !isInTheDom) {\n return;\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper();\n const tip = this._getTipElement();\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n const {\n container\n } = this._config;\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip);\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n }\n this._popper = this._createPopper(tip);\n tip.classList.add(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n if (this._isHovered === false) {\n this._leave();\n }\n this._isHovered = false;\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n hide() {\n if (!this._isShown()) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n if (hideEvent.defaultPrevented) {\n return;\n }\n const tip = this._getTipElement();\n tip.classList.remove(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n this._activeTrigger[TRIGGER_CLICK] = false;\n this._activeTrigger[TRIGGER_FOCUS] = false;\n this._activeTrigger[TRIGGER_HOVER] = false;\n this._isHovered = null; // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return;\n }\n if (!this._isHovered) {\n this._disposePopper();\n }\n this._element.removeAttribute('aria-describedby');\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n update() {\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle());\n }\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n }\n return this.tip;\n }\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml();\n\n // TODO: remove this check in v6\n if (!tip) {\n return null;\n }\n tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2);\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n const tipId = getUID(this.constructor.NAME).toString();\n tip.setAttribute('id', tipId);\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE$2);\n }\n return tip;\n }\n setContent(content) {\n this._newContent = content;\n if (this._isShown()) {\n this._disposePopper();\n this.show();\n }\n }\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content);\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n });\n }\n return this._templateFactory;\n }\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n };\n }\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n }\n _isAnimated() {\n return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n }\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n }\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element]);\n const attachment = AttachmentMap[placement.toUpperCase()];\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment));\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element]);\n }\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [{\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }, {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n }, {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n }\n }]\n };\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _setListeners() {\n const triggers = this._config.trigger.split(' ');\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context.toggle();\n });\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n context._enter();\n });\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n context._leave();\n });\n }\n }\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide();\n }\n };\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n }\n _fixTitle() {\n const title = this._element.getAttribute('title');\n if (!title) {\n return;\n }\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title);\n }\n this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title');\n }\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true;\n return;\n }\n this._isHovered = true;\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show();\n }\n }, this._config.delay.show);\n }\n _leave() {\n if (this._isWithActiveTrigger()) {\n return;\n }\n this._isHovered = false;\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide();\n }\n }, this._config.delay.hide);\n }\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout);\n this._timeout = setTimeout(handler, timeout);\n }\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true);\n }\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element);\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute];\n }\n }\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n };\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container);\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n };\n }\n if (typeof config.title === 'number') {\n config.title = config.title.toString();\n }\n if (typeof config.content === 'number') {\n config.content = config.content.toString();\n }\n return config;\n }\n _getDelegateConfig() {\n const config = {};\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value;\n }\n }\n config.selector = false;\n config.trigger = 'manual';\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config;\n }\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy();\n this._popper = null;\n }\n if (this.tip) {\n this.tip.remove();\n this.tip = null;\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$3 = 'popover';\nconst SELECTOR_TITLE = '.popover-header';\nconst SELECTOR_CONTENT = '.popover-body';\nconst Default$2 = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' + '
' + '

' + '
' + '
',\n trigger: 'click'\n};\nconst DefaultType$2 = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n};\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default$2;\n }\n static get DefaultType() {\n return DefaultType$2;\n }\n static get NAME() {\n return NAME$3;\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent();\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n };\n }\n _getContent() {\n return this._resolvePossibleFunction(this._config.content);\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$2 = 'scrollspy';\nconst DATA_KEY$2 = 'bs.scrollspy';\nconst EVENT_KEY$2 = `.${DATA_KEY$2}`;\nconst DATA_API_KEY = '.data-api';\nconst EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\nconst EVENT_CLICK = `click${EVENT_KEY$2}`;\nconst EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\nconst CLASS_NAME_ACTIVE$1 = 'active';\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\nconst SELECTOR_TARGET_LINKS = '[href]';\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\nconst SELECTOR_NAV_LINKS = '.nav-link';\nconst SELECTOR_NAV_ITEMS = '.nav-item';\nconst SELECTOR_LIST_ITEMS = '.list-group-item';\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\nconst SELECTOR_DROPDOWN = '.dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\nconst Default$1 = {\n offset: null,\n // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n};\nconst DefaultType$1 = {\n offset: '(number|null)',\n // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n};\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map();\n this._observableSections = new Map();\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n this._activeTarget = null;\n this._observer = null;\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n };\n this.refresh(); // initialize\n }\n\n // Getters\n static get Default() {\n return Default$1;\n }\n static get DefaultType() {\n return DefaultType$1;\n }\n static get NAME() {\n return NAME$2;\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables();\n this._maybeEnableSmoothScroll();\n if (this._observer) {\n this._observer.disconnect();\n } else {\n this._observer = this._getNewObserver();\n }\n for (const section of this._observableSections.values()) {\n this._observer.observe(section);\n }\n }\n dispose() {\n this._observer.disconnect();\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body;\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n }\n return config;\n }\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return;\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK);\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash);\n if (observableSection) {\n event.preventDefault();\n const root = this._rootElement || window;\n const height = observableSection.offsetTop - this._element.offsetTop;\n if (root.scrollTo) {\n root.scrollTo({\n top: height,\n behavior: 'smooth'\n });\n return;\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height;\n }\n });\n }\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n };\n return new IntersectionObserver(entries => this._observerCallback(entries), options);\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n this._process(targetElement(entry));\n };\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n this._previousScrollData.parentScrollTop = parentScrollTop;\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null;\n this._clearActiveClass(targetElement(entry));\n continue;\n }\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry);\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return;\n }\n continue;\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry);\n }\n }\n }\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map();\n this._observableSections = new Map();\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue;\n }\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor);\n this._observableSections.set(anchor.hash, observableSection);\n }\n }\n }\n _process(target) {\n if (this._activeTarget === target) {\n return;\n }\n this._clearActiveClass(this._config.target);\n this._activeTarget = target;\n target.classList.add(CLASS_NAME_ACTIVE$1);\n this._activateParents(target);\n EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n relatedTarget: target\n });\n }\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n return;\n }\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both