From fcf92829e86c0fef56051a43462f9191efdb400e Mon Sep 17 00:00:00 2001 From: VirtuBrick <139835327+VirtuBrick@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:59:13 -0500 Subject: [PATCH] Initial commit --- .gitignore | 10 + README.md | 100 + app/__init__.py | 415 ++ app/enums/AssetType.py | 70 + app/enums/BanType.py | 10 + app/enums/ChatStyle.py | 6 + app/enums/CryptomusPaymentStatus.py | 17 + app/enums/GiftcardType.py | 8 + app/enums/LimitedItemTransferMethod.py | 7 + app/enums/MembershipType.py | 7 + app/enums/PlaceRigChoice.py | 6 + app/enums/PlaceYear.py | 17 + app/enums/TradeStatus.py | 8 + app/enums/TransactionType.py | 9 + app/extensions.py | 708 +++ app/files/2012Studio.lua | 0 app/files/2014Gameserver.lua | 367 ++ app/files/2014Join.lua | 331 ++ app/files/2016Gameserver.lua | 213 + app/files/AnimationThumbnail.png | Bin 0 -> 44652 bytes app/files/AudioThumbnail.png | Bin 0 -> 42139 bytes app/files/Baseplate.rbxlx | 415 ++ app/files/CoreGui/107893730 | 1107 +++++ app/files/CoreGui/152908679 | 264 ++ app/files/CoreGui/153556783 | 564 +++ app/files/CoreGui/153556822 | 247 ++ app/files/CoreGui/157877000 | 1038 +++++ app/files/CoreGui/158948138 | 175 + app/files/CoreGui/35914081 | 290 ++ app/files/CoreGui/35914620 | 70 + app/files/CoreGui/36040464 | 35 + app/files/CoreGui/36040483 | 36 + app/files/CoreGui/36040495 | 36 + app/files/CoreGui/36051740 | 707 +++ app/files/CoreGui/36868950 | 109 + app/files/CoreGui/37801172 | 115 + app/files/CoreGui/37801173 | 74 + app/files/CoreGui/39250920 | 560 +++ app/files/CoreGui/45284430 | 3844 +++++++++++++++++ app/files/CoreGui/45374389 | 23 + app/files/CoreGui/46295863 | 2153 +++++++++ app/files/CoreGui/46295864 | 2098 +++++++++ app/files/CoreGui/48488235 | 3074 +++++++++++++ app/files/CoreGui/48488236 | 2425 +++++++++++ app/files/CoreGui/48488398 | 306 ++ app/files/CoreGui/48488451 | 72 + app/files/CoreGui/53878047 | 874 ++++ app/files/CoreGui/53878048 | 790 ++++ app/files/CoreGui/53878053 | 968 +++++ app/files/CoreGui/53878057 | 1098 +++++ app/files/CoreGui/53878058 | 883 ++++ app/files/CoreGui/59002209 | 1 + app/files/CoreGui/59431535 | 204 + app/files/CoreGui/60595411 | 1115 +++++ app/files/CoreGui/60595695 | 26 + app/files/CoreGui/60595696 | 26 + app/files/CoreGui/64164692 | 146 + app/files/CoreGui/73157242 | 2207 ++++++++++ app/files/CoreGui/89449008 | 876 ++++ app/files/CoreGui/89449009 | 842 ++++ app/files/CoreGui/89449093 | 443 ++ app/files/CoreGui/89449094 | 360 ++ app/files/CoreGui/97188756 | 1376 ++++++ app/files/CoreGui/97188757 | 1211 ++++++ app/files/LuaThumbnail.png | Bin 0 -> 56304 bytes app/files/NoRender.png | Bin 0 -> 4621 bytes app/files/Old2014Join.lua | 418 ++ app/files/Pants.rbxmx | 13 + app/files/Shirt.rbxmx | 13 + app/files/ShutdownServer.lua | 37 + app/files/Studio.lua | 27 + app/files/TShirt.rbxmx | 13 + app/files/Visit.lua | 132 + app/models/admin_permissions.py | 16 + app/models/asset.py | 83 + app/models/asset_favorite.py | 13 + app/models/asset_moderation_link.py | 27 + app/models/asset_rap.py | 23 + app/models/asset_thumbnail.py | 36 + app/models/asset_version.py | 35 + app/models/asset_votes.py | 15 + app/models/cryptomus_invoice.py | 52 + app/models/exchange_offer.py | 54 + app/models/fflag_group.py | 35 + app/models/fflag_value.py | 29 + app/models/follow_relationship.py | 20 + app/models/friend_relationship.py | 16 + app/models/friend_request.py | 17 + app/models/game_session_log.py | 36 + app/models/gamepass_link.py | 24 + app/models/gameservers.py | 45 + app/models/giftcard_key.py | 31 + app/models/groups.py | 296 ++ app/models/invite_key.py | 25 + app/models/kofi_transaction.py | 41 + app/models/legacy_data_persistence.py | 31 + app/models/limited_item_transfers.py | 33 + app/models/linked_discord.py | 49 + app/models/login_records.py | 21 + app/models/messages.py | 33 + app/models/moderator_note.py | 36 + app/models/package_asset.py | 13 + app/models/past_usernames.py | 16 + app/models/place.py | 51 + app/models/place_badge.py | 48 + app/models/place_datastore.py | 34 + app/models/place_developer_product.py | 48 + app/models/place_icon.py | 22 + app/models/place_ordered_datastore.py | 33 + app/models/placeserver_players.py | 23 + app/models/placeservers.py | 52 + app/models/pointsservice.py | 15 + app/models/previously_played.py | 16 + app/models/product_receipt.py | 31 + app/models/universe.py | 56 + app/models/user.py | 25 + app/models/user_avatar.py | 43 + app/models/user_avatar_asset.py | 22 + app/models/user_ban.py | 40 + app/models/user_email.py | 19 + app/models/user_hwid_log.py | 18 + app/models/user_membership.py | 24 + app/models/user_thumbnail.py | 22 + app/models/user_trade_items.py | 17 + app/models/user_trades.py | 39 + app/models/user_transactions.py | 51 + app/models/userassets.py | 43 + app/models/usereconomy.py | 16 + app/pages/403.html | 12 + app/pages/404.html | 12 + app/pages/405.html | 12 + app/pages/500.html | 25 + app/pages/__layout__.html | 186 + app/pages/about.html | 7 + app/pages/admin/admin.py | 2780 ++++++++++++ app/pages/admin/assetcopier.html | 39 + app/pages/admin/assetmoderation.html | 119 + app/pages/admin/bundlecopier.html | 39 + app/pages/admin/createasset.html | 59 + app/pages/admin/creategiftcard.html | 67 + app/pages/admin/createuser.html | 43 + app/pages/admin/fflagsettings/index.html | 41 + app/pages/admin/fflagsettings/view.html | 84 + app/pages/admin/gameservers/create.html | 64 + app/pages/admin/gameservers/delete.html | 24 + app/pages/admin/gameservers/index.html | 47 + .../admin/gameservers/refresh_accesskey.html | 24 + app/pages/admin/gameservers/view.html | 84 + app/pages/admin/index.html | 41 + app/pages/admin/insertitemrelasepool.html | 101 + app/pages/admin/itemreleasepool.html | 59 + app/pages/admin/lottery/index.html | 64 + app/pages/admin/manageassets.html | 142 + app/pages/admin/moderateUGC.html | 112 + app/pages/admin/permissionsdefinition.py | 129 + app/pages/admin/updateassetfile.html | 42 + app/pages/admin/usermanage/ban.html | 86 + app/pages/admin/usermanage/banhistory.html | 69 + app/pages/admin/usermanage/gamesessions.html | 65 + app/pages/admin/usermanage/invitekeys.html | 74 + app/pages/admin/usermanage/loginhistory.html | 105 + .../admin/usermanage/manage-admin-perms.html | 208 + .../admin/usermanage/moderatornotes.html | 61 + app/pages/admin/usermanage/search.html | 82 + app/pages/admin/usermanage/transactions.html | 116 + app/pages/admin/usermanage/view.html | 191 + app/pages/admin/websitefeatures.html | 43 + app/pages/admin/websitefeaturesdefinition.py | 83 + app/pages/admin/websitewidemsg.html | 20 + app/pages/audiomigrator/audiomigrator.py | 104 + app/pages/audiomigrator/index.html | 54 + app/pages/avatar/avatar.html | 125 + app/pages/avatar/avatar.py | 239 + app/pages/catalog/asset.html | 184 + app/pages/catalog/badges.html | 53 + app/pages/catalog/catalog.py | 824 ++++ app/pages/catalog/catalogtypes.py | 76 + app/pages/catalog/index.html | 185 + app/pages/catalog/library.html | 186 + app/pages/catalog/resell.html | 65 + app/pages/clientpages/clientpages.py | 11 + app/pages/clientpages/screenshot.html | 17 + app/pages/clothingmigrator/index.html | 41 + app/pages/clothingmigrator/migrator.py | 61 + app/pages/cryptomus/dashboard.html | 74 + app/pages/cryptomus/view_payment.html | 63 + app/pages/currencyexchange/controller.py | 378 ++ app/pages/currencyexchange/create.html | 168 + app/pages/currencyexchange/index.html | 114 + app/pages/currencyexchange/view.html | 78 + app/pages/develop/develop.py | 2002 +++++++++ app/pages/develop/edit.html | 79 + app/pages/develop/games/access.html | 12 + app/pages/develop/games/manage-template.html | 66 + app/pages/develop/games/manage.html | 52 + app/pages/develop/games/upload-icon.html | 25 + app/pages/develop/games/upload-thumbnail.html | 25 + app/pages/develop/games/upload-version.html | 12 + app/pages/develop/games/version-history.html | 25 + app/pages/develop/subpages/games.html | 25 + app/pages/develop/subpages/image.html | 32 + app/pages/develop/subpages/pants.html | 32 + app/pages/develop/subpages/shirts.html | 32 + app/pages/develop/subpages/sound.html | 32 + app/pages/develop/subpages/tshirt.html | 31 + app/pages/develop/template.html | 54 + app/pages/develop/universes/access.html | 24 + app/pages/develop/universes/badges.html | 30 + app/pages/develop/universes/create-badge.html | 27 + .../develop/universes/create-gamepass.html | 27 + app/pages/develop/universes/create-place.html | 15 + .../develop/universes/create-product.html | 27 + .../develop/universes/developerproducts.html | 29 + app/pages/develop/universes/edit-badge.html | 35 + .../develop/universes/edit-gamepass.html | 53 + app/pages/develop/universes/edit-product.html | 53 + app/pages/develop/universes/gamepasses.html | 29 + .../develop/universes/manage-template.html | 64 + app/pages/develop/universes/manage.html | 21 + app/pages/develop/universes/places.html | 27 + app/pages/discourse/leaving-syntax.html | 24 + app/pages/discourse/sso-confirm.html | 35 + app/pages/downloads.html | 66 + app/pages/drivers.html | 17 + app/pages/games/games.py | 296 ++ app/pages/games/genre.html | 47 + app/pages/games/index.html | 203 + app/pages/games/view.html | 380 ++ app/pages/giftcardredeem/index.html | 47 + app/pages/giftcardredeem/redeem.py | 139 + .../groups/admin_subpage/create_role.html | 29 + app/pages/groups/admin_subpage/groupinfo.html | 42 + .../groups/admin_subpage/join_requests.html | 52 + app/pages/groups/admin_subpage/members.html | 116 + app/pages/groups/admin_subpage/payout.html | 42 + app/pages/groups/admin_subpage/roles.html | 198 + app/pages/groups/admin_subpage/settings.html | 60 + app/pages/groups/admin_template.html | 125 + app/pages/groups/create.html | 62 + app/pages/groups/groupspage.py | 1268 ++++++ app/pages/groups/search.html | 61 + app/pages/groups/view.html | 207 + app/pages/home/home.html | 96 + app/pages/home/home.py | 92 + app/pages/invitekeys/handler.py | 68 + app/pages/invitekeys/index.html | 80 + app/pages/login/login.html | 43 + app/pages/login/login.py | 477 ++ app/pages/login/reset_password.html | 57 + app/pages/login/send_reset.html | 43 + app/pages/login/totpvalidate.html | 32 + app/pages/membership/index.html | 80 + app/pages/membership/membership.py | 17 + app/pages/membership/payment_methods.html | 160 + app/pages/messages/index.html | 54 + app/pages/messages/messages.py | 148 + app/pages/messages/new.html | 43 + app/pages/messages/view.html | 42 + app/pages/notapproved/banpage.html | 36 + app/pages/notapproved/notapproved.py | 88 + app/pages/privacy.html | 43 + app/pages/profiles/followers.html | 64 + app/pages/profiles/following.html | 64 + app/pages/profiles/friends.html | 74 + app/pages/profiles/inventory.html | 183 + app/pages/profiles/profile.html | 254 ++ app/pages/profiles/profile.py | 536 +++ app/pages/profiles/requests.html | 82 + app/pages/settings/changeemail.html | 56 + app/pages/settings/changepassword.html | 52 + app/pages/settings/changeusername.html | 35 + app/pages/settings/emailverify_success.html | 21 + app/pages/settings/enableTOTP.html | 34 + app/pages/settings/settings.html | 52 + app/pages/settings/settings.py | 556 +++ app/pages/signup/signup.html | 45 + app/pages/signup/signup.py | 263 ++ app/pages/static.py | 28 + app/pages/studio/myPlaces.html | 94 + app/pages/studio/studiopages.py | 15 + app/pages/swaggerdocs.html | 19 + app/pages/terms.html | 52 + app/pages/trades/create.html | 133 + app/pages/trades/index.html | 65 + app/pages/trades/trades.py | 550 +++ app/pages/trades/view.html | 134 + app/pages/transactions/transactions.html | 102 + app/pages/transactions/transactions.py | 86 + app/pages/users/index.html | 70 + app/pages/users/users_page.py | 37 + app/routes/accountsettingsapi.py | 70 + app/routes/asset.py | 1147 +++++ app/routes/authentication.py | 216 + app/routes/avatarapi.py | 317 ++ app/routes/badgesapi.py | 343 ++ app/routes/bootstrapper.py | 7 + app/routes/clientinfo.py | 654 +++ app/routes/cryptomus_handler.py | 383 ++ app/routes/datastoreservice.py | 277 ++ app/routes/discord_internal.py | 294 ++ app/routes/discourse_sso.py | 96 + app/routes/fflagssettings.py | 68 + app/routes/friendapi.py | 311 ++ app/routes/gamejoin.py | 714 +++ app/routes/gamesapi.py | 504 +++ app/routes/gametransactions.py | 89 + app/routes/image.py | 652 +++ app/routes/inventoryapi.py | 181 + app/routes/jobreporthandler.py | 708 +++ app/routes/kofihandler.py | 124 + app/routes/legacydatapersistence.py | 125 + app/routes/luawebservice.py | 560 +++ app/routes/marketplace.py | 408 ++ app/routes/mobile.py | 28 + app/routes/pointsservice.py | 52 + app/routes/presence.py | 17 + app/routes/presenceapi.py | 105 + app/routes/prometheus.py | 100 + app/routes/publicapi.py | 525 +++ app/routes/rate.py | 131 + app/routes/rbxapi.py | 186 + app/routes/rolimons.py | 307 ++ app/routes/sets.py | 51 + app/routes/teleportservice.py | 139 + app/routes/thumbnailer.py | 518 +++ app/routes/usersapi.py | 219 + app/services/economy.py | 270 ++ app/services/gameserver_comm.py | 117 + app/services/groups.py | 690 +++ app/services/invitekeys.py | 70 + app/services/proxydetection.py | 82 + app/services/user_relationships/followings.py | 216 + app/services/user_relationships/friends.py | 200 + app/shell_commands.py | 475 ++ app/static/avatarrules.json | 553 +++ app/static/css/admin.css | 48 + app/static/css/avatar.css | 100 + app/static/css/bootstrapv3.min.css | 6 + app/static/css/catalog.css | 11 + app/static/css/develop.css | 15 + app/static/css/gameview.css | 135 + app/static/css/global.css | 423 ++ app/static/css/home.css | 37 + app/static/css/icons.css | 1549 +++++++ app/static/css/login.css | 77 + app/static/css/message.css | 11 + app/static/css/profile.css | 52 + app/static/css/settings.css | 18 + app/static/css/signup.css | 81 + app/static/css/terms.css | 15 + app/static/img/BC.png | Bin 0 -> 1166 bytes app/static/img/ChristmasHat.png | Bin 0 -> 3424 bytes app/static/img/ContentDeleted.png | Bin 0 -> 4475 bytes app/static/img/LoginImage.png | Bin 0 -> 409080 bytes app/static/img/LoginImage2.png | Bin 0 -> 435235 bytes app/static/img/LuaThumbnail.png | Bin 0 -> 56304 bytes app/static/img/NBC.png | Bin 0 -> 1023 bytes app/static/img/OBC.png | Bin 0 -> 1155 bytes app/static/img/SignupImage.png | Bin 0 -> 717070 bytes app/static/img/Style/bc.svg | 204 + app/static/img/Style/icon_labels.svg | 300 ++ app/static/img/SyntaxLogo.png | Bin 0 -> 43551 bytes app/static/img/TBC.png | Bin 0 -> 1207 bytes app/static/img/TemplatePants.png | Bin 0 -> 64099 bytes app/static/img/TemplateShirt.png | Bin 0 -> 66301 bytes app/static/img/branded.svg | 297 ++ app/static/img/games.svg | 482 +++ app/static/img/linux-penguin.svg | 7 + app/static/img/placeholder.png | Bin 0 -> 6289 bytes app/static/img/syntax.ico | Bin 0 -> 117136 bytes app/static/img/thumbs.svg | 13 + app/static/img/thumbsup.png | Bin 0 -> 4447 bytes app/static/js/AvatarEditor.js | 467 ++ app/static/js/assetrate.js | 216 + app/static/js/bootstrapv3.min.js | 7 + app/static/js/catalog.js | 153 + app/static/js/groupMembers.js | 110 + app/static/js/newTrade.js | 369 ++ app/static/js/presence.js | 5 + app/static/svg/navigation.svg | 484 +++ app/static/swagger/favicon-16x16.png | Bin 0 -> 665 bytes app/static/swagger/favicon-32x32.png | Bin 0 -> 628 bytes app/static/swagger/index.css | 16 + app/static/swagger/oauth2-redirect.html | 79 + app/static/swagger/swagger-docs.json | 1608 +++++++ app/static/swagger/swagger-initializer.js | 20 + app/static/swagger/swagger-ui-bundle.js | 3 + app/static/swagger/swagger-ui-bundle.js.map | 1 + .../swagger/swagger-ui-es-bundle-core.js | 3 + .../swagger/swagger-ui-es-bundle-core.js.map | 1 + app/static/swagger/swagger-ui-es-bundle.js | 3 + .../swagger/swagger-ui-es-bundle.js.map | 1 + .../swagger/swagger-ui-standalone-preset.js | 3 + .../swagger-ui-standalone-preset.js.map | 1 + app/static/swagger/swagger-ui.css | 3 + app/static/swagger/swagger-ui.css.map | 1 + app/static/swagger/swagger-ui.js | 2 + app/static/swagger/swagger-ui.js.map | 1 + app/util/RBXMesh.py | 991 +++++ app/util/assetvalidation.py | 146 + app/util/assetversion.py | 62 + app/util/auth.py | 273 ++ app/util/badwords.py | 83 + app/util/discord.py | 122 + app/util/friends.py | 80 + app/util/membership.py | 167 + app/util/placeinfo.py | 74 + app/util/redislock.py | 24 + app/util/s3helper.py | 175 + app/util/signscript.py | 57 + app/util/textfilter.py | 33 + app/util/transactions.py | 87 + app/util/turnstile.py | 25 + app/util/websiteFeatures.py | 18 + config.example.py | 92 + debug.sh | 2 + download_cache/KEEPME | 0 logs/KEEPME | 0 requirements.txt | 22 + start.sh | 2 + tools/generate_new_keys.py | 47 + 421 files changed, 82302 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/__init__.py create mode 100644 app/enums/AssetType.py create mode 100644 app/enums/BanType.py create mode 100644 app/enums/ChatStyle.py create mode 100644 app/enums/CryptomusPaymentStatus.py create mode 100644 app/enums/GiftcardType.py create mode 100644 app/enums/LimitedItemTransferMethod.py create mode 100644 app/enums/MembershipType.py create mode 100644 app/enums/PlaceRigChoice.py create mode 100644 app/enums/PlaceYear.py create mode 100644 app/enums/TradeStatus.py create mode 100644 app/enums/TransactionType.py create mode 100644 app/extensions.py create mode 100644 app/files/2012Studio.lua create mode 100644 app/files/2014Gameserver.lua create mode 100644 app/files/2014Join.lua create mode 100644 app/files/2016Gameserver.lua create mode 100644 app/files/AnimationThumbnail.png create mode 100644 app/files/AudioThumbnail.png create mode 100644 app/files/Baseplate.rbxlx create mode 100644 app/files/CoreGui/107893730 create mode 100644 app/files/CoreGui/152908679 create mode 100644 app/files/CoreGui/153556783 create mode 100644 app/files/CoreGui/153556822 create mode 100644 app/files/CoreGui/157877000 create mode 100644 app/files/CoreGui/158948138 create mode 100644 app/files/CoreGui/35914081 create mode 100644 app/files/CoreGui/35914620 create mode 100644 app/files/CoreGui/36040464 create mode 100644 app/files/CoreGui/36040483 create mode 100644 app/files/CoreGui/36040495 create mode 100644 app/files/CoreGui/36051740 create mode 100644 app/files/CoreGui/36868950 create mode 100644 app/files/CoreGui/37801172 create mode 100644 app/files/CoreGui/37801173 create mode 100644 app/files/CoreGui/39250920 create mode 100644 app/files/CoreGui/45284430 create mode 100644 app/files/CoreGui/45374389 create mode 100644 app/files/CoreGui/46295863 create mode 100644 app/files/CoreGui/46295864 create mode 100644 app/files/CoreGui/48488235 create mode 100644 app/files/CoreGui/48488236 create mode 100644 app/files/CoreGui/48488398 create mode 100644 app/files/CoreGui/48488451 create mode 100644 app/files/CoreGui/53878047 create mode 100644 app/files/CoreGui/53878048 create mode 100644 app/files/CoreGui/53878053 create mode 100644 app/files/CoreGui/53878057 create mode 100644 app/files/CoreGui/53878058 create mode 100644 app/files/CoreGui/59002209 create mode 100644 app/files/CoreGui/59431535 create mode 100644 app/files/CoreGui/60595411 create mode 100644 app/files/CoreGui/60595695 create mode 100644 app/files/CoreGui/60595696 create mode 100644 app/files/CoreGui/64164692 create mode 100644 app/files/CoreGui/73157242 create mode 100644 app/files/CoreGui/89449008 create mode 100644 app/files/CoreGui/89449009 create mode 100644 app/files/CoreGui/89449093 create mode 100644 app/files/CoreGui/89449094 create mode 100644 app/files/CoreGui/97188756 create mode 100644 app/files/CoreGui/97188757 create mode 100644 app/files/LuaThumbnail.png create mode 100644 app/files/NoRender.png create mode 100644 app/files/Old2014Join.lua create mode 100644 app/files/Pants.rbxmx create mode 100644 app/files/Shirt.rbxmx create mode 100644 app/files/ShutdownServer.lua create mode 100644 app/files/Studio.lua create mode 100644 app/files/TShirt.rbxmx create mode 100644 app/files/Visit.lua create mode 100644 app/models/admin_permissions.py create mode 100644 app/models/asset.py create mode 100644 app/models/asset_favorite.py create mode 100644 app/models/asset_moderation_link.py create mode 100644 app/models/asset_rap.py create mode 100644 app/models/asset_thumbnail.py create mode 100644 app/models/asset_version.py create mode 100644 app/models/asset_votes.py create mode 100644 app/models/cryptomus_invoice.py create mode 100644 app/models/exchange_offer.py create mode 100644 app/models/fflag_group.py create mode 100644 app/models/fflag_value.py create mode 100644 app/models/follow_relationship.py create mode 100644 app/models/friend_relationship.py create mode 100644 app/models/friend_request.py create mode 100644 app/models/game_session_log.py create mode 100644 app/models/gamepass_link.py create mode 100644 app/models/gameservers.py create mode 100644 app/models/giftcard_key.py create mode 100644 app/models/groups.py create mode 100644 app/models/invite_key.py create mode 100644 app/models/kofi_transaction.py create mode 100644 app/models/legacy_data_persistence.py create mode 100644 app/models/limited_item_transfers.py create mode 100644 app/models/linked_discord.py create mode 100644 app/models/login_records.py create mode 100644 app/models/messages.py create mode 100644 app/models/moderator_note.py create mode 100644 app/models/package_asset.py create mode 100644 app/models/past_usernames.py create mode 100644 app/models/place.py create mode 100644 app/models/place_badge.py create mode 100644 app/models/place_datastore.py create mode 100644 app/models/place_developer_product.py create mode 100644 app/models/place_icon.py create mode 100644 app/models/place_ordered_datastore.py create mode 100644 app/models/placeserver_players.py create mode 100644 app/models/placeservers.py create mode 100644 app/models/pointsservice.py create mode 100644 app/models/previously_played.py create mode 100644 app/models/product_receipt.py create mode 100644 app/models/universe.py create mode 100644 app/models/user.py create mode 100644 app/models/user_avatar.py create mode 100644 app/models/user_avatar_asset.py create mode 100644 app/models/user_ban.py create mode 100644 app/models/user_email.py create mode 100644 app/models/user_hwid_log.py create mode 100644 app/models/user_membership.py create mode 100644 app/models/user_thumbnail.py create mode 100644 app/models/user_trade_items.py create mode 100644 app/models/user_trades.py create mode 100644 app/models/user_transactions.py create mode 100644 app/models/userassets.py create mode 100644 app/models/usereconomy.py create mode 100644 app/pages/403.html create mode 100644 app/pages/404.html create mode 100644 app/pages/405.html create mode 100644 app/pages/500.html create mode 100644 app/pages/__layout__.html create mode 100644 app/pages/about.html create mode 100644 app/pages/admin/admin.py create mode 100644 app/pages/admin/assetcopier.html create mode 100644 app/pages/admin/assetmoderation.html create mode 100644 app/pages/admin/bundlecopier.html create mode 100644 app/pages/admin/createasset.html create mode 100644 app/pages/admin/creategiftcard.html create mode 100644 app/pages/admin/createuser.html create mode 100644 app/pages/admin/fflagsettings/index.html create mode 100644 app/pages/admin/fflagsettings/view.html create mode 100644 app/pages/admin/gameservers/create.html create mode 100644 app/pages/admin/gameservers/delete.html create mode 100644 app/pages/admin/gameservers/index.html create mode 100644 app/pages/admin/gameservers/refresh_accesskey.html create mode 100644 app/pages/admin/gameservers/view.html create mode 100644 app/pages/admin/index.html create mode 100644 app/pages/admin/insertitemrelasepool.html create mode 100644 app/pages/admin/itemreleasepool.html create mode 100644 app/pages/admin/lottery/index.html create mode 100644 app/pages/admin/manageassets.html create mode 100644 app/pages/admin/moderateUGC.html create mode 100644 app/pages/admin/permissionsdefinition.py create mode 100644 app/pages/admin/updateassetfile.html create mode 100644 app/pages/admin/usermanage/ban.html create mode 100644 app/pages/admin/usermanage/banhistory.html create mode 100644 app/pages/admin/usermanage/gamesessions.html create mode 100644 app/pages/admin/usermanage/invitekeys.html create mode 100644 app/pages/admin/usermanage/loginhistory.html create mode 100644 app/pages/admin/usermanage/manage-admin-perms.html create mode 100644 app/pages/admin/usermanage/moderatornotes.html create mode 100644 app/pages/admin/usermanage/search.html create mode 100644 app/pages/admin/usermanage/transactions.html create mode 100644 app/pages/admin/usermanage/view.html create mode 100644 app/pages/admin/websitefeatures.html create mode 100644 app/pages/admin/websitefeaturesdefinition.py create mode 100644 app/pages/admin/websitewidemsg.html create mode 100644 app/pages/audiomigrator/audiomigrator.py create mode 100644 app/pages/audiomigrator/index.html create mode 100644 app/pages/avatar/avatar.html create mode 100644 app/pages/avatar/avatar.py create mode 100644 app/pages/catalog/asset.html create mode 100644 app/pages/catalog/badges.html create mode 100644 app/pages/catalog/catalog.py create mode 100644 app/pages/catalog/catalogtypes.py create mode 100644 app/pages/catalog/index.html create mode 100644 app/pages/catalog/library.html create mode 100644 app/pages/catalog/resell.html create mode 100644 app/pages/clientpages/clientpages.py create mode 100644 app/pages/clientpages/screenshot.html create mode 100644 app/pages/clothingmigrator/index.html create mode 100644 app/pages/clothingmigrator/migrator.py create mode 100644 app/pages/cryptomus/dashboard.html create mode 100644 app/pages/cryptomus/view_payment.html create mode 100644 app/pages/currencyexchange/controller.py create mode 100644 app/pages/currencyexchange/create.html create mode 100644 app/pages/currencyexchange/index.html create mode 100644 app/pages/currencyexchange/view.html create mode 100644 app/pages/develop/develop.py create mode 100644 app/pages/develop/edit.html create mode 100644 app/pages/develop/games/access.html create mode 100644 app/pages/develop/games/manage-template.html create mode 100644 app/pages/develop/games/manage.html create mode 100644 app/pages/develop/games/upload-icon.html create mode 100644 app/pages/develop/games/upload-thumbnail.html create mode 100644 app/pages/develop/games/upload-version.html create mode 100644 app/pages/develop/games/version-history.html create mode 100644 app/pages/develop/subpages/games.html create mode 100644 app/pages/develop/subpages/image.html create mode 100644 app/pages/develop/subpages/pants.html create mode 100644 app/pages/develop/subpages/shirts.html create mode 100644 app/pages/develop/subpages/sound.html create mode 100644 app/pages/develop/subpages/tshirt.html create mode 100644 app/pages/develop/template.html create mode 100644 app/pages/develop/universes/access.html create mode 100644 app/pages/develop/universes/badges.html create mode 100644 app/pages/develop/universes/create-badge.html create mode 100644 app/pages/develop/universes/create-gamepass.html create mode 100644 app/pages/develop/universes/create-place.html create mode 100644 app/pages/develop/universes/create-product.html create mode 100644 app/pages/develop/universes/developerproducts.html create mode 100644 app/pages/develop/universes/edit-badge.html create mode 100644 app/pages/develop/universes/edit-gamepass.html create mode 100644 app/pages/develop/universes/edit-product.html create mode 100644 app/pages/develop/universes/gamepasses.html create mode 100644 app/pages/develop/universes/manage-template.html create mode 100644 app/pages/develop/universes/manage.html create mode 100644 app/pages/develop/universes/places.html create mode 100644 app/pages/discourse/leaving-syntax.html create mode 100644 app/pages/discourse/sso-confirm.html create mode 100644 app/pages/downloads.html create mode 100644 app/pages/drivers.html create mode 100644 app/pages/games/games.py create mode 100644 app/pages/games/genre.html create mode 100644 app/pages/games/index.html create mode 100644 app/pages/games/view.html create mode 100644 app/pages/giftcardredeem/index.html create mode 100644 app/pages/giftcardredeem/redeem.py create mode 100644 app/pages/groups/admin_subpage/create_role.html create mode 100644 app/pages/groups/admin_subpage/groupinfo.html create mode 100644 app/pages/groups/admin_subpage/join_requests.html create mode 100644 app/pages/groups/admin_subpage/members.html create mode 100644 app/pages/groups/admin_subpage/payout.html create mode 100644 app/pages/groups/admin_subpage/roles.html create mode 100644 app/pages/groups/admin_subpage/settings.html create mode 100644 app/pages/groups/admin_template.html create mode 100644 app/pages/groups/create.html create mode 100644 app/pages/groups/groupspage.py create mode 100644 app/pages/groups/search.html create mode 100644 app/pages/groups/view.html create mode 100644 app/pages/home/home.html create mode 100644 app/pages/home/home.py create mode 100644 app/pages/invitekeys/handler.py create mode 100644 app/pages/invitekeys/index.html create mode 100644 app/pages/login/login.html create mode 100644 app/pages/login/login.py create mode 100644 app/pages/login/reset_password.html create mode 100644 app/pages/login/send_reset.html create mode 100644 app/pages/login/totpvalidate.html create mode 100644 app/pages/membership/index.html create mode 100644 app/pages/membership/membership.py create mode 100644 app/pages/membership/payment_methods.html create mode 100644 app/pages/messages/index.html create mode 100644 app/pages/messages/messages.py create mode 100644 app/pages/messages/new.html create mode 100644 app/pages/messages/view.html create mode 100644 app/pages/notapproved/banpage.html create mode 100644 app/pages/notapproved/notapproved.py create mode 100644 app/pages/privacy.html create mode 100644 app/pages/profiles/followers.html create mode 100644 app/pages/profiles/following.html create mode 100644 app/pages/profiles/friends.html create mode 100644 app/pages/profiles/inventory.html create mode 100644 app/pages/profiles/profile.html create mode 100644 app/pages/profiles/profile.py create mode 100644 app/pages/profiles/requests.html create mode 100644 app/pages/settings/changeemail.html create mode 100644 app/pages/settings/changepassword.html create mode 100644 app/pages/settings/changeusername.html create mode 100644 app/pages/settings/emailverify_success.html create mode 100644 app/pages/settings/enableTOTP.html create mode 100644 app/pages/settings/settings.html create mode 100644 app/pages/settings/settings.py create mode 100644 app/pages/signup/signup.html create mode 100644 app/pages/signup/signup.py create mode 100644 app/pages/static.py create mode 100644 app/pages/studio/myPlaces.html create mode 100644 app/pages/studio/studiopages.py create mode 100644 app/pages/swaggerdocs.html create mode 100644 app/pages/terms.html create mode 100644 app/pages/trades/create.html create mode 100644 app/pages/trades/index.html create mode 100644 app/pages/trades/trades.py create mode 100644 app/pages/trades/view.html create mode 100644 app/pages/transactions/transactions.html create mode 100644 app/pages/transactions/transactions.py create mode 100644 app/pages/users/index.html create mode 100644 app/pages/users/users_page.py create mode 100644 app/routes/accountsettingsapi.py create mode 100644 app/routes/asset.py create mode 100644 app/routes/authentication.py create mode 100644 app/routes/avatarapi.py create mode 100644 app/routes/badgesapi.py create mode 100644 app/routes/bootstrapper.py create mode 100644 app/routes/clientinfo.py create mode 100644 app/routes/cryptomus_handler.py create mode 100644 app/routes/datastoreservice.py create mode 100644 app/routes/discord_internal.py create mode 100644 app/routes/discourse_sso.py create mode 100644 app/routes/fflagssettings.py create mode 100644 app/routes/friendapi.py create mode 100644 app/routes/gamejoin.py create mode 100644 app/routes/gamesapi.py create mode 100644 app/routes/gametransactions.py create mode 100644 app/routes/image.py create mode 100644 app/routes/inventoryapi.py create mode 100644 app/routes/jobreporthandler.py create mode 100644 app/routes/kofihandler.py create mode 100644 app/routes/legacydatapersistence.py create mode 100644 app/routes/luawebservice.py create mode 100644 app/routes/marketplace.py create mode 100644 app/routes/mobile.py create mode 100644 app/routes/pointsservice.py create mode 100644 app/routes/presence.py create mode 100644 app/routes/presenceapi.py create mode 100644 app/routes/prometheus.py create mode 100644 app/routes/publicapi.py create mode 100644 app/routes/rate.py create mode 100644 app/routes/rbxapi.py create mode 100644 app/routes/rolimons.py create mode 100644 app/routes/sets.py create mode 100644 app/routes/teleportservice.py create mode 100644 app/routes/thumbnailer.py create mode 100644 app/routes/usersapi.py create mode 100644 app/services/economy.py create mode 100644 app/services/gameserver_comm.py create mode 100644 app/services/groups.py create mode 100644 app/services/invitekeys.py create mode 100644 app/services/proxydetection.py create mode 100644 app/services/user_relationships/followings.py create mode 100644 app/services/user_relationships/friends.py create mode 100644 app/shell_commands.py create mode 100644 app/static/avatarrules.json create mode 100644 app/static/css/admin.css create mode 100644 app/static/css/avatar.css create mode 100644 app/static/css/bootstrapv3.min.css create mode 100644 app/static/css/catalog.css create mode 100644 app/static/css/develop.css create mode 100644 app/static/css/gameview.css create mode 100644 app/static/css/global.css create mode 100644 app/static/css/home.css create mode 100644 app/static/css/icons.css create mode 100644 app/static/css/login.css create mode 100644 app/static/css/message.css create mode 100644 app/static/css/profile.css create mode 100644 app/static/css/settings.css create mode 100644 app/static/css/signup.css create mode 100644 app/static/css/terms.css create mode 100644 app/static/img/BC.png create mode 100644 app/static/img/ChristmasHat.png create mode 100644 app/static/img/ContentDeleted.png create mode 100644 app/static/img/LoginImage.png create mode 100644 app/static/img/LoginImage2.png create mode 100644 app/static/img/LuaThumbnail.png create mode 100644 app/static/img/NBC.png create mode 100644 app/static/img/OBC.png create mode 100644 app/static/img/SignupImage.png create mode 100644 app/static/img/Style/bc.svg create mode 100644 app/static/img/Style/icon_labels.svg create mode 100644 app/static/img/SyntaxLogo.png create mode 100644 app/static/img/TBC.png create mode 100644 app/static/img/TemplatePants.png create mode 100644 app/static/img/TemplateShirt.png create mode 100644 app/static/img/branded.svg create mode 100644 app/static/img/games.svg create mode 100644 app/static/img/linux-penguin.svg create mode 100644 app/static/img/placeholder.png create mode 100644 app/static/img/syntax.ico create mode 100644 app/static/img/thumbs.svg create mode 100644 app/static/img/thumbsup.png create mode 100644 app/static/js/AvatarEditor.js create mode 100644 app/static/js/assetrate.js create mode 100644 app/static/js/bootstrapv3.min.js create mode 100644 app/static/js/catalog.js create mode 100644 app/static/js/groupMembers.js create mode 100644 app/static/js/newTrade.js create mode 100644 app/static/js/presence.js create mode 100644 app/static/svg/navigation.svg create mode 100644 app/static/swagger/favicon-16x16.png create mode 100644 app/static/swagger/favicon-32x32.png create mode 100644 app/static/swagger/index.css create mode 100644 app/static/swagger/oauth2-redirect.html create mode 100644 app/static/swagger/swagger-docs.json create mode 100644 app/static/swagger/swagger-initializer.js create mode 100644 app/static/swagger/swagger-ui-bundle.js create mode 100644 app/static/swagger/swagger-ui-bundle.js.map create mode 100644 app/static/swagger/swagger-ui-es-bundle-core.js create mode 100644 app/static/swagger/swagger-ui-es-bundle-core.js.map create mode 100644 app/static/swagger/swagger-ui-es-bundle.js create mode 100644 app/static/swagger/swagger-ui-es-bundle.js.map create mode 100644 app/static/swagger/swagger-ui-standalone-preset.js create mode 100644 app/static/swagger/swagger-ui-standalone-preset.js.map create mode 100644 app/static/swagger/swagger-ui.css create mode 100644 app/static/swagger/swagger-ui.css.map create mode 100644 app/static/swagger/swagger-ui.js create mode 100644 app/static/swagger/swagger-ui.js.map create mode 100644 app/util/RBXMesh.py create mode 100644 app/util/assetvalidation.py create mode 100644 app/util/assetversion.py create mode 100644 app/util/auth.py create mode 100644 app/util/badwords.py create mode 100644 app/util/discord.py create mode 100644 app/util/friends.py create mode 100644 app/util/membership.py create mode 100644 app/util/placeinfo.py create mode 100644 app/util/redislock.py create mode 100644 app/util/s3helper.py create mode 100644 app/util/signscript.py create mode 100644 app/util/textfilter.py create mode 100644 app/util/transactions.py create mode 100644 app/util/turnstile.py create mode 100644 app/util/websiteFeatures.py create mode 100644 config.example.py create mode 100644 debug.sh create mode 100644 download_cache/KEEPME create mode 100644 logs/KEEPME create mode 100644 requirements.txt create mode 100644 start.sh create mode 100644 tools/generate_new_keys.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c3dafe --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +config.py +__pycache__/ +assets/ +proxies.txt +*.pem +logs/*.log* +download_cache/* +*.key +*.crt +!download_cache/KEEPME \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f56b37b --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Syntax Backend +**Last Updated: 20/2/2024** + +## What you need +### Requirements + - Linux Server ( For Production environment ) + - PostgreSQL Server + - Redis Server + - NGINX + - Cloudflare Account + - Domain with Cloudflare protection + - Python 3.12+ + - FFmpeg + - Gunicorn + +> Note: These are the bare minimum needed for Syntax Backend to run, please do not attempt to host a publicly accessible version of Syntax if you do not know what you are doing + +### Optional Services + - Syntax Gameserver running on Windows Server ( Needed for rendering and games ) + - Syntax Discord Bot + - Ko-Fi ( Please modify code if you are not going to use this ) + - Cryptomus + - MailJet ( Email Vericiation, modification to the code is needed as email templates are not included ) + - HTTP Proxies for faster asset migration ( [webshare.io](https://webshare.io/) is recommended ) + - Amazon S3 Bucket ( **USE_LOCAL_STORAGE** must be enabled if you are not planning to use a S3 Bucket ) + +## Configuration +Copy `config.example.py` and name it as `config.py` then place it in the same directory as this readme file + +1. **FLASK_SESSION_KEY** - Used for salting passwords and 2FA Secret Generation, please change to a random long string and never change it ever again! +~~2. **AuthorizationKey** - Added for debugging and bypassing ratelimits, please also change to a random long string~~ Removed from codebase +3. **SQLALCHEMY_DATABASE_URI** - URI for connecting to the postgres database, refer to [Documentation](https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/) for creating a database URI +4. **FLASK_LIMITED_STORAGE_URI** - Redis Server URI, can leave as default if your redis server is hosted locally and does not require authorization +5. **BaseDomain** - Change to your domain *(eg. roblox.com)*, please do not host on a subdomain as it is not supported! +6. **CloudflareTurnstileSiteKey** - Please setup turnstile on the domain you are hosting and then grab the turnstile site key from there +7. **CloudflareTurnstileSecretKey** - Read above +8. **DISCORD_CLIENT_ID** - Go to the Discord Developer Portal and go to the Discord Application you are going to use, then place its ClientID here +9. **DiscordBotToken** - Use your Discord Bot Token +10. **DISCORD_CLIENT_SECRET** - Discord Application Client Secret +11. **DISCORD_BOT_AUTHTOKEN** - Authorization Token for Syntax Discord Bot, use random long string +12. **DISCORD_BOT_AUTHORISED_IPS** - List of IPs which are allowed to access Discord Bot internal APIs +13. **DISCORD_ADMIN_LOGS_WEBHOOK** - Discord Webhook for logging Moderation Actions +14. **MAILJET** - You will have to modify the code and the config for this as your email template will be different +15. **KOFI_VERIFICATION_TOKEN** - Used for verifying requests from Ko-Fi to automate donations processing, please change to a random long string if you do not plan on using this. If you do you can find the verification token in your Ko-Fi API Panel. +16. **VERIFIED_EMAIL_REWARD_ASSET** - The AssetId the user is rewarded with once they verify their email, you can change this after setting up everything +17. **ASSETMIGRATOR_ROBLOSECURITY** - Used for private audio migration +18. **ASSETMIGRATOR_USE_PROXIES** - If you want to use proxies for Asset Migration ( Which you should as it speeds up everything ) +19. **ASSETMIGRATOR_PROXY_LIST_LOCATION** - The path to the file which contains the proxies +20. **RSA_PRIVATE_KEY_PATH** - The path to the private key, expects a 1024 Bit RSA private key used for signing JoinScripts and everyting else **This is required!!** +21. **RSA_PRIVATE_KEY_PATH2** - Same thing for above but expects a 2048 Bit RSA private key +22. **USE_LOCAL_STORAGE** - Uses local storage for storing and reading files, bypasses S3 and uses **AWS_S3_DOWNLOAD_CACHE_DIR** as its storage directory ( SHOULD ONLY BE USED IN A DEVELOPMENT ENVIRONMENT ) +23. **AWS_ACCESS_KEY** - Your AWS Access Key, please create one in your AWS IAM Manager +24. **AWS_SECRET_KEY** - The Secret Key for the access key +25. **AWS_S3_BUCKET_NAME** - The bucket name assets and images will be uploaded to +26. **AWS_S3_DOWNLOAD_CACHE_DIR** - Where files downloaded from S3 will be cached +27. **AWS_REGION_NAME** - The region of the bucket +28. **CDN_URL** - Change to where the CDN is +29. **DISCOURSE_SSO_ENABLED** - Allows authentication with Syntax for [Discourse](https://www.discourse.org/) Forums +30. **DISCOURSE_FORUM_BASEURL** - The location of the forum +31. **DISCOURSE_SECRET_KEY** - The secret key for signing +32. **ADMIN_GROUP_ID** - The GroupId where admins are in, used for showing the admin badges ingame +33. **ITEMRELEASER_DISCORD_WEBHOOK** - The Discord Webhook to use for announcing an item release +34. **ITEMRELEASER_ITEM_PING_ROLE_ID** - The Discord Role ID to ping for announcing an item release +35. **PROMETHEUS_ENABLED** - If the Prometheus endpoint is enabled +36. **PROMETHEUS_ALLOWED_IPS** - IPs which are allowed to query the Prometheus endpoint +37. **CHEATER_REPORTS_DISCORD_WEBHOOK** - The Discord webhook to use for cheater reports from RCCService +38. **ROLIMONS_API_ENABLED** - Used for Synmons +39. **ROLIMONS_API_KEY** - Used for Synmons +40. **GAMESERVER_COMM_PRIVATE_KEY_LOCATION** - The Private key location used for signing requests sent to gameservers +41. **CRYPTOMUS_PAYMENT_ENABLED** - If the cryptomus payment system is enabled +42. **CRYPTOMUS_MERCHANT_ID** - Your Cryptomus merchant ID +43. **CRYPTOMUS_API_KEY** - Your Cryptoumus API Key +44. **IPAPI_AUTH_KEY** - API Key for [IPAPI](https://ipapi.co/) used for VPN and proxy detection on signup + +## KeyPair Generation +The SYNTAX Backend requires some keys for it to sign and communicate with gameservers, run the script below to generate those keys. +``` +python tools/generate_new_keys.py +``` + +In the `tools` directory 6 new files should have been created, 2 key pairs are for joinscript signing which is needed by the Client and RCCService to authenticate and verify properly. +Another keypair is for signing requests to communicate with all gameservers, take the public key and place it in your gameserver directory. + +## First Time Setup + +First install all required dependencies by running `pip install -r requirements.txt` in this directory + +Next run the command `flask shell` in the same directory as this README.md file, then in the shell run `db.create_all()`. +This will automatically create all the tables needed in your PostgreSQL database. + +Next use type in the following command in your flask shell +``` +from app.shell_commands import create_admin_user +create_admin_user() +``` +This will create an admin user with all existing admin privileges + +Now we can finally start the website, please make sure you have [gunicorn](https://gunicorn.org/) installed on your Linux Machine, gunicorn does not support Windows Machines. To start run the shell script `./start.sh` which will start a webserver on port `3003`. Please make sure you have NGINX configured as a reverse proxy to proxy the website and also have configured Cloudflare to serve your website on the main and all subdomains. + +If you are running a Windows Machine and want to run in debug mode run `flask run --port 3006 --debug`, this will open the website on port 3006 diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..c675ecc --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,415 @@ +import base64 +import redis +import sys +import os +import hashlib +import traceback +import string +import random +import re +import logging +from config import Config +from logging.handlers import TimedRotatingFileHandler +from flask import Flask, jsonify, render_template, session, redirect, url_for, request, make_response, Response +from datetime import datetime, timedelta +from urllib.parse import urlparse + +from app.models.user import User +from app.models.messages import Message +from app.models.user_trades import UserTrade +from app.models.friend_request import FriendRequest +from app.models.asset import Asset +from app.models.asset_version import AssetVersion +from app.models.game_session_log import GameSessionLog +from app.enums.TradeStatus import TradeStatus +from app.enums.AssetType import AssetType +from app.enums.TransactionType import TransactionType +from app.util import auth, assetversion, s3helper, signscript, transactions +from app.services.economy import IncrementTargetBalance, GetUserBalance +from app.extensions import db, limiter, scheduler, CORS, redis_controller, csrf, get_remote_address +import app.shell_commands as cmd + +logging.basicConfig( + level = logging.INFO, + format = "%(asctime)s [%(levelname)s] %(message)s" +) +logger = logging.getLogger(__name__) +logname = "./logs/syntaxweb.log" +handler = TimedRotatingFileHandler(logname, when="midnight", backupCount=30) +handler.suffix = "%Y%m%d" + +logging.getLogger().addHandler(handler) + +TwelveClientAssets = [37801173, 46295864, 48488236, 53870848, 53870858, 60595696, 89449009, 89449094, 97188757] +def create_app(config_class=Config): + app = Flask(__name__, template_folder="pages") + app.config.from_object(config_class) + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config["SECRET_KEY"] = config_class.FLASK_SESSION_KEY + app.config['CORS_HEADERS'] = 'Content-Type' + app.config['SESSION_TYPE'] = 'redis' + app.config['SESSION_REDIS'] = redis.from_url(Config.FLASK_LIMITED_STORAGE_URI) + app.config['MAX_CONTENT_LENGTH'] = 32 * 1024 * 1024 + app.config["SQUEEZE_MIN_SIZE"] = 0 + #if app.debug is False: + #app.config["SERVER_NAME"] = config_class.BaseDomain + + db.init_app(app) + limiter.init_app(app) + csrf.init_app(app) + clean_domain = config_class.BaseDomain.replace('.', r'\.') + CORS.init_app(app, supports_credentials=True, resources={r"/*": {"origins": [f"https://{clean_domain}", f"http://{clean_domain}", f"https://.+{clean_domain}", f"http://.+{clean_domain}"]}}) + scheduler.init_app(app) + scheduler.start() + + from app.pages.login.login import login + from app.pages.signup.signup import signup + from app.pages.static import static + from app.pages.settings.settings import settings + from app.pages.home.home import home + from app.pages.admin.admin import AdminRoute, GetAmountOfPendingAssets, IsUserAnAdministrator + from app.routes.asset import AssetRoute + from app.routes.authentication import AuthenticationRoute + from app.routes.jobreporthandler import JobReportHandler + from app.routes.clientinfo import ClientInfo + from app.routes.thumbnailer import Thumbnailer + from app.routes.image import ImageRoute + from app.routes.fflagssettings import FFlagRoute + from app.pages.profiles.profile import Profile + from app.routes.gamejoin import GameJoinRoute + from app.routes.marketplace import MarketPlaceRoute, EconomyV1Route + from app.routes.presence import PresenceRoute + from app.pages.messages.messages import MessageRoute + #from app.pages.clothingmigrator.migrator import ClothingMigratorRoute + from app.pages.catalog.catalog import CatalogRoute + from app.pages.avatar.avatar import AvatarRoute + from app.routes.pointsservice import PointsServiceRoute + from app.routes.datastoreservice import DataStoreRoute + from app.pages.clientpages.clientpages import ClientPages + from app.routes.luawebservice import LuaWebServiceRoute + from app.pages.develop.develop import DevelopPagesRoute + from app.routes.bootstrapper import BootstrapperRoute + from app.pages.studio.studiopages import StudioPagesRoute + from app.pages.games.games import GamePagesRoute + from app.pages.membership.membership import MembershipPages + from app.routes.rate import AssetRateRoute + from app.pages.trades.trades import TradesPageRoute + from app.routes.sets import SetsRoute + from app.pages.notapproved.notapproved import NotApprovedRoute + from app.pages.groups.groupspage import groups_page + from app.pages.giftcardredeem.redeem import GiftcardRedeemRoute + from app.pages.currencyexchange.controller import CurrencyExchangeRoute + from app.routes.kofihandler import KofiHandlerRoute + #from app.pages.invitekeys.handler import inviteKeyRoute + from app.routes.discord_internal import DiscordInternal + from app.routes.publicapi import PublicAPIRoute + from app.pages.catalog.catalog import LibraryRoute + from app.routes.discourse_sso import discourse_sso + from app.pages.transactions.transactions import TransactionsRoute + from app.routes.gametransactions import GameTransactionsRoute + from app.pages.audiomigrator.audiomigrator import AudioMigratorRoute + from app.routes.rbxapi import RBXAPIRoute + from app.routes.legacydatapersistence import LegacyDataPersistenceRoute + from app.routes.friendapi import FriendsAPIRoute + from app.routes.inventoryapi import InventoryAPI + from app.routes.usersapi import UsersAPI + from app.routes.mobile import MobileAPIRoute + from app.routes.gamesapi import GamesAPIRoute + from app.routes.accountsettingsapi import AccountSettingsAPIRoute + from app.routes.presenceapi import PresenceAPIRoute + from app.routes.avatarapi import AvatarAPIRoute + from app.routes.badgesapi import BadgesAPIRoute + from app.pages.catalog.catalog import BadgesPageRoute + from app.pages.users.users_page import users_page + from app.routes.teleportservice import TeleportServiceRoute + from app.routes.prometheus import PrometheusRoute + from app.routes.rolimons import RolimonsAPI + from app.routes.cryptomus_handler import CryptomusHandler + app.register_blueprint(login, url_prefix="/") + app.register_blueprint(signup, url_prefix="/") + app.register_blueprint(static, url_prefix="/") + app.register_blueprint(settings, url_prefix="/") + app.register_blueprint(home, url_prefix="/") + app.register_blueprint(AssetRoute, url_prefix="/") + app.register_blueprint(AuthenticationRoute, url_prefix="/") + app.register_blueprint(AdminRoute, url_prefix="/admin") + app.register_blueprint(JobReportHandler, url_prefix="/") + app.register_blueprint(ClientInfo, url_prefix="/") + app.register_blueprint(Thumbnailer, url_prefix="/internal") + app.register_blueprint(ImageRoute, url_prefix="/") + app.register_blueprint(FFlagRoute, url_prefix="/") + app.register_blueprint(Profile, url_prefix="/") + app.register_blueprint(GameJoinRoute, url_prefix="/") + app.register_blueprint(MarketPlaceRoute, url_prefix="/marketplace") + app.register_blueprint(EconomyV1Route, url_prefix="/") + app.register_blueprint(PresenceRoute, url_prefix="/presence") + app.register_blueprint(MessageRoute, url_prefix="/messages") + #app.register_blueprint(ClothingMigratorRoute, url_prefix="/") + app.register_blueprint(CatalogRoute, url_prefix="/catalog") + app.register_blueprint(AvatarRoute, url_prefix="/") + app.register_blueprint(PointsServiceRoute, url_prefix="/") + app.register_blueprint(DataStoreRoute, url_prefix="/") + app.register_blueprint(ClientPages, url_prefix="/") + app.register_blueprint(LuaWebServiceRoute, url_prefix="/") + app.register_blueprint(DevelopPagesRoute, url_prefix="/") + app.register_blueprint(BootstrapperRoute, url_prefix="/") + app.register_blueprint(StudioPagesRoute, url_prefix="/") + app.register_blueprint(GamePagesRoute, url_prefix="/") + app.register_blueprint(MembershipPages, url_prefix="/") + app.register_blueprint(AssetRateRoute, url_prefix="/") + app.register_blueprint(TradesPageRoute, url_prefix="/") + app.register_blueprint(SetsRoute, url_prefix="/") + app.register_blueprint(NotApprovedRoute, url_prefix="/") + app.register_blueprint(groups_page, url_prefix="/") + app.register_blueprint(GiftcardRedeemRoute, url_prefix="/") + app.register_blueprint(CurrencyExchangeRoute, url_prefix="/currency-exchange") + app.register_blueprint(KofiHandlerRoute, url_prefix="/") + #app.register_blueprint(inviteKeyRoute, url_prefix="/") + app.register_blueprint(DiscordInternal, url_prefix="/internal/discord_bot") + app.register_blueprint(PublicAPIRoute, url_prefix="/public-api") + app.register_blueprint(LibraryRoute, url_prefix="/library") + app.register_blueprint(discourse_sso, url_prefix="/discourse") + app.register_blueprint(TransactionsRoute, url_prefix="/transactions") + app.register_blueprint(GameTransactionsRoute, url_prefix="/") + app.register_blueprint(AudioMigratorRoute, url_prefix="/") + app.register_blueprint(RBXAPIRoute, url_prefix="/") + app.register_blueprint(LegacyDataPersistenceRoute, url_prefix="/persistence/legacy") + app.register_blueprint(FriendsAPIRoute, url_prefix="/") + app.register_blueprint(InventoryAPI, url_prefix="/") + app.register_blueprint(UsersAPI, url_prefix="/") + app.register_blueprint(MobileAPIRoute, url_prefix="/") + app.register_blueprint(GamesAPIRoute, url_prefix="/") + app.register_blueprint(AccountSettingsAPIRoute, url_prefix="/") + app.register_blueprint(PresenceAPIRoute, url_prefix="/") + app.register_blueprint(AvatarAPIRoute, url_prefix="/") + app.register_blueprint(BadgesAPIRoute, url_prefix="/") + app.register_blueprint(BadgesPageRoute, url_prefix="/badges") + app.register_blueprint(users_page, url_prefix="/") + app.register_blueprint(TeleportServiceRoute, url_prefix="/reservedservers") + app.register_blueprint(PrometheusRoute, url_prefix="/") + app.register_blueprint(RolimonsAPI, url_prefix="/api/internal_rolimons") + app.register_blueprint(CryptomusHandler, url_prefix="/cryptomus_service") + + def ConvertDatetimeToDayMonthYear(date): + return date.strftime("%d/%m/%Y") + + app.jinja_env.globals.update(round=round, b64decode=base64.b64decode, len=len, ConvertDatetimeToDayMonthYear=ConvertDatetimeToDayMonthYear, datetime_utcnow = datetime.utcnow) + + @app.before_request + def before_request(): + BrowserUserAgent = request.headers.get('User-Agent', default="Unknown") + if "Roblox" not in BrowserUserAgent: + if request.method == "GET": + CloudFlareScheme = request.headers.get('CF-Visitor') + if CloudFlareScheme is not None: + if "https" not in CloudFlareScheme: + return redirect(request.url.replace("http://", "https://", 1), code=301) + elif BrowserUserAgent == "Roblox/WinInet": + requestReferer = request.headers.get( key = "Referer", default = None ) + if requestReferer is not None: + try: + if urlparse( requestReferer ).hostname[ -len( config_class.BaseDomain ): ] != config_class.BaseDomain: + logging.warn(f"Bad Referer - ref : {requestReferer} - target : {request.url}") + return "Bad Referer", 403 + except: + pass + + @app.after_request + def after_request( response : Response ): + if get_remote_address() in config_class.DEBUG_IPS: + logging.info(f"Debug - {response.status_code} - {request.host} - {request.path} - {request.args}") + if hasattr(response, 'direct_passthrough') and not response.direct_passthrough: + UserObj : User = auth.GetCurrentUser() + if UserObj is not None: + if UserObj.accountstatus != 1: + auth.invalidateToken(request.cookies.get(".ROBLOSECURITY")) + session["not-approved-viewer"] = UserObj.id + resp = make_response(redirect("/not-approved")) + resp.set_cookie(".ROBLOSECURITY", "", expires=0) + return resp + if request.cookies.get(key="t", default=None, type=str) is None: + NewToken = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(128)) + response.set_cookie("t", NewToken, expires=datetime.utcnow() + timedelta(days=365), domain=f".{config_class.BaseDomain}") + if "not-approved-viewer" in session: + UserObj : User = auth.GetCurrentUser() + if UserObj is not None: + session.pop("not-approved-viewer") + return response + + @app.context_processor + def inject_user(): + if ".ROBLOSECURITY" in request.cookies: + AuthenticatedUser : User = auth.GetCurrentUser() + if AuthenticatedUser is None: + return {} + + def award_daily_login_bonus(): + if redis_controller.get(f"daily_login_bonus:{str(AuthenticatedUser.id)}") is not None: + return + if AuthenticatedUser.created > datetime.utcnow() - timedelta( days = 1 ): + return + if GameSessionLog.query.filter_by(user_id=AuthenticatedUser.id).filter( GameSessionLog.joined_at > datetime.utcnow() - timedelta( days = 3 ) ).first() is None: + return + + redis_controller.setex(f"daily_login_bonus:{str(AuthenticatedUser.id)}", 60 * 60 * 24 ,"1") + IncrementTargetBalance(AuthenticatedUser, 10, 1) + transactions.CreateTransaction( + Reciever = AuthenticatedUser, + Sender = User.query.filter_by(id=1).first(), + CurrencyAmount = 10, + CurrencyType = 1, + TransactionType = TransactionType.BuildersClubStipend, + CustomText = "Daily Login Bonus" + ) + + if redis_controller.exists(f"award_daily_login_bonus_attempt:{str(AuthenticatedUser.id)}") is None: + redis_controller.setex(f"award_daily_login_bonus_attempt:{str(AuthenticatedUser.id)}", 60, "1") + award_daily_login_bonus() + + unreadMessages = Message.query.filter_by(recipient_id=AuthenticatedUser.id, read=False).count() + inboundTrades = UserTrade.query.filter_by(recipient_userid=AuthenticatedUser.id, status=TradeStatus.Pending).count() + friendRequests = FriendRequest.query.filter_by(requestee_id=AuthenticatedUser.id).count() + AuthenticatedUser.lastonline = datetime.utcnow() + db.session.commit() + userRobux, userTix = GetUserBalance(AuthenticatedUser) + isAdministrator = IsUserAnAdministrator( AuthenticatedUser ) + PendingAssetsCount = 0 + if isAdministrator: + PendingAssetsCount = GetAmountOfPendingAssets() + + return { + "currentuser": { + "id": AuthenticatedUser.id, + "username": AuthenticatedUser.username, + "robux": userRobux, + "tix": userTix, + "unread_messages": unreadMessages, + "inbound_trades": inboundTrades, + "friend_requests": friendRequests, + "is_admin": isAdministrator, + "pending_asset_count": PendingAssetsCount + }, + } + return {} + + @app.context_processor + def inject_website_wide_message(): + if redis_controller.exists("website_wide_message"): + url_pattern = re.compile(r'(https?://\S+)') + website_message = website_wide_message=redis_controller.get("website_wide_message") + website_message = url_pattern.sub(r'\1', website_message) + return dict(website_wide_message=website_message) + return {} + + @app.context_processor + def injecthcaptcha_sitekey(): + return dict(turnstilekey=Config.CloudflareTurnstileSiteKey) + + @app.route('/') + def main(): + if "user" in session: + return redirect("/home") + else: + return redirect("/login") + + @app.errorhandler(404) + def page_not_found(e): + BrowserUserAgent = request.headers.get('User-Agent') + if BrowserUserAgent is not None: + if "RobloxStudio" in BrowserUserAgent: + return "
Zh7qE!3*8Qh`mT&LXsDD?&Nh`lb2?I=`J`ktXQpvOU#&?6ZOK%>O9`9 ziJ9mbdkVZP(vfck|#n z=PFtQg9N|Tm-pFCrgp;430nwWVn@I@^%=bWVJ<7SA5$n7ZvP=a&jPP1K|#yrF6E5? zYiLKFaL@+~Q;bRW+4SyBQXzF3`ag5@i^gG0y|E+iY@g2C&P`#IJO{D!qxmiA`Z)*w zuE>Q4QXl3K7L_eE`yrublqu`pU=f7R)T0bcSVhT!pIH=r?29Hxda{wW!7Q10%hwN4 zkV0CvY__r)Qa)Boh8aS>7$vn1@$1$>6SEa>Fob6e-hx*+WYSVJ$yw-1qE|+;%&jua zHH;)3vo+;`09&a%MkWE-$ydM9ep3fizVd3N54(v#L+|(z^Zvj*GlkY%g2o}u4is(g z;m=lv(&`|-Uxy##GiSTQ;_1FU^@*T^J7(5fdB!*h4gS3uAgZ;0 =PTtm+Rv4TsHH0NWzR;_Y7wgd~Mq1WOTJi}nh$!%==8rqkB zfh*lngwN;Gz3kTRVT-BhE~>M{^NvMt=ask%)%bVGW$*$cP)z1-*_rE)9Q8het!us? zBQph6T}mxzfM}AXb7$7Mli?fwqZmjRyH4v*RRZO61*z>y4yd~1e-q%(MbrDu0!Ct& z3$Fs82h(V)nYz>mniU&hbtEB*vbzWImomx?Ew(K}{DV%o4qtxv4M+`+mVF7YMqNl3 zO2DR=bslYA$<%AdN uzS&f#ETu!iRsfe zNVR-|F 0pWHBf8o&*dRf(&(--tt+^s=le~wb=tiMqd zG~?QoodL5Rb|MXVG+bsw(%FoQFtY{bsepk2E)!V}aoQh#$!0Ta*AJU+6%0)Ki(fm) z1qBWWYistkK>8_qWoZ~?Eb8me-)WtGT=(;{@YI_S!kzMPtYf2khA>wNZx{UsW*+G9 zw^i3b{_sE9v`}?hdsPzB(f1Td%uK|jYwiG_bjhlFoxJfoL@07rUJ>OBu!=VGk&X X-ZeT*y&nRfk*w6c+^|o|2SuGpUl$gDP z&;G;0AqVxFk1|ri&JsCsc54QSBRPR0D}BA;{`ro|9^;8~7ZN`dVqOu^`IZThy|b%S z%iMTw6Jx6Zz1gEdcdF>)D^BCkqSgPUOsb`=I9bUMYrX~3M3L)zJa}a=D(@wN6Y$sY zjj`4 H}9~Ij{L$m}bNRJ;7Dx(9gyyYfQvICUv&M zW)wb8?qK@Ij$kv=Tg#~ ;uTF ?r76@CJD;JMIal0cMUhU~2B9fuk+Oa%x~EzxH<>z3IWI2YscTvb4u9hQ zK=~Gp+3Z*E^Vdd&<0ZQt|4Km$*tBIABZH Z_o6h}9oKAweG{bL8l4%C=S}Qe?&l`@%+fzcm4y2?589u*T5A!2SL- zJ++`;AMZ( jk0VCc33u!8KYbZ+PfG~-79nD zdXAWF&x-Q5W)f#+Q5t`NR`-GUt0Di_s^RFSgR_PhWAM8W_$9g1=+coj-TfGmG!}b1 zzg0`hAss7L5vSV8s4hqk5B_rOa(*Yb|Hu!^JvMyAC_Aynwe+@%e^8*5c2B-6&L#V= z$d^)uhTz`^3mps}uc8u2`I6<%fzonYWgdAudiWz_l jLEJourORqz-stOXma zvxj0_k6mH&ujc*5eQ<9G>llnHVX!`fFNl4KQN@d__vl^E3}Xi6q7qulX!3dE&{<6f zDoG54HI>3jw2XR!8;$hiSfC+@y5-|59U^yg{l0iN`7J!0vcc+?H)EG|VmN}|s+V!9 zja*mHre33Dkrrq=gTlO(bFPY5P?N6^<)D=9fkQf#&oZaNJ%i1Rx1i$ZBdT!NnG8q- zzkBJ|X!eCKD_-F?-@+|B&vE$NK*JJd;b&)4TsdB~oNqPzNQ&9tDtI4DIjK|65={uN zC3TaW$8x{$waz4r0sVP>ByV!-A^(02S8G~iCs6!4(|lrIYa*00SV}N!t_fUN |n6=!0IzYahbEJw3@RNxD*$Q z#~~N`#*VB@qM8AN$u&37;;jBi^`aHcly;M2J0A2rMnXlVQXVZK@*qk=Ot{b|L;{F_!2I1e#!tLJ~ zMWLn;S<>ZFY1SHUk-lINEpqr6%7Q#oTDgrX(*>DUTJGs^yc@pA;Ps)eWxhe(_B;qY z>K-?V_9I8lo;G&Qt1djgC77faf*)70%b-)?lx_Gz&fX}#7vn^ U4X_$2 zksVu~#4H?Z7(@NQ1D#3Q*#Cuz o*mMJ0PE@ws5M zVG~wz;2T?Lz)r^jLace$_cI$|eAV6WOWKC9f44|EF*p=_m<|4-ZG)jXZR5dg9fG$$ z)Ms|zyRYRKbgzkz{^c~FH^VJ3?6&1+{CjYE6O{_zw|Zksg2j^U)j6SRMG(9b8WI0i zQQ2M)JSTMQv}&CPLIpAJADcxL)JyQx?zXQns5!p$IxL4UFW==~F2UTc`!znyeQ}$9 z`Hd733deyFxxevnmfmhhAO)C(NS{b%NR7gG!srH3if~{^(O^D-i(+vt#)<8dVCF9@ zcq?TLbwUc`rTCaTEeW}hR^_x}7A{D6!k@Mz_IG3f&fIDsF8a7%&`guyPbegI0CNW7 z7^Vj+WN7-A `r3g&PMxDgux@qr4-G9@nu^uS=W+XyOhqizlFx 6_gn4Qfu$$CO?959E+_ z{v#+0Qj%r8DyrGH=)J5Sqv?n(|E22wJK?@fVSmnjGed!odHZ;eNdI?aG}4wKIKcH7 zaawyzS&f8P^H&K-@B!$ZrN{E?u;Dyf(`jlt u}WI^xfXK)2~XQ|@n{M1Bo zl(34(HyIBtRXV5r(i?8=rKEPl3$_<74yxFfIOTFdt~UiydSP-ySZwnM?YC4eQ(Snz zc3%_<^__O)V^bgvoy|46K`>5Fv{vvztkIM6Qzt9)_*8z|;imtVu9%qkcTVx3`M!EC z>$4m>&A98(_Na{fEkWkcB!M_$3_qFz($SI%Y^G5ue!Z;oYb1XVvhTKg=xJ1=KouY@ z&~LO378A$bw(3&pwiqHas4Ut`he#^Nic($Etf(loC-xl6K-DWfgs=wxc)7-ezD^)k z2!Te5eCO-wOW #l4d}!y zv`Waxm-j8|tY#*&LNR{CQw ^^sC&Wwij8tN{A)e;Ht1d)Mhr+{!8b0nLqYc>aM4fS>H~A~B|X7W zm~;MJ+d;DDG;({Bj#~5uqz*L%QmoYUa!U?6OB4QGGWJl&zXCN>aE;V6CbI^b9GUIr zI0^#%SLPb>9NvXOGv<8w5dy$LgYc^>&JYX2L)rxU#arnPP%Z{S2>e-*@klqA3nmKu zi_{Yt;g|8unzNZxAsC`Qx23fgqSbOY00|198rMB9WO0v<_C#ktoc8Y$ec>$8lJzQl z023#Et}r%04Um|F_PjXxkfL8k;u*miC0iqfRhHIQ$kJK5e_P?6njdJbj52l9|BV|o z5sNBK2 ;MO#$R_&J^uUd9v@mmN|3%OgKQ(q zvPII!0Gl8akOHE?M$`jz+R&}Qwd}zwkl0HO>Xw9!8;4%I1*C=XyzcL4$&x2(GLK1$ z7T=VAm;1i6SkJ1a+LH2iy7T6*yMz!0^Ys!fAD7Hz4I$U5--lu}W c=W$jn?*}Bw32m&KbTZjtI#Y-)4)%Gd;q}@k<(pP`Sk6y%pHdbU+g{f ztvn0ry8w{l5N>=wq1O-aM>qU{fv=)GN$7bL4tBP&lH%iy^2zC`rzcuwI)Z$Xtz6Tmg{sZ?F@>V-j~avMJmWdoIs4YtjyU2w9wJf zD^YEfb5Xx>0;S-t )uVn!hpf%);Jd5_<)gK5=PTguKZk2vd0n)N$n>KWrz*J8;TuqGU zP)kLu(kY-T-V4`h-%jV5m#%}FqRgUHL*_aerb;xG20l>UpfWEX`=XR4e?2b2 z^ga13@2o!U=kd1Mb%v;)@`3w=Nx(*liSJB(8Yv^})$=vW>$%sl;rtMVXVK*wY_ER= zpu%64y7>)|DBV_*x<%zhtDq?_52bxez3AVS^T7QrSo)&nf0jLb`<{s?CARF%FS?Ph zM9o^#U1pP6DG#e lJ|0M(R}F)S#ayh)H{$@`?$yMoNWlo zv5`uK&lsm00N22YQ{B Y#KN)f91oa`1`iGs-)Ab-j49=9~%ZrOaeU>q=WiZcLaY3B@A7u5!$*{q&q!X&``p z`z@EZElJ|V2+zr<56|mnTmm~@e?pt4#Y2eIk*u7i75!p^wpD>zvEZlx55yJq!T`Kf z7$9uiF#B>4fi#fHMRcnXFAG30UJ%N|W0mcw>G6D%gQl1CydbOVmUa N7Wv4;|V1B9a?1OV47s-pgHVd1KBAp|gK;0R{}JDpcD zA%n{39f 71x>j+AI-pwgA5sH1#p35Kefko?JteQ|YiZdAJ0ls2eox8No0v2(k*hwgWk; z5u7&L{7Rew#wVy!%AB&B6B(z2SbmNHt Q9$F}ZWLr5?tN#?!iF~e&f|J(F>hfFDxlmi`LVfQgWT!-Bc#d& zgq~xM2KvnHUT0{QuMb)g?Z=sZTrmVzfB-x{I@pI#-v~{$%F+^)ZB4o{;J%9Y|ArFm zgAF;Tn*ezhUU$ouc^Xut+b?8+*{h_%luo&|BB~LJimzB@`W }04DO_UB~iL6KjkA0rhD6993qoPMNwn$Sb~PfWMDge^+mpb17odW3;iS2@VBUF zg*PfFK@9FBYV}s&e*~~~aH}E*^#exjYY$a9q)m4j{PUwc%kf0S7<25FTF1i`s4?nA z+_@la51;^T+yN;&Ea37q{iEPTof&}QEpc|9|BR<$u$434gpNAa?}+iTE}V*|f1h7T zZ>_h?u(98d_dFM0bUDb)QOrPB7AgzHgBNTdGLKSsuI-8T&XXV~ $61G+{lfRC|jzx&N$!)J}RvVg2gFi*u zgzyG8ABHXB#=ql%W(aBZkqnj)TQ|R2(HtBd3iOmna%dt2i#YZ=(!*sYFH>=p{UXHc zZ2IK{%k!@b|K6|D%!VO0&L1|@9pZ@v1QOARCTT-o&7qv20LMnZ`bqoDkq|G73vW@V z`7*$6zqu_Fl_nKwl*vQh-4U>CddnW=*sQ+(bJtARVxEg`#Ayp^_j$Xs%=uHR;dkv+ zUFuT*R{Gcu{$hsX#Sa{(b{mF?rj2|rpmuCPya=_X51HF^Gf2H>F^B7@ww6A0W^An( zIS`fYhQE`PLxIRxM)`?$^XC8~LYoY}4g|T`n#f?od${avtp;5!w5Tr=31sgebs=mm z!q|1^-%%hW;Vi3>y@R~^-wXc@30Ly-IM_L=#@IlZ(e$V_LY&@-1lP{&^QBs@W~NMw z1RW}*jK#Z+$BCx8{7Iib76z@L!~5iI+d(v0fT$4Y097*PhGQY<9T$A8bqy0y%b%}S z7Xd!;0IV=Wx|JOL$fNTnQJbtd>^I*KnBbAFaV$(Az0ypg |OM5 zGllyxEEqa+osI}xIaT%*(*Ep3U3v+ jLJCkZ0qhBdJ&>=No!;X?rE6#Bg_j z2la%w?$Ik0lo>8zDVqLA3T6cU=lEv-Jpo!~H#XuBsJWhQ`-}`6EV^>hjP*;YQnxVM zSbwu0y(D7z+=ePR>L}YP#pwa)pb{j4(`Tv^jK`Q_52x+idT5rH@84!?JCKC(6*2m& zWU N#t^f`C{c)+g1zL+rTPv3Ou&w^={xwlFwwD*fhfdbG8VX0eH zNSG)p+=7wvz%lKccL>A;Z;fHf8#a}G5T~`&^?W)v7?u{L_gx-S&K08Re16X!Hxi3B zbBMP8{zR$n`%LH*8 f)Dk9MIxt|{#Av%Dzt-@+u+{j8fAO<&txLiMF7S>svDu# z83L?5nnNrDN6#>kTKo*JE+lmTMU2qRbjJ;BUV `m^W4Ut_m>H4 z(AaTyELD4sGdhJVZB4?NVM%XV1CYq|>6^DLWIte~aAwN?^=yB9KPQv`q(}GrT{efl zdREHL5^8%lWKLsT59OzTmWu8-a2Y{EyDhzj8{ZM?*7G`t>g~up&;yNT)*O!HjZl$e zH-Inz)*x(HlsC1I6L-K9)$0pwCUTg}@Y}y{by2%DIj9|9G l9s|7RNz^39*Q)Un_EjZ%xLxaqIBJGc?z|q+L5PyC-Hki#%f;2O^W3C zXl99uAavW89-@(yD8bIxyZ2=cga}n%+!0RyQws!z$GRS(dKstX4XO;Kx%JrEWKIGM zi*n9{VWm3AULkDteGFdAYr%0RGi6ns$xRO+;w9o-37JA 9q}UGy%pL`~9ByAC?LtOHb)A5C;U%1$1JOd(PkflD`B?-TxD3$mrJ+ z)yHc0)?yMYYA8gH#QhpdlUc#V;p3*gPuY5z^l4 TLC==W2cOu6v*k-;dB0T!b*N z1{~{h>KdaS;Oz1ecoc5rAj9P|VU?MvS`a%~!|K#gAq>)flg3f1>CEmRgvW=ZH9-Im zRiiIYJtDxiU2-ueulr(u{b)2CK_4L%v77fvGh@x2%Rw&`v#Rgs9ejykRP$v10Tb4P z!Kd)rEOV+vfSHlue lH2LMp#7cyZa-<04u{y4OY7!f+}!f^KBLCaLs3)GPDkbKHYkW37OF}2xG7ZX zcJKFQUqnbhTM4AdAa^#DHg&N|!e8ngNm#m(2&Nu?aI#UnR)yP&-k}}2T$irbi2Nz2 zZQ1HbGT-a!%oNt9LFc=~S@AOx_WiqP#PM|Vncj2e_bmbS@EIetNu%~h&{)R(5<2L9 znBBGGJ8Y^%ts@4^LSL9BP>L+J78CIarQ7RZSwD*$cZMj%M9E@Fy>!TAGtKMvvu}82 z5p>vnz`DI|`me90reN`FGb=HW*rW30WcN!|fq)U;Iw-(Wz@^Q%b~=McK&!Luqi}6( zyYvKwyc$#4eK_Cc7RyLVJJMn)4lk2u+3*H4L%biIZPgn=8fxgK1qv~SNc|_0+6T}i zM4v~}z59p%HQKpoPPimz-(pvwE4%GqPt9}7t556f`%#w;QMiq+D`&S|*nlNIq6soq z+$;j)jlyl|c;4@(20mfo+vrmyc*#gkxq#9dqbG8eXuTRDz#2kU4=R>}32AAnO*eww z;Y9BJO)MzHkyBO3X0jXy>U+G=?kvJQR9bAdpW$F*_lYC-HMyd19l*U`DTh_iWA$6= z?(oBr)t)(DTHXV2D%JeqcuhSwJ*r{Vja&S7{DB8RkLvZeB*5py_HI4L#O=Doh=(LW zba)D0_``ci5E<3g{MBZ>pQCA>@qvwbJLI?-c7HY~c?U{}{Gx+#8sBmz!?$|V7;Kc8 zW({pkctEY93peAb@E;f&m_e^z6Stay!*LCDZUg~Q8t=b!arga6D`BaWaj?#M?gB7k z9mwTlgSOMbmw|gZ4(dFL+Zrsn*AL#87Q7)&n+d+Co0KVy&l9}H^?vma_Xiz&XsZ2) zH+tqzpDLtf2C4juho#O`Cd7Bb;V{+()W2zc0iQ{@(`jRrkIN}@ j?Ofk;S%0tJvN=mBMpx%j&!9*4z1yk^W%iFerfYzKP)s!t7ccykA^_t=;W;_qId* zA!1Bb%ukc!%W-r9%F`&ji;MDB3uWEkDgl69T>o}Ne~v!j;Pa{|dBfi!3Qy1P`yiUi zw1mHz@@hZQY FeD!h7 zS#jlXv%3wdwD*GY0Oz^(qz4&{MGZcjSkBvZztBIN@xEV!#hLr{^&Zd-lu>nrN=ioE zT^M-~1Q-0Yyh_zx3;Io{(Ix6=Pb1FOQx)Ar*d8)6-ur<=OGxk=$`m7j_Xc2@ENqrp zaLY9Y5xHY&fWCSb ;xUC*O%3u+{OeZi8Dd z-maU>@U}CLQ#&==S5GvUUi3mldt)CX`qAWqZ$0O6&l|KYA`qC@2c6w%f(7xXxm~Mx z2ppim=$CS+!fRoCLo4R60NyZF+YJN!TG sF0cRa(}=GhWk~L99|7nAki-F^xy|#Q45uu)!hm^+{FJam;(D? zmaoufz0~q3ews5$!o~5r;F~}q9SR@iMeZ6JBCOQyg3W34eUxhS+CX~XS@N_ BA1&-eBxJ$Vp~AyrAv=h68aX``K+v91g2 zgjFQw`agy8UL+N8Wj@;${2SfR5YS2tM?n(Za@jW1qOsnymy|TfO^4EAK1aqwpd1PT z(>Qu`Yg2!eFBqCs{wXO|wbyQ$zz7We2%y87OW*c}J`V|CcvRd&fQWifQ;j0l>4w@% zU~>p3DK6-&Ins7EFafjFZZ9)mt@h4&pU#zaT>n4v6T@Y}Vzir-mGx){eQ0^-<)MUC z5rfZ%3VtpW{Ar29T4`VR42V=O-zkJxjE&h^wBKyg0W4 nPBRyKprVu5nA IF}hh7 _Zn`fQLvqfK*L|sl8Zw^lDE%ne9*V@30Zv0KM4n5ozfR$LX^100xiZ zANI<4J;NW@gU4DYg{qZshCEhC_!I1&xWHxT@f&TIpnEWU8OflNt(J42o?aQloMm=D znru#%b_R#N6gg&ABc7eEK|$ysnY=`UOF>Ru04XlGbQG#LS?Q#!~*Ph&S=I}Bt9F?=-cnHU{@m|;Lvgh7_ zPY<$$s0*BZ$*%e<_;28C(J7S)l?HW@42(DW@>7TSFCjYASCRk?sO&(Bv}z&5A?o2K z19+KW7I~_KGDQMQR!TftA;@l|T#(Hv9haCEq;9X7N1Y#E$~3Y~ KlUqI`rs&7Vv)rIc#Ji)BC6v*J4sPV)?_i!IIv`RpE*D6Oj5og(i0U+)c6-uN})s z=kd(1uBYFFbCt7EH*U^Sj_h|pE+!&XeQ|-}dqD$qbxoTwLMGj~i-x%1b*Y}nnED1Q zlUz2K51jelB_HV}`?FzFaCh!Um0seEHRT NWHJ zh6h7|$jPF3(ZM9)E`jd>OEknNw3uBPtC?-WIiR$_A}I8cx%O%D9(T3dF62q|#D4>% zE{)gVP8dxdIa!pad#HnRBb(poeZX&eU2sO}OXD7^3G|?+#zm}aKj O2#@u*K+{`=ll9K3TcVfg)XC?H#_?_c9BGAh6kCvhT?&W-O0jBwYyi02+ zF}B2~$7@jR6HI~tctAtCl{^ryPkJI1JmU%|I%p?E& M9_*%Z+{PaH&m7>TP-!_X2TC|V%&-WCRptOYF{YQ68>@0@d$!+eJj;-VvS8z) zDJ>z=43Z(h*7)7%WCnQj_V$+;0misf&e^~%Ba|6EKX$V6*GPAy?3&3?*uPQPyr;EK zf!jg4Pdckd(r@stwnYnfw)mN2#;c7vHv7MIqQI%){pQ^98+r3UE{b7KV~+8I^TlI2 z!4p*rp7H9W3J|vVJX8HSz@UXfG5OlG4}rF)d2nfbA*HlRjVFfxNt$XbM4%LTC%LKt zmzN<@X!`HH8+niz@rOkIkqGXtOJb;Zd*)1XNiT>ecX&}+S+kn)aBUPGv5_*1jB+cg zisdf+vPKi??F`D~+Dq~Aqeoy34?#(h^!pp`lY1MR^w(SF`rC8ho(4XvzNq@@OS(HM z3qBv%hWqe}QtiFnT8FrskgPqbM@{G1fX+&_`+ zjl=DFTiESw?Pw85SVqeKNdM=?G<}IP6@ex6VaNv;@Vl?K_bmSY%hWTQza))G;=zCq z3ck?%9XSx0&?Y9biuY6A;0y0V&1_$AL-6_Cw>SHsgQWe&)i|^gWbbt&>;pya5VLx8 zP}!wad%hWA0|ff_ L(pbkO-=3d`n;9n^C zLRO>Y#mpU=N2UKCuFH!151tmH=amf+)m1MDy-NP#s!oHIi@qDKiGkKW%D@@Vuc-FJ zFDtJuKjq%Ux~M!>X5|LkAa}fH8gZ(9lt2yZ)w!U$)rt%zA#)yY9)PfbI(jk>l;%g8 zc~}hRpT0lly Y2py;OB|pFSeOW%MW=< zzkP$O=!Kjd9mPqKZYHO^A$GPNctA8`BD4V>kR-YUy_;L0qn_g*o%tw7SY9fPFdEyE z=XHrjZL8#UWW?j{y>O_jk1SK!tCHh2u%x07F(1T6Icl42^86NnJ!5_>g{%bHXBs1; zkb2&13Kfa=4`&~G3_XmUf8BXVeRS6wW}E2Z#gs!92ARPnib&_N*(p`>C7k0kDbDWl zR9CN!vLq? LWNBjDvOW%5L6$MFJjZ-A#7|5c_38ktTdgi;i_-!1 zzeVRB&+m_>GcvBO<456sqicCTuSQn?H!QRgmMB6*L)~m;Xy5bN^_-4i;f`S;@4cgx z(*?aXDSqax>yIT1+G?cADL_q;(AdvZA?#;LGZFN_G3%U=&ql1Z%~N;#{jMO7;nR9x z4ez7#f|EpSqa@rnnZx&FTWrB$&xvo=vdVQuvyg*dc!d{-SoZF8^yhlmU#wE^!*27K zRMw5CWcMdkd1q>X!y9V%!Ar`1?P4+V2$wg=%l2{f+5D02udHX0L-k3ZegWl>lqwVv zzk@FI#a6}|QF-c<;@<3}e4#{!B3HyaGs6b@u<`NPW2@VNd-cqO;h4r{QNjPDKlBgk zz$P^L(i-zbx7ZU=gPfgFQ~hqI2*t;riuk*ad$EGo6pfa_OyoxYbx5+c_xjL)ILe_c zoNn&=O3qJiYz6#*bZs-d-Dew@p|YC$`QKg%(R~`v#vf5#h~C02$F{u#g`vSIi_^T5 zK$+@5KDr40ZocimPpb|_w>Mgqi!CeN`E6(7R&Era*J31sMg1ivb!Tr=;bU)JLR`B_ ze$c{zM# #Q2dDF{awO1BMBQY_i|+QowV(46~qt6t}`VWVTb(J z(yItgNfF}v_9A`d=7s2E- m69OT&HsYx#&)DG-43cee;*kd-M-*=edJ@_Bmx3W zam_4TP>tX}C>ritq5DBuvGV20g=s|^s&WcM^!a7ZDCZw6?*?xZBgAU1rmEt}ZN0et zxUco=WK1()q$Q8JWIy(Sdz1z95?wQDBz%{}C{GbN<-j;l0?N)jq0>ex)oDT?bKE)h ztFTaB*(4#0YsqnoL#ZI`YreXR zi%(p6-CI?pBzd^C`UISI%1~O~cca}Un$9G!v>!RYNt?Sbb!Qc=P7r^|&nV9s`Fpx# zL9G;343~Pq{P~nag8hwZonXo~+=_hHS_?hr?RmMFXr>$YFei*06og$L?+x7I1NW7B zA|6Ei#Ss$q&D(J*jY%EzVNV-v@i$(;wTeL5qhW7aF8NA(de?8)`LUINIfGvnC`!3a zQA}40APxI;<=%J&zS0X#=Os{_(ZcNry2$LcOwH{lDYHRufj&On-K$b+7EIY8#}WES zf7P#dyYHbIf5I>{wS--)-+>Tlb?2o|3Hv`ZiBM=1u1^)>RSeqj5Q0|irs8*1m1%SO z$MdE^_k@SVcvd?FMSn;2eZB>+L(<4I8+F(549y<|k=4(-fFD_*n*VkZ6 FIHJ zQP#ROSX~gv*P=d1wr!1(fzd}c9k=PF^%qCkQ@YU%DDf;I%e~wSnI8s{KogUKUas>W zJ-F;fitRBwi0W9XBVQXujjFVhz>g1C>!n>#@x8L?j^9Ki_p8F$0ey86_Q~_{snEk0 zuUAsp<&bOVV25a*Kak|*%DFC>s9i;)eh34>3NCTX|E2qGo36Keyu0>zmo25CIJpKO zgelyLg)00;GB7i*%-r9(Rp&Ykp@%{(7?)Cm^3~Kwo<%lIx5*M^? _uBM u98Xye;B;L;EhSPq38)5Y4ZW@XssVx m5Ta7s1;#~}U?^QntY4pG)lb-HtxbTIxI){~~3fY%)b^85SGIusm zRUOWCWpf^?p&YHgy;I(SXztHR?J&xQ-?5)H8OVD|?919?^`Bil>sw?7YqZ84#+51K z=f?bWiWrUhG^xrHYECNWnkk&aBKLci<)Sv|&*N{K$$x!fpdlgM #i6|l$IERX&kxJ)nA__+&+~Q5v=mq=ZQ@73A4>C;6F0r zLB3Jr8Hp%$I0!qIj#L$N_$qW;b{q8XzaBOMU4pg`4Z-*Ts2*R|(FJ8Bwhx9Svpx@} zOdr1^isLawb_w>U(LV1jsAzbx-%O2`Nd!{m+35-&-oI|{Rc4KEWc%W&;OSpFj3@L5 zyLL6F<4m?z*Q4j`$lQ$N^u`aYyPPTmd#&p+gWM+Q(%ws0z<(9JU?tw)e|}T> 9byR;^2`#G`ZmvMcCobl}nuGC9 z4 |~eNl*bD#SIpK2*0bv4T_wa zQecegGLT ivZr=*L zYoHIE3?$=q@ng+L5%sgMT1O+`1c{MVQGegu31*Q`Tzxj@OBgv(3e}Ievo&r2A~4ES z|8@=AZY~p*=yG}m0?KwiSBj}3(~0AuKDNs4x8cewxo<>4k@?r4+jXJ`Lh1bnc>%?G zG#2EeuQbn`Qc#R@em(TQau>8U*=2idtWCeUxQpPwYkKNvGY(XKO?NHWk_WY0j;%gE zy9U 7e6B&yh3`CA%NZ^H$8n0AoaPKhj zF@6t*x{TkpE=#xJ^qNN>>a5sLHMsV} yeXSn- zmy TfH}Awr6~V ze1tObRw`TG(2525NREu}a+ryr&LUfMb(-XKSs8@*$(7bYyDf8MP{qLQqU$Sg1V7F? zUH+Ua`W(8PHnWAyuFS`oI||1BEt<-FPm5H u{T5zsDhK@JWK}%1dDWUSY3O zts}W7cy-!zKtW4dQYt;3nDSqoGvg?|vszfseUtQl4 XpEd%$%SOjn%z&FCF=s(_L*opsC#{*?Gjf92~{Tns~#<} ztng*AB00otZP0J9&^>3*qizSLdHJKS z2)8~ZHqC<#xF2wr)=@V!YO-vg@(NPpZN*pzU)ne4)o?^sVTKSn2XJ-+^a+mc|!NmGtc zojZxRDnQMGec5qEJ2?IQg5N_+s`gE}I4t?2h}4#~98L>wPz=kV=tL_gAY>B%W?VAE z>Mm?f`H4^^#=D-WmfeQGbR93pt8+NzJ+)QTV?O_U;0?Zh2wkeAa?34TEi*e&k-OUG zd7F4Y{c`7#h=bn~NY!xO&Vv;pbZ$Q^9&P!CezG0Y@!YX&LO+YlbzZYb4-#9=wK+h& zqHicH2#Y3heTf2G4|!I&8w$O1?cM^bwgDMAHA$hOz-@?Foe}khuiM7l6aQv~gX!_} z5;Q5lIVe6GaJ3{fpAfihndhcSLzKh*6qDx9?pQnYdTO_pdv!%<6BSATdx{`u?4?Kk zQ0cU~jUTw&ETp>)s<(V!!`aFFmHWHEGZ -20c>n{y0o#>kD&%H? nlNiU>c6z+M5ea FI3Un-cSN?DSOV5&j?=Ko)GwFH9@4&t9gbi zYSepA@JgLm=!$o{O4*rMP}f7({@XffE50;Q?f+8#b=L&u;$OW}(p#^7uNRA#HrFq@ z>TUV2XQGD&brq_d6Bl$dH*o`-M5tiTqW`_|Zm+uD&c?Sl`K=e3q$ji231nKt7dZ9l z&rZ-zy9AW(Zy|S5h;c-Vf7ZD^u5T_ nDJ+s}SX7!aNM&1I_G+SJ)_cBJe`H!HRG7u$?#(joW`aH??0eqY2 zZf0)V9=M-vHNZR%1KH6K4`>^bbceXU-^6KB%QU!5%4~2;Yyg>GD@hLUwspp-MX4#a znzpiofN7s@e@eUN|EPHyvCZ`=!@>3SBlgByrjT K8I7S+p03X@2u+g`avL)9;FLrM@K6vhUNG#qpMPku*z!V}2NU`oIkgKZc)`Rg} zZ^ PXnUK@aL@VI z@zSSgm0vcqBgP~0_*UEF$0r>HJlCOy0gc>08s1j>SAWelNNCTQ>BEpR$J F0 z3%Lxy20f4;{o4!sGwxx~eznK&JGj9VD)OdJh5-4c4_fdY+daM$cn!tdvjW68ZHu!M zp8v_35*A>g-TE jx^C67t cx75?y^rDUX #fEJ60UfCmoN2nkItS~(9z;+5J}H_4B!XXnZ@7HsN%OanrHVC2YM zAV`Iawn{cXLNVc59;J{2MVuz+r{+pEF08 }WAsi4IbeNfgxRWnX zPrQZME8P0;%0DU8SIyG(%t|RnPP@p%toovkSLg4T6Pbe9uek0eMjU=YqJd2wG=k?f zYa*wG#j-v2-(snMi;7!u) rkqZJs;Fp~i+Ol mx`@YjZ)0KR>Kg}VPSC?_Bk4Quk+fofI zEdGLz1~GoE#4G5Qj6WKO0wMWHlHtyOvQYYVIa-KcM|0F1k0Qwb{l3fo!C;WjrJUri zb5z3l;+Y=}FzeBmeV{yreKFu1FBD(2@Yo*al1ejHi`6{DT_B7y6a-2fu?s1D%f9YQ z*@ahDeEI@y5U^8YRtE)sySiUtlMl$lazq54Se6bEe~~XMgg Y;3 zfDr%`hG+sW3A>zVuI_U}B_C%V+dmrN;zAQnQZS!Y1WO@`%kUz9p~+L=b}VsK`6{|e z{2A=xiE% ~BkJv8o_uU6%$ y0&J=d=ML5i79kF((mT_ETKUU*gg8Rf% -dpV^MM}ylqeI#1MWvl!HSr0WLmm7#7!M1- zR1ND|w$(#A`2zPbLq4tr8H!A!k}oV`6xj~(@euuY9XH2{R-IEcHs)gT+JEe?P0Z$| zozN2)T0pHjV3J)n`))s7CK@^_E4KIebQ%k4Lw)+zP$a>PvL8Vl7Wyvell)^2?&&=% z4(B(7qKY9-{g4nD{OLB fUZZ&Ukqrp^wr Xb``O5F*JJbslBj8?7~Def{_g&s5$LBS z7>EY8K1+;y&UqKixu_Dgo3M0iYEF}|1#)PcVCtok@2$?IaKqdWze)QT{fJA~4yt#w z&(>-V316W2X+MW<{EA@1E9#eV4E-y2QiFUsUgDu~po^@}$x9zdjEwclO$#E#d y|hMy0#&DO@8tujs~?w@M1gE{^-h`Wha(-bdwc8St<|3B z`@H<~&e^iNGpT1;Z%!oUAOBjuxldplEHe@L_C-J}cOQk&&KglEA$6XBXCm!Crf}A& zO(~P9BR4>sG=j~X(=0#>4!b?$qm7`}*qIb7^yh{1h2DFt3|YX)P><^wubg$wGVlG^ zDrl!bO~Gw!&2@l^^>ywC Y#2lV^WWUMkXX%@F$DUWe>egUDA$-zrPJETwPKK* z=^%ll=1=$#po%StWRl2k?xTubC?P{Dh2E@az`l-3?wFcibt_s3aRSxwb4Cwu}59 zHX?X@?@HWV2N1;rU)XBq e6O>TkJffT6N{c2gQE8b zlc1X2lvy3f2 ~`@n0xguf@MP3B|^S(gCi ) zDc=i2_ndo|j@7feMT}cQ`>f^;p9ngA#&}+(*=__^M}@Jf^ZRDR1qe7@Y~K8#G2?2h z ! znN;gOqu$jo_2Sg{;JXREPYv@YC5<_ (f`UIk{@rd%k`Fh+Q1{JlLsrcbC%Jn*z8n!; zd3r0k<14gM(f3{3wv+?Y&FU`0k#BwJW=3%{i7C_gsrNgbqL#|iO&lxZryeN!pf-xb z>D7zc2PoRQkdb!~Hq&hPQS>wn{f2aY1q_QFmlm3^2zqSX6X#KZqffX`sJtZzEtxY@ zK(#rHh3Nlp39fAEE<0%yhOObefuZ(Akd2u0?;+ZNs2d-Pm9qEPM}y}`t6YV5qp@#f zmx1rrbL^R1_u^*HDofLsqIhuGy%t1SL|IVS;8fa;*z|sdGmXTVJljeby`;{oeF=)M zt~3=C Q~jbsrB%}{Dd0V5CbtWe8b@lZFJ?|XUC7eO7*hEHfl^DU z1qwVttYgnDcK02PRJiu?w(wOhReW5vxk+(y=e!%Rf&MD^cmC$g4?WYuqb1#jo=K4s zfNrMN>G?G;o&8y>aNgm{U7I!+UMo# ?3FTK@z0ao9`uyk5+}m#j!~r?NLei?3$TqaODg2!^DHzuo%%x|M$#(BXmi*rM@jU z?egbNSjHm4&c;O+`)qPSX3ZrA?q^SMp#!9SGA~T)P;&$?&?G1;<^4b*Mxo>V3x+$_ zwf h1hOj-T?+S)!w+{(7uIMN8 z_)RPJ2}kczi)?w*bQoEa-C%>IN_?%pyX^PsICEL+3%^GUAO+2u-Ra%F_8-RjMGKup zx4w?im*HG6()>4i|D^!zUVxFFbM%)q4b5psa|KMfk)aMKg+}_kdp%2XQXdV|hVl~q zFQy4abz)AQiIv cm@rmtytpT>|-^(&E?M+Jq+dZhTSv)D&Se-PST^bLEfoQ(|+bjk7$x$3|^R zcoe4DFr0GcE0OmYM(-bd5P_-49mKtFWa1$TFs}FCKmq&4Di 8#Sf?bVw1EHE}wiIw0O2wUJ}i2y8L zmpRgx{lV4Z3yTovt?JhULGFqfy-$$q8dy}e*l`acF`A=jCZQ`sT}RD)Cs%wZ#jXg( zMshZ) V3Fl?md-kto;vj98eLME)mMe z=MKX{a_ZW#`eEgNYf|<5p%1G*bQ>y1y)ue~c@3b2O){nZ^7o4z+1m%}0}qsA9^4HV zV2zGk=w=JLlQ(`dFf~tilIj~J{p>1_gkd*4LI-qqC1mR8!@P(EidBJPy0FPJ-M!x{ zsoktU&Q7^t8^&|3Os++_<0m^g)Wl~gTq)nf>1%!OOO~U!UhP3xMT0M+zDSl5%6$Cu z%Pi)1h~gW&8ProZ5UB7T x5uLT)mDbMq|3M#Dtr zy{l`a?-B+0Ijc5s T&xp}yFZ4g1$wplu{9@74Bwk1`)WGvfP{2=4j1v1uB(_2>`UueLw*m_kGX z;+dpU#)76EGER7@PoQ2X?^Laz@4+o%oVbvemHDcw)7?Bz`B5ew^hCe`5@fwkOThoj zTKg4;V?bQ}NYqzi|AX 6tj!Y@US}^2h!^#z Q4kofm2*!cw +_56d<`1~iuI91`CNUdX+Z)k950@R)x9GE`{5AdP(g?*dmJh2e;(X$Uta4F zZjd=QORX7&fIQMOQJE2LP?NGACFp9-qn!?bwcPcniLz(-NIFwyE2Hk;eNrb7551o& z_@c+IW|8~kHuQSmX^=k9Ardx|!TUJGR%#gXwN$jr_DshG52aVsRb8s^0s%bz1QBuN9Obn~{GC2%<%@Fn4T^ zW^%(XrGPXRlww}QyzFI8ti`&+@L58rP+x|B#)jEeGkToE=-B7r-D K!+b$+pAwIBv74i8`eP7{uv26(Tm%Rqv}6!<#`s!_j` L-I}g46%OF|Eq|%YX3u*54aiyTPT743%{`TKG47m~EgL!4GObU)lf8ngl5{161o~ z37L~`iU5{r#h)Gbi}Nx^2kMk7SA>DdG=&ENiGm^@N1yd?sdnC?u@{XtV;EE~u|dC) zYMfWb$1&J#r !^*lsI6Yx4&eJ^xT2k5D@~ZnuIKBW2L%el}>!p zs@~O%+xO+6!TaH4?}|hu0HLK4dssZZQsqZ!ME=pPA8j2+)Riq%AaIQj@+=_!siv6f z)bR`A8pIDf4G;~g2KU88(neDo50Ik$1P*6}HD`r2FPP3K)H=BWR)O-u-?`;AGIvkj z-1Dgt<50;4mU?yoadQKB(6^j@L&7?@t34|R&M+IbJLbt5W*ZfArMh6C3U uP=MZaNH@J;O#cg*!j#b7?Zj6g`0)jy1 zN?C-!C{{R>yt?}1(WYETj5#fu3N9o3(y*W*pdZu@H^G`li(pX70)P1atAg&LW 4XabL>dwb5` zD8tNYFP6?_W45hbteUtOnXaT+_{y3iNMBv>8*OCh+p$J?A3dNkKMgpd7H4_3Wv0c? z%vjJj7l7yhAqWQ|DkFQHKtlXp3V6wer26MKwOD->FGlv%K`Vzi%u@S@Z|a;p@dLp+ zYC&npWqZ}=YUzV&r`iA|Gj~AuwvZubdFYKN6IHw(5;iInqoH}(2@YflBo-_f{r1ae z%p-qXQ6Fb*#&xYtR#d*!_W+Cfv3;1XA>gO=egoXwB8sc_hk_)M_O+D9t*YKvif3`T zFf@MeEoqtMij$Lkpof%+N^?n?rD}}xDg5#PIpy_kFyL2mU0uIvG|eZSUlA8xFBgTJ zf+W;LR^Rhw1z(50mh22gL2~}`hdPPX+Pr+ssXG!M|9xqOXMDS`udc=4c(JXdIQ?2; z4v*{n`?U8ePw-$YSzq*VD*I80KoKp|q=n0W1-y^W$H5lu4lPF!)Qg&soL3~&ddzm` znu;7p`Cq{+rsy_-ZFg!?AX>}d^Z}twL ONIVW4sdfHdeq~^mFk0wj< I92D)%Xa^$aeT`1WUk>cEGcK l7P* FX>)ZI`n zS_|#}pmJZc1-;GcBFO3HkOd3vSSKb5-=Cn)$*?@-znOi{$J>2aoZ%j8K8O!}ifBTj z;pw1}JPRfby*(X2x?!_-X(8&PYc)~+&;M{#Z!pb9Yso$&vd{);ApSwvPVs`7AMQ)0 zRJY@(_i#S_=(JzlR&6&gL90vr&~ap<0e7X+rs zF1Av0S&lM&X;*R(kc;F5dovc#xgxT#YUkx9lH@MLEKE&$s!}Sy%)L&{wi!dnC4Mb& z=wM*eD=M0QM0F1WYqw}p;nyeku*!~#%*iSc5!B8 PN*Hi`6HLhQ>CPPvDLKMTkLV$5;&{86Y(d+L*|H6mG9UDf&TPRH4 z{m15%+^tw;J*!zeSiRHwg8KxW{U|{ZS-+GfcVF$s9eh~pSOy~L6ycJj)mIG);}cQg zT#Gx@GN4P@c2uatUjfNE 7nm^kK0;?yRYFxkH6d2UrTQ##A^^bbDGZg#jPMKo{!hQ(2^z7D-vG4 zgj>h ;wZlPP+mtbgnkzHKrvam|1f3+ zZGOo`G*aS;1ujvt^mV$IE3MRw$6o9$b@1AV(`J;wv+Gn7Rdeixarh-ClE#AuLV1GE z6F+FI6MMH2g9#hu5x#q?yq;?=`=l?|#L18u#AZ^3m6`U O$fV!KaIhCvXzBP=|kqKE8!+@A5N+fTs17=3aio9Ds*LJ zCM5Y2{?O!bol>6Qa8xLUl43GH3q+5GNb(=ALZ vEkx#;T zBI7Dw Cw;1W&C?$WkKqArjDpu@Dy6<{+c?1Y*xB>y02vQ z-la#ZYVU7gRQ=xH5sMEG4?ac~+EjZ$UR7jCEb zJR6V+=IE3`@TZ*WYwnK50YQ6iz3u18IfhMRDorm#!53WMu&GJCfBj+aSAGWl^i>dV zL@`T8uWFt>897xqB*7oL?7+cf1^3n6r6&F?Z2|7%8vOT8$Zu!3OA0VaXksz0uZzb` z>xv}6jcT9hqdY44=%ya7)Gg7 (O?%4pS+&1OwK(x#*UYh4@Jl3!O~%mBPv&5R->mWltMyx#+7VR0{+$$AKEnC0 zaX0$j%*QYUqCwxTh$xx}y*2o~n5nz^7NkA=(NIw3!cJ36agU|`22N1_I&n)2)r%h* zb7hPakU#QEdX^b|zAbOP@$hH=*mBRS!Bu+}_rGQ+dtd)d+fVe7|M2{~{U9V+-_kn7 z#q*^#r~h45 aa6X{_jX^Q^#$^7C-O2=iRj3P>bWhoClBZ`nGy) zDq!PUm$uiXnKgd_IltJ+qshU69)} iKf8B_7F{)Q2SlZ^$4#) zs9o(=l&n3j-L7^^Z|RBQJR)e>#3$st&<8|b%%oAafJv9r`m50qQ7Ozfk6*f`XIjTQ zf>r07{y0~0*qU{lq1ZLrUDmgUjFxU)4?+|tg|&=$Q*G=NFEdaJOAusvuL{m*>DR*o zmlGWqINxQnj)i+){cZe*!^?G2feGpP)%@v%(T7{tq=O+IQGLmH> zsA5Pco0vGmaMGKf3g)3g%%0Z^@}**$9j}32WrGZ4MFGN4yT4F-5YT?3@sNh5 Rdq+2T +5g*7Vtj4Y|Ofa?fJ zq=0EQ?*v?g{p`I2dCDwytR(zKBb>xB{Ipvhu0%X2`14UCtrNEdjFbY!ugvC`TYWq8 z1xvpQmVVF?ER|bdT?I(>s}DMkhvU4=Ug8L$7%c_K>!+biMe>(git?Jx-wNYRHoH`S zC74P0lvm*`x4RJ@U!KHw<3kz1dx?nKP9NkDyF5q!_%Chh6_IIKa||&yJ*dV@0L{a+ zRPBPrCDMkbk}KDTOI(baM0z;SyxhAS#O%^4OsK8GyUq$FF+RMj6?7wnw>_uTTmHG) zAy_ILOlf-1&d-L9?7~)gar3)5m)aja`2%>z@41wCz3MCX%idBOSCZ~BADhMvDl5)G zCeD+5Xeop|zpvg}s;Nx-dc1A|VVQ33VXRnGY!4$*O%!=^z=S3Qek;! hxT7_f|7BMx{_50jZDYU9vbO=P|KZ_1|MEP3b>!zWxv+35 zF=H1L6&aRWS8Y(XO7_XKp1Dq)>2zo64Eg5Z(5DQhb?Ago{l#p2qcY(x!>~i4NF)E$ zG1IGTn@01c`sA}g#klevPG|Jj9b$_3PcjwjaZ$ZRxB*d)~I$DXc)T;}mj;Eow#g zStiPqjZi5)bD2%5&%dMs(#Uo}x0|h%J0(FkTQb e7$!=36Ky807#?OQjiQ|2f7*5URF=}$aGmia$aV$X_kuyA9wt_mL zxvAdd@;d#O`VRrNm?5=HUU=Io7}_=3Z=kF=km~RD+us;PHfU)exqPMSET{#RoBKg* zm-7hKJp?omG=ok4IR1?!amFlh?V??`*(|SIvue_2J4hjiy65NH)RfV k~FI1E(lIOS;I}os?Rg RiM2VX&wJu~675?3G zdn5MC1tJHgO`_V*W7nK=D#Q{?{%1jlG{SmV3Es`)^Ix|lUmDv8=G_ye7Ff>)D8YL? z;oy@#J$w&e?c$M7dC|2@gOtG=AMir;#AQA@(Pi?HP^Z;#92Q=CBFlK@gOU6PO|n(Q ze93J_;svsVohN>`KP`p#sMXCA=PsV@vWZC{9X7TTKNxwhMUDM_CGcbDKt@69#oNT( zRu9E@T-9-i%MW3nLybAgPPk53qE>?N$!rc)9Mf5Bl~?qyz@~N=qbCUq*;-ihbJ{oK z-}+wF_Zh3&s*%&Knq`q>fiQ03-3R|w+FZM!-)?gyadF=)d*?OAr2p0>INoo6GCT@A z0+oH8M6d=sF2tt5N73#Bahk+$t 5g{Z&y*Y3{SqIdO@edC}yKkNMX-k|LLeFMlceKv^K)7Q3!RftUXn zh9E5mT^8vui5@p+^+_=&tZ)pCyJe-s*LYhFO142onPTER?}BmH?4Or9IV=^dkYA4< z<;&Q;h@GBB{R+TD)+HUYfpXLZMVvfI3_Y6|<+LNoaR )BL@iuo2Aoxk#A+60D`A7gIz#L5`%E^s&{T!ZDC<;xnci@MYe z2_iref;E1D-0f$22|~MOiY+Y+*HNe21QA|3kphYlt#2KpTZK<0`EZyy-ZEXMU}5N( zNlfFwk6s*BYtrWIH&XMq*B2iFEVsT=_(&y&;HiPYf`G?41#kwYP*jtRP%$^DQ{9(7 z{tF*1n>zI~{`Qv2sHVvWWWo5O(3!FlZ|hvgtw)XQ%qZ1&yf=f*ZdOJNA{|~-A@V3O z&_5iZv2>2|QN)K{PmqfCT(6It%fNdahOjN621LW^<5A%%`M*B>YUJQHIxv`S)}_%H zuv@(VsuAR&m^|umw}YL-DeSon#iVN7vBl(?@+=W%qcZenFu| b?5CjL1lS=ypzQMdiE1z1&?s~a+n(xbrDl!Vo}FA|XMEOdS2M=#h@;kE+y;iz z;R7JP9@w?RYt7+_s<@NqFp9!GQIyzYmT3LjK4$e_niZx74@z0g`-qpnnA)Wc`OQz8 zF@<}5!BwtoZv*$>;-ekfKcMOuE_2|97h}Wd^=8c*^^p|bsH TozC_w@E8QgsFn}V?ew7|*pP$V{R3gLzB?Rx zwR3b;%^5|v_uRo6>bm-5NB3Xq)ZkM{D(ClxLbyqYRB>}I@;WjPMjtB1&o yU4>rI6$-mh^njJ^bRtg{uao(9)28W2V268B%wM#NJdz`Ux6pU2J)B!*AO|Hu@ z`~I$herh20kEldz1*tP_L>^3O%dJh(Midkpu#n!Nwu1H7LLc1P6jko6nA8sq?5~^a zs2kuQzRMhF1QQQ3QG1QrX&9rb-4W_v<8HzcFTlVc=lPo1qkOSp#wN2vSfMT2R}t)@ zsFC4>6;K i=b zx4y-RqLawV!yDF;!H@$!&Kg{Kt`xFF)*pU=1=8VF(0f#IOr$TkREPlowU*PFbrd&? z{|V3`GHzMZ1o#_`+^64il`6bP3!C~(ol;NAjWO=$hm$uN6-QUU7eOt-5Dq46wJ1OQ zlKZ)%XBaKRi9PLm|Kn5WG0N#DyO#k0<4GZMo6G(rh%-o6Hz Vb`;00B$ @;J^2Tu^NX00L<}s@y`C0M9?2=6_~GqV|Gn!TEKOz5 z#LCi46tbp{XsN#me}RBbJByXWrQo%iw*$pJpU Njn-XOga-0J5|uw2~%H%1K)!Gr?q zJ{JfSK>>!3j^KiPM>0Ka8p;
D(YvsY}`}*FU>M_=J|v+yG+GCz5u5U zM;1(0%BOPty-`yEMNGlycv)p{JWl^{61n1B_LpkX;^k2M&=KXpXN=+;^IXVj2EYsM zhP%0Ne=jE5B-^kKNmjmmCmHl=S9))n#kM yJE#W%?H?~8(m2()Uv9A6+z3El%H2Qq9FZjvHE8^;2z(J=EAW&btg^Wn(S4jP zHBknsmEQWA72PEZu{`0F0qrVm-m!`(dLSCgAR&^=e@saCKPb8e)1?Ejn-KNU_<{&n z2VbUg(e$5|^V}P6u*%+#bRwA0@rVUV7r`4t{7cycM}v|LJ5NHg086>8ZOK*|DV|zl zyKf- `3H!ZAL6rHOMZV71l2~B-GqN-U=Rwz5338!|P0{#3Q z;IUr<<%sAfH!L09Um#sJLrzgtFZGhFE<5Yu!l@;~;ErQyVA1H9Em{X(Vs7zPgABW* zlJe&>3c6uYrMiKN-(wriY0vDkpVJth6ey%#5+BhaR>AYJ(r5NB^yZoT|A|~NPcK1# zg1ZQmCBV;T0mXze%l8Az*-ktgWtPa_wv%~25zX%t*`pX}$zLh|MY*8)& C;A=fE;LbuvC(G)y%83nYz;{>RDl?xgptY8&@d( Uk> E9jSXv{02gb66?hrv zr%9=B6<%l3hj`9vsHwccApvl5oGL|&ZeWC$O4cM(Nozj^l^+{)^j|GkV _4=FEa*x&bJzFte@uQ!xe#G-7Y-K4s$0=^`PaQ!b` z0g{ZiF%HRvY|Id&M`m!@7E>qD<4dK##)6_Lo{`-};$z%G7L&c7v~phMHCQX3=i`Jk zPI(Pu@#v&mvk+WzD_ 8?a9EBzxTr4b F6oDLH`ue2wEThE%yoae$N$+ V6A;58ZlFADIp0;cHZ{6@nI`QR7KPwx8e z3yLJ x&k2 z^36a^8#YqznJvpuCvkk_K9-TUC?M2Wjqc&Nh7%$==M16*eXvkQ7)PRTp E87V(C?t0i=X9LlCUZk>e$jaU<9@!Ud{jIz1QM$F!&Nn9X9Hw zW<|7bUtjtBk|CBXFb$#qF^4ZKbT0L+IOb8+-iL>ppZ#h2%T_<*)x=hjSjhe`Xj$fj zj4#HWWU5+{2TIZ9iyVcy{E#d`{%p)=Ucsc-JDf+3z$41W>r!XDH|&n_W;%U_ZmRBb zc$c#+%Y2+~haFjek2w6BZxU#V{;J676$Tf|U>A~8GlG#*+P0z7I~yTyM~~0}fJa_c z@wvVd*7NrV#+bEdbjrEk`ZkTqt#f!`%~)>eI M(+|uv@q=lRs$_RNGWIC1 zp3c!G%dw{KQ~CDTY)doW>`cesvzSTGc( G% zXS7PF!HC_B{nhmKopV7!%Y9s~jk_;m5)?IIARi&(0ib*f^OOZh1krDSp!kQw2+aQh zoW%4a+8o#pOAJ%zxa+i@f^k=GKN&HMaL&QV_@l^FFWY${#bIbmLK&@{>$@r;?Jk^4 zIFN)NeM$Y<>wQiuH!ROI-)B0UzsTN`?;rqkRi9)MZWp?|+G+~dA1^9{e`9UE1M9DA zu&QPTD8oh6M-xxy_hIy11z?zild``>v;Qls%xsn$de=fUIb(cUD(r(e66Dwi;Q4kg zXVkW|RBZg8?JIwiwq*>pKNa+^=l@C~?wcWh5Y~|DOI@Q4ClXpPWhej5*}s)1OxjF* zEW%mgU0A=A*}N1TzwVFPE;TC}eXtTCL_d|`&-%+r?!^NXpt@J JFmbs8#QeI{1YJOAzg zbi4(4pg~rFF J!VM>g5wJVD&{GQ{mu0At`sqo<<<1^<%MJM5G#{ssbauGC+M?7YG z$oN$s^N9x{7Zdu9aZ^+G=Ga2O$cw79CX9Rv%>MzEf_p`cvI3 lkx3-T(SBf%5Ht9wT<)RKkRmfL6Fp`PB%SvBTSM-M!!tMnJ* zs(HI&r~8{sawTfS)7f;}fJCv$>0J5TwOnD3Kk}Xkxdq(ifZBVegBa+yG(5_(G+MhY zPu2An=bj8dI~!B$=iy+hq26rgs4s4**-p7Xoa0H-z)+e&)Y{yBFFvS?dzH;m6f&Un z@e3`-h6LNp3na|NzyM-Rem6!KO(BVA*%lE^@c?R23_%+zb8!$Yr}t}l_<~R=zDaM+ z{ymo!)G}Pf_5B6t#z_HdT6uUim~^E%21Zp@V~TOW)a0>B6JMEz^$F)a6H@z+%s3-E zk`e4$>YTM!)POBp5lN@|Uej2%N|dr@jNglW%|^7y3`mzcNHG2O16S|1RwUrPzZT5e z+idUs=T=pS-s(<4bh^)*a24~VuIHt~V*r9u(G}xb6y8QjWgKW%@N3c!NBXvtp36I< z?f>ff>ZqpQ@a=7Mr!+{1)aX`_5JpLi6dckqq*HW&fiRGkE@9Fjsf1D@DBX+{kVZg4 zMAY};`+LuO&ilvjFF1_n?6YUj_POutzGC# *h0{xO&473-Y+Z0+A2zAHbwa=mTm--o& z=Yw#Jtiqv;?Ja>q3H4V9v)h(3&55mCgD44f5`t}sK1B4(=89xqChSN-7Qw94X8wL3 zI7B+* E=pr72qH-c@F}m0AX9A$P0ZLe-sH!|nV3$|m`YNx^*^6( z*ABRuW+ba>47IaD-$@DNvmdoB3;ayvhDLXXkaKDts4n0hw;)=hF?7#F3{&{qXqJyq ze>UFJPan@G8i;HtWaY`b5JJ=lKw{U!XkC3mX<3;pLL9$whJQH7IWCO092J#%)q|## z*w6spVzP#!q ;;r*#Abxki^&jIMX^;$W9QpHf%?&PHF)h~+%ki+13C!KDcQnQZK za>ydtvU!PDPJZ!m&UE;RFRo|dEiQ=_eZ4trfPymgC=OU*vNddg7%ZR*m)mSLB; {#saztIi#Er5w%7XLa>kzrt(u#Pu6{LvbR-Lss`Nr zzh)-)9KO0HG_qcD61JQ#qD`y=%`kP^QVEU9ontop0iq!dH#3vi)0V`%8Y3) Z$&8@A#^T!v3ysrU@yIQSaO+ z&u8P*3Bl-K$R|^m)7a;rs+t+BNBjI<&gBeb5{*LGvh`9spdqdView$u-bF;PA^}J% zbQjSwu>dj$)qiZT5;aEhWJqWfXB#I3Z10plp`K+?Y;GS+Old(Z)7%1*-5)+vIRA%q zJqzr>vI=kRjJNu6`~l6e>$$P95R0d@HV_$;*F?;^fi`dnBPUJeX$cV~P#oUL>9erD z+psWAk4Xp903+Ls9!#=+c44J{Ej)feS2n$k5%uDfN+)2C(yB%B)`t(QD2{g9Y@Rrg zCmu5VpL*UY@CjYLYFyVa=Uwzm8<}uac73Vwb%X7=EWp~wfh92YJBkh?j!HWl`eGJv zI&2TUCL+hJHESz?D5MCu+MbBm&~oZBcWwu)H-~#2vt*b~Z`9}NxDb-ReM-iw)~P!# z`#j?pN7b}lg9Va2gK4Yn+BIAQtsq5We3E~wR7shG&e|RCqqy^&n2*D{FQ6_x4(deb zcbloqi jj2RVqqQRo=Z^nhT!ty;h!DK;%yot> m;Ok*IWPKU#2ODpr%E0U0BX6l@Z}8n)_QF1txvJOL!X0zWw>?Y9XR~9` zk@5P3|ApVen7s+(1((dgb?#3i@5F_wM|7O=877i|>$1&1s-@?eT6{J`s}N_u<+|<* zC$gkuZD=4^OtB1JahVtf9NlI}h$=>M %gsio5sxt~ znx;@d3v@whca NhZtqG!}32~cDeFp}ii z3}uCmER6hC=TBT`nO#nx`nut;^ArgS8n-2DSnrWl%<~=xedJ0!rS>axPC7Xld{q*F z+0+#vBC344Y2T3X9p;k+reka?KU9&g 94Ei-H{zXQZy4j;VnIIgaG?za zHBgmdEBw|eX;FQaqbCRVxZdc5mwWVC0s>A6w+D3H)h-|=GXwTWOc93T_;jO{4*u%A z_F=XmxNZv}*JQ$k=5C#Sg2*JQ)cM%ON}bq>fU27#x^Wpv>?~EiC#oab>kBK8z(kS4 zGJ%ovr%t)dL$|qk0lj#~puNyNi)^aBcUBd}h0!Aa# gSbYm-(nVM(thZy}=pP44?u9QT|Bv^&E~hc+U1= 8ZAd_(K!}*l)9~_Au`J( z7_k7UrGB_StOOO^KSSy8>5nTx`^EF~@oO-%6igX0Q2(419AO^>{QOqJ{5I;T2uf$F z>zh<>OUf1|e`@s<(qO0_1j~S-B#26SY!nDtW`91Vf(jp6h29X8i9N3zKlfv;t&aq- zJ4FViAC>^Mr8 SP6LN!2qcV3&4 zBMuvH*zO|j>Nu<+zj#hpiGe$V&;hFiN|wo`C(f@*SpH=TVt2-rM(jy5`9rEi*wpTA z^3aGI1+upvrJB1p8~5_D33cdaQW}t*Ky`v{gJ@5whHmC_adN>5m^?$61WT2z$+=O+ zBnvQqmE=Z80agis0YB1b-~sJcW}x>QL26FIUI|Vg#sbpnEW@O^4n((q`^Y9xL3B iyFeQv@%QxFn0&jJ*EpT*laS{LxQ>(z&q!3Yben^-fsCom@>P z8`>Rj1F|xH)iML1kp-;q`=eL=5ycBZy?ocaKmupzuA4ui)YxSrSoI%u&sHng 8EYtKF*^4S5oGn!EZ`u-U*-Ue6d+&(jUg$~* zWk)TrU*af^z<$K&Hk-vJoDN3J!#%6sA9p#iU{P* ?m-_Zf$^O{N>{^s5cVYK`6cp oMAJ6Z`V9|6f-4VdK(}?BAcb z71LoBQeE;^eVwmrH3Qyaj8qO;doP36WbVE#ub b+g7&vl$xRmvUPHSVU;5=9>{^zni5iANu;i@A z_7@|)y^j0iLr_17fmXGhYb9V)o_u_-bmmQijK65Tn^smzBx6@aSM$dt8eD9;RWF$! zlbKC4pEE}kt6Wp~jhCvllj2n)uXQC%R%5^j32-wFrmZfyj`vhRM584UYSX@;j`~*X zwNJzG5AsH91;e@PUynqZD*VUhD~rz%;VIyMFn@&AA=j0^B+mTz_Y@jL96+i8fGiFk zq1JtvNe?#pKM}FsJ%nSvt1Q!h!9}5n|Nfn!D-~Wi=547Cq%T)#Q>T(pdS^Peh)nxS zIwaxkEM3rpY1$N=#}*V!&|}6P+_45PW-OkhuD-0|lA}llDVc$Ta1v*@mEm`iRQV3c z+5z6DM8Z?hWrUegoa`lE($~t>puj0G{k}oiLMf%iEN5xaqI^>(iBo&QbT;i5h_GE~ z@WXtEdwl;R7bitisXJ{hOB?qi#wS+~ZadX_Q@o!`y&YyO+gVF7B`ughT?FgP3HB_+ zZ|i+POzU6n+0EZXwCH8e?AHw%Jq2-6!!oh3#0TR#w!te!6f7sJ>#P9G7sIqisEz;r zgE)=Dq4|R^U!l$8DcAuSQ0^DKuSL@?J%o_Bb@_N1JROdI+0hrzTIDC`+zZ849 #FE^=}yzs(P_!y6$tZs6a zmk(hK|EY#(K6OIS|J^SR4-A8FC3HeP|J! (=9<0}|if26pJpH%E z4Ojvd{F6=zhCm4WrCkZ-awNn^!BBOGFsNH_EfJziZ;G@ZyJ K1czF+iv<)H U*FvR$olGctRs6x(T*iPIW-Amx@e#wDu}a` z`SEYw%Br;CTh3;c9zFNDLL=%l?+`PE&zpU}fje5wrjVK59UN4uNlS`k_cV<7-~- zscW_HUMW7dQGo2M^_lEXPl@Rdh~aO;9~x^%WSXk)>%qQ%cx4&B{l_*?373*2Z&L@N z23&(0!p4}M?6)(Y$8T^fRO0`A$4AW0zwnljIOQDvdKMle&lM5!%<|Ur_fW3}=g?{t z%dA_hr3dIraH5{N{}cZ41vc}%GpmfsZW_cSGi_PV L9yqYq|?w{0{x})*$#plifn`CYgXA5u&aRtz YQ ?N8GU4jvwf*4Ia(rQdFpclE1@5bIH{pCe4;I#?6b~n|g7Dv* zZZHnHTINDu$Fn-yMV&^Km!miQjnR)O`vw0mMGr2#DxXVNJorY2BYozeADam10ks04 z7KGKp{yrWoT#pJZ-rvPc xQhM{&4CFnSljcjRd4*gdvr4OJ&j8_!HMx a zE4zc3+^x67NltCtbvqC40!^t8K}o {uI2e)QMk-l9*JaIRM!k@=D%nA<=E! z_F4eT;TI!SnL4FHp){dHkrpi{96 B*P<^>CEa{4i(($ zk45*4y0VC>BsU^X-51WOc(a>^-w{!wIaIT(f(gJRIG#F@USuywu_-M&NZIpQePIl5 z-P0>cv(*Ya#5zq>IBjb0m<-eH`8Px*@g`CN|IuZmCT9TY)!RrjYsf`Oqr)-~&ehCa zckhea+mbCYxU|=PkR7cL@Lqq6>2cKYBtwit4SH@f!ny3V(ywScalwek$pCp%+U$0g zGLBI>3zsF_-HA;W+iR-PzAJs-M$;Z(4HyRs7GGFGstB5a+j^k%Wh#5aB=V5lc8)`c z0e9jxO`pel*wF_?c!K-AGOmF@-8&btm63Fsi%z?Tq|Xid<> zWd6OT?9$ps!Nkxn9KAEOQm;&DT^OH=lz3+v;248ee{^G$kHyd%8b+Q<(Yi0|zbX?Y zMP?@H{|`GC-sWtML?)@qaeB!{MAj#I5>jU4$~~ab&x+FdPQO;;YzanAZE3-Qe}So9 zK8tWa^uorI- 7^~e`L=#7EL}h=h zcOZXdHh0d=(2rFm2l&+R4=oz>`6{_iF^+=X@U?-+F9UZsQ2gYwD&>t %b7|0T^8q+VljPej1j+*gPsL^|Brk?DO1kx%O9oh zAPTCpQX(rp5Aaz^H7)EW-Si#HVMEdHTT|wdRQboEkd%n7--OCQ#%dd!VN-Q$1EF2) zig+$%J8nMuPIEN$J6v45CW_lH? Ck%f%Ol$5fV-rVos zzu--}c? |cw0>(0R%hOk4r}lsgJ>dC%-Xu31{{a@1muFcb8!am)d8rbMA8XsNf{A2; zT_ r$g3Q _m>M zLptsXbr`O}WnaVtQdM#(uAU@GMQNb+X8Xmwd{1F#%;K#CX 2mSzTH(z_cyeK1x#v9#SWJ*AJ!U z80f(YKu|k&6}Kmy@;TNB35mtIUx8~o70Ek#B(`W=x6bl&ajK%a0TK|tRo;i!NflJ0 z2iH4p=ns!6+n)TU-dw02Z$&CjXA`LyV_#h%0w!}$RUi#+3r~mqoEX+-Kr=CyRL*c^ z(YLejo=y|voyAk3RsTp`jalK7|2DlPFA5)!t|hq?ApHidP?6+f}}kQ)r+r8Y2npN4>sY z@EZM9*l^SgK(y)y{Mn_gEM_KD_d@X}*H(W*ch|kK0*1I%_iiWOca0{ex8n9nd0b-_ z>p|7O*U=G&qp;r @ zKs=@g)$)a=BB+`|sY7Sxb)c=!S?(4+KasOeQ39v$%w41gt@Z10PrS6QYF7#wJ@!*X zQXKwofMTCoH UJqcJDTui$*O9$nGnAv3YjAWU^HoX!Jl9F0j+wvd!Gc zqaVJg%fj&BFwv5>pJJ?%@trWiM9~dSyXkPk#BfFR$)m4xL4Zm43KV3kY(8&^lBTXt zjLV{_9`HP?aowCBS@kNC#ZzK9&3}ELsu}vO2*km}XA2Qk0j6;+5!q`wp-}a6C|?cN z9bb@NN$}Yh225V^={ItN+zIr=Sp~1@+=X~v^V-wbe!*=jyLAQSKJxxc*nRwVlwTX9 zPqgA|3hPID?46;rkZ400*mAjM&X_(wcc7iOqH#fkE-KaEL8pS3X?2vVXL~-Ud6LIj z5#@C5^dAZ|-NIoKhWn1q6UHo?{6m=TB;zLVT&yqbvT$jH>_wt1N~ceeP~ky{Re>Qh zY-EQJ!nc??O1xs%F_eu#b@06P)!PZ|o_{oN79^;&kb}vL2^*WeTI;I9ZM#RR*f5bS zTtm*1{nAk=nROPsQCZ{*YojU^z;}3I&5L4!*m@v(wK%E=P8tOYS5sMitxN%t0(mf5 zF0-b&6V?7ojgSS|p@n?&KuLnNMEUjHW8oM^rhxI2Bh(2(Z6oeQ)ue~qgh%81@^VAi z%R7)M=2DaL7tfQlp_=8f<@I_UwFHivMs0=(xeQ`>s@E`6B=Si;DJ5LHkVjWUZ0zI; zBov8MG5I-#K+Eo=hy8B!sl7c}LoYGXQot3-$zeV}EB-9n4$1G@(X&&v2{xG(tNwG{ zDLw+?neaZ!B1`TI8E?hV#%p&rbL8N7o+YfHw4B_gIh);m7r-kiHe0(SX49c)L}5!% z_<%+~sj&C(6IN|$`_P-5bM(>&I)n|1bSZZy)9SV3)b{FhxdFOXV|_cIjTlArS4%W< zn&=N#^RXSEPGl;f2l|M|02Efk5h=vTuDhdD$9g(?ZHlYVCWUhCvC=yYBVWk(XUl@H z@*5AL=oQP#-eXt=Oz?YmHrtdL?BDo3?-m2_RL?gIo#IwBW9kZt0VfQE%IiN~7{azc ze8DcPRK+)QJLQ3k(+!#xPL%^NIkTzRqU1$`^rGW@B6x+n&5vH=+ib%Hvi8guvRUoo zZLI6TZ;SVv+Bk$L^p!!aw*%XFr;uytfJ6>P-Zii)jE@brYxeJ?v^uZO#qHl~_HJ$P zuF|zzA>^QMpM!tsE--f(6<|>iSy o<=f&K`=ovF;kr z;A4a04_luK$Y0$C2#!1aI|;DVDI)53C1Y~{>oSC~ygTNs@Rg5*opIuG-PIIaCWt v8M`4cjtYvuZb;NmutSXegU%DA{^EKq_TCuB85wUCYmDv4{zkz2TaL3pG^tK7>(& zCz)sClQP0hGzD@PwKUAVvp(>kXXD8HnbTo1cRkbOg?u`a`7#5%O@?E~L`Hz7Q1c5^ zr&_y)%son!1lVK-+8Sjxv&)o^K2$yM{wCsg;0rGyr#=P52&t#VOzp~i;>}rvd~RCn zJgChm;yjawwi5+%0IZW&z{#>h&1^b5zZ2}BWFmG^sa60Biz~9}d^&8?IryFGdPb6* z&ByKNW}?($_apDvbY>#qB>~Llt>f0Vu< ^T3Rc@7qk~IoD^e8cn)Sd-%TU$l@4u450mlISP@kRv}-fN z*@QDDWzkQkT4V#uuqL>D#q3pc3|;1&B6K5)*@%paLg(1~x5On6_|U%D-awBtO5_)m zkN-JqC_GPIC jmGKj^!w6j^_stA{MHmM;WZ zJ;Eh{msn+B%oFOu$>8hjtZ2}A>CPQ1|t1O z*qg*jZCw_Z% iWaSV)3E~%gtLczCtR1I{0O8hD)87W8XG# zN3;aB5+${5Rm%-sN5Vc>)N;xJ3u+M tT?63EMdfweH_Hj!@U2{C3j0Ju&itMAaU591efi I)X?M8D zlvqg_0iUh^N9!ROrDZod{_REXI sb?$_jFemWWM4kq5UfU=O?ZPq5o zICbI&_##v6Bfh(~Dn`zzzof`IZe#eX@=I$ hv)gVmUHtJoq(# z%r3#q4JXa^xXE B~AzjPD6zpHY$ zAyQX}EW4(^$$by;9DJ9~BrWk|_&a0*OS5liPhCbvT?nJ$xIAm?^iv*FjYtL)iII;L zLvI$ype1q#!>RP5d+Ul7*R0MspE<^o?>`=h$&y+3>PS8!bL(!i^gIe;cjf#<rj#H}$;HkCCp>s#nhuUk+&PmCbxAPzSKkjA|ZRo5#2p$M%f!A3$2`K%(#75PzC z!_MPZB1d&&e>W-l(8e>&7*}@qgnuHV _8jEOpm*UX=Uve03W osqGO24{Ge4qC F(%FI>w*t9`=h&!Fsup*i~~Q@csdeZy9x8jPA&qsMZqX`PfZY zL_HZfe0Tc~C0*PqF+U}5)flBMdr8Z3sKV>VN(Rwah^|Y_PQNhbi57MoIX=t$)8IY5 zRj+C=o6NK!Rm`1>ij$zTw%N<$Sk)d1g{a6GR;(^3N;Gpc+{Rb!guSHBXIA|1rCiQq z4u*TcqK>$iHFRpg*cnyhdtDT<(`agGY|h {6Oird6d#{SS9^kU{NQtb zXFAjlT=_gef7)KzYE9|=;gNEAt98?2+e@K`%0>L2^ 1k%y3$_`YWe;FKi&@# zJ+6Ej876n4g_8zadqdZ?&n)dqnC^ON B=kayA-*?M-(wl4GMir24 I9$RF&YmtlPpajl2 jI(oCQ6_|YhOSZqnjNVjzq|_zc)kww z;muvzqI9Jf6(g@(y--=K{}QElSXIpewpvus@cGLL_2j5$NO+(_qB<|gckWCL^0`Q; zh;6)uZM##X(Qq}#E$!tL`^f1Wuc8n}L`3-GQ0MQp5YdC{^;>*e)85$z!J3c 7c~E$Ml<6) ^4#r2u`1 zQKd%pl@KZKf*`j(qb^7>vEP|G&iqD-ckh%G4t0@}HH_XhbsZylR4gvZyGj*8FU41E zAi_#*bn`L6ay~nP_J!b2iU&->nOq5IVke!nxA}L^lS1bV7_}@EhlkLxI<*;~|7fe{ zAmQe+c*g?jQsdjwN?NyIaMu$aQ$nmz%m9dd0_C4y&U}G6q^7g{is3LNonf=8dKn*t zk4~ms5Q3eL>I1S4*WWD7N+v)b&IAt>>3+jm&`|QX&&a+~Sbts7Q^r8c#nD6+UMFAk z5a{5(El;^f!USp)4!Yq2dHfIbr?Db> 0;s0BJzVGDj68gU{ qpnUc@4lEG Wh@Q5QR`pF()c*k}Rs*X5 literal 0 HcmV?d00001 diff --git a/app/files/AudioThumbnail.png b/app/files/AudioThumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..1f72a790bb6bb94436f6f167359089069c9e15c1 GIT binary patch literal 42139 zcmW)n1yCE^*Ty%vyHi|)JCp*&-JL>_;zf!VC%EOMxVw9c6pDx9h2j*4P`p@*Yx(m3 zGRf@BW|C~~z2~0u{GPM%y4ouEI5ao_0N|^sD(V9OsQiB~EDYqCP)$8i V zxsOHwU 8BtLV$1g=|>*@jJtVKk7`oxWATFZCc+UVYdyjgt)!I4HD# z_bb8P> FLJfzX#;LxhKk ztQ92KkxH6)(L!nc`w`#q=Y3G@R{aQJV*XBI>PA4FprZwo?ViX}hjs~?hX1_JuSlxZ z@|*YS+HQetY>|_8k%GoaGw4Bf9?GA;s-`$g2IQbqQwEXr@P7Y{+kAl&?Wi;7lJ)|V z*w~7ZzatseUxW8OUp|-L$;>z_+x&5vscs>z$rb-z7Ja;VMQ`!H@4(x8uc1+2bewhP z(;q5WuqyA*gWvYuh~L{o` ufT V;%x=YhckhNG8jOa7JgJU$R9O}mv$gQIl$E5Hp6;zdg&02YW&P1r{ zY4q=eK>OAEe6NoiqLTb(x9(ZpHQ&Bxv|6`1uKIC%6M_k%=|=w2jU1Y0E|l||#z^I` zYXzrXy&e23Pyk{~4lvHuUNiVJ7@$6gs@Bg5)2nd!bh=)eg-bu{3yAn?3wn24jYhto zTOPwP;bLv+9lSp+dF^QlOzV@YI7te2&>etNJX6S^D4mtDWz{*!W=!p|ZEhbi*GH|P z