├── .github └── workflows │ ├── deploy.yaml │ └── lint.yaml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── build.ts ├── deno.jsonc ├── deno.lock ├── src ├── CityManager.ts ├── CreateTrade.ts ├── Military.ts ├── Nations.ts └── lib │ ├── localStorage.ts │ ├── sessionStorage.ts │ └── utils.ts ├── static ├── CNAME ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── css │ └── main.css ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── icons │ ├── green.png │ ├── green_red.png │ └── red.png ├── index.html ├── js │ └── main.js ├── scripts │ ├── HideAllianceDescriptions.user.js │ ├── HideNationDescriptions.user.js │ ├── Human.user.js │ ├── Keno.user.js │ ├── PlayBaseball.user.js │ ├── RewardAds.user.js │ ├── SendMessage.user.js │ ├── TeamBuilding.user.js │ ├── ViewTrades.user.js │ └── aSyncly.user.js └── site.webmanifest ├── test.ts ├── ts └── main.ts └── utils.ts /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | concurrency: 12 | group: "deploy" 13 | cancel-in-progress: true 14 | jobs: 15 | deploy: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: denoland/setup-deno@v2 20 | - run: deno run -A build.ts 21 | - uses: actions/configure-pages@v5 22 | - uses: actions/upload-pages-artifact@v3 23 | with: 24 | path: "static/" 25 | - uses: actions/deploy-pages@v4 26 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | workflow_dispatch: 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: denoland/setup-deno@v2 14 | - run: deno fmt --check 15 | - run: deno lint 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | static/js/ 2 | tests/ 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "cSpell.words": [ 4 | "acctrade", 5 | "asynciterable", 6 | "bindgen", 7 | "buyaircraft", 8 | "buysell", 9 | "buyships", 10 | "buysoldiers", 11 | "buytanks", 12 | "cancelhomegame", 13 | "ceci", 14 | "deno", 15 | "docscripts", 16 | "drydock", 17 | "ECMASCRIPT", 18 | "KHVHDTFT", 19 | "leftcolumn", 20 | "leptos", 21 | "luca", 22 | "mistrade", 23 | "Mistrade", 24 | "mistrades", 25 | "nationtable", 26 | "nums", 27 | "outfile", 28 | "priceper", 29 | "Qlqphrq", 30 | "rcustomamount", 31 | "reqwest", 32 | "rightcolumn", 33 | "rustup", 34 | "submitawaygame", 35 | "submithomegame", 36 | "Syncly", 37 | "tinyflag", 38 | "tradeaccid", 39 | "tradedelid" 40 | ], 41 | "liveServer.settings.root": "docs/" 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 BlackAsLight 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DocScripts 2 | 3 | This repo contains the source code for the scripts maintained by Doctor for a 4 | game called [Politics and War](http://politicsandwar.com). 5 | 6 | These scripts offer QoL things to the game in various specific areas. To find 7 | out more about how to use the scripts head to 8 | [DocScripts](https://docscripts.stagintin.com/) 9 | 10 | One of the scripts "Play Baseball" has a discord server that lets you find 11 | anothers to play with. It's called the 12 | [Orbital Baseball League](https://discord.gg/dfmnW2xt7B) 13 | 14 | ## DO NOT 15 | 16 | ...ask me how to do this or that unless you have read, followed, and _failed_ 17 | the [installation](https://docscripts.stagintin.com/#installation) instructions 18 | found on the website. 19 | 20 | ## Installation 21 | 22 | DocScripts can be installed on many browsers and platforms. You can find the 23 | installation instructions 24 | [here](https://docscripts.stagintin.com/#installation). 25 | 26 | ## Update 27 | 28 | On most platforms you can receive DocScripts' updates automatically but on a few 29 | you'll need to manually update. You're also able to disable updates. You can 30 | find information about updates [here](https://docscripts.stagintin.com/#updates) 31 | 32 | ## Contributing 33 | 34 | If you'd like to contribute then you're welcome to fork this project and open a 35 | pull request. The repo is currently under a slow shift to moving the scripts to 36 | TypeScript instead of JavaScript so any new contributions will need to be done 37 | in TypeScript. You'll need Deno to convert the ts to js to test locally, but the 38 | Github Actions will end up doing the final conversion for the website version. 39 | Do make sure you update the server version in the script. 40 | -------------------------------------------------------------------------------- /build.ts: -------------------------------------------------------------------------------- 1 | import { build, stop } from "esbuild"; 2 | import { denoPlugins } from "@luca/esbuild-deno-loader"; 3 | import { basename } from "@std/path"; 4 | import { extname } from "@std/path"; 5 | import { normalize } from "@std/path"; 6 | 7 | async function esbuild(inPath: string, outPath: string): Promise { 8 | const { errors, warnings } = await build({ 9 | plugins: denoPlugins(), 10 | entryPoints: [inPath], 11 | outfile: outPath, 12 | format: "esm", 13 | bundle: true, 14 | minify: true, 15 | sourcemap: "inline", 16 | }); 17 | errors.forEach((error) => console.error(error)); 18 | warnings.forEach((warning) => console.warn(warning)); 19 | } 20 | 21 | async function createScript(path: string, outDir: string): Promise { 22 | const startTime = performance.now(); 23 | if (extname(path) !== ".ts") { 24 | throw new TypeError( 25 | `Expected a ".ts" type extension. Got ${extname(path)} in ${path}`, 26 | ); 27 | } 28 | 29 | const name = basename(path).slice(0, -3); 30 | const minPath = normalize(outDir + "/" + name + "min.js"); 31 | const promise = esbuild(path, minPath); 32 | const file = await Deno.create(normalize(outDir + "/" + name + ".user.js")); 33 | await file.write(new TextEncoder().encode( 34 | await async function (): Promise { 35 | const text = await Deno.readTextFile(path); 36 | const a = text.indexOf("// ==UserScript=="); 37 | if (a === -1) { 38 | throw new SyntaxError( 39 | `Failed to locate "// ==UserScript==" in ${path}`, 40 | ); 41 | } 42 | const b = text.indexOf("// ==/UserScript==", a) + 43 | "// ==/UserScript==".length; 44 | if (b === -1) { 45 | throw new SyntaxError( 46 | `Failed to locate "// ==/UserScript==" in ${path}`, 47 | ); 48 | } 49 | return text.slice(a, b) + "\n'use strict';\n"; 50 | }(), 51 | )); 52 | await promise; 53 | await (await Deno.open(minPath)) 54 | .readable 55 | .pipeTo(file.writable); 56 | await Deno.remove(minPath); 57 | 58 | console.log( 59 | `(${ 60 | (performance.now() - startTime).toLocaleString("en-US", { 61 | maximumFractionDigits: 2, 62 | }) 63 | }ms)\t${path}`, 64 | ); 65 | } 66 | 67 | await Promise.allSettled([ 68 | Deno.mkdir("static/js/", { recursive: true }) 69 | .then(() => esbuild("ts/main.ts", "static/js/main.js")), 70 | Deno.mkdir("static/scripts/", { recursive: true }) 71 | .then(async () => { 72 | const promises: Promise[] = []; 73 | for await (const dirEntry of Deno.readDir("src/")) { 74 | if (dirEntry.isFile && extname(dirEntry.name) === ".ts") { 75 | promises.push( 76 | createScript("src/" + dirEntry.name, "static/scripts/"), 77 | ); 78 | } 79 | } 80 | await Promise.allSettled(promises); 81 | }), 82 | ]); 83 | stop(); 84 | 85 | console.log( 86 | `${ 87 | performance.now().toLocaleString("en-US", { maximumFractionDigits: 2 }) 88 | }ms`, 89 | ); 90 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"] 4 | }, 5 | "imports": { 6 | "@doctor/create-tag": "jsr:@doctor/create-tag@^0.2.0", 7 | "@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.1", 8 | "@std/path": "jsr:@std/path@^1.0.8", 9 | "esbuild": "npm:esbuild@^0.24.0" 10 | }, 11 | "exclude": ["**/*.js"], 12 | "tasks": { 13 | "test": "deno run -A test.ts", 14 | "build": "deno run -A build.ts", 15 | "ok": "deno fmt && deno lint && deno task build" 16 | }, 17 | "lint": { 18 | "rules": { 19 | "tags": ["recommended"], 20 | "include": [ 21 | "camelcase", 22 | "explicit-function-return-type", 23 | "explicit-module-boundary-types", 24 | "no-eval", 25 | "no-inferrable-types", 26 | "no-sparse-arrays", 27 | "no-sync-fn-in-async-fn", 28 | "no-throw-literal", 29 | // "no-undef", 30 | "prefer-ascii", 31 | "single-var-declarator", 32 | "verbatim-module-syntax" 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@doctor/create-tag@0.2": "0.2.0", 5 | "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", 6 | "jsr:@std/bytes@^1.0.2": "1.0.4", 7 | "jsr:@std/bytes@^1.0.3": "1.0.4", 8 | "jsr:@std/encoding@^1.0.5": "1.0.5", 9 | "jsr:@std/path@^1.0.6": "1.0.8", 10 | "jsr:@std/path@^1.0.8": "1.0.8", 11 | "npm:esbuild@0.24": "0.24.0" 12 | }, 13 | "jsr": { 14 | "@doctor/create-tag@0.2.0": { 15 | "integrity": "26dfc0b23edb3eade94e6362f79c14e1a5fcd064e3d96b9a558f468bff237b60" 16 | }, 17 | "@luca/esbuild-deno-loader@0.11.1": { 18 | "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267", 19 | "dependencies": [ 20 | "jsr:@std/bytes@^1.0.2", 21 | "jsr:@std/encoding", 22 | "jsr:@std/path@^1.0.6" 23 | ] 24 | }, 25 | "@std/bytes@1.0.4": { 26 | "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" 27 | }, 28 | "@std/encoding@1.0.5": { 29 | "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" 30 | }, 31 | "@std/path@1.0.8": { 32 | "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" 33 | } 34 | }, 35 | "npm": { 36 | "@esbuild/aix-ppc64@0.24.0": { 37 | "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==" 38 | }, 39 | "@esbuild/android-arm64@0.24.0": { 40 | "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==" 41 | }, 42 | "@esbuild/android-arm@0.24.0": { 43 | "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==" 44 | }, 45 | "@esbuild/android-x64@0.24.0": { 46 | "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==" 47 | }, 48 | "@esbuild/darwin-arm64@0.24.0": { 49 | "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==" 50 | }, 51 | "@esbuild/darwin-x64@0.24.0": { 52 | "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==" 53 | }, 54 | "@esbuild/freebsd-arm64@0.24.0": { 55 | "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==" 56 | }, 57 | "@esbuild/freebsd-x64@0.24.0": { 58 | "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==" 59 | }, 60 | "@esbuild/linux-arm64@0.24.0": { 61 | "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==" 62 | }, 63 | "@esbuild/linux-arm@0.24.0": { 64 | "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==" 65 | }, 66 | "@esbuild/linux-ia32@0.24.0": { 67 | "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==" 68 | }, 69 | "@esbuild/linux-loong64@0.24.0": { 70 | "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==" 71 | }, 72 | "@esbuild/linux-mips64el@0.24.0": { 73 | "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==" 74 | }, 75 | "@esbuild/linux-ppc64@0.24.0": { 76 | "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==" 77 | }, 78 | "@esbuild/linux-riscv64@0.24.0": { 79 | "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==" 80 | }, 81 | "@esbuild/linux-s390x@0.24.0": { 82 | "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==" 83 | }, 84 | "@esbuild/linux-x64@0.24.0": { 85 | "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==" 86 | }, 87 | "@esbuild/netbsd-x64@0.24.0": { 88 | "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==" 89 | }, 90 | "@esbuild/openbsd-arm64@0.24.0": { 91 | "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==" 92 | }, 93 | "@esbuild/openbsd-x64@0.24.0": { 94 | "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==" 95 | }, 96 | "@esbuild/sunos-x64@0.24.0": { 97 | "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==" 98 | }, 99 | "@esbuild/win32-arm64@0.24.0": { 100 | "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==" 101 | }, 102 | "@esbuild/win32-ia32@0.24.0": { 103 | "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==" 104 | }, 105 | "@esbuild/win32-x64@0.24.0": { 106 | "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==" 107 | }, 108 | "esbuild@0.24.0": { 109 | "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", 110 | "dependencies": [ 111 | "@esbuild/aix-ppc64", 112 | "@esbuild/android-arm", 113 | "@esbuild/android-arm64", 114 | "@esbuild/android-x64", 115 | "@esbuild/darwin-arm64", 116 | "@esbuild/darwin-x64", 117 | "@esbuild/freebsd-arm64", 118 | "@esbuild/freebsd-x64", 119 | "@esbuild/linux-arm", 120 | "@esbuild/linux-arm64", 121 | "@esbuild/linux-ia32", 122 | "@esbuild/linux-loong64", 123 | "@esbuild/linux-mips64el", 124 | "@esbuild/linux-ppc64", 125 | "@esbuild/linux-riscv64", 126 | "@esbuild/linux-s390x", 127 | "@esbuild/linux-x64", 128 | "@esbuild/netbsd-x64", 129 | "@esbuild/openbsd-arm64", 130 | "@esbuild/openbsd-x64", 131 | "@esbuild/sunos-x64", 132 | "@esbuild/win32-arm64", 133 | "@esbuild/win32-ia32", 134 | "@esbuild/win32-x64" 135 | ] 136 | } 137 | }, 138 | "workspace": { 139 | "dependencies": [ 140 | "jsr:@doctor/create-tag@0.2", 141 | "jsr:@luca/esbuild-deno-loader@~0.11.1", 142 | "jsr:@std/path@^1.0.8", 143 | "npm:esbuild@0.24" 144 | ] 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/CityManager.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: City Manager 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 10.0.1 5 | // @description Improving the experience of switching improvements. 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/city/id=* 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | import { pass, sleep, waitTilFalse, wrap } from "../utils.ts"; 13 | import { createTag } from "@doctor/create-tag"; 14 | 15 | /* Double Injection Protection 16 | -------------------------*/ 17 | if (document.querySelector("#Doc_CityManager")) { 18 | throw Error("This script was already injected..."); 19 | } 20 | document.body.append( 21 | createTag( 22 | "div", 23 | { id: "Doc_CityManger" }, 24 | (divTag) => divTag.style.setProperty("display", "none"), 25 | ), 26 | ); 27 | 28 | /* Global Variables 29 | -------------------------*/ 30 | let token = 31 | (document.querySelector('input[name="token"]') as HTMLInputElement).value; 32 | let submitting = false; 33 | /* Main 34 | -------------------------*/ 35 | document.querySelectorAll('form[action*="#improvements"]') 36 | .forEach((formTag) => 37 | (formTag as HTMLFormElement) 38 | .addEventListener("click", async function (event): Promise { 39 | if ( 40 | !event.target || 41 | (event.target as HTMLElement).nodeName !== "INPUT" 42 | ) { 43 | return; 44 | } 45 | event.preventDefault(); 46 | 47 | const inputTag = event.target as HTMLInputElement; 48 | inputTag.toggleAttribute("disabled", true); 49 | await waitTilFalse(() => submitting); 50 | submitting = true; 51 | 52 | const dom = new DOMParser().parseFromString( 53 | await (await fetch(this.action, { 54 | method: "POST", 55 | body: (() => { 56 | const formData = new FormData(); 57 | formData.append(inputTag.name, inputTag.value); 58 | formData.append("token", token); 59 | return formData; 60 | })(), 61 | })).text(), 62 | "text/html", 63 | ); 64 | token = (dom.querySelector( 65 | 'input[name="token"]', 66 | ) as HTMLInputElement).value; 67 | wrap(inputTag.parentElement as HTMLParagraphElement, (pTag) => 68 | (pTag.nextElementSibling 69 | ? pTag.nextElementSibling 70 | : pTag.previousElementSibling) as HTMLParagraphElement) 71 | .textContent = wrap( 72 | (dom.querySelector( 73 | `input[name="${inputTag.name}"]`, 74 | ) as HTMLInputElement) 75 | .parentElement as HTMLParagraphElement, 76 | (pTag) => 77 | (pTag.nextElementSibling ? pTag.nextElementSibling : pTag 78 | .previousElementSibling) as HTMLParagraphElement, 79 | ).textContent; 80 | const spanTags = [ 81 | ...document.querySelectorAll(".improvementQuantity"), 82 | ]; 83 | dom.querySelectorAll(".improvementQuantity").forEach(( 84 | spanTag, 85 | i, 86 | ) => 87 | spanTags[i].textContent = spanTag.textContent 88 | ); 89 | (document.querySelector( 90 | "#improvements", 91 | ) as HTMLParagraphElement).insertBefore( 92 | dom.querySelector("#improvements>span") as HTMLSpanElement, 93 | pass( 94 | document.querySelector( 95 | "#improvements>span", 96 | ) as HTMLSpanElement, 97 | async (spanTag) => { 98 | await sleep(0); 99 | spanTag.remove(); 100 | }, 101 | ), 102 | ); 103 | 104 | submitting = false; 105 | inputTag.toggleAttribute("disabled", false); 106 | }) 107 | ); 108 | -------------------------------------------------------------------------------- /src/CreateTrade.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Create Trade 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 10.0.3 5 | // @description Makes script, View Trades, Outbid and Match buttons work. 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/nation/trade/create/* 8 | // @match https://politicsandwar.com/index.php?id=27* 9 | // @icon https://avatars.githubusercontent.com/u/44320105 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | import { sleep } from "../utils.ts"; 14 | import { createTag } from "@doctor/create-tag"; 15 | 16 | /* Double Injection Protection 17 | -------------------------*/ 18 | if (document.querySelector("#Doc_CreateTrade")) { 19 | throw Error("This script was already injected..."); 20 | } 21 | document.body.append( 22 | createTag( 23 | "div", 24 | { id: "Doc_CreateTrade" }, 25 | (divTag) => divTag.style.setProperty("display", "none"), 26 | ), 27 | ); 28 | 29 | /* Global Variables 30 | -------------------------*/ 31 | const enum LocalStorageKeys { 32 | Ticks = "!Doc_CT1", 33 | Recursive = "!Doc_CT2", 34 | Price = "!Doc_CT3", 35 | MarketView = "Doc_MarketView", 36 | } 37 | const { resource, p, q, t } = Object.fromEntries( 38 | self.location.search.slice(1).split("&").map((args) => { 39 | const [key, value] = args.split("="); 40 | const number = parseFloat(value); 41 | if (`${number}` === "NaN") { 42 | return [key, value]; 43 | } 44 | return [key, number]; 45 | }), 46 | ) as { 47 | resource: string | undefined; 48 | p: number | undefined; 49 | q: number | undefined; 50 | t: string | undefined; 51 | }; 52 | 53 | /* Main 54 | -------------------------*/ 55 | // If successfully made a trade offer. 56 | if (document.querySelector(".alert-success")) { 57 | localStorage.setItem( 58 | LocalStorageKeys.Ticks, 59 | `${new Date().getTime() + 5000}`, 60 | ); 61 | if ( 62 | q && q > 100_000_000 && localStorage.getItem(LocalStorageKeys.Recursive) 63 | ) { 64 | const args = self.location.search.slice(1).split("&"); 65 | args[args.findIndex((arg) => arg.startsWith("q="))] = `q=${ 66 | q - 100_000_000 67 | }`; 68 | if (localStorage.getItem(LocalStorageKeys.Price)) { 69 | args[args.findIndex((arg) => arg.startsWith("p="))] = `p=${ 70 | localStorage.getItem(LocalStorageKeys.Price) 71 | }`; 72 | localStorage.removeItem(LocalStorageKeys.Price); 73 | } 74 | self.location.href = self.location.origin + self.location.pathname + 75 | "?" + args.join("&"); 76 | } else { 77 | self.location.href = 78 | `https://politicsandwar.com/index.php?id=26&display=world&resource1=${ 79 | resource ?? 80 | document.querySelector( 81 | '.alert-success a[href^="https://politicsandwar.com/index.php"]', 82 | )!.href.split("?")[1].split("&").find((arg) => 83 | arg.startsWith("resource1=") 84 | )?.split("=")[1] 85 | }&buysell=${ 86 | [ 87 | "buy", 88 | "sell", 89 | ][parseInt(localStorage.getItem("Doc_MarketView")!)] ?? "" 90 | }&ob=price&od=DEF&maximum=100&minimum=0&search=Go`; 91 | } 92 | } else if (resource && document.querySelector("form#createTrade")) { 93 | localStorage.removeItem(LocalStorageKeys.Recursive); 94 | const formTag = document.querySelector("form#createTrade")!; 95 | formTag.scrollIntoView({ 96 | behavior: "smooth", 97 | block: "center", 98 | }); 99 | formTag.addEventListener("submit", (_event) => { 100 | if (p) { 101 | const price = document.querySelector("input#priceper")! 102 | .valueAsNumber; 103 | if (price !== p) { 104 | localStorage.setItem(LocalStorageKeys.Price, `${price}`); 105 | } 106 | } 107 | if ( 108 | q && q >= 100_000_000 && 109 | 100_000_000 === 110 | document.querySelector("input#amount")! 111 | .valueAsNumber 112 | ) { 113 | localStorage.setItem(LocalStorageKeys.Recursive, "0"); 114 | } 115 | }); 116 | 117 | if (p) { 118 | document.querySelector("input#priceper")! 119 | .setAttribute("value", `${p}`); 120 | } 121 | if (q) { 122 | document.querySelector("input#amount")!.setAttribute( 123 | "value", 124 | `${Math.min(q, 100_000_000)}`, 125 | ); 126 | } 127 | if (t) { 128 | document.querySelector( 129 | `button[data-target="#${t === "s" ? "buy" : "sell"}Confirmation"]`, 130 | )!.remove(); 131 | const buttonTag = document.querySelector( 132 | `button[data-target="#${t === "s" ? "sell" : "buy"}Confirmation"]`, 133 | )!; 134 | buttonTag.style.setProperty("border-radius", "6px"); 135 | buttonTag.setAttribute("type", "submit"); 136 | buttonTag.setAttribute("name", "submit"); 137 | buttonTag.setAttribute("value", t === "s" ? "Sell" : "Buy"); 138 | buttonTag.removeAttribute("data-target"); 139 | const ticks = 140 | parseInt(localStorage.getItem(LocalStorageKeys.Ticks) ?? "0") - 141 | new Date().getTime(); 142 | if (ticks > 0) { 143 | buttonTag.toggleAttribute("disabled", true); 144 | sleep(ticks) 145 | .then(() => buttonTag.toggleAttribute("disabled", false)); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Military.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Military 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 10.0.2 5 | // @description Making it easier to militarise and demilitarise your army. 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/nation/military/ 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | import * as localStorage from "./lib/localStorage.ts"; 13 | import * as sessionStorage from "./lib/sessionStorage.ts"; 14 | import { divSpacer, userConfigAPIKey, userConfigLabel } from "./lib/utils.ts"; 15 | import { createTag } from "@doctor/create-tag"; 16 | 17 | /* Double Injection Protection 18 | -------------------------*/ 19 | if (document.querySelector("#Doc_Military")) { 20 | throw Error("This script was already injected..."); 21 | } 22 | document.body.append( 23 | createTag( 24 | "div", 25 | { id: "Doc_Military" }, 26 | (divTag) => divTag.style.setProperty("display", "none"), 27 | ), 28 | ); 29 | 30 | /* Global Variables 31 | -------------------------*/ 32 | const data: Promise<{ 33 | soldiers: number; 34 | soldiers_today: number; 35 | tanks: number; 36 | tanks_today: number; 37 | aircraft: number; 38 | aircraft_today: number; 39 | ships: number; 40 | ships_today: number; 41 | spies: number; 42 | spies_today: number; 43 | missiles: number; 44 | missiles_today: number; 45 | nukes: number; 46 | nukes_today: number; 47 | cities: { 48 | barracks: number; 49 | factory: number; 50 | hangar: number; 51 | drydock: number; 52 | }[]; 53 | propaganda_bureau: boolean; 54 | central_intelligence_agency: boolean; 55 | spy_satellite: boolean; 56 | missile_launch_pad: boolean; 57 | space_program: boolean; 58 | nuclear_research_facility: boolean; 59 | }> = fetch( 60 | `https://api.politicsandwar.com/graphql?api_key=${localStorage.APIKey()}`, 61 | { 62 | method: "POST", 63 | headers: { 64 | "Content-Type": "application/json", 65 | }, 66 | body: JSON.stringify({ 67 | query: 68 | "{me{nation{soldiers,soldiers_today,tanks,tanks_today,aircraft,aircraft_today,ships,ships_today,spies,spies_today,missiles,missiles_today,nukes,nukes_today,cities{barracks,factory,hangar,drydock},propaganda_bureau,central_intelligence_agency,spy_satellite,missile_launch_pad,space_program,nuclear_research_facility}}}", 69 | }), 70 | }, 71 | ) 72 | .then((x) => x.json()) 73 | .then((x) => x.data.me.nation); 74 | 75 | /* User Configuration Settings 76 | -------------------------*/ 77 | userConfigLabel("Military"); 78 | userConfigAPIKey(); 79 | 80 | /* Styling 81 | -------------------------*/ 82 | document.head.append(createTag("style", (styleTag) => { 83 | styleTag.textContent += 84 | ".doc_military { display: grid; grid-template-columns: repeat(2, calc(50% - 0.5rem)); gap: 1rem; }"; 85 | styleTag.textContent += 86 | ".doc_military a { grid-column: 1 / 3; text-align: center; }"; 87 | 88 | styleTag.textContent += 89 | ".spacer-row { display: flex; flex-direction: row; align-items: center; }"; 90 | styleTag.textContent += ".spacer { flex-grow: 1; }"; 91 | 92 | styleTag.append( 93 | "#Doc_Config { text-align: center; padding: 0 1em; font-size: 0.8em; }", 94 | ); 95 | styleTag.append("#Doc_Config b { font-size: 1.25em; }"); 96 | styleTag.append( 97 | "#Doc_Config button { font-size: inherit; font-weight: normal; padding: 0; }", 98 | ); 99 | styleTag.append("#Doc_Config hr { margin: 0.5em 0; }"); 100 | })); 101 | 102 | /* Main 103 | -------------------------*/ 104 | // Soldiers 105 | createTag("form", (formTag) => { 106 | const divTag = document.querySelector("#rightcolumn>.row")!; 107 | divTag.parentElement!.insertBefore(formTag, divTag); 108 | divTag.remove(); 109 | 110 | formTag.classList.add("doc_military"); 111 | formTag.append( 112 | createTag("label", (labelTag) => { 113 | labelTag.classList.add("spacer-row"); 114 | labelTag.append( 115 | "Soldiers Enlisted:", 116 | divSpacer(), 117 | createTag("span", (spanTag) => { 118 | spanTag.append("?"); 119 | data.then((nation) => 120 | spanTag.textContent = nation.soldiers.toString() 121 | ); 122 | }), 123 | ); 124 | }), 125 | createTag("label", (labelTag) => { 126 | labelTag.classList.add("spacer-row"); 127 | labelTag.append( 128 | "Soldiers Enlisted Today:", 129 | divSpacer(), 130 | createTag("span", (spanTag) => { 131 | spanTag.append("?"); 132 | data.then((nation) => 133 | spanTag.textContent = nation.soldiers_today.toString() 134 | ); 135 | }), 136 | ); 137 | }), 138 | createTag("label", (labelTag) => { 139 | labelTag.classList.add("spacer-row"); 140 | labelTag.append( 141 | "Enlist/Discharge:", 142 | divSpacer(), 143 | createTag("input", (inputTag) => { 144 | inputTag.setAttribute("type", "number"); 145 | inputTag.setAttribute("name", "soldiers"); 146 | inputTag.setAttribute("value", "0"); 147 | data.then((nation) => { 148 | const maxUnits = nation.cities.reduce((sum, city) => 149 | sum + city.barracks, 0) * 3000; 150 | inputTag.value = Math.min( 151 | Math.round( 152 | maxUnits / 3 * 153 | (nation.propaganda_bureau ? 1.1 : 1) - 154 | nation.soldiers_today, 155 | ), 156 | maxUnits - nation.soldiers, 157 | ).toString(); 158 | }); 159 | }), 160 | ); 161 | }), 162 | createTag("input", (inputTag) => { 163 | inputTag.setAttribute("type", "submit"); 164 | inputTag.setAttribute("name", "buysoldiers"); 165 | inputTag.setAttribute("value", "Enlist/Discharge Soldiers"); 166 | }), 167 | createTag("a", (aTag) => { 168 | aTag.setAttribute( 169 | "href", 170 | "https://politicsandwar.com/nation/military/soldiers/", 171 | ); 172 | aTag.textContent = "Go to Page"; 173 | }), 174 | ); 175 | 176 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 177 | 178 | formTag.querySelectorAll("input").forEach((inputTag) => 179 | inputTag.toggleAttribute("disabled", true) 180 | ); 181 | Promise.all([data, sessionStorage.Token(() => undefined)]) 182 | .then((_token) => 183 | formTag.querySelectorAll("input").forEach( 184 | (inputTag) => inputTag.toggleAttribute("disabled", false), 185 | ) 186 | ); 187 | }); 188 | 189 | // Tanks 190 | createTag("form", (formTag) => { 191 | const divTag = document.querySelector("#rightcolumn>.row")!; 192 | divTag.parentElement!.insertBefore(formTag, divTag); 193 | divTag.remove(); 194 | 195 | formTag.classList.add("doc_military"); 196 | formTag.append( 197 | createTag("label", (labelTag) => { 198 | labelTag.classList.add("spacer-row"); 199 | labelTag.append( 200 | "Tanks Possessed: ", 201 | divSpacer(), 202 | createTag("span", (spanTag) => { 203 | spanTag.append("?"); 204 | data.then((nation) => spanTag.textContent = nation.tanks.toString()); 205 | }), 206 | ); 207 | }), 208 | createTag("label", (labelTag) => { 209 | labelTag.classList.add("spacer-row"); 210 | labelTag.append( 211 | "Tanks Manufactured Today: ", 212 | divSpacer(), 213 | createTag("span", (spanTag) => { 214 | spanTag.append("?"); 215 | data.then((nation) => 216 | spanTag.textContent = nation.tanks_today.toString() 217 | ); 218 | }), 219 | ); 220 | }), 221 | createTag("label", (labelTag) => { 222 | labelTag.classList.add("spacer-row"); 223 | labelTag.append( 224 | "Manufacture/Decommission: ", 225 | divSpacer(), 226 | createTag("input", (inputTag) => { 227 | inputTag.setAttribute("type", "number"); 228 | inputTag.setAttribute("name", "tanks"); 229 | inputTag.setAttribute("value", "0"); 230 | data.then((nation) => { 231 | const maxUnits = nation.cities.reduce((sum, city) => 232 | sum + city.factory, 0) * 250; 233 | inputTag.value = Math.min( 234 | Math.round( 235 | maxUnits / 5 * 236 | (nation.propaganda_bureau ? 1.1 : 1) - 237 | nation.tanks_today, 238 | ), 239 | maxUnits - nation.tanks, 240 | ).toString(); 241 | }); 242 | }), 243 | ); 244 | }), 245 | createTag("input", (inputTag) => { 246 | inputTag.setAttribute("type", "submit"); 247 | inputTag.setAttribute("name", "buytanks"); 248 | inputTag.setAttribute("value", "Manufacture/Decommission Tanks"); 249 | }), 250 | createTag("a", (aTag) => { 251 | aTag.setAttribute( 252 | "href", 253 | "https://politicsandwar.com/nation/military/tanks/", 254 | ); 255 | aTag.textContent = "Go to Page"; 256 | }), 257 | ); 258 | 259 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 260 | 261 | formTag.querySelectorAll("input").forEach((inputTag) => 262 | inputTag.toggleAttribute("disabled", true) 263 | ); 264 | Promise.all([data, sessionStorage.Token(() => undefined)]) 265 | .then((_token) => 266 | formTag.querySelectorAll("input").forEach( 267 | (inputTag) => inputTag.toggleAttribute("disabled", false), 268 | ) 269 | ); 270 | }); 271 | 272 | // Aircraft 273 | createTag("form", (formTag) => { 274 | const divTag = document.querySelector("#rightcolumn>.row")!; 275 | divTag.parentElement!.insertBefore(formTag, divTag); 276 | divTag.remove(); 277 | 278 | formTag.classList.add("doc_military"); 279 | formTag.append( 280 | createTag("label", (labelTag) => { 281 | labelTag.classList.add("spacer-row"); 282 | labelTag.append( 283 | "Aircraft Possessed: ", 284 | divSpacer(), 285 | createTag("span", (spanTag) => { 286 | spanTag.append("?"); 287 | data.then((nation) => 288 | spanTag.textContent = nation.aircraft.toString() 289 | ); 290 | }), 291 | ); 292 | }), 293 | createTag("label", (labelTag) => { 294 | labelTag.classList.add("spacer-row"); 295 | labelTag.append( 296 | "Aircraft Manufactured Today: ", 297 | divSpacer(), 298 | createTag("span", (spanTag) => { 299 | spanTag.append("?"); 300 | data.then((nation) => 301 | spanTag.textContent = nation.aircraft_today.toString() 302 | ); 303 | }), 304 | ); 305 | }), 306 | createTag("label", (labelTag) => { 307 | labelTag.classList.add("spacer-row"); 308 | labelTag.append( 309 | "Manufacture/Decommission: ", 310 | divSpacer(), 311 | createTag("input", (inputTag) => { 312 | inputTag.setAttribute("type", "number"); 313 | inputTag.setAttribute("name", "aircraft"); 314 | inputTag.setAttribute("value", "0"); 315 | data.then((nation) => { 316 | const maxUnits = nation.cities.reduce((sum, city) => 317 | sum + city.hangar, 0) * 15; 318 | inputTag.value = Math.min( 319 | Math.round( 320 | maxUnits / 5 * 321 | (nation.propaganda_bureau ? 1.1 : 1) - 322 | nation.aircraft_today, 323 | ), 324 | maxUnits - nation.aircraft, 325 | ).toString(); 326 | }); 327 | }), 328 | ); 329 | }), 330 | createTag("input", (inputTag) => { 331 | inputTag.setAttribute("type", "submit"); 332 | inputTag.setAttribute("name", "buyaircraft"); 333 | inputTag.setAttribute("value", "Manufacture/Decommission Aircraft"); 334 | }), 335 | createTag("a", (aTag) => { 336 | aTag.setAttribute( 337 | "href", 338 | "https://politicsandwar.com/nation/military/aircraft/", 339 | ); 340 | aTag.textContent = "Go to Page"; 341 | }), 342 | ); 343 | 344 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 345 | 346 | formTag.querySelectorAll("input").forEach((inputTag) => 347 | inputTag.toggleAttribute("disabled", true) 348 | ); 349 | Promise.all([data, sessionStorage.Token(() => undefined)]) 350 | .then((_token) => 351 | formTag.querySelectorAll("input").forEach( 352 | (inputTag) => inputTag.toggleAttribute("disabled", false), 353 | ) 354 | ); 355 | }); 356 | 357 | // Navel Ships 358 | createTag("form", (formTag) => { 359 | const divTag = document.querySelector("#rightcolumn>.row")!; 360 | divTag.parentElement!.insertBefore(formTag, divTag); 361 | divTag.remove(); 362 | 363 | formTag.classList.add("doc_military"); 364 | formTag.append( 365 | createTag("label", (labelTag) => { 366 | labelTag.classList.add("spacer-row"); 367 | labelTag.append( 368 | "Ships Possessed: ", 369 | divSpacer(), 370 | createTag("span", (spanTag) => { 371 | spanTag.append("?"); 372 | data.then((nation) => spanTag.textContent = nation.ships.toString()); 373 | }), 374 | ); 375 | }), 376 | createTag("label", (labelTag) => { 377 | labelTag.classList.add("spacer-row"); 378 | labelTag.append( 379 | "Ships Manufactured Today: ", 380 | divSpacer(), 381 | createTag("span", (spanTag) => { 382 | spanTag.append("?"); 383 | data.then((nation) => 384 | spanTag.textContent = nation.ships_today.toString() 385 | ); 386 | }), 387 | ); 388 | }), 389 | createTag("label", (labelTag) => { 390 | labelTag.classList.add("spacer-row"); 391 | labelTag.append( 392 | "Manufacture/Decommission: ", 393 | divSpacer(), 394 | createTag("input", (inputTag) => { 395 | inputTag.setAttribute("type", "number"); 396 | inputTag.setAttribute("name", "ships"); 397 | inputTag.setAttribute("value", "0"); 398 | data.then((nation) => { 399 | const maxUnits = nation.cities.reduce((sum, city) => 400 | sum + city.drydock, 0) * 5; 401 | inputTag.value = Math.min( 402 | Math.round( 403 | maxUnits / 5 * 404 | (nation.propaganda_bureau ? 1.1 : 1) - 405 | nation.ships_today, 406 | ), 407 | maxUnits - nation.ships, 408 | ).toString(); 409 | }); 410 | }), 411 | ); 412 | }), 413 | createTag("input", (inputTag) => { 414 | inputTag.setAttribute("type", "submit"); 415 | inputTag.setAttribute("name", "buyships"); 416 | inputTag.setAttribute("value", "Manufacture/Decommission Ships"); 417 | }), 418 | createTag("a", (aTag) => { 419 | aTag.setAttribute( 420 | "href", 421 | "https://politicsandwar.com/nation/military/navy/", 422 | ); 423 | aTag.textContent = "Go to Page"; 424 | }), 425 | ); 426 | 427 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 428 | 429 | formTag.querySelectorAll("input").forEach((inputTag) => 430 | inputTag.toggleAttribute("disabled", true) 431 | ); 432 | Promise.all([data, sessionStorage.Token(() => undefined)]) 433 | .then(() => 434 | formTag.querySelectorAll("input").forEach( 435 | (inputTag) => inputTag.toggleAttribute("disabled", false), 436 | ) 437 | ); 438 | }); 439 | 440 | // Spies 441 | createTag("form", (formTag) => { 442 | const divTag = document.querySelector("#rightcolumn>.row")!; 443 | divTag.parentElement!.insertBefore(formTag, divTag); 444 | divTag.remove(); 445 | 446 | formTag.classList.add("doc_military"); 447 | formTag.append( 448 | createTag("label", (labelTag) => { 449 | labelTag.classList.add("spacer-row"); 450 | labelTag.append( 451 | "Spies Enlisted: ", 452 | divSpacer(), 453 | createTag("span", (spanTag) => { 454 | spanTag.append("?"); 455 | data.then((nation) => spanTag.textContent = nation.spies.toString()); 456 | }), 457 | ); 458 | }), 459 | createTag("label", (labelTag) => { 460 | labelTag.classList.add("spacer-row"); 461 | labelTag.append( 462 | "Spies Enlisted Today: ", 463 | divSpacer(), 464 | createTag("span", (spanTag) => { 465 | spanTag.append("?"); 466 | data.then((nation) => 467 | spanTag.textContent = nation.spies_today.toString() 468 | ); 469 | }), 470 | ); 471 | }), 472 | createTag("label", (labelTag) => { 473 | labelTag.classList.add("spacer-row"); 474 | labelTag.append( 475 | "Enlist/Discharge: ", 476 | divSpacer(), 477 | createTag("input", (inputTag) => { 478 | inputTag.setAttribute("type", "number"); 479 | inputTag.setAttribute("name", "spies"); 480 | inputTag.setAttribute("value", "0"); 481 | data.then((nation) => 482 | inputTag.value = Math.min( 483 | (nation.central_intelligence_agency ? 3 : 2) + 484 | (nation.spy_satellite ? 1 : 0) - 485 | nation.spies_today, 486 | (nation.central_intelligence_agency ? 60 : 50) - 487 | nation.spies, 488 | ).toString() 489 | ); 490 | }), 491 | ); 492 | }), 493 | createTag("input", (inputTag) => { 494 | inputTag.setAttribute("type", "submit"); 495 | inputTag.setAttribute("name", "train_spies"); 496 | inputTag.setAttribute("value", "Enlist/Discharge Spies"); 497 | }), 498 | createTag("a", (aTag) => { 499 | aTag.setAttribute( 500 | "href", 501 | "https://politicsandwar.com/nation/military/spies/", 502 | ); 503 | aTag.textContent = "Go to Page"; 504 | }), 505 | ); 506 | 507 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 508 | 509 | formTag.querySelectorAll("input").forEach((inputTag) => 510 | inputTag.toggleAttribute("disabled", true) 511 | ); 512 | Promise.all([data, sessionStorage.Token(() => undefined)]) 513 | .then(() => 514 | formTag.querySelectorAll("input").forEach( 515 | (inputTag) => inputTag.toggleAttribute("disabled", false), 516 | ) 517 | ); 518 | }); 519 | 520 | // Missiles 521 | createTag("form", (formTag) => { 522 | const divTag = document.querySelector("#rightcolumn>.row")!; 523 | divTag.parentElement!.insertBefore(formTag, divTag); 524 | divTag.remove(); 525 | 526 | formTag.classList.add("doc_military"); 527 | formTag.append( 528 | createTag("label", (labelTag) => { 529 | labelTag.classList.add("spacer-row"); 530 | labelTag.append( 531 | "Missiles Stockpiled: ", 532 | divSpacer(), 533 | createTag("span", (spanTag) => { 534 | spanTag.append("?"); 535 | data.then((nation) => 536 | spanTag.textContent = nation.missiles.toString() 537 | ); 538 | }), 539 | ); 540 | }), 541 | createTag("label", (labelTag) => { 542 | labelTag.classList.add("spacer-row"); 543 | labelTag.append( 544 | "Missiles Manufactured Today: ", 545 | divSpacer(), 546 | createTag("span", (spanTag) => { 547 | spanTag.append("?"); 548 | data.then((nation) => 549 | spanTag.textContent = nation.missiles_today.toString() 550 | ); 551 | }), 552 | ); 553 | }), 554 | createTag("label", (labelTag) => { 555 | labelTag.classList.add("spacer-row"); 556 | labelTag.append( 557 | "Manufacture/Decommission: ", 558 | divSpacer(), 559 | createTag("input", (inputTag) => { 560 | inputTag.setAttribute("type", "number"); 561 | inputTag.setAttribute( 562 | "name", 563 | "missile_purchase_input_amount", 564 | ); 565 | inputTag.setAttribute("value", "0"); 566 | data.then((nation) => 567 | inputTag.value = ((nation.space_program ? 3 : 2) - 568 | nation.missiles_today).toString() 569 | ); 570 | }), 571 | ); 572 | }), 573 | createTag("input", (inputTag) => { 574 | inputTag.setAttribute("type", "submit"); 575 | inputTag.setAttribute("name", "missile_purchase_form_submit"); 576 | inputTag.setAttribute("value", "Manufacture/Decommission Missiles"); 577 | }), 578 | createTag("a", (aTag) => { 579 | aTag.setAttribute( 580 | "href", 581 | "https://politicsandwar.com/nation/military/missiles/", 582 | ); 583 | aTag.textContent = "Go to Page"; 584 | }), 585 | ); 586 | 587 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 588 | 589 | formTag.querySelectorAll("input").forEach((inputTag) => 590 | inputTag.toggleAttribute("disabled", true) 591 | ); 592 | data.then(async (nation) => { 593 | if (!nation.missile_launch_pad) { 594 | return; 595 | } 596 | await sessionStorage.Token(() => undefined); 597 | formTag.querySelectorAll("input").forEach( 598 | (inputTag) => inputTag.toggleAttribute("disabled", false), 599 | ); 600 | }); 601 | }); 602 | 603 | // Nukes 604 | createTag("form", (formTag) => { 605 | const divTag = document.querySelector("#rightcolumn>.row")!; 606 | divTag.parentElement!.insertBefore(formTag, divTag); 607 | divTag.remove(); 608 | 609 | formTag.classList.add("doc_military"); 610 | formTag.append( 611 | createTag("label", (labelTag) => { 612 | labelTag.classList.add("spacer-row"); 613 | labelTag.append( 614 | "Nuclear Weapons Possessed: ", 615 | divSpacer(), 616 | createTag("span", (spanTag) => { 617 | spanTag.append("?"); 618 | data.then((nation) => spanTag.textContent = nation.nukes.toString()); 619 | }), 620 | ); 621 | }), 622 | createTag("label", (labelTag) => { 623 | labelTag.classList.add("spacer-row"); 624 | labelTag.append( 625 | "Nuclear Weapons Manufactured Today: ", 626 | divSpacer(), 627 | createTag("span", (spanTag) => { 628 | spanTag.append("?"); 629 | data.then((nation) => 630 | spanTag.textContent = nation.nukes_today.toString() 631 | ); 632 | }), 633 | ); 634 | }), 635 | createTag("label", (labelTag) => { 636 | labelTag.classList.add("spacer-row"); 637 | labelTag.append( 638 | "Manufacture/Decommission: ", 639 | divSpacer(), 640 | createTag("input", (inputTag) => { 641 | inputTag.setAttribute("type", "number"); 642 | inputTag.setAttribute("name", "ships"); 643 | inputTag.setAttribute("value", "0"); 644 | data.then((nation) => 645 | inputTag.value = (1 - nation.nukes_today).toString() 646 | ); 647 | }), 648 | ); 649 | }), 650 | createTag("input", (inputTag) => { 651 | inputTag.setAttribute("type", "submit"); 652 | inputTag.setAttribute("name", "buyships"); 653 | inputTag.setAttribute( 654 | "value", 655 | "Manufacture/Decommission Nuclear Weapons", 656 | ); 657 | }), 658 | createTag("a", (aTag) => { 659 | aTag.setAttribute( 660 | "href", 661 | "https://politicsandwar.com/nation/military/nukes/", 662 | ); 663 | aTag.textContent = "Go to Page"; 664 | }), 665 | ); 666 | 667 | formTag.addEventListener("submit", formSubmitEvent, { passive: false }); 668 | 669 | formTag.querySelectorAll("input").forEach((inputTag) => 670 | inputTag.toggleAttribute("disabled", true) 671 | ); 672 | data.then(async (nation) => { 673 | if (!nation.nuclear_research_facility) { 674 | return; 675 | } 676 | await sessionStorage.Token(() => undefined); 677 | formTag.querySelectorAll("input").forEach( 678 | (inputTag) => inputTag.toggleAttribute("disabled", false), 679 | ); 680 | }); 681 | }); 682 | 683 | /* Functions 684 | -------------------------*/ 685 | async function formSubmitEvent( 686 | this: HTMLFormElement, 687 | event: SubmitEvent, 688 | ): Promise { 689 | event.preventDefault(); 690 | this.querySelectorAll("input").forEach((inputTag) => 691 | inputTag.toggleAttribute("disabled", true) 692 | ); 693 | await sessionStorage.Token(async (token) => 694 | new DOMParser().parseFromString( 695 | await (await fetch( 696 | this.querySelector("a")!.href, 697 | { 698 | method: "POST", 699 | body: [ 700 | ...this.querySelectorAll( 701 | "input[name][value]", 702 | ), 703 | { name: "token", value: token }, 704 | ] 705 | .reduce( 706 | ( 707 | formData, 708 | inputTag, 709 | ) => (formData.append( 710 | inputTag.name, 711 | inputTag.value, 712 | ), 713 | formData), 714 | new FormData(), 715 | ), 716 | }, 717 | )).text(), 718 | "text/html", 719 | ).querySelector('input[name="token"]')?.value ?? null 720 | ); 721 | this.querySelector('input[type="number"]')!.value = "0"; 722 | this.querySelectorAll("input").forEach((inputTag) => 723 | inputTag.toggleAttribute("disabled", false) 724 | ); 725 | } 726 | -------------------------------------------------------------------------------- /src/Nations.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Nations 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 10.0.1 5 | // @description Improves the Nations page UI 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/nations/ 8 | // @include https://politicsandwar.com/index.php?id=15* 9 | // @icon https://avatars.githubusercontent.com/u/44320105 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | import { createTag } from "@doctor/create-tag"; 14 | 15 | /* Double Injection Protection 16 | -------------------------*/ 17 | if (document.querySelector("#Doc_Nations")) { 18 | throw Error("This script was already injected..."); 19 | } 20 | document.body.append( 21 | createTag( 22 | "div", 23 | { id: "Doc_Nations" }, 24 | (divTag) => divTag.style.setProperty("display", "none"), 25 | ), 26 | ); 27 | 28 | /* Global Variables 29 | -------------------------*/ 30 | const scoreKey = "Doc_N1"; 31 | const ticksKey = "Doc_N2"; 32 | 33 | /* Main 34 | -------------------------*/ 35 | const formTag = document.querySelector('form[method="GET"]') as HTMLFormElement; 36 | formTag.parentElement?.insertBefore( 37 | createTag("div", (divTag) => 38 | divTag.append( 39 | createTag("label", { 40 | htmlFor: "Doc_Score", 41 | textContent: "Nation Score:", 42 | }), 43 | createTag("input", { 44 | id: "Doc_Score", 45 | type: "number", 46 | value: localStorage.getItem(scoreKey) ?? "0", 47 | }, (inputTag) => 48 | inputTag.addEventListener("change", function (_event): void { 49 | if (this.valueAsNumber.toString() === "NaN") { 50 | return; 51 | } 52 | const date = new Date(); 53 | if (this.nextSibling) { 54 | this.nextSibling.textContent = date.toJSON(); 55 | } 56 | localStorage.setItem(ticksKey, date.getTime().toString()); 57 | localStorage.setItem(scoreKey, this.valueAsNumber.toString()); 58 | updateIcons(this.valueAsNumber); 59 | })), 60 | createTag("br"), 61 | new Date(parseInt(localStorage.getItem(ticksKey) ?? "0")).toJSON(), 62 | createTag("button", { 63 | className: "btn btn-primary", 64 | textContent: "Refresh", 65 | }, (buttonTag) => { 66 | buttonTag.addEventListener( 67 | "click", 68 | async function (_event): Promise { 69 | this.disabled = true; 70 | const score = parseFloat( 71 | new DOMParser() 72 | .parseFromString( 73 | await (await fetch("https://politicsandwar.com/nation/war/")) 74 | .text(), 75 | "text/html", 76 | ) 77 | .querySelector( 78 | 'a[href^="/index.php?id=15"]', 79 | )! 80 | .href 81 | .split("?")[1] 82 | .split("&") 83 | .find((arg) => 84 | arg.startsWith("keyword=") 85 | ) 86 | ?.slice(8) ?? 87 | "0", 88 | ); 89 | console.log(`Score: ${score}`); 90 | const date = new Date(); 91 | if (this.previousSibling) { 92 | this.previousSibling.textContent = date.toJSON(); 93 | } 94 | localStorage.setItem(ticksKey, `${date.getTime()}`); 95 | localStorage.setItem(scoreKey, `${score}`); 96 | updateIcons(score); 97 | (this.previousElementSibling! 98 | .previousElementSibling as HTMLInputElement).valueAsNumber = 99 | score; 100 | this.disabled = false; 101 | }, 102 | ); 103 | buttonTag.style.setProperty("margin-inline", "0.5em"); 104 | }), 105 | )), 106 | formTag.nextElementSibling, 107 | ); 108 | updateIcons(parseFloat(localStorage.getItem(scoreKey) ?? "0")); 109 | 110 | /* Functions 111 | -------------------------*/ 112 | function inSpyRange(score: number, rangeScore: number): boolean { 113 | return rangeScore * 0.4 <= score && score <= rangeScore * 2.5; 114 | } 115 | 116 | function inWarRange(score: number, rangeScore: number): boolean { 117 | return rangeScore * 0.75 <= score && score <= rangeScore * 1.75; 118 | } 119 | 120 | function updateIcons(myScore: number): void { 121 | [...document.querySelectorAll( 122 | ".nationtable tr", 123 | )].slice(1).forEach((trTag) => { 124 | const tdTag = trTag.lastElementChild as HTMLTableCellElement; 125 | const theirScore = parseFloat( 126 | tdTag.lastChild!.textContent!.replaceAll(",", ""), 127 | ); 128 | [...tdTag.querySelectorAll("img")].forEach((imgTag) => { 129 | if ( 130 | imgTag.src === "https://politicsandwar.com/img/icons/16/plus_shield.png" 131 | ) { 132 | return; 133 | } 134 | imgTag.remove(); 135 | }); 136 | if (inSpyRange(theirScore, myScore)) { 137 | tdTag.insertBefore( 138 | createTag("img", { 139 | src: "https://politicsandwar.com/img/icons/16/emotion_spy.png", 140 | }), 141 | tdTag.lastChild, 142 | ); 143 | } 144 | if (inWarRange(theirScore, myScore)) { 145 | if (inWarRange(myScore, theirScore)) { 146 | tdTag.insertBefore( 147 | createTag("img", { 148 | src: "https://docscripts.stagintin.com/icons/green_red.png", 149 | }), 150 | tdTag.lastChild, 151 | ); 152 | } else { 153 | tdTag.insertBefore( 154 | createTag("img", { 155 | src: "https://docscripts.stagintin.com/icons/green.png", 156 | }), 157 | tdTag.lastChild, 158 | ); 159 | } 160 | } else if (inWarRange(myScore, theirScore)) { 161 | tdTag.insertBefore( 162 | createTag("img", { 163 | src: "https://docscripts.stagintin.com/icons/red.png", 164 | }), 165 | tdTag.lastChild, 166 | ); 167 | } 168 | }); 169 | } 170 | -------------------------------------------------------------------------------- /src/lib/localStorage.ts: -------------------------------------------------------------------------------- 1 | import type { Irrelevant } from "./utils.ts"; 2 | 3 | export const APIKey: { 4 | (): string | null; 5 | (set: string | null): void; 6 | } = function (set?: string | null): Irrelevant { 7 | return localStorage 8 | [set === undefined ? "getItem" : set !== null ? "setItem" : "removeItem"]( 9 | "Doc_APIKey", 10 | set!, 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/sessionStorage.ts: -------------------------------------------------------------------------------- 1 | import { lock, sleep } from "./utils.ts"; 2 | 3 | export function Token( 4 | func: (token: string) => string | null | void | Promise, 5 | ): Promise { 6 | return lock("Doc_Token", async () => { 7 | let token = sessionStorage.getItem("Doc_Token") ?? 8 | document.querySelector('input[name="token"]')?.value; 9 | if (token == undefined) { 10 | let response: Response; 11 | let dom: Document; 12 | while (true) { 13 | response = await fetch("https://politicsandwar.com/city/"); 14 | if (response.status !== 200) { 15 | throw `Failed to get Token | Status Code: ${response.status}`; 16 | } 17 | dom = new DOMParser().parseFromString( 18 | await response.text(), 19 | "text/html", 20 | ); 21 | token = dom.querySelector('input[name="token"]') 22 | ?.value; 23 | if (token) { 24 | break; 25 | } 26 | if ( 27 | dom.querySelector('img[alt="Politics & Snore"]') 28 | ) { 29 | await sleep(5000); 30 | } else { 31 | throw new Error("Failed to get Token | Token not Found"); 32 | } 33 | } 34 | } 35 | 36 | const response = await func(token); 37 | if (response === undefined) { 38 | return; 39 | } 40 | if (response === null) { 41 | return sessionStorage.removeItem("Doc_Token"); 42 | } 43 | sessionStorage.setItem("Doc_Token", response); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { createTag } from "@doctor/create-tag"; 2 | import * as localStorage from "./localStorage.ts"; 3 | 4 | export type None = undefined | null; 5 | // deno-lint-ignore no-explicit-any 6 | export type Irrelevant = any; 7 | 8 | const locks: Record = {}; 9 | export async function lock(key: string, func: () => Promise): Promise { 10 | while (locks[key]) { 11 | await sleep(50); 12 | } 13 | locks[key] = true; 14 | try { 15 | return await func(); 16 | } catch (error) { 17 | throw error; 18 | } finally { 19 | delete locks[key]; 20 | } 21 | } 22 | 23 | export enum GetLocalStorageKey { 24 | // deno-lint-ignore camelcase 25 | Doc_APIKey = "APIKey", 26 | } 27 | 28 | export const enum Ticks { 29 | Day1 = 86_400_000, 30 | Minute15 = 900_000, 31 | Minute5 = 300_000, 32 | } 33 | 34 | export function sleep(ms: number): Promise { 35 | return new Promise((a) => setTimeout(() => a(true), ms)); 36 | } 37 | 38 | export function divSpacer(): HTMLDivElement { 39 | return createTag( 40 | "div", 41 | (divTag) => divTag.classList.add("spacer"), 42 | ); 43 | } 44 | 45 | function userConfig(): HTMLDivElement { 46 | return document.querySelector("#Doc_Config") ?? 47 | createTag("div", (divTag) => { 48 | document.querySelector("#leftcolumn")!.append(divTag); 49 | divTag.setAttribute("id", "Doc_Config"); 50 | }); 51 | } 52 | 53 | export function userConfigLabel(label: string): void { 54 | const divTag = userConfig(); 55 | divTag.append(document.createElement("hr")); 56 | divTag.append(createTag("b", (bTag) => bTag.append(label))); 57 | } 58 | 59 | export function userConfigAPIKey(): void { 60 | const divTag = userConfig(); 61 | divTag.append(document.createElement("br")); 62 | divTag.append(createTag("button", (buttonTag) => { 63 | const apiKey = localStorage.APIKey(); 64 | buttonTag.append(apiKey ? "Update API Key" : "Insert API Key"); 65 | buttonTag.addEventListener("click", (_event) => { 66 | const response = prompt( 67 | "Insert API Key | It can be found at the bottom of the Accounts Page:", 68 | apiKey ?? "", 69 | ); 70 | if (response === null) { 71 | return; 72 | } 73 | localStorage.APIKey(response || null); 74 | location.reload(); 75 | }); 76 | })); 77 | } 78 | -------------------------------------------------------------------------------- /static/CNAME: -------------------------------------------------------------------------------- 1 | docscripts.stagintin.com 2 | -------------------------------------------------------------------------------- /static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | @import "https://unpkg.com/open-props"; 2 | 3 | :root { 4 | --background: var(--gray-9); 5 | --light-background: var(--gray-8); 6 | --color: var(--gray-0); 7 | --border: var(--gray-3); 8 | --highlight: var(--green-4); 9 | } 10 | 11 | * { 12 | box-sizing: border-box; 13 | } 14 | 15 | html { 16 | scroll-behavior: smooth; 17 | margin: 0; 18 | } 19 | 20 | body { 21 | background-color: var(--background); 22 | color: var(--color); 23 | display: flex; 24 | flex-direction: column; 25 | font-family: var(--font-sans); 26 | font-size: var(--font-size-1); 27 | font-weight: var(--font-weight-4); 28 | margin: 0; 29 | min-height: 100vh; 30 | padding: var(--size-3) var(--size-6); 31 | } 32 | 33 | header { 34 | background-color: var(--light-background); 35 | border-color: var(--border); 36 | border-width: var(--border-size-1); 37 | border-style: none solid; 38 | padding-inline: var(--size-3); 39 | } 40 | 41 | article > hr { 42 | margin-block-end: 0; 43 | } 44 | 45 | article > div { 46 | background-color: var(--light-background); 47 | padding: var(--size-3); 48 | border-radius: 0 0 var(--radius-2) var(--radius-2); 49 | } 50 | 51 | .spacer { 52 | flex-grow: 1; 53 | height: var(--size-3); 54 | } 55 | 56 | footer { 57 | background-color: var(--light-background); 58 | border-color: var(--border); 59 | border-style: none solid; 60 | border-width: var(--border-size-1); 61 | font-size: var(--font-size-0); 62 | padding: var(--size-3); 63 | text-align: center; 64 | } 65 | 66 | footer > div { 67 | display: grid; 68 | grid-template-columns: auto auto; 69 | } 70 | 71 | @media screen and (max-width: 480px) { 72 | footer > div { 73 | grid-template-columns: auto; 74 | } 75 | } 76 | 77 | hr { 78 | color: var(--border); 79 | } 80 | 81 | a { 82 | color: var(--highlight); 83 | } 84 | 85 | li { 86 | overflow-wrap: break-word; 87 | } 88 | 89 | code { 90 | background-color: var(--background); 91 | display: inline-block; 92 | font-family: var(--font-mono); 93 | padding-inline: var(--size-1); 94 | } 95 | 96 | blockquote { 97 | background-color: var(--background); 98 | border-color: var(--border); 99 | border-radius: var(--radius-3); 100 | border-style: solid; 101 | border-width: var(--border-size-1); 102 | color: var(--border); 103 | font-family: var(--font-mono); 104 | font-size: var(--font-size-0); 105 | margin: 0; 106 | padding: var(--size-3); 107 | } 108 | -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/favicon.ico -------------------------------------------------------------------------------- /static/icons/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/icons/green.png -------------------------------------------------------------------------------- /static/icons/green_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/icons/green_red.png -------------------------------------------------------------------------------- /static/icons/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackAsLight/DocScripts/fe3731eb19dd6391bb03aed4ce6874efa55b74cc/static/icons/red.png -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | document.querySelectorAll('a[href^="#"]').forEach(e=>e.addEventListener("click",t));function t(e){e.preventDefault(),document.querySelector(this.hash)?.scrollIntoView()} 2 | //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vdHMvbWFpbi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbDxIVE1MQW5jaG9yRWxlbWVudD4oJ2FbaHJlZl49XCIjXCJdJykuZm9yRWFjaCgoYVRhZykgPT5cbiAgYVRhZy5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgY2xpY2spXG4pO1xuXG5mdW5jdGlvbiBjbGljayh0aGlzOiBIVE1MQW5jaG9yRWxlbWVudCwgZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgZG9jdW1lbnQucXVlcnlTZWxlY3Rvcih0aGlzLmhhc2gpPy5zY3JvbGxJbnRvVmlldygpO1xufVxuIl0sCiAgIm1hcHBpbmdzIjogIkFBQUEsU0FBUyxpQkFBb0MsY0FBYyxFQUFFLFFBQVNBLEdBQ3BFQSxFQUFLLGlCQUFpQixRQUFTQyxDQUFLLENBQ3RDLEVBRUEsU0FBU0EsRUFBK0JDLEVBQXlCLENBQy9EQSxFQUFNLGVBQWUsRUFDckIsU0FBUyxjQUFjLEtBQUssSUFBSSxHQUFHLGVBQWUsQ0FDcEQiLAogICJuYW1lcyI6IFsiYVRhZyIsICJjbGljayIsICJldmVudCJdCn0K 3 | -------------------------------------------------------------------------------- /static/scripts/HideAllianceDescriptions.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Hide Alliance Descriptions 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 0.5 5 | // @description Hides Alliances's Descriptions set up by their government. Why? Because some people like to make them excessively long. 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/alliance/id=* 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | 'use strict'; 13 | /* Double Injection Protection 14 | -------------------------*/ 15 | if (document.querySelector('#Doc_AllianceDescription')) { 16 | return; 17 | } 18 | document.body.append(CreateElement('div', divTag => { 19 | divTag.id = 'Doc_AllianceDescription'; 20 | divTag.style.display = 'none'; 21 | })); 22 | 23 | function CreateElement(type, func) { 24 | const tag = document.createElement(type); 25 | func(tag); 26 | return tag; 27 | } 28 | 29 | const contTag = document.getElementsByClassName('ck-content')[0]; 30 | 31 | if (contTag.childElementCount) { 32 | // Insert button to toggle between Show/Hide Alliance's Description. 33 | contTag.parentElement.insertBefore((() => { 34 | const divTag = document.createElement('div'); 35 | divTag.style.display = 'block'; 36 | divTag.style.paddingBottom = '1em'; 37 | divTag.style.textAlign = 'center'; 38 | divTag.appendChild((() => { 39 | const buttonTag = document.createElement('button'); 40 | buttonTag.id = 'Doc_Button'; 41 | buttonTag.style.backgroundColor = '#337AB7'; 42 | buttonTag.style.color = '#FFFFFF'; 43 | buttonTag.style.padding = '1em'; 44 | buttonTag.style.borderRadius = '6px'; 45 | buttonTag.innerText = 'Hide Description!'; 46 | buttonTag.onclick = buttonClick; 47 | return buttonTag; 48 | })()); 49 | return divTag; 50 | })(), contTag); 51 | 52 | // Insert second button to Hide Alliance's Description. 53 | contTag.appendChild((() => { 54 | const divTag = document.createElement('div'); 55 | divTag.style.paddingBottom = '1em'; 56 | divTag.style.textAlign = 'center'; 57 | divTag.appendChild((() => { 58 | const buttonTag = document.createElement('button'); 59 | buttonTag.style.backgroundColor = '#337AB7'; 60 | buttonTag.style.color = '#FFFFFF'; 61 | buttonTag.style.padding = '1em'; 62 | buttonTag.style.borderRadius = '6px'; 63 | buttonTag.innerText = 'Hide Description!'; 64 | buttonTag.onclick = buttonClick; 65 | return buttonTag; 66 | })()); 67 | return divTag; 68 | })()); 69 | } 70 | 71 | // Function to Switch between Displaying and Hiding the Nation's Description. 72 | function buttonClick() { 73 | if (contTag.style.display == 'none') { 74 | document.getElementById('Doc_Button').innerText = 'Hide Description!'; 75 | contTag.style.display = ''; 76 | } 77 | else { 78 | document.getElementById('Doc_Button').innerText = 'Show Description!'; 79 | contTag.style.display = 'none'; 80 | } 81 | } -------------------------------------------------------------------------------- /static/scripts/HideNationDescriptions.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Hide Nation Descriptions 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 0.8 5 | // @description Hides Nation's Descriptions set up by the user. Why? Because some people like to make them excessively long. 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/nation/id=* 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | 'use strict'; 13 | /* Double Injection Protection 14 | -------------------------*/ 15 | if (document.querySelector('#Doc_NationDescription')) { 16 | return; 17 | } 18 | document.body.append(CreateElement('div', divTag => { 19 | divTag.id = 'Doc_NationDescription'; 20 | divTag.style.display = 'none'; 21 | })); 22 | 23 | function CreateElement(type, func) { 24 | const tag = document.createElement(type); 25 | func(tag); 26 | return tag; 27 | } 28 | 29 | const descDivTag = document.getElementById('descCollapseDiv'); 30 | 31 | // Hide Nation Description By Default. 32 | descDivTag.style.display = 'none'; 33 | 34 | // Only insert buttons if the Nation actually has a Description. 35 | if (descDivTag.children[0].childElementCount) { 36 | // Insert button to toggle between Show/Hide Nation's Description. 37 | descDivTag.parentElement.insertBefore((() => { 38 | const divTag = document.createElement('div'); 39 | divTag.style.display = 'block'; 40 | divTag.style.paddingBottom = '1em'; 41 | divTag.style.textAlign = 'center'; 42 | divTag.appendChild((() => { 43 | const buttonTag = CreateButton('Show Description!'); 44 | buttonTag.id = 'Doc_Button'; 45 | return buttonTag; 46 | })()); 47 | return divTag; 48 | })(), descDivTag); 49 | 50 | // Insert second button to Hide Nation's Description. 51 | descDivTag.insertBefore((() => { 52 | const divTag = document.createElement('div'); 53 | divTag.style.textAlign = 'center'; 54 | divTag.appendChild(CreateButton('Hide Description!')); 55 | return divTag; 56 | })(), descDivTag.children[1]); 57 | } 58 | 59 | // Remove Game's Nation Description Hide Button if User has it enabled. 60 | const descButtonTag = document.getElementById('descCollapseBtn'); 61 | if (descButtonTag) { 62 | descButtonTag.remove(); 63 | } 64 | 65 | // Function to Create Button to Show and Hide Nation's Description. 66 | function CreateButton(text) { 67 | const buttonTag = document.createElement('button'); 68 | buttonTag.style.backgroundColor = '#337AB7'; 69 | buttonTag.style.color = '#FFFFFF'; 70 | buttonTag.style.padding = '1em'; 71 | buttonTag.style.borderRadius = '6px'; 72 | buttonTag.innerText = text; 73 | buttonTag.onclick = ButtonClick; 74 | return buttonTag; 75 | } 76 | 77 | // Function to Switch between Displaying and Hiding the Nation's Description. 78 | function ButtonClick() { 79 | if (descDivTag.style.display == 'none') { 80 | document.getElementById('Doc_Button').innerText = 'Hide Description!'; 81 | descDivTag.style.display = ''; 82 | } 83 | else { 84 | document.getElementById('Doc_Button').innerText = 'Show Description!'; 85 | descDivTag.style.display = 'none'; 86 | } 87 | } -------------------------------------------------------------------------------- /static/scripts/Keno.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Keno 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 0.1 5 | // @description try to take over the world! 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/casino/keno/* 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | 'use strict'; 13 | /* Double Injection Protection 14 | -------------------------*/ 15 | if (document.querySelector('#Doc_Keno')) { 16 | throw Error('This script was already injected...'); 17 | } 18 | document.body.append(CreateElement('div', async divTag => { 19 | divTag.setAttribute('id', 'Doc_Keno'); 20 | divTag.style.setProperty('display', 'none'); 21 | await Sleep(10000); 22 | divTag.remove(); 23 | })); 24 | 25 | /* Global Variables 26 | -------------------------*/ 27 | const dataKey = 'Doc_K1'; 28 | const autoSelectKey = 'Doc_K2'; 29 | const selectionKey = 'Doc_K3'; 30 | 31 | /* User Configuration Settings 32 | -------------------------*/ 33 | document.querySelector('#leftcolumn').append(CreateElement('div', divTag => { 34 | divTag.classList.add('Doc_Config'); 35 | divTag.append(document.createElement('hr')); 36 | divTag.append(CreateElement('strong', strongTag => strongTag.append('Keno Config'))); 37 | 38 | divTag.append(document.createElement('br')); 39 | divTag.append('Auto Select: '); 40 | divTag.append(CreateElement('input', inputTag => { 41 | inputTag.setAttribute('type', 'checkbox'); 42 | inputTag.toggleAttribute('checked', localStorage.getItem(autoSelectKey)); 43 | inputTag.addEventListener('change', function () { 44 | if (this.getAttribute('checked') === null) { 45 | localStorage.setItem(autoSelectKey, 0); 46 | UpdateSelection(GetData()); 47 | } 48 | else { 49 | localStorage.removeItem(autoSelectKey); 50 | } 51 | }); 52 | })); 53 | 54 | divTag.append(document.createElement('br')); 55 | divTag.append(CreateElement('div', divTag => { 56 | divTag.append(CreateElement('strong', strongTag => strongTag.append('Select'))); 57 | 58 | divTag.append(CreateElement('label', labelTag => { 59 | labelTag.setAttribute('for', 'doc_strong'); 60 | labelTag.append('Most Likely: '); 61 | })); 62 | divTag.append(CreateElement('input', inputTag => { 63 | inputTag.setAttribute('id', 'doc_strong'); 64 | inputTag.setAttribute('type', 'radio'); 65 | inputTag.setAttribute('name', 'select'); 66 | inputTag.toggleAttribute('checked', !localStorage.getItem(selectionKey)); 67 | inputTag.addEventListener('change', () => { 68 | localStorage.removeItem(selectionKey); 69 | UpdateSelection(GetData()); 70 | }); 71 | })); 72 | 73 | divTag.append(document.createElement('br')); 74 | divTag.append(CreateElement('label', labelTag => { 75 | labelTag.setAttribute('for', 'doc_weak'); 76 | labelTag.append('Least Likely: '); 77 | })); 78 | divTag.append(CreateElement('input', inputTag => { 79 | inputTag.setAttribute('id', 'doc_weak'); 80 | inputTag.setAttribute('type', 'radio'); 81 | inputTag.setAttribute('name', 'select'); 82 | inputTag.toggleAttribute('checked', localStorage.getItem(selectionKey)); 83 | inputTag.addEventListener('change', () => { 84 | localStorage.setItem(selectionKey, 0); 85 | UpdateSelection(GetData()); 86 | }); 87 | })); 88 | })); 89 | })); 90 | 91 | /* Styling 92 | -------------------------*/ 93 | document.head.append(CreateElement('style', styleTag => { 94 | /* Config 95 | -------------------------*/ 96 | styleTag.append(CreateCSS('.Doc_Config', [ 97 | 'text-align: center', 98 | 'padding: 0 1em', 99 | 'font-size: 0.8em' 100 | ])); 101 | styleTag.append(CreateCSS('.Doc_Config hr', ['margin: 0.5em 0'])); 102 | styleTag.append(CreateCSS('.Doc_Config strong', ['font-size: 1.25em'])); 103 | styleTag.append(CreateCSS('.Doc_Config input', ['margin: 0'])); 104 | styleTag.append(CreateCSS('.Doc_Config div strong', ['display: block;'])); 105 | styleTag.append(CreateCSS('.Doc_Config div label', [ 106 | 'display: inline-block', 107 | 'font-weight: normal', 108 | 'margin: 0 0 0 25%', 109 | 'text-align: left', 110 | 'width: 25%' 111 | ])); 112 | styleTag.append(CreateCSS('.Doc_Config div input', [ 113 | 'display: inline-block', 114 | 'margin: 0 25% 0 0', 115 | 'width: 25%' 116 | ])); 117 | 118 | /* Tables 119 | -------------------------*/ 120 | styleTag.append(CreateCSS('#kenoStats, #kenoSelection', [ 121 | 'display: flex', 122 | 'width: 100%', 123 | 'flex-wrap: wrap', 124 | 'margin: 1em 0' 125 | ])); 126 | styleTag.append(CreateCSS('#kenoStats div, #kenoSelection div', [ 127 | 'display: block', 128 | 'flex-basis: 10%', 129 | 'text-align: center', 130 | 'border: 1px solid', 131 | ])); 132 | })); 133 | 134 | /* Functions 135 | -------------------------*/ 136 | function CreateElement(type, func) { 137 | const tag = document.createElement(type); 138 | func(tag); 139 | return tag; 140 | } 141 | 142 | function CreateCSS(selector, declarations) { 143 | return [selector, '{', ...declarations.map(declaration => declaration.endsWith(';') ? declaration : `${declaration};`), '}'].join(' '); 144 | } 145 | 146 | function Sleep(ms) { 147 | return new Promise(a => setTimeout(() => a(true), ms)); 148 | } 149 | 150 | function GetData() { 151 | const data = JSON.parse(localStorage.getItem(dataKey)); 152 | if (data) { 153 | return data; 154 | } 155 | let count = 80; 156 | const array = []; 157 | while (count--) { 158 | array.push(0); 159 | } 160 | return array; 161 | } 162 | 163 | function RecordData(data) { 164 | [...document.querySelectorAll('.miss, .hit')].forEach(tdTag => ++data[parseInt(tdTag.textContent) - 1]); 165 | localStorage.setItem(dataKey, JSON.stringify(data)); 166 | return data; 167 | } 168 | 169 | function UpdateStats(data) { 170 | const divTags = (() => { 171 | const divTag = document.querySelector('#kenoStats') || CreateElement('div', divTag => { 172 | divTag.setAttribute('id', 'kenoStats'); 173 | const scriptTag = document.querySelector('#kenoTable').parentElement.parentElement.nextElementSibling; 174 | scriptTag.parentElement.insertBefore(divTag, scriptTag); 175 | }); 176 | const divTags = [...divTag.children]; 177 | if (divTags.length) { 178 | return divTags; 179 | } 180 | for (const i in data) { 181 | divTag.append(CreateElement('div', divTag => divTag.setAttribute('data-i', i))); 182 | } 183 | return [...divTag.children]; 184 | })(); 185 | data.forEach((x, i) => { 186 | divTags.find(divTag => divTag.getAttribute('data-i') === i.toString()).textContent = `${i + 1}: ${x}`; 187 | }); 188 | } 189 | 190 | function UpdateSelection(data) { 191 | const divTag = document.querySelector('#kenoSelection') || CreateElement('div', divTag => { 192 | divTag.setAttribute('id', 'kenoSelection'); 193 | const scriptTag = document.querySelector('#kenoStats').nextElementSibling; 194 | scriptTag.parentElement.insertBefore(divTag, scriptTag); 195 | }); 196 | 197 | [...divTag.children].forEach(divTag => divTag.remove()); 198 | ([...document.querySelectorAll('.selected')] || []).forEach(tdTag => tdTag.click()); 199 | 200 | const tdTags = [...document.querySelectorAll('#kenoTable td')]; 201 | data = data.map((x, i) => [i, x]).sort((x, y) => localStorage.getItem(selectionKey) ? x[1] - y[1] : y[1] - x[1]).slice(0, 10).map(([i, x]) => { 202 | divTag.append(CreateElement('div', divTag => divTag.append(`${i + 1}: ${x}`))); 203 | return i; 204 | }); 205 | if (localStorage.getItem(autoSelectKey)) { 206 | data.forEach(i => tdTags[i].click()); 207 | } 208 | } 209 | 210 | /* Start 211 | -------------------------*/ 212 | Main(); 213 | async function Main() { 214 | await Sleep(1000); 215 | const data = RecordData(GetData()); 216 | UpdateStats(data); 217 | UpdateSelection(data); 218 | } -------------------------------------------------------------------------------- /static/scripts/RewardAds.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Reward-Ads 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 1.0 5 | // @description Autoplay Reward Ads 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/rewarded-ads/ 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | 'use strict'; 13 | /* Double Injection Protection 14 | -------------------------*/ 15 | if (document.querySelector('#Doc_RewardAds')) { 16 | return; 17 | } 18 | document.body.append(CreateElement('div', divTag => { 19 | divTag.id = 'Doc_RewardAds'; 20 | divTag.style.display = 'none'; 21 | })); 22 | 23 | function CreateElement(type, func) { 24 | const tag = document.createElement(type); 25 | func(tag); 26 | return tag; 27 | } 28 | 29 | // Creates an observer for mutations for elements. 30 | const observer = new MutationObserver((list) => { 31 | for (const mutation of list) { 32 | if (mutation.target.id == 'btnAds' && mutation.type == 'attributes' && mutation.attributeName == 'style') { 33 | // If "btnAds" button shows up again... 34 | if (mutation.target.style.display != 'none') { 35 | // Then check if we've hit the max for today... 36 | if (document.getElementById('rewarded_ads_watched_today').textContent == '25') { 37 | // If so then go to our Nation page. 38 | location.href = document.getElementsByClassName('sidebar')[1].getElementsByTagName('a')[0].href; 39 | } 40 | else { 41 | // If not then click button. 42 | mutation.target.click(); 43 | console.log('Clicked!'); 44 | } 45 | } 46 | // If "btnAds" button disappeared, and AggressiveMode is on... 47 | else if (localStorage.getItem('Doc_RewardAds')) { 48 | console.log('Aggressive Mode is Active!'); 49 | // Check if time until next ad needs to be updated. 50 | if (new Date(parseInt(localStorage.getItem('Doc_RewardAdsTimer'))) < new Date()) { 51 | console.log('Changing: ' + localStorage.getItem('Doc_RewardAdsTimer')); 52 | localStorage.setItem('Doc_RewardAdsTimer', new Date().getTime() + 1000 * 60 * 3); 53 | } 54 | 55 | // Set interval to check every second if timer is up. 56 | setInterval(() => { 57 | const ticks = parseInt(localStorage.getItem('Doc_RewardAdsTimer')) - new Date().getTime(); 58 | console.log(ticks); 59 | // If timer is up reset the page to run another ad. 60 | if (ticks < 0) { 61 | console.log('Resetting!'); 62 | 63 | // Hide Stuff. 64 | document.getElementById('ad-watched').style.display = 'none'; 65 | document.getElementById('countdown').style.display = 'none'; 66 | 67 | // Display the "btnAds" button. 68 | document.getElementById('btnAds').style.display = ''; 69 | console.log('Reset!'); 70 | 71 | // Clear all existing intervals. 72 | clearIntervals(); 73 | } 74 | }, 1000); 75 | } 76 | } 77 | } 78 | }); 79 | 80 | // Start observing attribute changes for the "btnAds" button. 81 | observer.observe(document.getElementById('btnAds'), { attributes: true, childList: false, subtree: false }); 82 | 83 | setTimeout(() => { 84 | if (!localStorage.getItem('Doc_RewardAdsTimer')) { 85 | localStorage.setItem('Doc_RewardAdsTimer', new Date().getTime()); 86 | } 87 | 88 | // Check if we've hit the max for today... 89 | if (document.getElementById('rewarded_ads_watched_today').textContent == '25') { 90 | // If so then go to our Nation page. 91 | location.href = document.getElementsByClassName('sidebar')[1].getElementsByTagName('a')[0].href; 92 | } 93 | else { 94 | // If not then if button is displayed... 95 | const adTag = document.getElementById('btnAds'); 96 | if (adTag.style.display != 'none') { 97 | // Click button. 98 | adTag.click(); 99 | console.log('Clicked!'); 100 | } 101 | } 102 | }, 2000); 103 | 104 | // Get User input on whether or not to set Aggressive Mode on. 105 | const codeTag = document.createElement('code'); 106 | codeTag.innerHTML = `Aggressive Mode: `; 107 | document.getElementById('leftcolumn').appendChild(codeTag); 108 | document.getElementById('aggressiveMode').onchange = () => { 109 | const inputTag = document.getElementById('aggressiveMode'); 110 | if (inputTag.checked) { 111 | localStorage.setItem('Doc_RewardAds', true); 112 | location.reload(); 113 | } 114 | else { 115 | localStorage.removeItem('Doc_RewardAds'); 116 | } 117 | }; 118 | 119 | function clearIntervals() { 120 | const id = setTimeout(() => { console.log('Cleared Old Timers!') }, 1000); 121 | for (let i = 0; i < id; ++i) { 122 | clearInterval(i); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /static/scripts/SendMessage.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Send Message 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 0.3 5 | // @description Easily Mass Message Players By Nation ID or Leader Name 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/inbox/message/ 8 | // @match https://politicsandwar.com/inbox/message/receiver=* 9 | // @match https://politicsandwar.com/inbox/message/id=* 10 | // @icon https://avatars.githubusercontent.com/u/44320105 11 | // @grant none 12 | // ==/UserScript== 13 | 14 | 'use strict'; 15 | /* Double Injection Protection 16 | -------------------------*/ 17 | if (document.querySelector('#Doc_SendMessage')) { 18 | throw Error('This script was already injected...'); 19 | } 20 | document.body.append(CreateElement('div', divTag => { 21 | divTag.id = 'Doc_SendMessage'; 22 | divTag.style.display = 'none'; 23 | })); 24 | 25 | /* Global Variables 26 | -------------------------*/ 27 | let total = 0; 28 | let magic; 29 | 30 | /* User Configuration Settings 31 | -------------------------*/ 32 | document.querySelector('#leftcolumn').append(CreateElement('div', divTag => { 33 | divTag.classList.add('Doc_Config'); 34 | divTag.append(document.createElement('hr')); 35 | divTag.append(CreateElement('b', bTag => bTag.append('Send Message Config'))); 36 | divTag.append(document.createElement('br')); 37 | divTag.append(CreateElement('button', buttonTag => { 38 | const apiKey = localStorage.getItem('Doc_APIKey'); 39 | buttonTag.append(apiKey ? 'Update API Key' : 'Insert API Key'); 40 | buttonTag.onclick = () => { 41 | const response = prompt('Insert API Key which can be found at the bottom of the Accounts Page:', apiKey || ''); 42 | if (response !== null) { 43 | if (response.length) { 44 | localStorage.setItem('Doc_APIKey', response); 45 | } 46 | else { 47 | localStorage.removeItem('Doc_APIKey'); 48 | } 49 | location.reload(); 50 | } 51 | }; 52 | })); 53 | })); 54 | 55 | /* Styling 56 | -------------------------*/ 57 | document.head.append(CreateElement('style', styleTag => { 58 | styleTag.append('#Message label { display: block; margin: 0.75em 0 0.25em 0; padding: 0 0 0 0.5em; }'); 59 | styleTag.append('#Message input { display: block; width: 100%; margin: 0; padding: 0.5em; }'); 60 | styleTag.append('#Message > button { display: block; margin: 0.5em auto; padding: 1em; font-weight: normal; font-size: inherit; color: #337ab7; border: 1px solid #337ab7; border-radius: 5px; }'); 61 | styleTag.append('#Message > button:hover { color: #fff; background-color: #337ab7; }'); 62 | styleTag.append('#Message > button:disabled { color: #fff; background-color: #70a1cc; text-decoration: none; }'); 63 | styleTag.append('#Message > .Failed:disabled { background-color: #ff8080; }'); 64 | styleTag.append('.Doc_Config { text-align: center; padding: 0 1em; font-size: 0.8em; }'); 65 | styleTag.append('.Doc_Config b { font-size: 1.25em; }'); 66 | styleTag.append('.Doc_Config button { font-size: inherit; font-weight: normal; padding: 0; }'); 67 | styleTag.append('.Doc_Config hr { margin: 0.5em 0; }'); 68 | })); 69 | 70 | /* Functions 71 | -------------------------*/ 72 | function CreateElement(type, func) { 73 | const tag = document.createElement(type); 74 | func(tag); 75 | return tag; 76 | } 77 | 78 | function Sleep(ms) { 79 | return new Promise(resolve => setTimeout(() => resolve(true), ms)); 80 | } 81 | 82 | async function SendMessage() { 83 | const body = document.querySelector('#Message .ck-content').innerHTML; 84 | if (!body) { 85 | return false; 86 | } 87 | 88 | const leaderNames = await (async () => { 89 | const text = document.querySelector('#to').value; 90 | if (!text.length) { 91 | return []; 92 | } 93 | 94 | const leaderNames = []; 95 | const nationIDs = []; 96 | text.split(',').map(x => x.trim()).forEach(x => { 97 | if (parseInt(x)) { 98 | nationIDs.push(x); 99 | } 100 | else { 101 | leaderNames.push(x); 102 | } 103 | }); 104 | 105 | const promises = []; 106 | while (nationIDs.length) { 107 | promises.push(new Promise(resolve => { 108 | fetch(`https://api.politicsandwar.com/graphql?api_key=${localStorage.getItem('Doc_APIKey')}&query={nations(first:500,id:[${nationIDs.splice(0, 500).join(',')}]){data{leader_name}}}`) 109 | .then(x => x.text()) 110 | .then(x => { 111 | JSON.parse(x).data.nations.data.forEach(y => leaderNames.push(y.leader_name)); 112 | resolve(true); 113 | }); 114 | })); 115 | } 116 | await Promise.all(promises); 117 | 118 | return [...new Set(leaderNames)].sort(); 119 | })(); 120 | if (!leaderNames.length) { 121 | return false; 122 | } 123 | const pTag = CreateElement('p', pTag => { 124 | pTag.classList.add('List'); 125 | }); 126 | document.querySelector('#Message').append(CreateElement('div', divTag => { 127 | divTag.append(CreateElement('h2', h2Tag => h2Tag.append('Sent To:'))); 128 | divTag.append(pTag); 129 | })); 130 | while (leaderNames.length) { 131 | const receivers = leaderNames.splice(0, 21); 132 | await fetch('https://politicsandwar.com/inbox/message/', { 133 | method: 'POST', 134 | body: (() => { 135 | const formData = new FormData(); 136 | formData.append('receiver', receivers[0]); 137 | formData.append('carboncopy', receivers.slice(1).join(',')); 138 | formData.append('subject', document.querySelector('#subject').value); 139 | formData.append('body', body); 140 | formData.append('sndmsg', true); 141 | formData.append('newconversation', true); 142 | return formData; 143 | })() 144 | }); 145 | pTag.textContent += `${receivers.join(', ')}, `; 146 | } 147 | pTag.textContent = pTag.textContent.slice(0, -2); 148 | return true; 149 | } 150 | 151 | async function GetPlaceHolder() { 152 | total = total || await fetch(`https://api.politicsandwar.com/graphql?api_key=${localStorage.getItem('Doc_APIKey')}&query={nations(first:1,page:1,vmode:false){paginatorInfo{total}}}`) 153 | .then(x => x.text()) 154 | .then(x => JSON.parse(x).data.nations.paginatorInfo.total) 155 | const count = Math.ceil(Math.random() * 10); 156 | const placeholders = await fetch(`https://api.politicsandwar.com/graphql?api_key=${localStorage.getItem('Doc_APIKey')}&query={nations(first: ${count},page:${Math.ceil(Math.random() * Math.ceil(total / count))},vmode:false){data{id leader_name}}}`) 157 | .then(x => x.text()) 158 | .then(x => JSON.parse(x).data.nations.data); 159 | return placeholders.map(x => x[Math.floor(Math.random() * 2) ? 'id' : 'leader_name']).join(', '); 160 | } 161 | 162 | async function DeletePlaceHolder(inputTag) { 163 | const text = inputTag.placeholder.split(''); 164 | while (text.length) { 165 | text.pop(); 166 | inputTag.placeholder = text.join(''); 167 | await Sleep(50); 168 | } 169 | } 170 | 171 | async function WritePlaceHolder(text, inputTag) { 172 | inputTag.placeholder = ''; 173 | text = text.split(''); 174 | while (text.length) { 175 | inputTag.placeholder += text.shift(); 176 | await Sleep(50 + Math.floor(Math.random() * 150)); 177 | } 178 | } 179 | 180 | async function WriteValue(text, inputTag) { 181 | inputTag.value = ''; 182 | text = text.split(''); 183 | while (text.length) { 184 | inputTag.value += text.shift(); 185 | await Sleep(50 + Math.floor(Math.random() * 150)); 186 | } 187 | } 188 | 189 | async function WaitForTag(doc, query) { 190 | let tag = doc.querySelector(query); 191 | while (!tag) { 192 | await Sleep(100); 193 | tag = doc.querySelector(query); 194 | } 195 | return tag; 196 | } 197 | 198 | async function NewMessage() { 199 | CreateElement('div', async divTag => { 200 | divTag.id = 'Message'; 201 | 202 | divTag.append(CreateElement('p', pTag => { 203 | pTag.append('To compose a new message enter the '); 204 | pTag.append(CreateElement('b', bTag => bTag.append('Nation ID'))); 205 | pTag.append(CreateElement('i', iTag => iTag.append(' or '))); 206 | pTag.append(CreateElement('b', bTag => bTag.append('Leader Name'))); 207 | pTag.append(' into the "To" field. '); 208 | pTag.append('To send duplicate messages, enter their Nation ID or Leader Name separated by commas into the "To" field. '); 209 | pTag.append('You\'re able to do a mixture of Nation IDs and Leader Names.'); 210 | })); 211 | 212 | divTag.append(CreateElement('label', labelTag => labelTag.append('To'))); 213 | divTag.append(CreateElement('input', inputTag => { 214 | inputTag.id = 'to'; 215 | inputTag.type = 'text'; 216 | })); 217 | 218 | divTag.append(CreateElement('label', labelTag => labelTag.append('Subject'))); 219 | divTag.append(CreateElement('input', inputTag => { 220 | inputTag.id = 'subject'; 221 | inputTag.type = 'text'; 222 | })); 223 | 224 | divTag.append(CreateElement('label', labelTag => labelTag.append('Body'))); 225 | const formTag = document.querySelector('input[name="newconversation"]').parentElement; 226 | formTag.previousElementSibling.remove(); 227 | formTag.parentElement.insertBefore(divTag, formTag); 228 | formTag.style.display = 'none'; 229 | 230 | divTag.append(await (async () => { 231 | const divTag = await WaitForTag(formTag, 'div'); 232 | divTag.querySelector('label').remove(); 233 | return divTag; 234 | })()); 235 | 236 | divTag.append(CreateElement('button', buttonTag => { 237 | buttonTag.disabled = localStorage.getItem('Doc_APIKey') === null; 238 | buttonTag.append('Send Message'); 239 | buttonTag.onclick = async () => { 240 | buttonTag.disabled = true; 241 | magic = false; 242 | buttonTag.textContent = 'Sending...'; 243 | if (await SendMessage()) { 244 | buttonTag.textContent = 'Sent!'; 245 | } 246 | else { 247 | buttonTag.textContent = 'Failed to Send Message'; 248 | buttonTag.classList.add('Failed'); 249 | setTimeout(() => { 250 | buttonTag.classList.remove('Failed'); 251 | buttonTag.textContent = 'Send Message'; 252 | buttonTag.disabled = false; 253 | }, 1500); 254 | } 255 | }; 256 | })); 257 | 258 | formTag.remove(); 259 | }); 260 | 261 | await WaitForTag(document, '#to'); 262 | if (location.href.includes('receiver=')) { 263 | Sleep(1500).then(() => WriteValue(location.href.slice(location.href.indexOf('/message/') + 9).split('&').filter(x => x.startsWith('receiver='))[0].split('=')[1].replaceAll('+', ' '), document.querySelector('#to'))); 264 | magic = false; 265 | } 266 | else { 267 | magic = localStorage.getItem('Doc_APIKey') !== null 268 | } 269 | while (magic) { 270 | const inputTag = document.querySelector('#to'); 271 | const text = await GetPlaceHolder(); 272 | await DeletePlaceHolder(inputTag); 273 | await Sleep(50 + Math.floor(Math.random() * 150)); 274 | await WritePlaceHolder(text, inputTag); 275 | await Sleep(5000); 276 | } 277 | } 278 | 279 | /* Start 280 | -------------------------*/ 281 | function Main() { 282 | if (location.href.includes('id=')) { 283 | const divTag = (document.querySelector('.blue-msg') || document.querySelector('.red-msg')).parentElement; 284 | divTag.style.removeProperty('height'); 285 | divTag.style.setProperty('max-height', '50vh'); 286 | return; 287 | } 288 | NewMessage(); 289 | } 290 | 291 | Main(); 292 | -------------------------------------------------------------------------------- /static/scripts/TeamBuilding.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Team Building 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 0.9 5 | // @description Increase Player Stats with less Clicks. 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/obl/team/id=* 8 | // @icon https://avatars.githubusercontent.com/u/44320105 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | 'use strict' 13 | /* Double Injection Protection 14 | -------------------------*/ 15 | if (document.querySelector('#Doc_TeamBuilding')) { 16 | throw Error('This script was already injected...') 17 | } 18 | document.body.append(CreateElement('div', divTag => { 19 | divTag.id = 'Doc_TeamBuilding' 20 | divTag.style.display = 'none' 21 | })) 22 | 23 | /* Global Variables 24 | -------------------------*/ 25 | let upping = false 26 | 27 | /* Styling 28 | -------------------------*/ 29 | document.head.append(CreateElement('style', styleTag => { 30 | styleTag.append('.Up0 { background-color: #FF0000; color: #FFFFFF; font-size: inherit; padding: 0.5em; }') 31 | styleTag.append('.Up1 { background-color: #2648DA; color: #FFFFFF; font-size: inherit; padding: 0.5em; }') 32 | styleTag.append('.Stop { background-color: #D9534F; color: #FFFFFF; display: none; font-size: inherit; padding: 0.5em; }') 33 | styleTag.append('.Text { display: inline; margin: 0; padding: 0.25em; }') 34 | styleTag.append('.Up:hover, .Stop:hover { color: #FFFFFF; }') 35 | })) 36 | 37 | /* Functions 38 | -------------------------*/ 39 | function CreateElement(type, func) { 40 | const tag = document.createElement(type) 41 | func(tag) 42 | return tag 43 | } 44 | 45 | function RandomText(length) { 46 | let text = '' 47 | const char = [ 48 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 49 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 50 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 51 | ] 52 | while (--length) { 53 | text += char[ Math.floor(Math.random() * char.length) ] 54 | } 55 | return text 56 | } 57 | 58 | async function IncreasePlayer(id, type, playerID, verify, buttonId) { 59 | [ ...document.querySelectorAll('.Up0, .Up1') ].forEach(buttonTag => { 60 | buttonTag.disabled = true 61 | }) 62 | document.querySelector(`#Up${ buttonId }_${ id }`).style.setProperty('display', 'none') 63 | document.querySelector(`#Stop${ buttonId }_${ id }`).style.setProperty('display', 'inline') 64 | upping = true 65 | let money = 0 66 | let total = 0 67 | try { 68 | while (upping) { 69 | const time = performance.now() 70 | const doc = new DOMParser().parseFromString(await (await fetch(location.href, { 71 | method: 'POST', 72 | body: (() => { 73 | const formData = new FormData() 74 | formData.append(type, '+') 75 | formData.append('playerid', playerID) 76 | formData.append('verify', verify) 77 | return formData 78 | })() 79 | })).text(), 'text/html') 80 | total += performance.now() - time 81 | const level = parseFloat([ ...doc.querySelectorAll('input[name="playerid"]') ] 82 | .filter(inputTag => inputTag.value == playerID) 83 | .slice(1) 84 | .map(inputTag => inputTag.parentElement) 85 | .find(formTag => formTag.children[ buttonId ].name === type).textContent.trim()) 86 | if (level === 100) { 87 | upping = false 88 | Sound() 89 | } 90 | document.querySelector(`#Text_${ id }`).textContent = level.toFixed(2) 91 | if (buttonId === 0) { 92 | money += 20000 93 | } else if (buttonId === 1) { 94 | money += 2000 95 | } 96 | } 97 | } 98 | catch { 99 | location.reload() 100 | } 101 | [ ...document.querySelectorAll('.Up0, .Up1') ].forEach(buttonTag => { 102 | buttonTag.disabled = false 103 | }) 104 | document.querySelector(`#Up0_${ id }`).style.removeProperty('display') 105 | document.querySelector(`#Up1_${ id }`).style.removeProperty('display') 106 | document.querySelector(`#Stop0_${ id }`).style.removeProperty('display') 107 | document.querySelector(`#Stop1_${ id }`).style.removeProperty('display') 108 | console.log(`Money Spent: $${ money.toLocaleString() } | Average Time: ${ (total / money * 2000).toFixed(0) }ms`) 109 | } 110 | 111 | /* Start 112 | -------------------------*/ 113 | function Main() { 114 | [ ...document.querySelectorAll('input') ] 115 | .filter(inputTag => inputTag.name === 'verify') 116 | .map(inputTag => inputTag.parentElement) 117 | .filter(formTag => formTag.action.endsWith('#roster')) 118 | .forEach(formTag => { 119 | const id = RandomText(10) 120 | formTag.parentElement.appendChild(CreateElement('button', buttonTag => { 121 | buttonTag.id = `Up0_${ id }` 122 | buttonTag.classList.add('btn') 123 | buttonTag.classList.add('Up0') 124 | buttonTag.append('+') 125 | buttonTag.onclick = () => { 126 | IncreasePlayer(id, formTag.children[ 0 ].name, formTag.children[ 2 ].value, formTag.children[ 3 ].value, 0) 127 | } 128 | })) 129 | formTag.parentElement.appendChild(CreateElement('button', buttonTag => { 130 | buttonTag.id = `Stop0_${ id }` 131 | buttonTag.classList.add('btn') 132 | buttonTag.classList.add('Stop') 133 | buttonTag.append('x') 134 | buttonTag.onclick = () => { 135 | upping = false 136 | } 137 | })) 138 | formTag.parentElement.appendChild(CreateElement('button', buttonTag => { 139 | buttonTag.id = `Up1_${ id }` 140 | buttonTag.classList.add('btn') 141 | buttonTag.classList.add('Up1') 142 | buttonTag.append('+') 143 | buttonTag.onclick = () => { 144 | IncreasePlayer(id, formTag.children[ 1 ].name, formTag.children[ 2 ].value, formTag.children[ 3 ].value, 1) 145 | } 146 | })) 147 | formTag.parentElement.appendChild(CreateElement('button', buttonTag => { 148 | buttonTag.id = `Stop1_${ id }` 149 | buttonTag.classList.add('btn') 150 | buttonTag.classList.add('Stop') 151 | buttonTag.append('x') 152 | buttonTag.onclick = () => { 153 | upping = false 154 | } 155 | })) 156 | formTag.parentElement.appendChild(CreateElement('p', pTag => { 157 | pTag.id = `Text_${ id }` 158 | pTag.classList.add('Text') 159 | pTag.append(formTag.textContent.trim()) 160 | })) 161 | formTag.remove() 162 | }) 163 | } 164 | 165 | if (document.querySelector('a[href^="https://politicsandwar.com/obl/team/id="]').href === location.href) { 166 | Main() 167 | } 168 | 169 | function Sound() { 170 | new Audio('data:audio/mpeg;base64,SUQzAwAAAAAAI1RJVDIAAAAZAAAAaHR0cDovL3d3dy5mcmVlc2Z4LmNvLnVrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//qQACOeAAAClmI+BjTgAFOMR7DINAAKbWMKHTGAAUMsIUOmMADwEgAS/gswG54jA8PALAf+E4OAef4kA/G5P/xIB4JAPwf//iWDwHgsEsb//5MaDRhuf///EggYJbxoaf///43PiQNCAOxuTUf/////JhOOECA4Q8FQG/+Hvhcx4oAcHhIxG/x5iYD0/yUEzHuS//iYD0KCZf//KYmA5CgOce//+boIILT///jka9MYQlG///8eg9DQlDRy4yZTHZ////+ShfeF4HIUAJ5E6k6XTVFFFnepSkn+SkRfsh8/8skyeSRju6UiLL75mXnH/pSU4eS1iZTOvrafotfWAq2VfA3IkiE8shOUsQ8OGqSPUHhPKWHFmyI22pPOmwRIE6idSdLqKKKLO7qep0vLv/M/5fI8siytPK9zJU6l/I8jLzIjOP2QsyvlORUq3shSY65I6lXzUlSzQPmRS9NxqenQ/mhMYt5TX6yzquhAga1qCsGdnWkanz5oboLUqUWU1yuOQ7Klu3Y96PZ9WpyOjIhNli/2qcY5DOnivikatvIh//qSADLoGAESjFbCgmIbclOrGFBgJW5O1IMVNbyAAd4QYva3oAD3yRKXEbUQZJA55WHJWa2cYz4Z3RGgeNivzejBD6JjfXFymGN/nd5Wb8/OU+G+H/Igtpkl49XYuS70MFjEaynvvUrlahtnq7MhzqclWc5mvu7lY6MNPcpJmMry3VkU4k12FybuUiyodSoPD6GeKROyMjAhIoAA2S7CBiSZORiMCIAsxMHRuMUBiQANIUjQRM2GpOhTRQQMqPDMgQyM3Aw0Dgh9odk8oliCBFkDFmlke5ZmwjVREQXDWvL8ML8pQTq/VUSsm8MP//////1dl8onNYV3IigYt+z7da+Y1qa6iq917mJ2v5aaU5NwTAITaTkZQDjkx8bQaJAcxkJFQAFBidhoCIZiEmnw5uYqZYvHDTxvw0Y+TllkDFVG9poxOCAMAAIGbmqiG9FmXUgo8CgpmzIYLdeURCB2fgECqNShqLE7f8/99///POJz8YxzuvuyyWBj//+izihb/vRrIU8UopqlagAABAIKIAAAQIekLyUpsrSbm+xuGVWOIf/6kgA49QwAAwwx0c5toABgRKp9zTQAC9ihQlnWgAF0EChLOtAAw6KcoTPrcicYMRGwgXrsABOC8U2PkYwKAOwEbEsqSapTBVBjDIO+6HTGAMRzl0l7f+5LpFxIuf/7GiJuxm//D4ZDAYP/+gmTSQAAAACYIFI8FwHA5AgGpp2cxNizNm1t1XsihuS5xAkp1XsGBBq/rTQBCxMjdbmqBoHMF8emZalMPAmHi/1pIa0ygZm5m9vTT9A+CAg6HYeJk/v6SBBBP/5MMkCCG//6SaAAAEIMBAGJlEcpYCEwqXoMCkxhHg+cQ4w1Fs3IccweDk/rEMwGJoiAIKAWRCMzQulLR2gevhIRijA/jGRJw4v8yJY6kv/y6iaqRb/8yUeInf1hoNHg7/yLWov/+wKgIAABCDALBmZRGKQBGYQMELBSYyiofaHQYXjGa/OWYQBaf8gaYBE4RAGCALIhKaAgClokoEx8SkYoyfxJlD2Jb/MikTZj/5qYmpkW/BoGSp38qAlBQf/1jQle//9xKgAm4AAAABDATwGAwGkCwMDbAMjBFAf/+pIAlVsLCdJnC0g3fSAAU+Fo8u+wAApQKxgPf2SBT4VjAe/skCzCnheU0NYZcNSdGYjD3Q/QwpAIVMIODUTABgbUwMQBbMA+AISAAELpNOWiBSFXOn8PU/eLIA5KAABABQwGcB2MCBAzjA8QBAwV4IvMM6HFTTMAtA18McUMRJEbDC3gk0wkgPVMAAB8zA7gHUwFAAtFAAYHABSulVQ+Wre6f0U/Yino//o/7v///6DA6BLMBkHYwCAvTAFE+MBQm0wf3NjEYPWIxycgaMBtCRTBNQH0wpgDvMCEAeT+xIFUoYfEwaoW2joR+s7/rrf9e/6/2abqzVqqV/v/VSk728XFTA+BNMCEHYwGAuzAhE6MEQmMw33IjGYPMoydUf8MACCKzBHwEkwowFlMCEArT+R41IBBx8RBqiLaQBH6x3/r2fW7+vt//IFn3r39O1ZQhsjSXymK1SgKzEwIDI0VzOgpDaFRz6rnjVIXfI3VwRoMPzCNzBhwhUwYQTKHAig8PkyCgEjS5ilbDIci9J3o/yXbVktfT/7G1F0czNUkWpfU//qSAJ2lJQ/SkQtGA7/RIE/hWNB3+iQKPC0aD39kgVMFYwXv7JCVSTs53i0TSokCZh4ERjqKpmITxrGnp5BxxpC7tMbI0IjGG0hAZgroOaYLkILAgHUPDjMkeBIdBIudrEOS+kf/+7/di/f1fj1eqh+tjnBZ3UtLH7m5lO+VMC0DwwOgUzBNB8MHgNMw4xqjIiUjM0fsbTS4BaMwdUEUMBaAvTBuwvIwG0FeOwSzNRQxABQHL1npmms9/9lH+j9n6en9r+s3ilL9Hpcf3ydT1EMDcEYwSgXTBjCIMJwOUxHx0DKkWVNALwTTW+hhkwlcEOMBEBATB3Q6AwH0GwPCYTPR4xQHCAJWGXTMasv/6qjqf6vq//9F1imV7WWaxYhvo2ENK0IgAYAwYDoJJgmBCGEiHOYhA5BkoqgmarWHZpGYwmYV0EhGC1AwhhAIX2YBuCmnSHhmIaChxAepou+G5Zbd/9Tfp///X+iy5bkfUr76VbhiC02IgHjAXBNMEQIowhQ8TD0HaMidXUzFq39NDHGSTClgkQwWgFyMIRCwTAPwTP/6kgDdoD6N8okKxovf2SBRwVjQe/skClgtGA7/RIFQBWMB3+iQTrjQzMJCCAv2pou+G5ZTnO/6qn/V/1fq1u+v0tcu/34/erKLfipiUGpiyFxkMHZmmMxr0dZ7fkRqHDh0bkiGaGGtAOhgTQNAYGsJMmAtBDZ7/hnFpgxiGLIo7fkN/Hp/0/tp/P/rd/2PS7XYx0cw7teqgJ1Q6l6g4YkBeYshIZEBWZsicbDF2fC1IalQwXG6RhTxhvAFcYFwDaGBQCWJgKgROd38ZReARiE1mUG0chvne//f/v+Lu29Hd6imyKX73piNFyJ0qb5pYrOoTTAoA9EYNRgEhZmBkJYYM5KZh0NrGJs89hi3w98YMWEzmCtgfphQQIAYDcArB+uTShMbEwImetSGJu59v+2nrR0//6MxV7PTqdVdRXT68frMCwEECg2A0LMwGhLDBBJTMJBt4wsPoyMKQHwDBQQlcwU0DbMKAAgjAdABYb2h6VGjYoBE416QxQiv/t/8Tft+j0O9LP8h7+ruFufcowfAQMKAyHB0ziEg2XJ0+YaA1OP/+pIAdExXj/J6C0YD39kgTEFIwHv7JAnALRgO/0SBNQWjAd/okDR43SoJCMPDBszBPQe8wNQS6MAVCJjK+xAqHRpAAW20iHJHOd6P9HRso99n/Q9y/Qa6Nnin6L670mKAMGxicARkKFpnEMRsmZp8hGBqWaP4bm0FuGHhg6Zgp4PmYIIJYGAFhEBrfIEVCMaMAFlsohyLznbaP00f6P/0+nrvIfb+invuR0taelkwKQRDAuBnMDcJwwThCzCXIOMYhjsyc3VDM6QG5zBCQawwMgA7MItCNjAewQY9Y1NFDAcUiQUv115LNXf/9lH7KP2fem3907KU/27u8iMuKj798XMC0EQwNAZTBBCWMGAQEwtR/jHEYPMshz3zQWhq8wVUGKMCuAazCGQoQwHsEqPQPzQxAFFIcDL9deSzQt/7K/2f7P9tOuKW0c5rR8rngPW44QesmbShRgFABGBaBeYLQMhhQhamJQLIZTqCZnk0RuammJlmF3A/xgpQMkYMuGxmAIgwJ9pxnSpggCK67GdxuWX+f+Q+rbEf/V2Z3Z9rLPV0//qSAAwteg3ynAtGA9/ZIFVBSMB7+yQKNC0aL39EgT2FY0Hv7JD2Wp6Y8jJmAMAQYE4GpgqA2GEyGCYjgvRlAormc9SRZp64oqYWsEEGCnAxBg14Z2YA2C2nBJBkYiAghB9djO4flnXd+/Vv/3///d8e9X7KNH+W0D2XKTDgLTD8NjFgUTJEjjQpLTnXwjOvoOI1Q8QAMJKAXzAEAQowS4NXMBWBiT+1DQGTBgkhWHS21S2sf/0/tp7dv/t69n2+EbUK6yl39lZiIGpiaHRjcKpliShqEnZ3j+BowEUYbHWIUGFlAF5gIAKcYJsH8mAxA6pwrQZIRmChBf1YaM2pbaO//v/3621mf+h11ef/03xJFFKzi31pc9DmDAZIYCAF4ABaMDEJ0wZBDjDDIRMbZhQycXNsMzYG9jCNwlkwWcFHMJGCIDAUAOQ8sfAzwGEYsAJ9sQhiN1O/+2Jv229Ps/0elrHn3o9Xi+xExcG9WSK0EMA4CkQAnGA6ESYIwdxhIjqmKUrYY7LcFGTIDGRg34QOYJiCCmEBAyhgJAFEdmHhDP/6kgDls5MN0l8LRoO/0SBUYVjAd/skCpwtGC9/ZIFRBWNF7+yQgkNoWL3a5GKep/663fX++vR/9+Up772JDqKr45KWJShDFVVqMIwYMTgHAx/ApnTXUJD04/zTYyn02zwCdMNdBEzA4wa8QCMhgHYQEaDmCUggGo7NFdiWT97vQr+S/ZJfs/74slU4s73f3ReLuQcFXQg5EkxBhCCQKJ0mP8DNAArzPVBBNOBEzzbVgF4w2sE7MD3BuTAQBGowDMIEMRzEKQUGpLNHdiWT949fd/u/et1nUn9FuEakGmZgjTjtemxRjxKynWYDIHhgJAxmAKE0YAwhZgLkHGDkxGYe/odmNSDbpgIgPMYIEAuGEXgcBgNYDQegFhkMJFJEBLcceJ0FX//ZR/o/Z/6Tv20f5AVQ1ma3b1UNlDAjBKMCAHMwGQsDAkEvMEslIw4m7TGWeuEyhwe5MAfCEjBFADownUFyMCBArj8B0BTwYajwUrQ0+JzVX/1Vf/9X8kvz70FVsz3z5hSKVtS5zaanTaZgCYPDD0GzHARjMEizV5FDxXH/+pIAYtWsD/KFC0YDv9EgT8FYwHf6JAoULRgPf2SBUwViwe/skJNHcZ8zYvQ80w1IHgMFIBvjBSRAUQA6J0Mhiz4IDJprka2/cUv/Z+dqQRo9vt//l1No7kzuK6HpH1WFdHIidZEyk5QJBoYchGY2CgZek6arJseA7yaLY6UmwIiGphowPoYKgDeGC0h9I6DhnewGTOgQInOsI1uH4pf+yn/T/kf0f+Uf1qO2tybrKiNGt34shVFZEGBgBeYFoIBgeAxmDGFYYXgsJjmolGWPSlxoF4lgYMcBvmAhgUBgtgUGYC0CTnInRkweBhJDVp05QTOTv+vf8V3+pf/3f/P/Xd0UEEW7NZDAyAtMDEDwwQgXTBtCgMM4U0x80FDL6o3A0T8RuMG6AuzAJQLowVYKxMBYBPTiUAyIRAwcj606irVcnf/noorX///8hqHfQP6futdXsthuBgDJgMAkGCSD+YQwbxh7jbmRkoMZlXVGGiGC6hhS4RWYKyC3mD0BZRgFwJsc+dmVBQOE0NFY2WQxL8P/X/3/2dr9Gj/+gOvZPrFa//qSAMVDxwnSqQtGC7/RIE6BaMB3+iQJ8Csar39kgT6FY0Xv7JJxClxwDkGsFZcgAQAuYBgHxgag4mDQGMYYoxpjjJJGUjzZhm3wqMYRGDymCUgmJg2wR8YBYB6HHlJkgEGB6ajJ3EjFPh/663fX/36/79FqmNualey+m+xKr7kTJhuGxjEG5kiIRnQNptCfh95q5q9ruub4kGsmHUgRJgWoOaYHCKAGA4hJxuUOZCZGDCIXAF4wRIJFZy6P2OR7HMKiRnoYnSZXYlJ5OcXF8SDTZQUAQKEgM9Z68JniCQNQ4WMAd0gYahgYxBaZKhgZ3CibWmUfpWaazKyzm/UBWZh5IGSYGmDwmBTijRgNIS0a9FmMmhgQiIQBfLwSCRTvej9jEerlP3/X6WrCes+qQXFHtUF2qS+toZBVTKHoD4OEzYqqMAMC4QAqmAAEcYEYe5gnDzGFitOYfJgNmITjM5gnAPsYI0BjGEHgXJgK4ByGvBM5kxIRAazHLjdTf/qX/V/q/96fr/lsslrHBCuNHk0UAafteowDQOjAABlEAUxgLv/6kgD64eOP0qAKxgPf2SBSYVjRe/skDJgtFA7/ZIF2haKB3+yQI6YHBFxg8s9mE5bgpgvY6eYJAEYGCcAYhhLYCyYDWACk9yPRRQalAWnA1+G6l3X/siv5H/OFKPlGXfEswoVcye3SusNdKVPraxpkwDwAHGgLcwJYAmMDzAVzBtAKww9kHTNZWO8zfIwpExAYHYMFWCEjA8RREwCoJNMAaAiAqAXjAAgAI4OgKmOcgFJHV7/9TXV3//f1q/+qN1K21l90VSK2j1emnZfIc8n9/pTerq2zW9+ZDSjtTSAIBxAwFmYEoAbGB4gOJg2AHAYeiE2msRKBpvY4ZiYgQD9GCzBCxgkYoGYA8EimAZAQxgAIBeIABACqH6CFxzyQKyPX3//fqf/9Xv0//tVGal9KpiO+/92StrUqla79adUz0lTOmqELSrIw15UwCYArMAZAQDAHgGcwDEC4MBqBczBFRGwxxaYqMrJE5jAwASswIkAkMGEBmzAUALkwJEAgMAqAAQ4AODQRYiQMDFJv///7//r//+v//9fVv+n/Slf/uZv/+pIAlUftD/KSCsYD39kgVoFosHv7JA29XRQPtE3JsavigfgJufTVff65M842ggDA+A/MC8FowOAizBZDuMKwdsxqVnDKdsDQz6sYuMFIBazAnwGQwe4JlMByBGTvzozwNBRENAq9oOmKoc/9sJd+2n6m9TaMvYV89XFg24lfrHGmbktszSDYqDL0oIQBTAuAwMGEGswrAwDE5F/MsBFgz/2ScNXEFIjDGQh8wV8GrMG0DjjACgZg4VTMgGDBQVAIrtlDuS+pvo+r+f6O+YZr+Xya4q69dhws1685KTg8WqRa1el9TFjACJgaAiGDaEOYYAdxisjpmYIqQaLTY7GvMDGhht4T2YMuDumEIB8BgDgN4dStmXDhhoOW0TXXI5EbpPd/u363zHv3hyHW0s3qoAsdnlWUa1GPxM61SHCoqfFiNTAtA+MFAEUwcwZjC0CxMUMWYy/EfTRt5uM2XYS/MLLAajAHQUkwWcQKMB1B3DnGky8kMKCS7ywsPVZ6l7/+j9ko6+lZ79Kzjk+q2RRDAnFiijTz8qdIRYNMAYZPHsYN//qSAFTK6Y/S/FdGg/ATcF1BSLF7+yQLXC0WD39kgW8FYoHv7JBTVGBaB4YKQH5g7gtGF8E6YqooRmPIOmk4x5JtNAiWYXqARGAjgsZgn4hoYDiD3HHNplJMYQEl1lyxqzIrOv/yv1Lo6KWbDl6mISSba9kkLjGMc1I8CiQiLuA54iskBkg8dUDxVpKb0jIEwJBTMCgJAwWw9zCwHkMY1ZMyN69RMtRGZTB/AiQwUoEmMIVB4TASQM07gaAzWHECSbBHHduUV/s/2Wfss+t+9vcNKLChRfTefQsm5RxYAeLMzIKKLHp8ilomF7agqBkIQZTAmCsMFsSMwsiNDGJZ9Mi227zK1R2cwhAJsMFyBODCaQdEwF0DFPaGANFiRgLAauGvuXKK9X+rP/U2//3C+BNQ0y8vXFwAfFlpnBCNatMFlhQexjhJe4VSfYsxEBsxRAwFIKHNUbBAye4n0ag8UAG4mgOhhvgJQYHcDfGAGiR5gJgQyZn0CE4hFI6syfeL0V/zfT779eef2V9dKKWrGuKrU9DVXFtoXtYKvMUJDLyATP/6kgAFFO2P8wcLRQPf2SBmAWige/skC9QtFg9/ZIF+BWKB7+yQX4iSaW8XQAxABUxPAAeQgzXAc2GA493C41DoPQNxhAlTDjAVAwQQHIMBVElTARAhsA+hEnFRSSrPnbh+TW/6X6yN1N16tSb5Tu1qtvegY4XHllAvO4pqcedchaG1lCIBA9EQyo4wNMDYEMwFQZDAGChMAMRAwGiGzCFZOMQS1xDHexxAwDcH1MEJAXTCRgP8wHQBxPXEgdEixUUBCtbuQBN1v/9tONzOn3RdeiTOdHvMMatztJo+bxYGWgHSPrFAATEFzgkoiYHAIZgOgyGAkE6YDYhhghEJmGux0YuZqVGTbjehgFgO2YHmAWmEdAsJgPAE6eqPAKJEiooDVOX8iE3W/3V/xuvTZ09dErMuZoFnyU6XStoWclRYPdO5DTrkjXuLKigBzD4GDHcRzMYjjWNHTyDNjSRWyI2SQP0MNlB7zBUQccwVYQYAIOodXKY5ADRKNjA3AhyR2O9H+j+tHQqnWSEVFdheQqg4k2JMvS5FYoKJ7z2wqhShWw3/+pIAABLpD9LgCsUDv9EgYMFooXf6JAv8LRQPf2SBcgVige/skCU1zDAGDGERTKYiDS1EzqrJDPoWqg1W4PQMLtBwTBLQYkwUYN8FQYw6tExxYGg0qGVuBDk/h7v93/XR+f9ebbOwGLOmxO/f6FIhjAUJElPMJWpFJgRgcGB0CeYJYPBg7BlmG+MwZECepmd9VyaYsKxmDtggBgJwGKYNOF8GA5grR1SSZiKGIAJdJgsnu01nD/9Hbs/RYynQtul8DJmxZbAocSVAzDqBdbzJUKuWLMIH1OdIsXNGBaCEYJQLJgwhCGEwG6YjI3RlPKwmfk3KprjwuaYS6B8GAaAhJg4odEYEKDVHeLpnI8YoEg4CU1kVWXWcP/FCKEfZV0hiynoadlRxhQUUd1jRIuA3AMDoeLnQssiEgEOEUhSdY4hNiZECAMGAyCGYJAPxhChxmHwN4ZGqjJmX1Z2aGqL3mFMBFxgrQLgYPqFgmAVgm50RsZeDgoUQHqkXfDcsr4//s7M0za1e91UbZWlRJCHWJGKq6n1pQuw66WabYNGuYVLt//qSAL8B6g/yxgtFA7/RIFKhWLB3+iQMQC0UD39kgaIFokHv7JAoFgHDATBJMEAIQweQ6zDkHMMgtUsy8izbM9pGITCcgjAwVgFeMIDChzAMwR06ouMxAwgYQrWIxOG5YDv/vs08Vlhz69LmwovrFA8haGzh01njS2A04yvEcw6vWihCoaZC5iQFZimExj4HJmOMhrUbZ6DcRpozM4bbuFrGGdAQBgTgMgYF4I0mAyBA54/BklYJEIMsygmfnb59P/zfpWbvr60ATa+YsaTAzEi0WlpaKsmXBZyFlHPGyZUhHKGngASc9ZiQE5imDxj8FZmaJRroVp6rNBp1ytQbfmEhGGrAWxgWwNEYEMI/mAvBCJ0fRjFYVEI6r6gufkNt//sm/Sh967tbp97UkdB1xZoGbDwGCiCncxThRBdgfc4OkjgnLlg+XKir7giqIAwGgJRCCMFwezAYDUMEEaUwkk6jDV6gAwwIWSMEDBrzA9wKcwcYCIMBGAKh9HKF0mF1CGbu5GK7P/yaVf9eWnWP0KWyhSTtFidFa46PWPLkdINOSf/6kgCL3e0P8vcLRQPf2SBggUige/skDBgrEg7/RIGQBWJB3+iQBI78gYEwGIBBWJQjzAMD2MC8eMwYFoDCEcAAwBYZcMDtB4TBEQKUwgYAqBQFgLvw06lYigSX+7kMV9f+utnGV38/Ssv+XUpahcMuQ8t2qMPFDss8+1oduFgy4USo4oVafDYsAgmCCMMfQdM0BMNdSfPVGcNNRNrja3gk4w3cGdMErByTA0BFwwC8H4MrtESQqiU3GRtYhyR2M+jm9VFG57Eovc5cia2ak49T4i0JSu9I9JNihWGqkLD9DGBMA3tByEhYMBIBDYY0hGZZCkagladxO0aGWfoGujBPRhhIL+YIeDImBtB4hgDoNgaG2FDQ6FUMaW4EUo7B/u/3fKvuoV1a1RQUsyydosSXZ8pexQsJhdgLuYBo5KxdSZMwA8AoMAYAPzAHwGQwDACzMBnBZjBEhFMxvOUlMrkExzAyQR4wHoAvMF7BoDAVwLYDRgdACJwFgaHTCwkmZpI////1N6v6f/66f/7V63rov/00odWvmbSi68e6eWKJc9//+pIAD0LojfLFCkWL39kgX0FYoHv7JAv8LRIO/0SBZQVigd/okJxNBgPgfGBcCyYHIQxgrBzmFSOUY0ivZlIN3CZ94MCmCngrhgRwDeYPCE1GA9gix3BuZ2GBhCgOXrC7tMdNf1f+tf1vI+uK5dqlnMRiYjaNcB1BMaVEAncdArHrQOEgXrexxgmwYYCgAhgWgZmC0DQYUIX5iXC8GVOh2Z71Htmpnig5hewQgYKsDMGDUhtIJBhzgk4x0XAwIkWsIw9/5Zn/FuxXsccsfWzmdCXyzkoOLnVCEESDEC0NlhQV2vh8YGGKYbKEkLr6zAgBDB8LzEYXjIMrzPpWjjX8TM+IYE0VMSnMKAB1jBLAVgwYULVMAFBQT8PDMjQcAUHYY1uG5Zn7/9/0Y4gq/+iiRQAbn8a5hUpJ3CfWH5U0L8OmnuNMY1hxVTDYJTDULDE4QzH8djOw/zjHRjNFnGY0wsNoMIQARTAFAPAwPwMBMBWBWT2yDNEwECS9a9Lcp0OdP+n6m3/6a+csaykWeHYqHqX6GvSbGEVhVrjgeLFg8ZpE//qSAL5K7g/zE0pFg/US8GNBSJB7+yQMVCsSD39kgWiFYoHf6JBAPHETDYITDcJjFIOzIcaDPY5Tkm/jNnmhI07cMSMIuADzAMgQQwN4MfMBSBZzxzDLFQCATla1S2p0OdP9iV+rTfl7SdfEjqAvqURUyxqTpJR54oQoNnDLhkeUaIZJRxKjAWAlBAJxgShDmCkHUYUo5Zi4KnGQ52lBlJAwwYO6EGmCZgipg/gNkYB4BcHWjIQwCQ2X4YO7kYlYY6Jn+lf72KCz7TrQpax0biAjWlEg9PNhdS9yxZwufHnlH2kzTkiVAhOCQLGAoBCIgQjATB1MDgMwweRlzESS6MZhoXjHdBWowXcHFMEDA2zBwATowDgCAOXCRZQGhNGhp78SiVhHo///+qpi7Ey7fZnBlabUKYbAdVAo9rHAGCp1Ilaql6FqMDwQMPADDjWHlZNPwMO1i7NCnHgzXcADkwucDzMDFBcTADA8QwEMG0MtdBJ4qA07mywxOT17/RUy7ZbVoeiaLS985alTQulJo8scgRnsAm1InmOSWewidqOJPv/6kgA/UOqP8uQKRQO/0SBdAUigd/okDKQpEg9/ZIFrBSKB7+yQEQdcG1PUUJmA4DBgwjRbgJLDRcBTn4BTOsQcs1JoB8MKTA7zAuQUkwDoNOMAxBfABPEJZDuok9cMTlHe/+tHR9sVRTnYr0/pvW25TkOWXbYt7jsUT7rHlFKaaDq0mAGBMYAwIIFBqMAILkwExfDBWSBMLjmlzF2BQkwAQFcMC0ALjBmQMIwE8BMOWAw5WGg1QtvYHldzf/q7aFDdDv+x9Hdva+QFwT8nJ3vaxZhKLaaZt2VMCpgEgYGAgCaYBYPhgNBpmByNcYVSipiXNbiZDoLUmAcgvhgYAAcYOaCXGAwgQB1gmDmoWGUsmgwuJ3N/y+rtbV3qfFo90ogiMiuFxGTsWBYReLxoy+SFWPUYFRjjwKnB1RZ7EhxSaiEoEQwvBExWDwydHE0aOI6DpkztBZtNQzDQTCugY4wQ4FuMEVDSTABgXs4E4xJUEAFK2sOHL5Zf9+LKzl6qunxWLpc7QTdjlstUVOArkXjhjEpGrYw6OgJYwPE2tLGxA7L/+pIAxEjrj/MVCsSDv9EgVYFooHf6JAsAKxQPf2SBmwViQe/skJULB4YVguYpCIZMj+aJH4c53SZzgyCmnLhyZhVwM8YIsC2GCXhmIEBbzmSjGkyyic7WHDjc5334kVun31XXJNv71I2an3CoxZ2DA4VeZQxZ1NKyJ0UE7GGi50OCNOpgBuYPMNApMJw2MOhZMWSeMrlDNs/wMpQh+jPmREAwYIDBMA1AmjBSgmswFwETP+4NALBwpK1p0grUr9v7dKovvu/nceXFrKz4gCZxTTq2NzjIjKvlV1kiQ8NICJFEJLIb1HQ6YWA+YQhUYaCKYpj8ZRIObM6IZNg4tGdBhwBguQESYAmBMGCFBH5gJwH+edgZYSW+U1caiype///RuT+PVe2rMUODgjddp6F2inY4WPMjg1RnErtSMAIAswDgMjAxBhMGQK4wwRXzG2QIMofieDM8hLYwgEG5MERBHTBighwwBADuP+hM8ALppqNPa5GLdT/f87eEHfn4vD7Uqcy/kHRVRxtNl1j1MCSjOtjzLWhKG4MsQZKJALjRgDAH//qSANrr7g/zCQrEg7/RIGDhWJB3+iQL/CkSDv9EgVAFYoHf6JCYBAGhgUgymC0FoYWItZjLIYGStx3Rl5Qm4YPGDdmCHghpgywPcYA0BsHCjYCQAwDSQae1yMWzlq/0OktbLC/jFOb69onPCYjfYcUy2QNsjSQo15S5bbUToDBsat6EueOMDQYMMgVMSQcMgA1M8yCOOHzM05RIzS/AdkwjMBRMBpBCzAbQwIwE0FYN1FMSQL2qVODQXrWDNX/Yj20HWoqZNv6O6980ACbSIH8uIS4ot8IoNhdxMULAyKih0PKOlGxQwLBYwyA8xLBIyECUz5GQ5GZ8zYM5hNNKBczCRwHUwHsEXMBJDEDASQWA10cwZJASqs6MxYv4M0eq64I+ltCDRBaiMK/5dZ4Y1STCxi6x51ibLh4jIuZQMrSyQJPSXmB9HQHhAB+FgaDANC3MDIWowe0UzDBJQEwfMTnMDcBdjAygIowZEBjMBBAIyMQKycoFVXt4/8buEuj1r0OV0790WIrOMUZcAosLqazVUtb0CoscrqFnlD0IbTiXEv/6kgDVP/EP8xULRIPf0SBjoUiQe/skDBArEg7/RIF/hWJB3+iQNnkyAVAYCFQIAYB+QA0AULcwGhajA/RXMGulGTA/hOUwKwFsMDAAcgEGPhAEWNipEnEQqrW3kPyu5utv/VV+3xcYOMZxlAZGFYBiVAjKnEBpDNco04EUoLLSI32h0couXI6GqIBAihgpmJQHGQIUmeAyHGKHmZ3E0Zo9IMyYScCDGBeAlBgRoW+YBICmmIhDI1DgzNwH/l9g1/629w+yu5QcNMU1zFORz91ZEyIEU1BdF6Fj3VJAM2DApWYFhMbA8m0DhgSEEDCeYlAwY/huZ3EAcWrOZmgY/mjTg+ZhJgI0YGMCVGBUha5gDwKSZaEIRqDDI2sQ/L5zrtXqS7fu29qbWrdIJrEFe54FUaeYeE3IpbtRzi23iIc02sLspaLvcQUwmB0wTC4wUFMwpJYxUS0zt64xeB+yMm5EEDAqAO0wGEAkME+BbTARgJk+5oDORIEr15pBcumv/s76a0qsUWqNMVfQ5aoVIjxTdSYDq6Sc+9bwgwWe5A+oYBL/+pIADTXrj/MOCkSD39kgXeFYkHv7JAv0KRIO/0SBdQViQd/okIYLMKERiTCgHTBcLDBoUTDMkDGZIzRHlDGrHsoyrsPqMDUA2TATwD4wTIGZMBIArT5oAE1DgS1XWk1yqHPT+iPVeq1xSt5MWh6o2pibxAw87QprhBqJWAkkuFnjyDb05t4PnSLSjmC4CLumCQJGGYemMA4GXJ1m2VaGUMryxmogaQYPOCnmBpgf5ghgRMVAPQ5KsxQJItdjjwxLKcI/0Vp0023JziNY5hhQ/od70o1uThW+UlhSQgEgq8+koJmUB5LShZ0wOBYwvEExcHgyxPk2euIyaVkSMyODeDB1wVUwNUD5MEcCCBEBzHPTmMAIprEafDEspzn9P/1qRS1J9MzNJIDW1GKHED7C5Qgu9qYVMgrKAmqhR4rFzzkDEj4qlCkwDBQwcBcwvCgxUE0yjLA2Ul8yYNR5M4UCazBewCIwAsCpMCtCMDAQwPY4Ssw4RFVh0Rrcvhv/Hn8pd8XQzsQ4XTWG20wscSRUPPoBRlrw0mUqSts6WnlUhywy//qQAF376w/zAApEg7/RIGLBSJB3+iQLNCkSDv9EgXmFIkHf6JAGOueaSYBgkYPAmYZhAYsCGZWk8bTRWZPSlgmdGBFYQGRGAQgW5gS4SMYCGCAH1aYoiKrDojW5fxf9yk/rlLV75CxqWXbPoaKB5izkQPitgothBQGF3PF9oBdUTPUAd5cKAkIwxMFBYMMShMaFANA/NMYqgjTG4RGEwUoFqMDSAuDBZQR8wBsB/PwKCG5EEWe2kPyup/5X9qLV6WE02JUfEXJOaNHbBgQxWWFhOdKBBoWHVmAExaxQQqOmjwOih5KjiD5UiIgOJQpMCBKMHyNMSkaMwdjMSqdEzE3Q+MwQAE2MCwAkDBOQMkwBkBVAdASVkwBi7yQ/K6nr3b2U/YjvJUllO4/nJ0rF64xB8iVPOE8gBCCFvGAglBtQuTGgNIfD6CbwggUCKHAbEQOJQGEChcTG3BwOkxCgP0SMb4Iwwcg1DAHJNMC4QEDBtAKCQ7wvR8E2bs/+///7/6UPv6dPqjbfrqtEtJaRGbVb+cEWTf77bCYphohMq7Qn//qSAKNV6w/y/QpEg7/RIFthWJB3+SQMlCsQDv9EgYAFIkHf6JDHW18hSyy1tA4EEeA2GgcAcGOBhdDG7AIOlwHI+iQaTHECiMHwNowKSTTAqEBAS0BoJDuC/HQTZuzt///t1v6PT7Kv7suy/7Uq/bbad0qvsmlGSjaLZM7LskyiZzZOU4ZeK7chL8e0xgwCoBCIKB+FR6MAEBMPcWMHOauDEgQ3IQgdZgN4A6YIgBGmAXAFIHfCx1EpqMLpKl1HJf/dszxvJsvJsKIVddDyrFDouKi4aUdbvFGFXOGLHxMRJhehQqGubQYAkAHgEAaBABmAQGYwA4DbMBVCqjCWlygxNsL2MAWAwTAVQAEwOYChMAoAMwEBPCwAimcB0Tx4QLb+3////dErW/29V7/6/9Grs+6Nfbfqmv1R21daezfJV92v7gkqSup7DCgCMZh8y8RDXKXPXbg6G/gD2MI4MdEQcwrQ8zC4IoMBEPQ46wAItNuj/wxSWzn7dq3ti20QaEpGnxRYqInD22rDTEBkJCdaC4CCxoQgYBMgJdIpgwITyv/6kgA32+kP8u89RIPUEuJfiRiQeoJeS2ApEg7/RIF/qyKB9Qm4PLBQ+XbmpsVnzLamDAEYnDpkwgGm0idu1xxS+QHb8RAYxodphMhnmFYOkYAoZhwwlkGBvBD8MVLfPd/6VLbj9+Vbdqj2Ukn3CySCVG7YsfKRNPHBccgm4oUihsVTeeCTguJyEQYwmZdQaieYC2BWGB4BJJi86dqZKoE1mBsgMxgBwCiaxsxjVJGQAqCgUsVxorfFg36PYqjZqtMUte9hBG2bhCVDsKz9hM8PSdFUDwOqVJsLvLgZ91MO1kSdqR0DDBQFzCELjDgVzHcvDSyfTHO1VMyz4KZMEVAYDAAAHEwL8G2MA/AuDlHwEOUFa8/N/Yq//Kf6XDwI5IvYyrX3nxwNFBZdjROdWcQoTszYLxpc2MNgBZEJC71hw0KsMg9OhKoFAQCQNMEAsMKxXMYC8M75BMXcT0THEQq8wRgDuMCMAljA1wR8LgQJ9ECQ6uHfikvp8PsltTMfTFXM4EZ32F2C0eEa7idazlqkhC9wkLHmtYQckjasmsYGzCD/+pIAyRbqj/MZCkQDnskgWGFYkHPZJAtwJRINf4KBjQTiAd/okLOAZBIZLQBUCTAgJDB8TTEoqDL2CzEwEgMxZcJrMDiAzjAdgHgwL8DfBoDGeQA0GyR+4pT04Z/s+3epaPbXUkkpSjGBPZ95xAGRLBk44VKSc42XWtWyLHyY8GjEQDAQiMTAkySEjSBHOvzI4UKnjwrEYMVoDAwOAoDA4HjMDANM34CyKmTWY9d7eP6P36cPW2sGQDW1U8sSojGIJGRZILvaYHDVEhWKPB2LFCgGJjC8usEjVryooDhw08wUAxiAAgoTGKAGZLBBpQZnZ34cQUbB4qhqGLMCQYIgVBgQD1mBcGua8RZ1W5wZJd7ez/9FSrqTJYptehCifG0KY21wuHzQBDo5xBpMLWrOh0Rlp4206ggJwkxAZNk3EHHQy0N1MEgHGQWHQ1MABmMFzZMPqmMITW8DAVgwIwHcDhMB3ATTA7AB4wBoAQJy5MUXG5kLnKTbO36rz6twpE+s6ccLhPMLtcAUImgGGSYgYwauOUmopNaAEMa15gNJiY8e//qSACEd6w/y+ApEg7/JIFXBOKB3+SQMTCkQDnskgYUFYgHPZJAYXFSRYF0GCQEiMGCoGoUGYwHNkwaqgwUtb8MFXDADAWQNkwHEA9CAdUIAPCMsVilxuZC5yoLdfor2SCLaKx0uc0oWC+8PAIULDTBgY6HSxpbEPdctDSgoUHpLnArizzCFiIgFSaxjECIGBAvMbAYzmJTjh+NtlUg5YA0jEpCDMFUJEwPhdDAVCgC81kN3giP1MDXu/+rj97Zhbuw3cpbilqwdc8uhjRyGNgehiTkm8oYNsSdLJKnnJLKOkGWDRAKvPY4wcQNTEaCsNstsY5OBADEpCNMFsJEyZbjCKeAoTWAbnAE3MAn5j/+96bmO6EIbhlaAjEmunQ0nUhCyzXiWgI7B6mChkwXDp060cwgDqgsAwABMAhEYGCWYRlIY9Q+YZqk6mL+BJpgLYD8YBgABmBXgahgFYCUfAIccrltp2/sKv0fW+9zJVhLY5jnIeeKNOXVHT6TkTH3MEouVCgwM0nrb7zJlYEUKgBiJAWIPEYBYIowRBAQ0AtGZ2f/6kgBEOOyP8xsKRAO/0SBgQTiAd/okCqwnEg57BIFNhKJBn3BQgShEGD4OWZnP25rfD3mDGDAYDwBpx+cZGkAJZEgJiLzTo89/9FxH+tWx0OGO0WaIRqhfOveV3QApy22LOlhRSbGlBsAuHtGjBxRAsGCy5hAJGKxmZaPBum9muZrmbn465iDBaGDYEIYRokoMCBPhEx2GQ/GKevhb//+HPjq899POqf/18ev9Gc/bUMKfR9oqJz/tJaTxNbhbzWVBdtj1Kf4g/ToXYaAtzYfdw0uJAMAkAEwDgFjApA3MFQIAw3xlTWO5JNs8fEw/AuDBsCAMJcRUQA8gaAEFz4goM2RQn02+3/3+uvV+fne+efZytlrJC0y1vzzS9advD/ufz/8jLS5dWebLzUqdtPBZPCeSQFOt6uTD6QMBaBgBjAMAJMCMBwwTAWTDPFENQiwo31ROjDIAHCwKBgmCTmBADuB5cHKigiAl5alr/6f/bf/X1+++7pkkWpZS76bTHeknfIRyNvre9NrZzq66I6KrOo8iSqcUeVWgrZwY+LZjyoH/+pIAqVL0D/MgCcQDv8kgUqEYkGfbFAxQKRAOewSJn6jiAeoNeYC0FAFGAYAKYE4CxgpAmmGyJQaolCxwIiLCQ1JgAArGCAJmYEAPZgKgIKWs6BaNlc23+//83/9d1M08z9vdl7SPJz5yMlGMna0NXlomV92ZEc7mdVmq3qSSSOMjBSKuDRTMqtFkQgA0YAEC4CZgBAWGA2DmYKwuxlP6YGS8NsYOIQpgigcmD4CwIAIgG9hcgYZIFc0Qq///3X9v839attttdn326oVUREXtayKirYru/bmtdl46aOmpERiqtqijcnbn6O/9kGAcaOCCFMAXGAqDqYJAvxkl61GOYN0YMYQZghgYn+hBQwLO0mGzyCfsBH/2t/Tb5UtU0Yx7GLwgmLmDxY4LLOEmYRlG3Q4sSLcOKknmmtmhV7Bzl0BhfkmaHwQUE8YZgHZqOjqh3ShhogdmBeDCCI0YZOgWBajUE0Feor//v//722S+/+137/o765XfFAsXT0s7kZZACMf2G1pucX1L3f/s3k/22XbZmqWE00iuy9hVzzIDEDSY//qSAPi08Y/zMU3EA9MS8mYrKIB44m5MMT8SD0xLyUyEokGfaFBsmRCaePOCzUpAmN0QDAw1QRTA1BjMAoRowFwdBRa2IJmK9TB76f19cZFhsXqXNChZYVOlTTIQcUUNC7HhUwVDMubTOJQ1TAQrSVEA0AKZJJAjQYhQZWTAAAAiAFFQFhgDUVB8MAgaMwf+jzLiHVJAcgcDIYQoKRgIgFASkhtQnEei0mmr+zL/1q1N26S2v5X4xn9PInTI+GvrKUye/qRZtkjb0iq0qgojhaP/ShFFdiNcj8/8iKF78IjmSULYTXq7AAAAgADEICwgAzBIPRgQDPmIfy6ZtQ5pgDg2EQLBhAg1GAqAsA1JDYhOI9GqaZzZ6+/9a1PdepXepf5ZH8y/7wovLu3S+JnMZ1W5mUx7S2Y0cySy3TNc7LPhmXC/KTfQjMfN8D9Ndiao4oooFE6wcAGLgZmxMc/Kmgcx6amYa5hVAsGBwCcYHoY5gFApgDglakV9H+3f7euzre4XsJk2T6L3OaLpeXAAAHjlPKoCh68MqSHDCklhcJDSRf/6kgCvEe8P8u0JRAM+4KJboTiAb9gkDTlVDg9Qa8mppyHB6g15QPhVgAFFoWGQ+0gGyYMWOXYMPAzKiQ4WTM3phM0YQyzCOBOMDAEAwOQozACBDAWC3sDPSHs5//savnsKiyZ2JS4huPmA9eybCKJB6kWSgGQ9B0CDgu7TWkk8iJzSAWW95MI6hKBgzDIwEgOTBRDsMkiFo0LQ8jBKAbEYDBnkeYcYhg8tV/qW1xbNHnGS7FfP4pscXAMi5fEbAkpKQOgCLAbLUExMsPFxIshIjxrx5Iug4HliYoHwAIzQMyIZ1CUCAmOBgLAamCyHAZPz9RowhxGCoAmSgPGbTJhxqEDSuX+pbSm8no1dM+7Tgw4WcoVKlF21qetgol7BjjTBYacjokU0kHhYAJiyz6xorYNKIFSKFicK1Q4B1kAXAFMAYBYwIQRTBiDuMtuQwy2BLjBxBdMDMDUwTgahUCoBmxOBJk4XzBBdf///fqey9f7qj9+eul9/n2OjLPrmYro7GcimyNI5XMtshUPHVag3OVMz1VQvq0qWRVBpnKFjBAf/+pIA3K3mD/LiCcQDfnkgVQE4kG/PJAtUJRAMe2KBYQRiAY9sUACLdBGACAAEjAXA9MEoNgyV4NjIwEMMFkE0wKQJzBEBOIQHA4BNtJGIFkIvt7fZ6870OzvZ//278P8v2Vf5nYW0T3OXKlP7CLT2/ZdIfLXp+W2ZLZpDOODcjfEQvrEvdCTBtaEgEFqobA4BEwIQDDBuBxMu89U0jATwMHGAQKzABCkMBEDYAOIjQ4Scdev9m1/tZB9/t1vvaqTr6qtKOc+y5kaZbOpi3ORbKhnRhPPE36ncbD/32xr0C36jgZRxeGIUoNgHfMLIZKmsHJMBsAIwYATzKjLDNAICswYwATABAlEMcYWXIqu1Ka13X7bkLcURr9CVLz7HGwMtYRWaUuFWkBMoTBUKiqiSxCXRQZFwTWE5cPNB4gs6WclrjoudREASAQYyh4MgIhYDIwBw0zECfDMJAPMwFANjASAGIge0E5MAg+chBh3/9G//rZapvWm1rNOdhJn/f8/udvDIu6medzPz537Ipnic0yfli5EhLNnudSTXiNy1YqZi//qSALN78Y/zMVVDg9IS8mRq+IB4Q25MXOUODzRLiWoEogGPbFBWL2aQSYwlDoQxEQGQJDVMMh8Uw8A8zAKA0MBAAAJ+SLKyHukM9e/3f////zv8b6t/4uc/olxW3JJD/+T9NuxDX9+aFm6LziZbJtkZ6Bk2ah9qeF+/NeLwFQVmuci2TBarWmYNAODg0MXwLOsoRPlxJMaAiMFAoMDSBMCAvWRE5oGCZ97f+7M1qWdXv7Oq7fWRd87YWVUzPtUsFZzItPf4k/SupP+uTa8VbjE5dVSfLVljrKZFSzr7wqm5FCyBkPKAhYurxA8DBIYngycuSmeHiaYsA8YIBAYGjWYBBKEkJM4ZmFLev+r6m6qk++yPXjmmSnezfarKfS+pn0i9N9b9P21lRW6fSmtf66XEJxTdWbGIE1GH3exde5HsXfABqhYClkS2R0AQLARmAaE4YRSzBkRhQGAcAmk0YEgHIEABSCcWeCzacvl8iKF0c0nKM2mUo4SLbKXTNGvLMrZ++KXJnxRa0sFYMUOAszsMyh+ZvnWJ22yxKoLVvzc5uf/6kgB8W+uP8vZWw4PFG3JaIShwY9kUTDVnDA6Ibcl8JOHB1o15kdjiYdcHf3FCwFLAlkjIAgMAhMBUJgwulbDJJCaMBYA9NYwIgPwAAGA4g5pLMfZd/6ta12rvnVV3Xr8L/9ueZsWLi5WXipCPRndym+ZP245e88z4x7+t5lFpGZ56LJgqXaAKukABowIjtiPoR6KwKfxr6pDAMAzCINjW/EDWIWDCUEzAMCzAwJBCAzL4xbQs//+dkfTwcpVozlKX/855nbvP4x+rXPIawzaU5sZRDtPc8/QtEuRxypXnSMk+kmwMwDmeSI1PLPoOgoBV9GvqAGAIDmEAfGsuhGoQvGEYKmAYFmBwPCMBACYsE85rb1/Tr5lPnTd9E1I1pOre7XVWPZXpXNsq7F0koyKY6LX+xzyAr3SzdNTyIuhgZgpi1ZgPGlf5s9UmCB1X+WiWqMFA9M05TNzgrIguQ7g0MAKAji01URv8v7p99x6JO62TqX/SSH8rXJdYn1OkNSHKtf2R3aryHshP5EGEBqVLHNyZyrbliiUorV+MOZwNBK7/+pIAHHrsj/MrWEMDwRtwZSkIYHmjXEuBZQ4OhG3JaqhhwdUJeYiGSiGmM5QEDqw8wEtkYLBuZyw6bqBGUBmhzC4ZAUBgARsVHlbbb+mqPvs6H0pWlOUsiyNyuRk0/peYcsjfI9oFHKM3CrAu53VjBRC7GqpQhlRYOKPc0R6ZZR7phiW4RlAXFCLBQPfJCYJH/ghn4oABgCF5lhQZgAIYAARHweAtmIQF2Ob3+9qe1Hn0at6tVrqyfaWh7pw75GftyMhmTw7DyzenXUodZ2oVtL89SBquOtLImRzYzKxELrBQp0GVtrRRVNkIC+kwSP/BDRxgAAKGJlJRphQIoIAJKwrAVooQF2Obb/371VOs1lu1dGOmr5nl9fkMz/0+eZmfOT/dsGWjmJ9MYwpazv0W4oxBhixOoV6OqsZSkVjooXhoY0cmHoebHvmqIim/elzNQEBHBFRHJiQAyAqBKYokESb9+b2vrralM89molt4+1v4bnDH0VD++UnmsKkUjSETtEzEkvLCjo6fToM9IqGVrNrKLSmZ03FDjki7jK49MGoO//qSAMQ16Y/y6VpDA6EbcGIqGGB0415LzVMMDqhryXYp4YHVDXkaRGtHely/QEHB6AH4YYELQKoeirNUuDBkfXfrM/ZemQUREyjEMgKf/+tGN3bH+v2pT3/rPON3hse7qPbd+83e5c6hbGTMzH3zus05bQ+JM7+I0Y04UqEnzP6w55+5jLRomIOErhhn4wEDIMmM+hR6YuqV/KOz1uld9KfaqKxiTLKVZrXUHhGWrFrUOarDJDzEPTLSNmOCO0JTMYE6EJzLHLIz2gpjE2Ma1EdAoVqfglowvJq4Rh2oQYWjYYxmNTIyYAWCElBjhXlDYxwMMv+jcQyAadYWHKOzl1lUeHIvxRkc4VZNN5Qz1eWnFU/mdO2medkYckQ25IRTNmCQSDHGeZqIJi3WpAwoMbsSKIYKSAwZop1lBEsLr+TrupdoSnspAxy5G3VN2BYSH5e+jvXdbtlIpeJt0b/ZlGRfAiSGxCYxgfxf+PU75uvC9yI/fIx1VEepxO5a7UVdeapjXLh3pk5GyylF6KNvbzaL5xma4qChJpIvfTWlF2EGpf/6kgAT8umP8r9PQwNlGvJf61hQbCZuDIVfCA4IbclsLCFBsI25juZTnzk4GOXI29pvzL4fuXww72evIjr55cgTMbeXagQUtGnEiSRvUnLOze8u9al15nXc3zSu83i2Zpa8Lj83Yr6VzU3/JMbdmXSwMnMXt1E7l2jEzSJ67U8ZTECrWrzKJj2QAGKLJR/DLo4VOTTm6njQL3isXG6y+Uady1KZIrt5tnJ/kbU3+fXpKqZ65RW+o+WVDtp8sclqr7u81HQclFY2J6kj7mIzFGYccsgfSbOijuRyJFO9Zejdve9veYRTNKicg0pUGKKlOPkMikRyaTfuQOEf/PpNk/+ay+Yg61kfqcRSvS4y3bXkvtc5E7LZbb1T2Wz5bvMcx1r3xJTHUnTxMIuaEc4GtkqKQMOabxie3ZVvVP5hRGYbUzZyf4l7YrHWmPUGMldJaKNC61nSy6XdxDt2tdt3mI0qZFuzO6yXk1FE2Xc/GyrlGbxqLdPJkvKZAzf2yra9MN1nghFo7aEuq/ENFovKRMEZnQfOfFJZhUwT7GjWlAQdKdb/+pIAbg7sD/MFWMIDITNyXur4QGQmbkxJaQYKDM3JeyzgwUGZuTKZZHj7SrCNBTLXux1qlOpaJoy7GIwzkiAbG3kNqamMFDmOZpPhkql6lPspDYtnmUKv6jqW17kc2n7RSXOOZd/3rR12Xa4K05mMw41UalsG7X7tpaI+ZzZ2eWWgTg3xWGKg70D0EGAxR2Xq0lMr8s+EUVTql5ZRCI60OXDmcZMjteJyzt9ZnxmZfVpe/uzsVj1N5kHZGveTO29p2Pj5JZzZqFNUyZ3Oii2Z1SlALZiM5CcRGp3Ba2plTsGEUCjcY1mhGgYyd1zi++7rqvpL+tvV7TZs26bbKS0cjZ04qCNSfe7Rfm3vTtKZ7Pl4ndgsp7UtDJF69ygXR/0jeSflZFXRWy1I3WE7RHeiJq6hgODkxWSjsjChTWW7Os484tWsg6FiVtoGMrMtmrVU7sf3Lst+kZf0yD1vYpnIRAn2f7ztND6d5rxs5mNs/Jt7xk7h613YNB1Nm3kvOz2mMRuu8oyc2nOa7doMKNqaCTHLT76eecfuv7MJJWgXapNs//qSAEb46Y/y+VrBAoIzcF6LWCBQJm4LjWsGCgzNwYKs4IFBmbnC0qWIoeFMrnFNQutP+XXzKE8M2MmmUNj8s7LRztNmrKwTVSTejZufavGdiBUteFfw29pfqOUYLx3spJF/Z3i7e86jaiFMihWxlPdVBtD4k1PGSP6pQgvBJUVMHIWQWZkumx6AWsFMnT1pOzKfkabF8oJpn+fze/esroxv0lzrHGPJLu+/bQumZ/Bypau0m37aIu6TqcJEn9vBz4jW2nlvSWPWs15ZtwZk96CJc0PqieaZUGmEM6oqz070xqPqDUx4uZQpk7qopMmlXcr38uMhXh3vcFspTd1pVAQlofEavTKsqria2duNtu3OtzWsp9at4o5yu33uyjitL1XyCep+8+OYnRXNaU0SDy5CTk63UksSlnNsU5MskhWgT5Ate/Ub5CIKZF6KCTWo1oOy83lq6Peu3ZsqoxWYG52chWQzIZ6vpMZ9Prd29Zte3xp0u5xJq71yv8S06yBUkcMmujrLRsvSAMkQhEj7X7H+XL37doDzHhB3Th2lIZZ1Jv/6kgA9S+oP8utZwQKDM3JgCyggUGZuS61pBAoMzcmALWCBQZm4m9GDSjqY0tcGMk913steoiNK/i+XOGcNDkaZm3vMlZCjxilK+r6YVmo7Wvr1iGy+vGeI2s3czcqHMuqCH1GpyPTkNslpjwTrCA3SFE0lnNFkrOKZLNwLKUL2aQShADxiRkiXcq2OJJJ2lIUyNrWhrstJkKbd7lCOc0RjVVYopm+cpkcWj58Zbqwd7vZO/SnS88RMPTZkLnx/n+ZPcqqfGbptB58oJXDo3VJ1deLWTiepInQwytNjYrHrCWMpFcXdgZSLClv0CkJApkbqQRdlN/Nft6eUe5fvPamyg6kk+EQsosmow0ttxPDfNTmZUKwyFN23Js/d3EtgyyUzzXfWYCd70z1GE2PfsKq1+5TX0Q/xOX0RDEJrMzTqQLdbdKzJp0FLQQMJjgYyTqXekyCndqR+RIdbIyi583bRCVl52lk8zPRwpcfZlupjYamf5Xz0o+POTHdUzTz/vdO4lE8q7RTStFjsKtZdtdoNloXQelqucRWpGDKVh2uQWoD/+pIAZMvqj/MIWkECgjNyYitYIFBmbgvNaQQKDM3Jei0ggUGZuciegLpMEy3JdDggMDGTrUu6+tVZHlkWWUNqfTpEvC5WT/phXi1VIwX1R2+M9fK8fX/b/PdeLxyv/F7mo5NrUSZ+f9gokzGjqaHtyxNp6UsTBXPs4Ntws1AmD1z7t7cIPPVFqTZO28vZROw8KZb2tbVf5cmR7WxMq/TzKWW9K3ITEQkQMA1GK8y5lNTbreNfsYhFlmXX7GZvmuq2fDYWpG3Su5LMukj0CiGGY+8w8cMVewcSizO1zEsYelhsMWRB+WF4vblqQSUUeowgFKN1J6qtmWf0iO9+FFPL3hGX8WLuylc9W174ae5lqx5a3ua/fGfc+v8tvRJi/CMFhEaVpPZIzBqcJ5rF/WxOM52bcOaWk2laq3w7CRsnk5SJV7bFJIoRmaST2bsGMlNvr2fLLhVLvlZmhGQUskSnywiNTyOxGPUteZStIGVaPnNF1LPcY5Jrp0m4zBYvF9EfIFkIikPHo9y6duJzLR7l2UW0Fr4GC70o+z3USkDJkaa4//qSAKJW6I/zBVjBAoMzcl8rSCBQZm5MBWkECgzNyXCsIMFBmbnxymDghKpziRSylCmVVkU9SbupFRmRlnS3LMzzubZGdkYG/zzKwmreQhA37jyx8vV2+Edt4tq1U3ebLPfry249HXtz6avib1ddqg6eebbmSpkydNY1omraHmkNlJU3dL3pGGZZLD6LTtYUy3aku93duZ9LM7C/RdWBn0ll5sTVZ2zSSneVtdFq/uP5xDWmHjdbv6y5aM/NSoonvF+jgQrTM1J41C++FPMSapDCrTat3xD3UK52mwtpOtPTGwjKBAihSZM8o7UAxRe7UGdSu3A6WSEaMyRMkp0aiNCGGZw4siUG3vZ516+46txq3sxUXDYd/J+U9FL5cYUc1eulpFzN1J0SUPDxKGMWuJO+lczMPTfTzs95dYZjFU+XRzjqTPlTVQYy7MplKWtaKZFSaWtZ2Fk9tv9snFJi8pu5Nv+aBbr02F8pozbPXkMmzWu/LPpNHJJw+0U3g175qMF4qYMQipMjUVMfpMzeS301kSKSa6eHtBdFgIceeWiFgv/6kgBd+eiP8v5awQKDQ3BeK0ggUGZuC7FpBAoMzcFrLWDBQJm4rJosRLMYxE6wkKZaDIK2ZS0tkcYitjAEIGgdXwhpAGAdZQmFoxadprx49M2wbM3BiLdqNeMynQtll4dDfIikOmlL5RNGrw1bmyef5NioKNh0/hbpYJlflEqUMljkzOXFLRdmsyxbHCgYy6q0q0lnG06T3aJTJqXtw+977VQdi62Gnvo3nmxNNWNmWvMUk+JXcL197N9TzEPMlNEHHsgQNfe9X9s5EjHJwaTX9NS91DU92hl1qquRGQg9xJEmndVJ+GKY+CEAxQ7vXUyNHvbKZ5h0MzSGdCOmyCOhGb4EIf4udrydFTbR9+es14n/HNzXSyY/0ue8ProfZ/zIbfVXCJRsBkJpeJWW2m7RWZCJiz9QzxKCMmY8Xqdp1tJXMgYyVWguzq613zMP+SeSFdg0nczNZcoh7wt47tepGbxf6sd98Z4fbafXzKljQvdqvWOdrtUgaiBpmlwYx1vkQeWy8TdHQPO+4Y9S6SRyqxNGmoiRm0yzefUJuAjYhHH/+pIAMfPrD/McWcECgzNyXktIIFAmbku1awQKDM3BYS0gwUCZuWW2DGTqZr6/R5m55c1sqno9pUyeH8noQv+B9+uUNNlveps62zVOcfI5LiqhTbmIWxRRyvNs1mns49D2tzqMW0dqskgXQesSIh0kixJZYsIDSddUw4azSVEuJkKU2xBCS0HrQq6YKZWS2VU1lqc9u3IsjnCSHkjvhzjPm8S7VZCDFLdsOzYb7Rd21Cc5zXamrEKPj+79UUchtF4qcPjN4GNw6TrUbl2lZ8AZjmVWxqCjT1GZKamvCL9yjXozlkVTCzjXshRjkgYy0anapm1fkbHT3kM4XKNEf317keVkucToKp239jcJ46OeHMeLcs55dGYvKXVuRNMi5zMcdR1BtSDpofxV0sqcJIekytec3cujjiV7WQaUQNHqnLJIuSJbrxCyB5psiwplbZCyqWtRH8n7mxXIv39Keqwzy4//WUUIJoVQzNmPetG611VPqTcqT0YbYSp4nYnuVGO2NpxBc1s0mj3KyVsUOQNYjFFa0zUlqnDzAzkCEVqL9pj0//qSAAMG7Q/y/1pBAoMzcmLrSCBQaG5MKWUECgzNyYAs4IFBmblKS2HogUHnQjQMZM31v6BHe6UVF3os1+V38y2PRrOQgUrNJdp8X7375d/DL3VO75lNuIPpzEvGmRuahZpcReoKMZuVZHT0Ec70TuoOnKLlfwsw8890iJhYGJhEh9xEpG7vHe/Z48GKGT16anVXOkdvmV/LOvbcoD9W/pxWTVZFLlOlONXPjZ73O3dpa+3z52Fl38xJn/vLpoVu/JJs6SmlsDTa+5OGNzz9KtVdyToaXj1wJ5JH4bZ4kgNasi4kkc6Jq4UyJ1W6SNWROanIu+VvE4t+UtIUcZlmhZNnYVujRcbbG6zv16nVI9jaNN29iDYk1UXUbfvdqdkzt3NirW8TqFy5YZq0/UGlGC8KQLQVvsastRHu30QQWZCUMUhSB3RVBijWhVqWqqgVyo/NE1I0cjSzZhyZIRRjLi/kzGhtmYycbJxn0/TL8J5BR+uhDsZZUpW507TPpbRzcyDrxFrh80CZnSSpG1m5KL8YUUNhUMKs2npWwvOeWGD0PP/6kgA33ekP8vdawQKDM3Bca0ggUEZuS51pBgoMzcl4LSCBQZm4GMlr+y7UFzqy08vvmr+eildZKimr3F2mVpOZx2/fGx4342pR8gvNPZPZKLnbNtNVmUV6Kn2aY+f+GViTJoMld0ecvEEFUVYciSMQZ7FGej9Ku940+Ie1JmmEF2X1jrNoGMmbQd6CmVXqnpwiPIsjhtuZzVdjhXNQyGSdP+ZbwzbN4aU87THZL3P6FXhR2qOrSTXL6lGVjSdMY7zeMgzw1mSVEmlYxh0n4q0g8RR5Okba9wDZsgx5QK2cVFn6acjoUyO9lValMnL/QiM7kt+2n1Xy8/MiORc9cJDJQRo6OtleGmnw3GmGiykd2aqUns8zdxSMPtrUzvtxU50Ws4qXlI8ZNW9kTNRaRrmoTkKOKLXNMhp4g+JPZbMkWWcbBzgCBjKtbKu6luyk/c+wvyZ87Jnn2ohvPlKRHD0xlprf/mvmtfvHzNgtZ0Q2YUU9G5NPekaey5tJGqHGeKpM0+F+yaU4DOcdMdzn0qU9Eo0tc1XYuxBRhKeaQeII0xT/+pAANrjrj/LWWcGCgTNyYAtYIFBmbgv9bQQKDM3BgCzggUGZuU8suCISiFAxk7UKCC/16X5c7fMb9yq5XUjNy+11VpXcmUi2+XsMYjkb82uezMvb0rttudjIZCSEGWzIJalRhNcMzJP8My5j7UWp1UhZVepyynpis2bSF7AlztgHgmcntDi7AqUBjJB9b0mskzrS4bUjrn6uf5O0YzzS0vvVBqMc28yfzDnHvkzVZqKkeXrj/id4bjoEPXjfuxbNz7v1J5+5CBrvF0W6NpWnNttnvdKbUez460ovEjDUi5OKk9RqVDk2IH2PCmV0FM1afQV5DP1ttMt8Htq/tnl2FEV04qH2kbNGype3po3Ne2yTY5L42RCR+otbVE7tqpRrMejrTVQR07HeGnOnLsQsqE0qL1Nfg/tgGikecW9Y7HnHrISDDBoIciWmbtUKZLsv6q2lM/Kmf3LmrzRKZVuFCIs7CUi5uYlouc8F2VPPJR3f23Tjw7Z732pktuYex16Vvf4hhRqss8tKl5iEYXBM/nGKkUWxvEobBbRD2OuQXSb/+pIAPYXrj/MVWkECgzNyXatIIFBmbkwZaQQKDM3JhK1ggUGZuJlpux9GsDJycI0ABjL1srvp6cz25Cm93MWzOWdVqVjk3O5zYzq137v1857a0eEc/hkHs0lXVqZmJ6m8565sK3I1P7Io6YleGqM+Wgy46Bi3hA85pEMddmm2oMbJQ85TMjdq9kjjyhAMZa+tPWtBi/+woRT4T02ZuVROhkZHSQjLclQ84d9G9VbNxDR67UrHLy5OrJ2072rZeGlekK+QieyRfl1fYx0EErupPSVPAs29xkB+zdWkl31Lo28qKkYmkSQIQR1zx4MZLWpb7KdGtKzfK8NNH+Xs5J9uVyLVdWYyEm+Jpl3f6fBaUu1ld8zt4vNuKcps8UQqYSK38/uizUU5eXFJG2xRgHbOoaRZRqkFlSmiTPSPZPLiHalNky0XEKjGkmXIegYyWkvdtTrTh0QhiIIAiYUU6IRkZHwmYkYwyHbRWMHzVtPg5ni5OuHjfLov834l3ipKdVa6GwzVu4UYVmXNtf1tFddHz6siaklKZUGHnwulC8lHawkV//qSAMzd6A/y+FnBAoMzclvrKCBQRm5L6WkECgzNyX4s4IFBmblF0ohwyApODkbCQYyoMr3db5kdtPiqaTMs8yLuPXKsTyQ3m59chCreXwqtdjq1s7ZlM7n1EprM52JN+zx/qeKttcmo46zc5LEw693pJFFDPpyqDwtVKxnQ2C3korhGGv5Mcxro4dELKJrkClBX9amssjlt7/dM+RiPsde9I37Ye8OpbDgsevj/qxrh88l5Veb76m8Nkuz3Hb5K6h6Q1B98cc/ezqLl4azWN69hJNjYhiVbLVsv9qql63lWT+rgKRwxBEoFMl7Kf6mbezk9kntf5XNp2e8QLvCjOdSTibtTef/UTTfbq3zkTPCFW3qUbbVkCuhrbPrc3qVmZUbtE5yE2ZBq3DFJWlK5xMgTTz1SZR9tG4ZSjII+xz1Z+FpC6gpkbdJ66PkMKxBiuZoQpgBnBGU4UQAzBsxCJiY7Zm9Ne225Vf6dXs9BWzXxHIi6ht3EHP2KhbeWScycOlpso/yQBrTyZ5CaspmRM2Eu02HM9o9bISknNagLwLSsGP/6kgAXY+kP8wJZwQKBM3JfyzggUGZuSyVpBgoMzclsrSCBQZm5KfFp1M7bJVNOnV3z/7Fsbl+vjlyZkhRSEkxmkKYqW7c6szK9hFwbBvzYOXbmQUVX1sUnfbnIGIRLYk6UC0aY63DFyVh5ElCunZM67SOpqOajVMoDhFHT4YtgzrWBpkBconCFzAxlXoJMnepCeVhzdib+eWdinJIzHogLy4Riaxk5zK5X21U7ZV5bpX9bp+/LpH6WVlV3AprbzLP6b61mHmJxsCkTdyZtpz7FJJhx5RhyYhFNSOS1FWW9WUWihgTucFZPoEBAUy3UrTTSVRyzLPzkKfzPI3VZlEBodbzhe5W5DSt7fdlztdCNp4x67zhzwlpUQyebv/+q2YfNor/aw1J3MwdlukjKRxRzk3JgW4PIabFFJScgHSItaT3Iw0CbSUyamTJndFUKZK9lstVaVfrtM3dJVstFKvYrFqMVnc6tRiEfb3tf3dK2nbWLb25jRtov9LvvjoU9tr/HtDwvw6VFEZlXVm9tonOnLUWiHKSMTRP6SfJJJuP6aaH/+pIABrPsj/LmWcECgTNyZCtIEFBmbkw5aQQKDM3Jhi0ggUGZuAmamOylpmTa5TsGMrq6Snaq65t3LJtPV2SSm/LK62qh7sRTju7szTdQhrNFXd+vfv4jpaKTfci5dNtNlBDxdE0GSxi/NWNLo90qSnKQINp9o0kgZhE2qdcFlKiaK+sUYenu6iiex1F6FMtl9bPVW5LQ2gpATJGmwlGREZExk2WwhBY8ZRCZ4djLNSZ3ZG9rfVRhW6xL1kmPLFa5yKGAUZDxUe4MJ3MS5RCsww0m2RpFNTl6RfC/j+nxy/skSzLx4okWatYgsqpApkRToOztrq5NmWZfmbdrlfzi5o+qlSTHBQ32DHTq0+NOdPtFHPLxX3fZVqv6uWhtQXmM0FsmhpcJMf8OkxcW5UyfUOb9KLUr2nunscK7UiRlKYNOuZLL7CbLiLIXiaRNBjJTq93Uy6+RTX/L0OZMIfLdq31KcmcV5YaKUclmr+q8RKL2wtnOuPV/f5wvVVXg+dhTGm60Qkcxu/axzLSPOq6yYTVSu3jGT3c5csajkqZM5rYm//qSAOy06A/y8FrBAoIzcF3rWCBQRm4L9WcECgTNyYEtIIFBmbkhVrEvCoQWJwKUUL9TVJqrPwRERCmTidEhNGwHBYgjIIyQkwWzF1r9tqVzHlLP2ztHlPD53E2t73bm/1VkRV4k5ReMj8qv8eC9msRJc9+VdVkWztaS3nY9nZBcH4vAI2k0h0gwMZLvduqtlQ17fSQ8qyyr8mie2hzfx0oTCItcoRO0xBWROMzRL085Zd9nnavs91gm0mypuCvSiV32PR/iv5GuahlTzpMLRXpUBFLfG2T5sgapRQ5EiQOtpIQWSIFUOKQgxlqRuj6SaIy3ViMY8ZBTMGysguOQoRjRCMYwGU/EJS8NmPbQ+pYul4523Cmg7K/yVxEMIL5TXdz/zN1nqDWJhotA/OakejjFdpSdcO+0QLTiGgoy6dG4W6bEjCSnSlUGKK6damvUr2/Lln/ZJSbqUsR9bzKx/VPyUgzR4mLTfQfC2lUoobY3Ufb9TFtFLbXTRTrbNbjkY2c2D5d1U03pRnofZ6GPMWzxZIpVso4ZahpRtFqOF0HCUf/6kgC+l+gP8uJZwQKDM3BbazgwUCZuS/lpBAoMzcl7rWCBQJm44ctQ8dAMZe1l2vTH4TDzYhwEA0pxkoDiaNRJkwNNygtzftt3rzFXjxr480UxLMMwnSsaGeGqTsLioSbbayNxHf0u7sLFJJIrPU886hFxKdY6S4ATUs1LDDA5+GKg6Q2llwJBjLWvUmyC2uT/sZ/kfDf/JjJrKUFlFNJkYp1GIdzYliD9plte66NGvTHY9vdopvuRM4UcxX/MKg0zZOTKEu5qRZF6MN9EkLP0pjfp03yFXiXUjrD6Y1IimYafqBxdn4U5kDzx4MZKarW7Krssh+RG+VPI2626alasyXWMReSRSxGy1zSXRFQ6qSsOg5E4zFlHDu6iGQ6FGLTvNj4V7HmXvRVGzNjDxqJAq5/FEDq2YeNFXEHoiIHk26WZbDjC2LGUYFR1U6oGMtdfXS5f8WQ5kTjzgf+mWIrWKjMbcivWSkRqVluz+D27zD+Ll7dLvFtr4VONM97ky11BeiqqNkijDs2pEWpMzUyxa96WtLAtVZLYA1F6iaZa1hD/+pIAaxnqj/LvWsGCg0NwXWtIIFAmbkyFYwQKDM3JgyxggUGhuXna9QkamegMEc8R0BTJpqZVFeplevPJaXpD773bFtYflF/yyOxh1ZaDiGc7aeppReJNOFHNemr6Z9XcWvneqWuCP6BjUQf//VXsFeZY9ySqO190pTsaRxsKrqpgJVSkcSKNXWlQ513tbA07AYyTRUjXoOtCyL52e6L5fETwf5fD3RFKMxxW6ol3K7MblN+frUgfGvbLrZX5w7rxOsr3lufe1kv2Yqq7kzGNpw5e3Qt0KyuQdu5YmHsmXkkG0kkp8T6NudsF3LIwGceeFMlTq77W+sKn1M89lXdTckL6tkeVl5IUBFZFh7PbtsQxiRk21PimevWJLOZKu3w+4gilu0gyUGVMzdk4v++kj0zhHmiNpW5CiTuljYWYh3lU0WUhBdmuf8VDIG+SCmR20ab61pNpP8vJKmtn+WcI6nAWSGeXYawPmcSfEFtc/cbri9eqjWk7mWB5VMlftKjum1PREqG55Z92ZMagRrZzL0luwtS8Lo35aCVCeQdboeiS//qSAKMl6A/y/1pBAoMzcF9raCBQZm4L9WsECgzNwXOs4IFBmbmfHW6ZwC5Gj2PoQDGVaTKv9llmw/Ce0+TJzfh7GimhoR3LUyIyhUpSLodUj5enh7btql48VIeFqNqiXQ4u1liyRySnJFmPBbXPSjByWiiWHZZGjpHlEFbCinMMLHwYsDRc9KIsKZaSapAdKDFDI/ulWjfOZy1pDLIk+53JARX8rVX9nfcyfupa8lpT4604eSwapI9SQjdxNdHTchYkxXSMSokARQrkwq1QFsoqmgE93YGgEIHdA9WjviwcZDFKYQCShdIMUaNPqtZi8fgDizFocHkzNk4kNpzYCsKb59172o35u7pba04ffK8a3k2cZqdTN5xmLhlu9Jsh2fNgMx03TfC2hC6MUsrZeik2VcOelpdxDOQkxSQ5eQYyqrqRsrdD0KcLkLxC7x605SMjJLkRNtqsJWVCdHWIuNiJpbo1C6W7mtzNJ2mpJVRakLQZKHQNGW1Mhr27iIRakyx8ji4uWVzWNEG3ZmUdIyhIg4hhMgQjyyocQhSEKDjIEv/6kgD7A+gP8vVZwQKDM3JgCwggUGhuSwlpBgoMbcFYLODBQJm4quDHyCdb07tUp0383+5xu60pnLmQtvpssge+1HwSEKObDQ1PLJKmXKbe7Rp/pDyzy0rv6yrLNJHlp2uycsRu+tQs2ze2gXJaNL+J0FnEG1ZLqELTYo9iRUHPgA0IoSpAfdF3wopMcFMq0K9S9rqFgpoJmNJgfDgIipmZXVkTNBRwAl//uVd9tm4QarYudZc97ss07SD5Kd98hRV3Bb7+xg9RXx4yMYjBqJOTDrRNtW4jWquzzjRqWJt0yaUml0c97j4gtwplrWv3o9wF+5hMhYbwzjTN0AyCAlTTiYEhka1Oe959j92TasVvrcbOZEIbynlH1rvN7sbJxm8uUyjTSmSt7ZadviVJE11EySJqPXDyLVZsIhd0bF6WLy8TW2oKZGRqQUu9bsou3hK67TaZHYzHSL4DOglpdYqiBFzmpvkNL5lPkVneba/L9UJPVFG5Pk0vavlNeME4m1rmsvYw2mgCtqFxtmb4UOmUj0kSkFLKDI1nsw2WPXJgUPn/+pIAty3vj/MjWkECg0NyZUtIEFBmbkutawQKBM3BaazggUCZucpAtKQ8KfVonWeuiupbm5GR/fIkLIya3KcWnTUg0fmqszvoCNU31mOj0/N3hmPbRuZL5SkRxhjUxux8KVMLkbYs2IStcR5Kn1C6oUC1YV4I8nBm/EbBpDaxZZx8sB82VErRUJXGLGhNsBTJXZ1vU9T8s6SFr5kypu0r5Z+aLnc89qrMJa4rF1Ts2bM1m9nazavanzBbNG/e9+Oinmp/EyKn6cURg9uW5aaagcy4vHpMs6GkomRRvsKY00Q/OJouhEAicDz3TLIhYMZep61KZb4xZOU+llMu7tfc9vh+jE6pfiP5Avr+2rKxyvetbTU3Tjkl7bHDSRepfZbsimgcpsxKpRcMXxAkJKPNHIao5R+TZhw1mYPuC2LDsTHyju1mheSw7HmSOKGGCAYo3vVTdleW6hMjN2OjAapzEGR2dsBGh1ModL9zOVn+/dic6TxOPu5Noxv1zo1409meme45cO2W3+72pPt2LMgc1nPFm2hDyUMcvnX9IUpFaLko//qSAFJW7I/zDlnBAoMzcmQrOBBQZm5L8V0ECgzNyYKtIIFBobnAdDXSkeFMjorVbdL4ntZP89CLcuz/fyJDO1CWycB1UuF1tfNZUnM0eolt1JUMleVhjYzy8q1zSjW1VTHI6VzlPUxexJ9MXRUsgNQSPVSVom10FqTI6b/IEaLNKTsrmXKwso2g8GMk73SdF3Vb/72mfbl8L1Ltuze2UQyQel2+sj48F4WlrO2faK2cnGxKbpgKrRmHSfW760GH/UKyCovCR/SVKRldqIOjqV8/DeTQOOPPasIkmtLTTMTxtc3UolxiBGU7CmVHZPbVW71jZsmxnk9t8krMVqyJsgNCJW+Y07pWH20NlkaPNlJ205DHctz5f0zUsigziNq1kWp2uT7oeg4NRBNNzSUTeqVpwtttNKNkq0zH3EJ5SI3SCUkLGkU9TonVBjK/R0XW0+ZqeWWtMlzpyFTZvkIIaLoRch/S6R5N+81pTHts++bvmWmz7eWdm2P6wRrrll7WWz02HJS76c39L6P8WkafBvMTWJSmyGu+vt0SOSJUGRZddP/6kgAsOeeP8sNZwYKBM3JfKzggUGZuS/FrBAoMzcF8rSCBQJm5zORAsQkBjKynZaS3q7R3LO5f0U5FrxboWWfCGbZHQvqe1NfSUW+vvZ6YuH27UXcb/hF9tnM+Yu0tqq57cgkxRLK2szS96fgzSnIlwc8pr5i2PLSvklxpRaJZSV5s9RMOiAKQ2T7BjJd0XWu6nf0nYRfE/65Z2IRXpHyVmxsrabXgtWza/jdt21je5++220nzoYlp1xU1DMaP1BFCysYrsqb0v+7WSu6KeT2qji0fqJaL0gVQEac8DzykbSfAgWbcJJYY6VIQFMjvVt6mU1c80Pmlncv/62ddZXf1yNFY+NmHX77dsvt87ZpX3CiOIw/zmUZF0jVzqLtB83KElL3U8ZbEzUoh1UxZ+we6BTF42rKO5Iv7UacWB73upvSrLJH2Z9AZNQpkop+1rPf56qe0Pv/SO1TRzLmXKOKb2IipiN3nbYsv3u/fsw/d/NOd29PdAsGZkbrLMqz6jl0jcpe03rTGOo94fUsH2fRVNmISu1vST23NpLdMRSQoldH/+pIA00rqD/LqWcECgzNyYAtYIFBmbgwVawQKDM3Bdq1ggUGZuEjQgIMmQKZVvUu7LVUzwjL2tK2kUuRUuQav7dYjW5lWVdiJV8eIyfiO9PwyKctZpzRUfcQvya7FoP6M+xb9kqSmfbmO6Xi7AsizE03uZ6zmRIlSlpRaPVVoougYwxc9KlIFourZAplZ+m9t1rIr5ZLt+aMW0IyzmhA7alfKcLd5lHNul5uHSVu4n22815cyrapeLjJRr7sNe7l5rlFXZlv+bJqEJTXRwJU0ZJI1It+TNY9jttKmQh0bQQKlBA0ovi3gwUyKa61I6De+mv9hzI7M8pw6S3TtyeoLzXMXMrZttTxnTdlS9933Hxy5euRD1MoMebFNE7DS+xWXXhKvfRTcgZbmU55AyztLhEoDNtRb5CKe4xSbsyLfJS4xzMKQidUKfVLTUpG7NRsXmzma+1L/36ex2udrMYqEtJtFaJhEeu7OYizZb/WRW0UpnZVab/4rtpTTRYjYOgrDPja0lrTJRQOnpuktLpC0gLK0EnUvGgenlxnFz9yBzSCC//qSAPt66g/y7FpBAoMzcl/LOCBQZm5LpV8ECgzNyXes4IFBmbnGlBSc6l0TClAYyVUjVvd657/LnLOZ2m2SnX41ayr30CsZDNEiyIu2bvkY8tfhTvCWdZd/NbG+F+SvCFFBrstDUS0tw7S97uwG7JO/JdzJbJQPJsGbbaCnN7vYbwgT0Ma6FKmzCzExwMZUEkbpb7Ocyzs/XNG52cOEXT2LTy+2NmOZFSQ2cyO/L1iJZb8uUj2rLopkZgpI3/5JzptV3tGiVtV7J80ViCIk9B4nZpRkdNvMEUdshCuLdSx2vHtAmMwpbpc59NhIMZM9NV1rRZbJ6yKZMd7rYkqurvRnq1XQ77Lt3onaY8x6Ttn+ep6n/eFRLVyqzTijnNCk5VNRyVMWZlZMHFGyhqHZnSZp1EC9qadTIRVBFHH5qJmzsyo68IjIJwdazy4GMtFfa3cjhdbMSLIDECKZOIphdmUTIyRHQI9Qy6qmrJu32rmEKaDXOvOqoPu/Fund5O+GeM6tLj1MkcMh5N0illszkCRx9lNK4TNEJnMwGRNIwBFJY//6kgCxSeuP8x5aQIKDM3Jeq0ggUGZuS+lpBAoMzcF9LSCBQRm5wSQJGjkhqINOFMtN7PRS3dvLWU9+1S825Xmm0Y8vB6EiCbU/PJK/3ZZoy38Rt48JzpcsniconlbuwixpySSTajTbDttkF20SKbNS086+Yh1UawE+IQRRHNpcqwt1AkpINiwQ7CDMecDIFYFMqla1VXSTZSebWxHvD9SuS7Z9i/s6Gcj31itGFdX8au93V636np3MjYdcxVzeY27ad10Lt5TeWMkrM67hlzefamXMPgwpGqlZzPh5yNli02w7Fu3h5JogssINgPBjJFl6T0V2qvpMtry/LN41LhIx5Glit1R7KVrli92quLe3x4/Z63I3v4hsqoqNhJ0/16+TZbQTNrKyH0qxxoxJJzudrnyiyBh9yVyN2e14zTRRSJxbF6UTs6ua1IHItQYyqR0treZwlP0eepWqd5H4p7kbqZm3foszSCVWM2svZ1oVOYhM69x2MFtPe7qj7TP7vk4lTKNm9Vl3B43c+lYqUHe0Nm+iKfUDSjEMXsFY3rTcLQT/+pIA9FDpD/L3WUECgTNyYYtYIFBmbgulZwQKDM3JfK2ggUGZuAsKOIA4pmZJHGwpR0lUq6a13KI9wGk1jO81CPA1pkHMnaNiRhxpxvmz4ae7znp6xna/bePDK3WVnb0jJmZOFNnec8u+okVIVRbkje8ojLQCsZ4d3QICUzN1IqS8KbmEcK+E1DAYySUyqOvQUs8z/U7qcy+nSlSkRfuNessbo2vCjeR+7tlTe4TY15v9kbPb1u3c1jtunHfJPoyJSdriHh0bNRlcQxRQdzBTTkJ6u03y49OMUeqKGwaRKQVMblJYRCpW4MZbKQT63Z1XIzvTSUiLSKUXU+NDooUZZgjY4dyg8Tm/zVFvcbbs7XB8Tb/5B8NmQZGUQolfZmnmSkmNc5VZMmZW3jUW9nMUc1smGLSxor2Kgnb90LYt9ACscm+OMB0yokUKZFtegmp7eV5tDKxtkRDGmQpNMJDJEQwmEUxkQru9rX8tlY7X7u7ouqxpQTx9aCd/SLJVUzacol9qpRHyt8SLc2krfw65CaU8HtoiUFfYSGSibEXgM1ik//qSAFG56I/y9lnBAoMzcloLODBQJm5L1WsECgzNwYStIIFBmbmNCDDZGnFDkUiQUyOta9FTqTasuilMEymkJMrQwhEYAo8ZAkaERAUMDM+94i9lmitVjtuxnqZu+amUT5mlkiVIthyM751PF6uH0g5HlqiT5OmTnioORRk9VApTmnlHQfiaJqFYiwosmdiMMh4MZIqWyWtfXM5lvXs70LMtU26L9q4cyHc4IxnDzG7eKd8+oxvivG66Lc5tEkIS8u1IECTOz4ppRk81RCu25Zp2eD6MN1409KCQUq73aXYFB9nwVCDEQ/fOyRdOeVow4kDFFtKtbMlU/S/z27YsiGXwylQzy1EQstuAfOBSXbd+fZlXrGYzZrcbPh7YnGs5hyM6jKWbWu6L4UfkfO5Wy9EC91PS51p8a5tW3s2O56lm6ow30U+NbTWIwK1Z0gYoUt3eimyFfk2IiR40TkMbA0OgsgDNAC6RJkmlZlneN7f3uxZXnYbS8KbtkSXBdP8aEWyu0vJS9jw82jtVIEf9w03rPeaZ62r7EDaqpWUxzVc4TP/6kgDkeuoP8w1aQQKBM3JiSxggUCZuS/FpBAoIzclyLWDBQZm4hBcRd5txiU4MZOszQW62sqk8ybIKenZ0DhTI89UMwEjdmEZkQAqi2/mvqV7GXn+T/EO+xfPc5bQ2Y5kYLeyllptp2nW3pktnTYpVokoKEWfC5MZYHNnKqEOxHMJEj3uuuQSdCjV2DGSF2r3sydct5tYhcddCXNXM2kNNWytIKYn2Z/6fPb1VPh/+EN2zJm7V6hFjJlnj12ox3ou4Qo+9stKFlpn42zOxdovApx9bw8KopAqierJr6SJJKR6TYlsXiy0GOMk3QYyro1VV1ULuXn/H/MyheZnpyCWyZQ8ymqIZn1D7p+E/vp4ivz4OJSxX+oM0R5t5lLZybujFmJejpRuNQcjm1sBKkumTSBrmZLCoayydkmxZBjgIoLL041B5h0YUmeRMQxUGKHvo1OlSz572Fd9MlPlyzIi+xlnESLDMVKRT3Ki/EPGfvuRbw+ufH12by3l8K9rl23Z8WWWdTJP4RzKYz03MyprGTw4LKlJep1pxN2OdPHtAszT/+pIAMoPoD/LfWcGCgTNyXgs4IFAmbgwNawQKDM3Bhq0ggUGZuMKMxEgiRcnjYMZV9alJ0aL8gv0YHTPhZsgAM6AzlGQiAbGBghetLtkxc3sQ5s7NtvJ7SFZtzSz9ETO0+9aCJvsuDJPK6FwnU48PsStjyRN0UYmIMgsh2Ux8TaKSjk4A1EkzMc8ndGlKgxkrUtGgtFV9OahcE1imMY4MKTOhCM4ScykIAphl7zMhznZrdjsKXZT3N4zFfHv5EbM7UVBsl3LGjdxzkceghyisSRAbBHP8XEH6idpzLtHU5NbakOoIUgNvW/liFJkpBiijVXurf1r+VL/t4vKl4SHzI5SU8qd3NQXOFrvYr2t47VITi9asbjS+UmmKolHm6L1hjYiXi8YyjIdheLpmHFULDjxzTI65GStQPa3FKocUcNLFBthMUUe9GMoGKFXQWndXd8sx2IgwGBsLZQikimY5qTyOZoEi/v/nen2VIV431jvmpfFbWNbR0GSkvwQKbZtN7rpM9s4nHQTu9o4jDk2OXllt6t325npLrXcWUQMDxC4R//qSAIrU6A/y41nBgoMzcl/LSCBQJm5L/WsECgTNwW2s4MFBobh3BpQYyX6Kk2syLZWfc02KomfnK0ypJNVUk495O+Z0Lus3b/+5/ZpHpZy/7Qe6a4yi1pXrIW3dZPavEQOoL1sZDmYXJuRjtOlFDw3pznJa8ED1SgiGTArm0qT0zGA1kYOUMMCnht9Cy2XmfbTVf4nE7TLjuotmM/K5iHY6i0wjgiLK19xjtTmZO2swyj/VxGNvo3KZkvAFEd+t41M8LspIgZKFYnTHP8UU2HVj13Y3n0oIQtBxpcATUmPpjMRFhAwRYBTDcQDGSeqnSTUpSbAmxBJyzwU0yOKSxozSIaRCZTFM+2z+NqEM9bLviZHtWZZh11EpoH50bNQMmW3PqDRpbFGvPicy7KJzc+m4Ebbpln6kkW2bRbpzTcYnO0+HWQxM6Ep6CmXW61ts12h5/qVczXzLJXvTSSFsmrvSLKQmBjQVt/mPcd8+fllGI2d7e+7HJqjeSk+aSiNeWQaK7T4T4tBIdhI3ASUGY3DKNE2k+HX3gxZPF5R8GkZNS//6kgBY2OoP8tZZQYKBM3Jfy1ggUGZuDF1nAgoMzcl4rOCBQJm5MwTEYmms+jnWBjKtNbvTdCvP0t/M5VjTJI+ykYJSnWq8yd1lUkY3//cNu4eXN95qjPVvXjCTmRuFfv3m8MpOzTTBiV2/6QqqRykpVhjX3J5VYmFPJhrHtxJyTRIsLtrLDIkbpTo3gDotEaDGSL3ZWug9PbRDhc1vabRKSj7Hql9s7zyZgRTbhFT5yMu5Q3IhC5yrD5s+diFP8WW5weYuzJrdKzp0i9U0PhR0aWUk0sBYaqAqUJwxzwQwgWuT4hqOHX0qBrKu3MOYVQDGVaKm1WTXn3pZtn8mh7/xIdCW5VcPwGcrtmdWia7P+0GbN71RGUxetRv7wah3aGldF0XDbBqPJ5e0pUEJTD+uxbn2g2m6+H0brGLQT7HlkrUeXJ0SWY9scCTWS5co1QYydTIaK0/dc3RG7sbdr+aQst6O01nO5nHmtbM2vDd6cm9u8mW+x86SEztZuJGXMagYo1iElgafy2QUibWE5dtdMHRUZMeEyK59o1czJdD1UQL/+pIA+VTqD/MIWkECgzNyYWtIIFBmbkwBZQQKDM3JfS1ggUGZuEdAYwjcED1GjaKAj4FrBjLemt6mZqiMC3yRxaICGFHBcCKQ00Q0EyJWRgZt273qMfZ2XaLfez3fgzsVF+fjW2sns/Ymobbi9krjDuillOmadZ8IOfqCsAibP0FYyBcHsTJKhI29HnPT6m6dgxQtNCrevrObnJcRT0BjrYbMACqiOOBEcTAXbcaHx8+tqWzt3T/5c1el/E/T38p3JNLpW29k9zclcRi9dqrzPxduCSwu8k68mD0ln20qKdkeUXJ/ygMyQKZatS1u67qc7no87/0pmfCp6s6bRVl9z6lsXHeZRFscrpbypRhu5uxyzE69NuLRbtlJ16fzgmKy6izpaDZV67QlSY02nQfkrpsSB5d5lzJRkLlCUGMMxUbCBNjJ5wp81vdaVknXU6ydaUr5ozm29ZmDVYCiZEw5MpMluyT7fnuZrrmCkkMg+tzwn2zoLQlOyb3NpHVVpQpUfOmKW4k/Hcw88xts+x89KqgDBk57oG08TuRVqKLfykXR//qSAHW65w/zAFrBAoIzcF0rWCBQJm4K7WMGCgTNyXYsYIFBmbnVA4wSFPQ2DGVdaTLsgpJByKc7M2OcI8zMsXl2kXQnYl+5pu0l81zbPlBoj+JSzIIUx+vif6LFpt4IHMXHfceF7vd6MmkUM1zDHbEmbc2mubSuSyduRol3NY8ysdKT3RSHXJiWRrI0DGSdFGp3dvpdh29LfppCU5nufN0JqynimOtoeTZRetlQ7+ZMm73cxjvjeJpymnfgE2lH2zbqHv+aRzxkmw2JWfsLQJyUUacUSJ6QpGJKsnBNHKNFUREzWZOLKNs9BJYsGMlquy9TUHX+RaSUi8vzjxWdBzYiUi5NdNMyCfwTfzNqmorLZJWbp7DPEO9a5naNrcnWRjMyNFre6pzrHxezF4eOU5h8EoQPWjElo301IDaNMdse6CTmMFlSfA3dk8LVBjKpSlU231+b9736U7TuRXu7000zh6NyqY1ME3m42XNtEyk261PcemiKO9J12zHQ673WUbRmvW7pdN6VGUJN6E+GwaqUy0jjbS2NPeguJLJLJbDnW//6kgD58ewP8xdYwIKDM3JgC1ggUGZuC/1nBAoMzcl+LGCBQZm5/LVoMoHSwKUHrpbIVrTUjUAqW7iZXPhNwiJA7MwkhmiBkDI3KQO+7f+nf93/rY8n6ikmWzs5W5GREazblbM6rX1J7owxeL1r9XkO5Zsls4HCm1NBA4VJZTxGSa0s7HFCtkBjK6k71qd0+dhalznHkM3IqWFpZIKO8FSDkdcot0/VblziWPdfpT8pWVGuZPT3Zn/DWpAtWWRMY2Ua1Z5dtb2fLY+bV5iTQekQQyjrKkjNaBVjYckixFOmGySVAHKjyEH6FMlmZdCzVVvq3mX+fDhIXJdXuxGZEvHQFJwsaB7YQjN3Mfdz1UdPtZeZGlMYUvTo9PTP6PyI21Ip/G8E9qK/37fZSybWnE0mHRUyIarQDH6xPED9TIwZXKTzRKtQU2oGPkd6VarpM8/h2+Epsa89kYsrwMR2yu2+zTLenSuX2cmC9dPaZcmY6M19iCLrp7wTGwowuNTPic26OY8tI1LlH7ZGGSs1rwm5AlFydVJas5WWFjbWOIIJ+9L/+pAAH3Xoj/LcWUECgzNwWws4MFAmbkwtawQKDM3BeizggUGZuQE3AUcjgUukmwpQ3s77JWcp7OoQUBM0ySWbkFCjkEjWENJGfu3r9nrb94qE5hym/afdsztm9Ty9t6hzo13ijeotVIcymmS0WKtMqnTtKFYiilGQxUks20MZbLM2IXMJ48g6YMZX2dtNFlt7ehrD8i3TyvT2YkWIRmJUuKkV1Zy0/626+T3h8stE/5lPVNTlyWyKUeHRx/Lp7jlM5sSxdVrJXdhro1JVMS0rWCgizMgjZEuM5ZRX0sfjoH18rVHE/AVSEGMmdT0Luk3gsssyTGJs0cZMRfREUTFHDjCui/V0xY1Zpola5eRirK6tc2iFUYXCE2WZopREsYtSr4i2UOl4dVV5PgkTweeKkjxRxvVwLSZS2cfJR0SpdkCHZYseDoqw+gYy7OkurUlzlpnD91PMuqUnF+kWbnlNWgprIG0tf7XzpyuWqtwvUJxmqzl2ze7mNUVWlbmoWkix8Nea3lzcyYRK5joAaKsVO2jTqJvXa5ZwerLMVJxyKbn/+pIA1aPqj/MYWcCCgzNyWWtYMFAmbgwFaQQKDM3JgC2ggUChuMcSSEhaKMDTgpku77ranucxKUUTSJuZ5jkMmQWIKiCYkKTJDQzVfaid29+vDT3p23a7EOcWTevrFQnyTJTNcGgjvepfcbcbw1Qk+O+6BWTs90x5iNRrn1oI4H2mJtka3qlNphadgxkgplK3QWp6i0OHSPc36m02LeyL7nU7kiqlyBpqaBP8jLybpnvDt5v7+HKh8eN1ck4hGWLZtqVoa0Nd9VNB1ZsnZue4usv2zkzSi47ztwDpE5JNESTfavIVgo41wpk+t1remuRTPLLs6/lD27M3PD51iKMzyIYkiI/+nbaTtMxDdsTfUHmruXyV0tj/FvkVNJQo5dukdiKZ6Rcp4Yn9RvdUVsIvDuebksLN50OJoHYkcuKilpIG6AlG5aaBEZUGKPf1LsuZq1Pnsiuz7dVa76k5Ts2zsimDAmS222Xxt/5varjHprm5a03hqUzXai06qPtbaG+FyXpbV4tWXO/XLRMiPDBoWgi9Z21i0TzKYoiSNikF2LoL//qSALu/6g/zAlrBAoMzcFzrWCBQJm4LpWcECgzNwYStIIFBmblGQ8KZWVWi93q/bP4b8T3OWkcokrpL3aWzCPDrORGdynen8VkNXg7GM2SrUhfl2LaP6kwje4XsIXdbdYZZ9Wx5IJt1GoRb6oiZ8KJlOaWkmeVK1WskyLquVrXoX4A0fKWnojQpls12ey1UYfObMm6YZxhMyuxxImhrGS1JCQfloTbvddmJN9ekMOzbcxS+roKrdPz37dyj9Rq16FIwlKqPTPKmqepQUu4mu7oorcoxFxCcOH8kqjqowIW+oJkkYOPBjJFFFF3X7Kc3Ph/tZPI6vn8VPpXWRDND8g5sZ2c2sStbY1pk+blu+5L4mrOYpEh7vVbUdE0rmnKSwszXZHdDxzLHnHKPGqejZIgQjHDsf0CzhxBVpYUYTIcRA2+MKgpksi60HWu9LFaNGyfPzhNu7v0tlQjUlPMQBQKqtQeDQ7lvvZoetT9sW0u0v9xnfMnG8eKo5bXkqvM+FtgTsz9PyYxEjU4q9tFBEsxziS04RqjMma0lcXl047ZuJf/6kgA+4+oP8tJZwYKCM3Jgi0ggUGZuS7FhBAoEzcl3rGCBQaG5nRggDGSlf1LSTzXRAlJsUzCorIwKWhmwkLGbIxHPu/7kZuO3fy7VsPj+aKi5N0K7rzXnrN9rg93TaDE0KqZrfA68hFZr6iNzuBUkU58kTExmvlRKUqQcYBygRBaQs0UeOBihVGuqu7fK9bhnhhGzxujLK2E0M+AUzC1w5/37bf20+dnCrw2c5r7jv3qlVc9nOvMjIrDcdqQolKM4XptoZ9OaKx5K7w0TpJB0DDzI8lmakKsFoMEOUAAp8XQe1673f7+lzOSJEMmKGqIR5mR0xGxEFraESi4nmeubfvuGUUUhGYg25u50uVZbdRsD/JTlv2eCnJIgY7YZkQlMgyd2B1k4ZrjzWCUyeFacNvQU9H412hiYNqZp5FjwADwIHwYyVdRgy10G9zIClzJO0Q5uwmIiR5yoQhcDEIAPl58NzOlPqrg8xbTc5PhV59qV0XqKE1NutvpuIo655PXDMfGWaXUC+LKLdR66TKQurZhrKRQwqyeMWmxVoUUXBwD/+pIArOHsj/L/WcECgzNyXetIIFAmbksBYwYKBM3JlKygQUGZuQY+Z130kE0lqkdzzpxIVq59im+bHhTzMZd/a2vDJyMYt61sWYU1P3eVdtyymy7h5nI3liI2sqHyEF6T1DNb6DWUmPw8qYS0pR5kvhbXcZCxsAekCdQeMRLKGJpC5kgEkkAHYSxUjAYoVup7K/bS9z37uyLRHKj2o+62xy3ZVepzt22v320XzPLTF45WVOv6Px3ok7PEn4zWi5kxOT9y6jTjr1zs1M1tNsuDSb6ut03pkj9TCz4I/dSOLNTU6+yMsTHAxlf1Krak3DLSefDSlTe9I0Rfcic5DqXOobOgPKM1ZTfZW1I26ou4ti4henW7ublVnyDtlM7kGi5k6faN4VTFxOomMkHdKkcVhgajGKJk5zC+1xRBAkMTSOnAgQgaGwGInC0fjQUlJYqYYYZ5555/d71EKRMi3M997PJIsI2tInM2BZFnOf1d6qHDJh4/WtmoSFOYR4QNs3rKKr8hoQ0JxL3CBo84IYYWhQKYFglR2joKymQbMnSslDFj//qSAEzN7Q/y8FbBAoEzcmYLSBBQZm5LbWkGCgjNyYmtIIFBmbk5AIVGwZikpLFJYqYU+eee+7z3DZH7RF/7X/yKyP8/7+mfeymvxMZkqZbtm93z98jNKp620riW8pPRHcpHzRTx6iGY1nat84ghvzeRxb3cq7TvrvDETMoqLN5uLgVlbl3hhzdUgaPNc9yP262MAQSFgD/zZ9SDE49VPNJ/zUZOzP4wXlrf5ilEGtVpKpd/mmzsYSL5pBU3atn/81otTDJaM9oY0imvyy///zEhbMnC8xMMTDZiNCn7eP5Zf//5lkYOE97sBUJGCwr/461a////M6MMhA5iYHmITQYhNBn9BGnV9Vq2a1buv////MNkYxGRzaUxMEggwgKwoJDDpqNUsQ2e3Pyq44byqzX/////5gESGJREZbTC8DE5OM0ngwiPjH4cAQGawYdIkqt4fjcrd5+P//////+ZZNRjZEmoEeZqK5gwfmNAoaXen/////Wy7Vq2ZUas5YfI08BQMST/zcdojFQ7WaPr/mlyVmiB9xXf+ZVZRulrZZf5sP/6kgDKOuoAAtpVQgVgYAJjCvgwrJgAXeVqwhneAAOnLNiDO8AB18Y6SZq5R5fv/80IcTFqENasQ2Gwsf+t//5iIumZjCZUJ5gMSGOzBlnjvn//+aTXhgYWmSC2ZiMoFBQFAFbLuOOv///zDYwRtLqmFBeY/ThrVNGczB3/xy3W////8xYaTMQ3CwOMAjYz2uDc8OMeJc14wzVyjyz5+M1Wvyn/////9p0jMSFcxUYzJwNMDhwwEFRgcGg1oAQMAB/KbHf///Wu///////5l43mSVMaxQRksYGHBqIBYZHI3/////V3ytWHKkxBTUUzLjkyIChhbHBoYSmqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuOTIgKGFscGhhKaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr/+pIARINcD/AAAGkHAAAAAAANIOAAAAAAAaQAAAAAAAA0gAAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq').play() 171 | } 172 | -------------------------------------------------------------------------------- /static/scripts/ViewTrades.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: View Trades 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 6.92 5 | // @description Make Trading on the market Better! 6 | // @author BlackAsLight 7 | // @include https://politicsandwar.com/index.php?id=26* 8 | // @include https://politicsandwar.com/index.php?id=90* 9 | // @match https://politicsandwar.com/nation/trade/* 10 | // @exclude https://politicsandwar.com/nation/trade/create/* 11 | // @icon https://avatars.githubusercontent.com/u/44320105 12 | // @grant none 13 | // ==/UserScript== 14 | 15 | 'use strict' 16 | /* Double Injection Protection 17 | -------------------------*/ 18 | if (document.querySelector('#Doc_ViewTrades')) { 19 | throw Error('This script was already injected...') 20 | } 21 | document.body.append(CreateElement('div', async divTag => { 22 | divTag.setAttribute('id', 'Doc_ViewTrades') 23 | divTag.style.setProperty('display', 'none') 24 | await Sleep(10000) 25 | divTag.remove() 26 | })) 27 | 28 | /* Global Variables 29 | -------------------------*/ 30 | const nationID = [...document.querySelectorAll('.sidebar a')].filter(aTag => aTag.href.includes('nation/id=')).map(aTag => parseInt(aTag.href.slice(37)))[0] 31 | const currentResource = Capitalize((location.search.slice(1).split('&').filter(arg => arg.startsWith('resource1='))[0] || '').slice(10)) || 'Money' 32 | const ascOrder = (location.search.slice(1).split('&').filter(arg => arg.startsWith('od='))[0] || '').slice(3) !== 'DESC' 33 | const token = (document.querySelector('input[name="token"]') || { value: null }).value 34 | 35 | const resourceBar = (() => { 36 | const resources = [...document.querySelectorAll('#resource-column .right')].map(tdTag => parseFloat(tdTag.textContent.replaceAll(',', ''))) 37 | return { 38 | Money: resources[0], 39 | Oil: resources[3], 40 | Coal: resources[2], 41 | Iron: resources[6], 42 | Bauxite: resources[7], 43 | Lead: resources[5], 44 | Uranium: resources[4], 45 | Food: resources[1], 46 | Gasoline: resources[8], 47 | Steel: resources[10], 48 | Aluminum: resources[11], 49 | Munitions: resources[9], 50 | Credits: resources[12] 51 | } 52 | })() 53 | 54 | // None = -1 55 | // Personal = 0 56 | // Alliance = 1 57 | // World = 2 58 | const marketType = (() => { 59 | if (location.pathname.startsWith('/nation/trade/')) { 60 | if (location.pathname.endsWith('world')) { 61 | return 2 62 | } 63 | if (location.pathname.endsWith('alliance')) { 64 | return 1 65 | } 66 | if (location.pathname === '/nation/trade/') { 67 | return 0 68 | } 69 | // E.g. Create Offer Page -|- The script wouldn't be injected in that specific case. 70 | return -1 71 | } 72 | const type = (location.search.slice(1).split('&').filter(arg => arg.startsWith('display'))[0] || '').slice(8) 73 | if (type === 'world') { 74 | return 2 75 | } 76 | if (type === 'alliance') { 77 | return 1 78 | } 79 | // "type" is either 'nation' or an empty string as display had no value or isn't present in the URL. 80 | // The game defaults to the personal market in these situations. 81 | return 0 82 | })() 83 | 84 | const myOffers = { 85 | Money: 0 86 | } 87 | 88 | /* User Configuration Settings 89 | -------------------------*/ 90 | document.querySelector('#leftcolumn').append(CreateElement('div', divTag => { 91 | divTag.classList.add('Doc_Config') 92 | divTag.append(document.createElement('hr')) 93 | divTag.append(CreateElement('strong', strongTag => strongTag.append('View Trades Config'))) 94 | 95 | divTag.append(document.createElement('br')) 96 | divTag.append('InfiniteScroll: ') 97 | divTag.append(CreateElement('input', inputTag => { 98 | inputTag.setAttribute('id', 'Doc_VT_InfiniteScroll') 99 | inputTag.setAttribute('type', 'checkbox') 100 | inputTag.checked = localStorage.getItem('Doc_VT_InfiniteScroll') ? true : false 101 | inputTag.onchange = () => { 102 | if (document.querySelector('#Doc_VT_InfiniteScroll').checked) { 103 | localStorage.setItem('Doc_VT_InfiniteScroll', true) 104 | } 105 | else { 106 | localStorage.removeItem('Doc_VT_InfiniteScroll') 107 | } 108 | location.reload() 109 | } 110 | })) 111 | 112 | divTag.append(document.createElement('br')) 113 | divTag.append('Zero Accountability: ') 114 | divTag.append(CreateElement('input', inputTag => { 115 | inputTag.setAttribute('id', 'Doc_VT_ZeroAccountability') 116 | inputTag.setAttribute('type', 'checkbox') 117 | inputTag.checked = localStorage.getItem('Doc_VT_ZeroAccountability') ? true : false 118 | inputTag.onchange = () => { 119 | if (document.querySelector('#Doc_VT_ZeroAccountability').checked) { 120 | localStorage.setItem('Doc_VT_ZeroAccountability', true) 121 | } 122 | else { 123 | localStorage.removeItem('Doc_VT_ZeroAccountability') 124 | } 125 | UpdateLinks() 126 | } 127 | })) 128 | 129 | if (currentResource !== 'Money') { 130 | divTag.append(document.createElement('br')) 131 | divTag.append(CreateElement('label', labelTag => labelTag.append( 132 | `Max ${currentResource}`, 133 | CreateElement('input', inputTag => { 134 | inputTag.type = 'number' 135 | inputTag.value = MaxAmount(currentResource) 136 | inputTag.addEventListener('change', function (_event) { 137 | const currentMax = MaxAmount(currentResource) 138 | const newMax = (Math.round(this.valueAsNumber * 100) / 100).toString() 139 | if (newMax !== 'NaN' && newMax !== currentMax) { 140 | const key = `Doc_MaxResource_${currentResource}` 141 | if (newMax > 0) { 142 | localStorage.setItem(key, newMax) 143 | } 144 | else if (currentMax > 0) { 145 | localStorage.removeItem(key) 146 | } 147 | UpdateLinks() 148 | } 149 | }) 150 | }) 151 | ))) 152 | } 153 | 154 | divTag.append(document.createElement('br')) 155 | divTag.append(CreateElement('label', labelTag => labelTag.append( 156 | `Min ${currentResource}`, 157 | CreateElement('input', inputTag => { 158 | inputTag.type = 'number' 159 | inputTag.value = MinAmount(currentResource) 160 | inputTag.addEventListener('change', function (_event) { 161 | const currentMin = MinAmount(currentResource) 162 | const newMin = (Math.round(this.valueAsNumber * 100) / 100).toString() 163 | if (newMin != 'NaN' && newMin != currentMin) { 164 | const key = `Doc_MinResource_${currentResource}` 165 | if (newMin > 0) { 166 | localStorage.setItem(key, newMin) 167 | } 168 | else if (currentMin > 0) { 169 | localStorage.removeItem(key) 170 | } 171 | UpdateQuantities() 172 | UpdateLinks() 173 | } 174 | }) 175 | }) 176 | ))) 177 | 178 | divTag.append(document.createElement('br')) 179 | divTag.append(CreateElement('div', divTag => { 180 | divTag.append(CreateElement('strong', strongTag => strongTag.append('MarketView'))) 181 | divTag.append(CreateElement('label', labelTag => { 182 | labelTag.setAttribute('for', 'both') 183 | labelTag.append('Both: ') 184 | })) 185 | divTag.append(CreateElement('input', inputTag => { 186 | inputTag.setAttribute('id', 'both') 187 | inputTag.setAttribute('type', 'radio') 188 | inputTag.setAttribute('name', 'marketView') 189 | inputTag.checked = !localStorage.getItem('Doc_MarketView') 190 | inputTag.onchange = () => { 191 | localStorage.removeItem('Doc_MarketView') 192 | UpdateMarketLinks() 193 | } 194 | })) 195 | divTag.append(document.createElement('br')) 196 | divTag.append(CreateElement('label', labelTag => { 197 | labelTag.setAttribute('for', 'sell') 198 | labelTag.append('Sell: ') 199 | })) 200 | divTag.append(CreateElement('input', inputTag => { 201 | inputTag.setAttribute('id', 'sell') 202 | inputTag.setAttribute('type', 'radio') 203 | inputTag.setAttribute('name', 'marketView') 204 | inputTag.checked = parseInt(localStorage.getItem('Doc_MarketView') || 1) ? false : true 205 | inputTag.onchange = () => { 206 | localStorage.setItem('Doc_MarketView', 0) 207 | UpdateMarketLinks() 208 | } 209 | })) 210 | divTag.append(document.createElement('br')) 211 | divTag.append(CreateElement('label', labelTag => { 212 | labelTag.setAttribute('for', 'buy') 213 | labelTag.append('Buy: ') 214 | })) 215 | divTag.append(CreateElement('input', inputTag => { 216 | inputTag.setAttribute('id', 'buy') 217 | inputTag.setAttribute('type', 'radio') 218 | inputTag.setAttribute('name', 'marketView') 219 | inputTag.checked = parseInt(localStorage.getItem('Doc_MarketView')) ? true : false 220 | inputTag.onchange = () => { 221 | localStorage.setItem('Doc_MarketView', 1) 222 | UpdateMarketLinks() 223 | } 224 | })) 225 | })) 226 | })) 227 | 228 | /* Styling 229 | -------------------------*/ 230 | document.head.append(CreateElement('style', styleTag => { 231 | /* Config 232 | -------------------------*/ 233 | styleTag.append('.Doc_Config { text-align: center; padding: 0 1em; font-size: 0.8em; }') 234 | styleTag.append('.Doc_Config hr { margin: 0.5em 0; }') 235 | styleTag.append('.Doc_Config strong { font-size: 1.25em; }') 236 | styleTag.append('.Doc_Config input { margin: 0; }') 237 | styleTag.append('.Doc_Config button { font-size: inherit; font-weight: normal; padding: 0; }') 238 | styleTag.append('.Doc_Config div strong { display: block; }') 239 | styleTag.append('.Doc_Config div label { display: inline-block; font-weight: normal; margin: 0 0 0 25%; text-align: left; width: 25%; }') 240 | styleTag.append('.Doc_Config div input { display: inline-block; margin: 0 25% 0 0; width: 25%; }') 241 | 242 | /* Market Links 243 | -------------------------*/ 244 | styleTag.append('#MarketLinks { text-align: center; }') 245 | styleTag.append('#MarketLinks a { cursor: pointer; }') 246 | styleTag.append('#MarketLinks form { display: none; }') 247 | 248 | /* ReGain 249 | -------------------------*/ 250 | styleTag.append('#ReGain { text-align: center; }') 251 | styleTag.append('#RegainStats { text-align: center; }') 252 | 253 | /* Table 254 | -------------------------*/ 255 | styleTag.append('#Offers { text-align: center; }') 256 | styleTag.append('#Offers hr { border: 0.25em solid #d9534f; margin: 0; }') 257 | styleTag.append('#Offers p { margin: 0; padding: 5px; }') 258 | styleTag.append('.Offer { align-items: center; display: grid; grid-gap: 1em; grid-template-areas: "Nations Nations Nations Nations Date Quantity Price Form" "Nations Nations Nations Nations Date Quantity Create Form"; grid-template-columns: repeat(8, 1fr); padding: 1em; }') 259 | styleTag.append('.Nations { align-items: center; display: grid; grid-gap: 1em; grid-template-areas: "Left Right"; grid-template-columns: repeat(2, 1fr); overflow-wrap: anywhere; }') 260 | styleTag.append('.Outline { outline-color: #d9534f; outline-style: solid; outline-width: 0.25em; }') 261 | // Media: 991px 262 | styleTag.append('@media only screen and (max-width: 991px) { ') 263 | styleTag.append('.Offer { grid-template-areas: "Nations Nations Date Quantity Price Form" "Nations Nations Date Quantity Create Form"; grid-template-columns: repeat(6, 1fr); }') 264 | styleTag.append('.Nations { grid-template-areas: "Left" "Right"; grid-template-columns: 1fr; }') 265 | styleTag.append('.Hide { display: none; }') 266 | styleTag.append(' }') 267 | // Media: 660px 268 | styleTag.append('@media only screen and (max-width: 660px) { ') 269 | styleTag.append('.Offer { grid-template-areas: "Nations Quantity Price Form" "Nations Date Create Form"; grid-template-columns: repeat(4, 1fr); }') 270 | styleTag.append(' }') 271 | // Media: 440px 272 | styleTag.append('@media only screen and (max-width: 440px) { ') 273 | styleTag.append('.Offer { grid-template-areas: "Nations Quantity Form" "Nations Price Form" "Nations Create Form" "Date Date Date"; grid-template-columns: repeat(3, 1fr); }') 274 | styleTag.append(' }') 275 | 276 | /* Table Form 277 | -------------------------*/ 278 | styleTag.append('#Offers input { width: 100%; }') 279 | styleTag.append('.sOffer input[type="submit"] { background-color: rgb(92, 184, 92) !important; }') 280 | styleTag.append('.sOffer input[type="submit"]:hover, .sOffer input[type="submit"]:focus { background-color: hsl(120, 39%, 64%) !important; }') 281 | styleTag.append('.bOffer input[type="submit"] { background-color: rgb(51, 122, 183) !important; }') 282 | styleTag.append('.bOffer input[type="submit"]:hover, .bOffer input[type="submit"]:focus { background-color: hsl(208, 56%, 56%) !important; }') 283 | styleTag.append('.Offer button { background-color: rgb(217, 83, 79); color: rgb(255, 255, 255); font: inherit; margin: 2px; padding: 10px; text-decoration: none; width: 100%; }') 284 | styleTag.append('.Offer button:hover, .Offer button:focus { background-color: hsl(2, 64%, 63%); }') 285 | 286 | styleTag.append('.Offer button, .Offer input[type="submit"] { border-radius: 3px; transition: background-color 300ms ease; }') 287 | })) 288 | 289 | document.head.append(CreateElement('style', styleTag => { 290 | styleTag.setAttribute('id', 'GameTheme') 291 | UpdateTheme(styleTag) 292 | })) 293 | 294 | // Dark Theme 2.0 = 2 295 | // Dark Theme 1.0 = 1 296 | // Light Theme = 0 297 | function GetTheme() { 298 | const links = [...document.querySelectorAll('link')].map(linkTag => linkTag.href) 299 | if (links.includes('https://politicsandwar.com/css/dark-theme-2.0-beta.css')) { 300 | return 2 301 | } 302 | if (links.includes('https://politicsandwar.com/css/dark-theme.min.css')) { 303 | return 1 304 | } 305 | return 0 306 | } 307 | 308 | function SetTheme(theme) { 309 | if (theme) { 310 | document.head.append(CreateElement('link', linkTag => { 311 | linkTag.setAttribute('rel', 'stylesheet') 312 | linkTag.setAttribute('href', theme === 2 ? 'https://politicsandwar.com/css/dark-theme-2.0-beta.css' : 'https://politicsandwar.com/css/dark-theme.min.css') 313 | })) 314 | } 315 | else { 316 | [...document.querySelectorAll('link')].filter(linkTag => linkTag.href === 'https://politicsandwar.com/css/dark-theme-2.0-beta.css' || linkTag.href === 'https://politicsandwar.com/css/dark-theme.min.css')[0].remove() 317 | } 318 | UpdateTheme() 319 | } 320 | 321 | function UpdateTheme(styleTag = document.querySelector('#GameTheme')) { 322 | const theme = GetTheme() 323 | styleTag.textContent = '' 324 | if (theme === 2) { 325 | styleTag.append('.Offer:nth-child(2n + 1) { background: rgb(39, 42, 47); }') 326 | styleTag.append('.Offer:nth-child(2n) { background: rgb(34, 36, 39); }') 327 | styleTag.append('.Doc_Config { color: rgb(255, 255, 255); }') 328 | styleTag.append('.Doc_Config strong { color: rgb(91, 117, 254); }') 329 | styleTag.append('.Doc_Config button { color: rgb(91, 117, 254); text-decoration: underline; }') 330 | styleTag.append('.Doc_Config button:hover { color: inherit; }') 331 | return 332 | } 333 | if (theme === 1) { 334 | styleTag.append('.Offer:nth-child(2n) { background: rgb(31, 31, 31); }') 335 | return 336 | } 337 | styleTag.append('.Offer:nth-child(2n) { background: rgb(204, 205, 227); }') 338 | return 339 | } 340 | 341 | /* Functions 342 | -------------------------*/ 343 | function CreateElement(type, func) { 344 | const tag = document.createElement(type) 345 | func(tag) 346 | return tag 347 | } 348 | 349 | function Sleep(ms) { 350 | return new Promise(a => setTimeout(() => a(true), ms)) 351 | } 352 | 353 | function Capitalize(text) { 354 | const words = text.split(' ').filter(word => word.length) 355 | for (const i in words) { 356 | words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1).toLowerCase() 357 | } 358 | return words.join(' ') 359 | } 360 | 361 | function MaxAmount(resource) { 362 | return parseFloat(localStorage.getItem(`Doc_MaxResource_${Capitalize(resource)}`)) || 0 363 | } 364 | 365 | function MinAmount(resource) { 366 | return parseFloat(localStorage.getItem(`Doc_MinResource_${Capitalize(resource)}`)) || 0 367 | } 368 | 369 | function ReplaceAll(text, search, replace) { 370 | if (search === replace || replace.search(search) != -1) { 371 | throw 'Infinite Loop!' 372 | } 373 | while (text.indexOf(search) != -1) { 374 | text = text.replaceAll(search, replace) 375 | } 376 | return text 377 | } 378 | 379 | function CreateOfferLink(resource, price, sellersWanted, quantity) { 380 | if (typeof quantity !== 'number') { 381 | const max = parseInt(localStorage.getItem(`Doc_MaxResource_${resource}`)) || Infinity 382 | if (localStorage.getItem('Doc_VT_ZeroAccountability')) { 383 | quantity = Math.max(Math.min(Math.floor(sellersWanted ? (resourceBar.Money - MinAmount('Money')) / price - ((quantity && GetQuantity(quantity)) ?? 0) : resourceBar[resource] - MinAmount(resource) - ((quantity && GetQuantity(quantity)) ?? 0)), max), 0) 384 | } 385 | else { 386 | quantity = Math.max(Math.min(Math.floor(sellersWanted ? (resourceBar.Money - MinAmount('Money') - myOffers.Money) / price : resourceBar[resource] - MinAmount(resource) - myOffers[resource]), max), 0) 387 | } 388 | } 389 | if (quantity) { 390 | return `https://politicsandwar.com/nation/trade/create/?resource=${resource.toLowerCase()}&p=${price}&q=${quantity}&t=${sellersWanted ? 'b' : 's'}` 391 | } 392 | } 393 | 394 | function UpdateLinks() { 395 | for (const resource in myOffers) { 396 | if (resource === 'Money') { 397 | continue 398 | } 399 | 400 | for (let i = 0; i < 2; ++i) { 401 | [...document.querySelectorAll(`.${i ? 's' : 'b'}Outbid_${resource}`)].forEach(aTag => UpdateLink(aTag, CreateOfferLink(resource, GetPrice(aTag.parentElement.parentElement) + (i ? 1 : -1), i ? true : false))); 402 | [...document.querySelectorAll(`.${i ? 's' : 'b'}Match_${resource}, .${i ? 's' : 'b'}TopUp_${resource}`)].forEach(aTag => UpdateLink(aTag, CreateOfferLink(resource, GetPrice(aTag.parentElement.parentElement), i ? true : false, aTag.classList.contains(`${i ? 's' : 'b'}TopUp_${resource}`) ? aTag.parentElement.parentElement : undefined))) 403 | } 404 | } 405 | } 406 | 407 | function UpdateLink(aTag, link) { 408 | if (link) { 409 | aTag.setAttribute('href', link) 410 | aTag.style.removeProperty('text-decoration') 411 | } 412 | else { 413 | aTag.removeAttribute('href') 414 | aTag.style.setProperty('text-decoration', 'line-through') 415 | } 416 | } 417 | 418 | function UpdateQuantities() { 419 | if (currentResource === 'Money') { 420 | [...document.querySelectorAll('.Offer')].forEach(divTag => { 421 | const offerType = [...divTag.classList].filter(x => x.startsWith('Type-'))[0].slice(5) 422 | if (offerType !== 'Receive-Public') { 423 | return 424 | } 425 | 426 | divTag.querySelector('.Amount').value = CalcUnits(offerType, divTag.querySelector('.Hide').textContent === 'SELLERS WANTED', Capitalize([...divTag.classList].filter(x => x.endsWith('Offer') && x.length > 6)[0].slice(0, -5)), GetQuantity(divTag), GetPrice(divTag)) 427 | }) 428 | return 429 | } 430 | 431 | const data = JSON.parse(localStorage.getItem(`Doc_VT_ReGain_${currentResource}`)); 432 | [...document.querySelectorAll('.Offer')].forEach(divTag => { 433 | const offerType = [...divTag.classList].filter(x => x.startsWith('Type-'))[0].slice(5) 434 | if (offerType !== 'Receive-Public') { 435 | return 436 | } 437 | 438 | const price = GetPrice(divTag) 439 | const units = CalcUnits(offerType, divTag.querySelector('.Hide').textContent === 'SELLERS WANTED', Capitalize([...divTag.classList].filter(x => x.endsWith('Offer') && x.length > 6)[0].slice(0, -5)), GetQuantity(divTag), price) 440 | if (data && divTag.classList.contains('sOffer') === data.bought) { 441 | const quantity = data.bought ? data.levels.reduce((quantity, level) => quantity + (price > level.price ? level.quantity : 0), 0) : data.levels.reduce((quantity, level) => quantity + (price < level.price ? level.quantity : 0), 0) 442 | divTag.querySelector('.Amount').value = Math.min(units, quantity) 443 | } 444 | else { 445 | divTag.querySelector('.Amount').value = units 446 | } 447 | }) 448 | } 449 | 450 | function CreateRow(tdTags) { 451 | const sellerWanted = tdTags[1].textContent === 'SELLER WANTED' 452 | const buyerWanted = tdTags[2].textContent === 'BUYER WANTED' 453 | const offerType = (() => { 454 | const type = tdTags[6].children[0].tagName 455 | if (type === 'FORM') { 456 | return sellerWanted || buyerWanted ? 'Receive-Public' : 'Receive-Personal' 457 | } 458 | if (type === 'A') { 459 | return sellerWanted || buyerWanted ? 'Send-Public' : 'Send-Personal' 460 | } 461 | return tdTags[6].textContent.includes('Accepted') ? 'Accepted' : 'Embargo' 462 | })() 463 | const sellUnits = offerType.endsWith('Public') || offerType === 'Embargo' ? sellerWanted : parseInt(tdTags[2].children[0].children[0].href.split('=')[1]) === nationID === (offerType !== 'Receive-Personal') 464 | const resource = Capitalize(tdTags[4].children[0].getAttribute('title')) 465 | if (myOffers[resource] === undefined) { 466 | myOffers[resource] = 0 467 | } 468 | const price = parseInt(tdTags[5].textContent.split('/')[0].trim().replaceAll(',', '')) 469 | const quantity = parseInt(tdTags[4].textContent.trim().replaceAll(',', '')) 470 | const units = CalcUnits(offerType, sellUnits, resource, quantity, price) 471 | const date = new Date(`${tdTags[3].childNodes[0].textContent} ${tdTags[3].childNodes[2].textContent}`) 472 | 473 | document.querySelector('#Offers').append(CreateElement('div', divTag => { 474 | divTag.classList.add('Offer') 475 | divTag.classList.add(`${sellUnits ? 's' : 'b'}Offer`) 476 | divTag.classList.add(`${resource.toLowerCase()}Offer`) 477 | divTag.classList.add(`Type-${offerType}`) 478 | 479 | // Nations 480 | divTag.append(CreateElement('div', divTag => { 481 | divTag.classList.add('Nations') 482 | divTag.style.setProperty('grid-area', 'Nations') 483 | divTag.append(CreateElement('div', divTag => { 484 | divTag.style.setProperty('grid-area', 'Left') 485 | GenerateNationBio(divTag, tdTags[1], offerType.endsWith('Public') || offerType === 'Embargo', sellUnits, 'SELLERS WANTED') 486 | })) 487 | divTag.append(CreateElement('div', divTag => { 488 | divTag.style.setProperty('grid-area', 'Right') 489 | GenerateNationBio(divTag, tdTags[2], offerType.endsWith('Public') || offerType === 'Embargo', !sellUnits, 'BUYERS WANTED') 490 | })) 491 | })) 492 | 493 | // Date 494 | divTag.append(CreateElement('div', divTag => { 495 | divTag.classList.add('Date') 496 | divTag.style.setProperty('grid-area', 'Date') 497 | divTag.append(FormatDate(date)) 498 | })) 499 | 500 | // Quantity 501 | divTag.append(CreateElement('div', divTag => { 502 | divTag.classList.add('Quantity') 503 | divTag.style.setProperty('grid-area', 'Quantity') 504 | divTag.append(CreateElement('img', imgTag => imgTag.setAttribute('src', tdTags[4].children[0].src))) 505 | divTag.append(' ') 506 | divTag.append(FormatNumber(quantity)) 507 | })) 508 | 509 | // Price 510 | divTag.append(CreateElement('div', divTag => { 511 | divTag.classList.add('Price') 512 | divTag.style.setProperty('grid-area', 'Price') 513 | divTag.append(FormatMoney(price)) 514 | divTag.append('/Ton') 515 | divTag.append(CreateElement('div', divTag => divTag.append(FormatMoney(price * (offerType.startsWith('Receive') ? units : quantity))))) 516 | })) 517 | 518 | // Outbid + Match || TopUp || Duplicate 519 | divTag.append(CreateElement('div', divTag => { 520 | divTag.classList.add('Create') 521 | divTag.style.setProperty('grid-area', 'Create') 522 | if (offerType.startsWith('Receive')) { 523 | // Outbid + Match 524 | divTag.append(CreateElement('a', aTag => { 525 | aTag.classList.add(`${sellUnits ? 's' : 'b'}Outbid_${resource}`) 526 | aTag.append('Outbid') 527 | })) 528 | divTag.append(document.createElement('br')) 529 | divTag.append(CreateElement('a', aTag => { 530 | aTag.classList.add(`${sellUnits ? 's' : 'b'}Match_${resource}`) 531 | aTag.append('Match') 532 | })) 533 | return 534 | } 535 | if (offerType === 'Send-Public') { 536 | // TopUp 537 | divTag.append(CreateElement('a', aTag => { 538 | aTag.classList.add(`${sellUnits ? 's' : 'b'}TopUp_${resource}`) 539 | aTag.append('TopUp') 540 | })) 541 | if (sellUnits) { 542 | myOffers.Money += price * quantity 543 | } 544 | else { 545 | myOffers[resource] += quantity 546 | } 547 | return 548 | } 549 | // Duplicate 550 | divTag.append(CreateElement('a', aTag => { 551 | aTag.setAttribute('href', CreateOfferLink(resource, price, offerType === 'Embargo' === sellUnits, quantity)) 552 | aTag.append('Duplicate') 553 | })) 554 | })) 555 | 556 | // Form/Delete || Accepted || Embargo 557 | divTag.append(CreateElement('div', divTag => { 558 | divTag.style.setProperty('grid-area', 'Form') 559 | if (offerType === 'Accepted' || offerType === 'Embargo') { 560 | divTag.append(CreateElement('img', imgTag => imgTag.setAttribute('src', tdTags[6].children[0].src))) 561 | if (offerType === 'Accepted') { 562 | divTag.append(' ') 563 | divTag.append(CreateElement('b', bTag => bTag.append(sellUnits ? 'SOLD' : 'BOUGHT'))) 564 | divTag.append(document.createElement('br')) 565 | const spanTag = tdTags[6].children[2] 566 | divTag.append(FormatDate(new Date(`${spanTag.childNodes[0].textContent} ${spanTag.childNodes[2].textContent}`))) 567 | } 568 | return 569 | } 570 | if (offerType.startsWith('Receive')) { 571 | divTag.append(CreateElement('form', formTag => { 572 | formTag.setAttribute('action', location.href) 573 | formTag.setAttribute('method', 'POST') 574 | formTag.append(CreateElement('input', inputTag => { 575 | inputTag.setAttribute('type', 'hidden') 576 | inputTag.setAttribute('name', 'tradeaccid') 577 | inputTag.setAttribute('value', tdTags[6].querySelector('input[name="tradeaccid"]').value) 578 | })) 579 | formTag.append(CreateElement('input', inputTag => { 580 | inputTag.setAttribute('type', 'hidden') 581 | inputTag.setAttribute('name', 'ver') 582 | inputTag.setAttribute('value', tdTags[6].querySelector('input[name="ver"]').value) 583 | })) 584 | formTag.append(CreateElement('input', inputTag => { 585 | inputTag.setAttribute('type', 'hidden') 586 | inputTag.setAttribute('name', 'token') 587 | inputTag.setAttribute('value', token) 588 | })) 589 | formTag.append(CreateElement('input', inputTag => { 590 | inputTag.classList.add('Amount') 591 | inputTag.setAttribute('type', 'number') 592 | inputTag.setAttribute('name', 'rcustomamount') 593 | inputTag.setAttribute('value', units) 594 | // "this" doesn't work with an arrow function. 595 | inputTag.onchange = function () { 596 | const divTag = this.parentElement.parentElement.parentElement 597 | divTag.querySelector('.Price div').textContent = FormatMoney(GetPrice(divTag) * this.value) 598 | } 599 | })) 600 | formTag.append(CreateElement('input', inputTag => { 601 | inputTag.classList.add('Accept') 602 | inputTag.classList.add(sellUnits ? 'Sell' : 'Buy') 603 | inputTag.setAttribute('type', 'submit') 604 | inputTag.setAttribute('name', 'acctrade') 605 | inputTag.setAttribute('value', sellUnits ? 'Sell' : 'Buy') 606 | })) 607 | })) 608 | } 609 | if (offerType.startsWith('Send') || offerType.endsWith('Personal')) { 610 | divTag.append(CreateElement('button', buttonTag => { 611 | buttonTag.setAttribute('href', `https://politicsandwar.com/index.php?id=26&${tdTags[6].querySelector('a').href.split('?')[1].split('&').filter(arg => arg.startsWith('tradedelid') || arg.startsWith('ver')).join('&')}`) 612 | buttonTag.setAttribute('checked', sellUnits) 613 | buttonTag.append(CreateElement('img', imgTag => imgTag.setAttribute('src', tdTags[6].querySelector('a img').src))) 614 | buttonTag.append(' Delete') 615 | // "this" doesn't work with an arrow function. 616 | buttonTag.onclick = async function () { 617 | this.disabled = true 618 | await fetch(this.getAttribute('href')) 619 | const divTag = this.parentElement.parentElement 620 | if (this.getAttribute('checked') === 'true') { 621 | myOffers.Money -= GetPrice(divTag) * GetQuantity(divTag) 622 | } 623 | else { 624 | myOffers[Capitalize([...divTag.classList].filter(x => x.length > 6)[0].slice(0, -5))] -= GetQuantity(divTag) 625 | } 626 | divTag.remove() 627 | if (!localStorage.getItem('Doc_VT_ZeroAccountability')) { 628 | UpdateLinks() 629 | } 630 | } 631 | })) 632 | } 633 | })) 634 | })) 635 | } 636 | 637 | function GenerateNationBio(divTag, tdTag, offerIsPublicOrEmbargo, offerWanted, wantedMessage) { 638 | if (offerIsPublicOrEmbargo) { 639 | if (offerWanted) { 640 | divTag.classList.add('Hide') 641 | divTag.append(CreateElement('b', bTag => bTag.append(wantedMessage))) 642 | return 643 | } 644 | } 645 | else { 646 | if (!offerWanted) { 647 | divTag.classList.add('Hide') 648 | } 649 | } 650 | divTag.append(CreateElement('a', aTag => { 651 | aTag.setAttribute('href', tdTag.children[0].children[0].href) 652 | aTag.append(tdTag.children[0].children[0].textContent) 653 | aTag.append(CreateElement('img', imgTag => { 654 | imgTag.classList.add('tinyflag') 655 | imgTag.setAttribute('src', tdTag.children[0].children[0].children[0].src) 656 | })) 657 | })) 658 | divTag.append(document.createElement('br')) 659 | divTag.append(tdTag.children[0].children[1].nextSibling.textContent.trim()) 660 | divTag.append(document.createElement('br')) 661 | if (tdTag.children[0].lastChild.nodeName === '#text') { 662 | divTag.append(CreateElement('i', iTag => iTag.append('None'))) 663 | } 664 | else { 665 | const tag = tdTag.children[0].lastChild.nodeName === 'A' ? tdTag.children[0].lastChild : tdTag.children[0].lastChild.previousElementSibling 666 | divTag.append(CreateElement('a', aTag => { 667 | aTag.setAttribute('href', tag.href) 668 | aTag.append(tag.textContent) 669 | })) 670 | } 671 | } 672 | 673 | function CalcUnits(offerType, sellerWanted, resource, quantity, price) { 674 | if (offerType.endsWith('Public') || offerType === 'Receive-Personal') { 675 | return Math.max(Math.min(Math.floor(sellerWanted ? resourceBar[resource] - MinAmount(resource) : (resourceBar.Money - MinAmount('Money')) / price), quantity), 0) 676 | } 677 | return quantity 678 | } 679 | 680 | function GetQuantity(divTag) { 681 | return parseInt(divTag.querySelector('.Quantity').textContent.trim().replaceAll(',', '')) 682 | } 683 | 684 | function GetPrice(divTag) { 685 | return parseInt(divTag.querySelector('.Price').textContent.slice(1).split('/')[0].replaceAll(',', '')) 686 | } 687 | 688 | function FormatDate(date = new Date()) { 689 | let text = '' 690 | text += date.getHours().toString().padStart(2, 0) 691 | text += ':' 692 | text += date.getMinutes().toString().padStart(2, 0) 693 | text += ' ' 694 | text += date.getDate().toString().padStart(2, 0) 695 | text += '/' 696 | text += ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][date.getMonth()] 697 | text += '/' 698 | text += date.getFullYear() 699 | return text 700 | } 701 | 702 | function FormatNumber(number, digits = 0) { 703 | return number.toLocaleString('en-US', { maximumFractionDigits: digits }) 704 | } 705 | 706 | function FormatMoney(money, digits = 0) { 707 | return money.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: digits }) 708 | } 709 | 710 | async function InfiniteScroll() { 711 | if (marketType < 1 || !localStorage.getItem('Doc_VT_InfiniteScroll')) { 712 | return 713 | } 714 | 715 | const { offset, pages } = (() => { 716 | const pTags = [...document.querySelectorAll('p.center')] 717 | const words = pTags[4].textContent.split(' ') 718 | const nums = words.splice(1, 2)[0].split('-').map(x => parseInt(x)) 719 | if (nums[0]) { 720 | location.href = GetMinURL(0) 721 | } 722 | pTags[4].textContent = words.join(' ') 723 | 724 | pTags[2].append(pTags[3].children[4]) 725 | if (nums[1] < 50) { 726 | pTags[3].textContent = '' 727 | pTags[3].append(`Note: The game by default only loaded ${offset} trade offers.`) 728 | pTags[3].append(document.createElement('br')) 729 | pTags[3].append('We strongly recommend going to your ') 730 | pTags[3].append(CreateElement('a', aTag => { 731 | aTag.setAttribute('href', 'https://politicsandwar.com/account/#4') 732 | aTag.setAttribute('target', '_blank') 733 | aTag.append('Account') 734 | })) 735 | pTags[3].append(' settings and changing the default search results to 50,') 736 | pTags[3].append(document.createElement('br')) 737 | pTags[3].append('or if this was a link provided by some bot, that you ask the maximum query in the link to be set to at least 50, preferably 100.') 738 | } 739 | else { 740 | pTags[3].remove() 741 | } 742 | pTags[5].remove() 743 | 744 | return { 745 | offset: nums[1], 746 | pages: Math.ceil((parseInt(words[1]) - nums[1]) / 100) 747 | } 748 | })() 749 | if (!pages) { 750 | return 751 | } 752 | 753 | for (let i = 0; i < pages; ++i) { 754 | console.time(`Load Page - ${i + 1}`) 755 | const doc = new DOMParser().parseFromString(await (await fetch(GetMinURL(100 * i + offset))).text(), 'text/html') 756 | console.timeEnd(`Load Page - ${i + 1}`) 757 | console.time('Convert Table') 758 | const trTags = [...doc.querySelectorAll('.nationtable tr')].slice(1) 759 | for (const trTag of trTags) { 760 | try { 761 | CreateRow([...trTag.children]) 762 | } 763 | catch (e) { 764 | console.error(e) 765 | } 766 | } 767 | console.timeEnd('Convert Table') 768 | } 769 | } 770 | 771 | function GetMinURL(min) { 772 | let minExists = false 773 | let maxExists = false 774 | const args = location.search.slice(1).split('&').map(arg => { 775 | if (arg.startsWith('minimum=')) { 776 | arg = `minimum=${min}` 777 | minExists = true 778 | } 779 | else if (arg.startsWith('maximum=')) { 780 | arg = 'maximum=100' 781 | maxExists = true 782 | } 783 | return arg 784 | }) 785 | if (!minExists) { 786 | args.push(`minimum=${min}`) 787 | } 788 | if (!maxExists) { 789 | args.push('maximum=100') 790 | } 791 | return location.origin + location.pathname + '?' + args.join('&') 792 | } 793 | 794 | function AutoScroll() { 795 | if (marketType < 1 || currentResource === 'Money' || (location.search.slice(1).split('&').filter(arg => arg.startsWith('buysell='))[0] || '').length > 8) { 796 | document.querySelector('.Hide').scrollIntoView({ 797 | behavior: 'smooth', 798 | block: 'center' 799 | }) 800 | return 801 | } 802 | const divTag = document.querySelector('#Offers'); 803 | (divTag.querySelector('p') || CreateElement('p', pTag => { 804 | divTag.insertBefore(pTag, CreateElement('hr', hrTag => divTag.insertBefore(hrTag, document.querySelector(`.${ascOrder ? 'b' : 's'}Offer`)))) 805 | divTag.insertBefore(document.createElement('hr'), pTag) 806 | pTag.append(`Profit Gap: ${FormatMoney((GetPrice(pTag.nextElementSibling.nextElementSibling) - GetPrice(pTag.previousElementSibling.previousElementSibling)) * (ascOrder ? 1 : -1))}/Ton`) 807 | })).scrollIntoView({ 808 | behavior: 'smooth', 809 | block: 'center' 810 | }) 811 | } 812 | 813 | function Mistrade() { 814 | if (marketType < 2) { 815 | return false 816 | } 817 | let checkOne = false 818 | let checkTwo = false 819 | location.search.slice(1).split('&').map(arg => arg.split('=')).forEach(arg => { 820 | if (arg[0] === 'buysell') { 821 | if (!arg[1].length) { 822 | checkOne = true 823 | } 824 | } 825 | else if (arg[0] === 'resource1') { 826 | if (arg[1].length) { 827 | checkTwo = true 828 | } 829 | } 830 | }) 831 | if (checkOne && checkTwo) { 832 | const sellTag = ascOrder ? [...document.querySelectorAll('.sOffer')].pop() : document.querySelector('.sOffer') 833 | const buyTag = ascOrder ? document.querySelector('.bOffer') : [...document.querySelectorAll('.bOffer')].pop() 834 | if (!(sellTag && buyTag)) { 835 | return false 836 | } 837 | if (GetPrice(sellTag) > GetPrice(buyTag)) { 838 | // Scroll To Mistrade! 839 | const misTag = (() => { 840 | const myTime = new Date().getTime() - 1000 * 60 // ms s 841 | const sellTime = new Date(sellTag.querySelector('.Date').textContent).getTime() 842 | const buyTime = new Date(buyTag.querySelector('.Date').textContent).getTime() 843 | if (sellTime > myTime || buyTag > myTime) { 844 | Sleep(0).then(() => { 845 | // Update Amounts 846 | const inputTag1 = sellTime > buyTime ? sellTag.querySelector('.Amount') : buyTag.querySelector('.Amount') 847 | const inputTag2 = sellTime > buyTime ? buyTag.querySelector('.Amount') : sellTag.querySelector('.Amount') 848 | if (inputTag1.value > inputTag2.value) { 849 | inputTag1.value = inputTag2.value 850 | } 851 | }) 852 | return sellTime > buyTime ? sellTag : buyTag 853 | } 854 | return GetQuantity(sellTag) < GetQuantity(buyTag) ? sellTag : buyTag 855 | })() 856 | misTag.scrollIntoView({ 857 | behavior: 'smooth', 858 | block: 'center' 859 | }) 860 | misTag.classList.add('Outline') 861 | 862 | 863 | // Switch Themes 864 | SetTheme(GetTheme() ? 0 : 2) 865 | 866 | // Announce that you detected a mistrade for Mistrade Detection Script. 867 | document.body.append(CreateElement('div', divTag => { 868 | divTag.setAttribute('id', 'Doc_Scrolled') 869 | divTag.style.setProperty('display', 'none') 870 | })) 871 | return true 872 | } 873 | } 874 | return false 875 | } 876 | 877 | function CreateMarketLinks() { 878 | const offerSide = parseInt(localStorage.getItem('Doc_MarketView')); 879 | [...document.querySelectorAll('#resource-column a')].forEach(aTag => { 880 | aTag.setAttribute('href', MarketLink(aTag.textContent.replaceAll('$', '').trim().slice(0, -1), offerSide)) 881 | }) 882 | return CreateElement('p', pTag => { 883 | pTag.setAttribute('id', 'MarketLinks') 884 | pTag.append(MarketTag('Oil', offerSide)) 885 | pTag.append(' | ') 886 | pTag.append(MarketTag('Coal', offerSide)) 887 | pTag.append(' | ') 888 | pTag.append(MarketTag('Iron', offerSide)) 889 | pTag.append(' | ') 890 | pTag.append(MarketTag('Bauxite', offerSide)) 891 | pTag.append(' | ') 892 | pTag.append(MarketTag('Lead', offerSide)) 893 | pTag.append(' | ') 894 | pTag.append(MarketTag('Uranium', offerSide)) 895 | pTag.append(' | ') 896 | pTag.append(MarketTag('Food', offerSide)) 897 | pTag.append(document.createElement('br')) 898 | pTag.append(MarketTag('Gasoline', offerSide)) 899 | pTag.append(' | ') 900 | pTag.append(MarketTag('Steel', offerSide)) 901 | pTag.append(' | ') 902 | pTag.append(MarketTag('Aluminum', offerSide)) 903 | pTag.append(' | ') 904 | pTag.append(MarketTag('Munitions', offerSide)) 905 | pTag.append(' | ') 906 | pTag.append(MarketTag('Credits', offerSide)) 907 | pTag.append(document.createElement('br')) 908 | pTag.append(CreateElement('a', aTag => { 909 | aTag.setAttribute('href', 'https://politicsandwar.com/index.php?id=26&display=nation&resource1=&buysell=&ob=date&od=DESC&maximum=100&minimum=0&search=Go') 910 | aTag.append('My Trades') 911 | })) 912 | pTag.append(' | ') 913 | pTag.append(CreateElement('a', aTag => { 914 | aTag.append('Activity') 915 | // "this" doesn't work with an arrow function. 916 | aTag.onclick = function () { 917 | this.parentElement.querySelector('input[type="submit"]').click() 918 | } 919 | })) 920 | pTag.append(CreateElement('form', formTag => { 921 | formTag.setAttribute('action', `https://politicsandwar.com/nation/id=${nationID}&display=trade`) 922 | formTag.setAttribute('method', 'POST') 923 | formTag.append(CreateElement('input', inputTag => { 924 | inputTag.setAttribute('type', 'number') 925 | inputTag.setAttribute('name', 'maximum') 926 | inputTag.setAttribute('value', 1000) 927 | })) 928 | formTag.append(CreateElement('input', inputTag => { 929 | inputTag.setAttribute('type', 'number') 930 | inputTag.setAttribute('name', 'minimum') 931 | inputTag.setAttribute('value', 0) 932 | })) 933 | formTag.append(CreateElement('input', inputTag => { 934 | inputTag.setAttribute('type', 'submit') 935 | inputTag.setAttribute('name', 'search') 936 | inputTag.setAttribute('value', 'Go') 937 | })) 938 | })) 939 | }) 940 | } 941 | 942 | function UpdateMarketLinks() { 943 | const pTag = document.querySelector('#MarketLinks') 944 | const hrTag = pTag.nextElementSibling 945 | pTag.remove() 946 | hrTag.parentElement.insertBefore(CreateMarketLinks(), hrTag) 947 | } 948 | 949 | function InjectMarketLinks() { 950 | const formTag = document.querySelector('#rightcolumn form[method="GET"]') 951 | formTag.parentElement.insertBefore(CreateMarketLinks(), formTag) 952 | formTag.parentElement.insertBefore(document.createElement('hr'), formTag) 953 | } 954 | 955 | function MarketTag(resource, offerSide) { 956 | return CreateElement('a', aTag => { 957 | aTag.setAttribute('href', MarketLink(resource, offerSide)) 958 | aTag.append(CreateElement('img', imgTag => { 959 | if (resource === 'Food') { 960 | imgTag.setAttribute('src', 'https://politicsandwar.com/img/icons/16/steak_meat.png') 961 | } 962 | else if (resource === 'Credits') { 963 | imgTag.setAttribute('src', 'https://politicsandwar.com/img/icons/16/point_gold.png') 964 | } 965 | else { 966 | imgTag.setAttribute('src', `https://politicsandwar.com/img/resources/${resource.toLowerCase()}.png`) 967 | } 968 | })) 969 | aTag.append(` ${resource}`) 970 | if (localStorage.getItem(`Doc_VT_ReGain_${Capitalize(resource)}`)) { 971 | aTag.append('*') 972 | } 973 | }) 974 | } 975 | 976 | function MarketLink(resource, offerSide) { 977 | if (resource === 'Money') { 978 | return 'https://politicsandwar.com/index.php?id=26&display=nation&resource1=&buysell=&ob=date&od=DESC&maximum=100&minimum=0&search=Go' 979 | } 980 | return `https://politicsandwar.com/index.php?id=26&display=world&resource1=${resource.toLowerCase()}&buysell=${offerSide ? 'sell' : (offerSide === 0 ? 'buy' : '')}&ob=price&od=DEF&maximum=100&minimum=0&search=Go` 981 | } 982 | 983 | function ReGain() { 984 | const pTag = (document.querySelector('img[alt="Success"]') || { parentElement: null }).parentElement 985 | if (!pTag) { 986 | return 987 | } 988 | pTag.parentElement.parentElement.insertBefore(document.createElement('hr'), pTag.nextElementSibling) 989 | const words = ReplaceAll(pTag.textContent.trim(), ' ', ' ').split(' ') 990 | if (words[2] !== 'accepted') { 991 | return 992 | } 993 | 994 | pTag.setAttribute('id', 'ReGain') 995 | let profit = 0 996 | let quantity = parseInt(words[8].replaceAll(',', '')) 997 | const price = parseInt(words[14].slice(1, -1).replaceAll(',', '')) / quantity 998 | const bought = words[7] === 'bought' 999 | const key = `Doc_VT_ReGain_${Capitalize(words[9].slice(0, -1))}` 1000 | const data = JSON.parse(localStorage.getItem(key)) 1001 | pTag.append(` ${FormatMoney(price)}/Ton.`) 1002 | pTag.append(document.createElement('br')) 1003 | 1004 | if (data && data.bought !== bought) { 1005 | for (const i in data.levels) { 1006 | if ((!bought && price > data.levels[i].price) || (bought && price < data.levels[i].price)) { 1007 | const amount = Math.min(data.levels[i].quantity, quantity) 1008 | profit += amount * Math.abs(data.levels[i].price - price) 1009 | data.levels[i].quantity -= amount 1010 | quantity -= amount 1011 | } 1012 | if (!quantity) { 1013 | break 1014 | } 1015 | } 1016 | data.levels = data.levels.filter(level => level.quantity) 1017 | } 1018 | 1019 | /* One would think that based off the if statement above we'd only need to check if quantity was true-ish, 1020 | but in the one circumstance where the User sells a quantity at a price below the levels, either partly or fully below, 1021 | there would still be levels left to be regained and quantity left over. In this case we would not want to offer the button. 1022 | This circumstance also applies in the reverse. Where the User buys a quantity at a price above the levels.*/ 1023 | let buttonExists = false 1024 | if (quantity && (!data || data.bought === bought || !data.levels.length)) { 1025 | buttonExists = true 1026 | pTag.append(CreateElement('a', aTag => { 1027 | aTag.setAttribute('id', 'Doc_ReGain') 1028 | aTag.append(`Re${bought ? 'sell' : 'buy'} for Profit?`) 1029 | aTag.onclick = () => { 1030 | const data = JSON.parse(localStorage.getItem(key)) 1031 | if (data) { 1032 | const index = data.levels.findIndex(level => level.price === price) 1033 | if (index > -1) { 1034 | data.levels[index].quantity += quantity 1035 | } 1036 | else { 1037 | data.levels.push({ 1038 | quantity: quantity, 1039 | price: price 1040 | }) 1041 | data.levels.sort((x, y) => x.price - y.price) 1042 | if (bought) { 1043 | data.levels.reverse() 1044 | } 1045 | } 1046 | localStorage.setItem(key, JSON.stringify(data)) 1047 | } 1048 | else { 1049 | localStorage.setItem(key, JSON.stringify({ 1050 | bought: bought, 1051 | levels: [ 1052 | { 1053 | quantity: quantity, 1054 | price: price 1055 | } 1056 | ] 1057 | })) 1058 | } 1059 | aTag.remove() 1060 | UpdateReGainStats() 1061 | } 1062 | })) 1063 | } 1064 | else if (data) { 1065 | if (data.levels.length) { 1066 | localStorage.setItem(key, JSON.stringify(data)) 1067 | } 1068 | else { 1069 | localStorage.removeItem(key) 1070 | } 1071 | } 1072 | 1073 | if (profit) { 1074 | if (buttonExists) { 1075 | pTag.append(' | ') 1076 | } 1077 | pTag.append(`Made ${FormatMoney(profit, 2)} Profit.`) 1078 | } 1079 | } 1080 | 1081 | function InjectReGainStats(divTag) { 1082 | const formTag = document.querySelector('#rightcolumn form[method="GET"]') 1083 | formTag.parentElement.insertBefore(divTag, formTag) 1084 | formTag.parentElement.insertBefore(document.querySelector('hr'), formTag) 1085 | } 1086 | 1087 | function UpdateReGainStats() { 1088 | const hrTag = (document.querySelector('#RegainStats') || { nextElementSibling: null }).nextElementSibling 1089 | if (hrTag) { 1090 | hrTag.previousElementSibling.remove() 1091 | } 1092 | const divTag = CreateReGainStats() 1093 | if (divTag) { 1094 | if (hrTag) { 1095 | hrTag.parentElement.insertBefore(divTag, hrTag) 1096 | } 1097 | else { 1098 | InjectReGainStats(divTag) 1099 | } 1100 | } 1101 | } 1102 | 1103 | function CreateReGainStats() { 1104 | if (currentResource === 'Money') { 1105 | return 1106 | } 1107 | const data = JSON.parse(localStorage.getItem(`Doc_VT_ReGain_${currentResource}`)) 1108 | if (!data) { 1109 | return 1110 | } 1111 | return CreateElement('div', divTag => { 1112 | divTag.setAttribute('id', 'RegainStats') 1113 | for (const level of data.levels) { 1114 | divTag.append(CreateElement('p', pTag => { 1115 | pTag.append(`${data.bought ? 'Bought' : 'Sold'} ${FormatNumber(level.quantity)} Ton${level.quantity > 1 ? 's' : ''} @ `) 1116 | pTag.append(FormatMoney(level.price)) 1117 | pTag.append('/ton | ') 1118 | pTag.append(CreateElement('a', aTag => { 1119 | aTag.append('Forget') 1120 | // "this" doesn't work with an arrow function. 1121 | aTag.onclick = function () { 1122 | const price = parseInt(this.parentElement.childNodes[1].textContent.slice(1).replaceAll(',', '')) 1123 | const data = JSON.parse(localStorage.getItem(`Doc_VT_ReGain_${currentResource}`)) 1124 | data.levels = data.levels.filter(level => level.price !== price) 1125 | if (data.levels.length) { 1126 | localStorage.setItem(`Doc_VT_ReGain_${currentResource}`, JSON.stringify(data)) 1127 | this.parentElement.remove() 1128 | } 1129 | else { 1130 | localStorage.removeItem(`Doc_VT_ReGain_${currentResource}`) 1131 | const divTag = this.parentElement.parentElement 1132 | divTag.nextElementSibling.remove() 1133 | divTag.remove() 1134 | UpdateMarketLinks() 1135 | } 1136 | } 1137 | })) 1138 | })) 1139 | } 1140 | if (data.levels.length > 1) { 1141 | divTag.append(CreateElement('a', aTag => { 1142 | aTag.append('Forget All') 1143 | // "this" doesn't work with an arrow function. 1144 | aTag.onclick = function () { 1145 | localStorage.removeItem(`Doc_VT_ReGain_${currentResource}`) 1146 | const divTag = this.parentElement 1147 | divTag.nextElementSibling.remove() 1148 | divTag.remove() 1149 | UpdateMarketLinks() 1150 | } 1151 | })) 1152 | } 1153 | }) 1154 | } 1155 | 1156 | /* Start 1157 | -------------------------*/ 1158 | async function Main() { 1159 | console.time('Convert Table') 1160 | CreateElement('div', divTag => { 1161 | divTag.setAttribute('id', 'Offers') 1162 | const tableTag = document.querySelector('.nationtable') 1163 | tableTag.parentElement.insertBefore(divTag, tableTag) 1164 | 1165 | const trTags = [...tableTag.querySelectorAll('tr')].slice(1) 1166 | for (const trTag of trTags) { 1167 | try { 1168 | CreateRow([...trTag.children]) 1169 | } 1170 | catch (e) { 1171 | console.error(e) 1172 | } 1173 | } 1174 | tableTag.remove() 1175 | }) 1176 | console.timeEnd('Convert Table') 1177 | const mistradeExists = Mistrade() 1178 | ReGain() 1179 | InjectMarketLinks() 1180 | UpdateReGainStats() 1181 | await InfiniteScroll() 1182 | if (mistradeExists && document.querySelector('#ReGain')) { 1183 | document.querySelector('#Doc_ReGain').click() 1184 | } 1185 | else if (!(mistradeExists || Mistrade())) { 1186 | const pTag = document.querySelector('#ReGain') 1187 | if (pTag) { 1188 | pTag.scrollIntoView({ 1189 | behavior: 'smooth', 1190 | block: 'center' 1191 | }) 1192 | await Sleep(3000) 1193 | } 1194 | AutoScroll() 1195 | } 1196 | console.time('Updating') 1197 | UpdateQuantities() 1198 | UpdateLinks() 1199 | console.timeEnd('Updating') 1200 | } 1201 | 1202 | if (marketType > -1) { 1203 | try { 1204 | Main() 1205 | } 1206 | catch (e) { 1207 | console.error(e) 1208 | } 1209 | } 1210 | -------------------------------------------------------------------------------- /static/scripts/aSyncly.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Doc: Sync the aSyncly 3 | // @namespace https://politicsandwar.com/nation/id=19818 4 | // @version 1.3 5 | // @description Saves Settings to the Dossier Page 6 | // @author BlackAsLight 7 | // @match https://politicsandwar.com/* 8 | // @exclude https://politicsandwar.com/human/ 9 | // @icon https://avatars.githubusercontent.com/u/44320105 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | 'use strict'; 14 | /* Double Injection Protection 15 | -------------------------*/ 16 | if (document.querySelector('#Doc_aSyncly')) { 17 | throw Error('This script was already injected...'); 18 | } 19 | document.body.append(CreateElement('div', async divTag => { 20 | divTag.setAttribute('id', 'Doc_aSyncly'); 21 | divTag.style.setProperty('display', 'none'); 22 | await Sleep(10000); 23 | divTag.remove(); 24 | })); 25 | 26 | /* Migration 27 | -------------------------*/ 28 | if (localStorage.getItem('Doc_Commendations')) { 29 | localStorage.removeItem('Doc_Commendations'); 30 | } 31 | if (localStorage.getItem('Doc_Commendations_Nations')) { 32 | localStorage.removeItem('Doc_Commendations_Nations'); 33 | } 34 | 35 | /* Global Variables 36 | -------------------------*/ 37 | const lastCheckedKey = '!Doc_aS1'; 38 | let updating = false; 39 | 40 | /* Functions 41 | -------------------------*/ 42 | function CreateElement(type, func) { 43 | const tag = document.createElement(type); 44 | func(tag); 45 | return tag; 46 | } 47 | 48 | function Sleep(ms) { 49 | return new Promise(a => setTimeout(() => a(true), ms)); 50 | } 51 | 52 | function Parse(text) { 53 | const num = text - 0; 54 | if (isNaN(num)) { 55 | try { 56 | return JSON.parse(text); 57 | } 58 | catch { 59 | return text; 60 | } 61 | } 62 | return num; 63 | } 64 | 65 | async function Sync(lastChecked) { 66 | const { json, token } = await (async () => { 67 | const doc = new DOMParser().parseFromString(await (await fetch('https://politicsandwar.com/nation/dossier/')).text(), 'text/html'); 68 | const token = doc.querySelector('input[name="token"]').value; 69 | const text = doc.querySelector('textarea[name="dossier"]').textContent; 70 | try { 71 | return { 72 | json: JSON.parse(text), 73 | token: token 74 | }; 75 | } 76 | catch { 77 | console.error('Text in Dossier was invalid JSON.', text); 78 | return { token: token }; 79 | } 80 | })(); 81 | 82 | if (json && json[0] > lastChecked) { 83 | console.info('aSyncly: Updating localStorage'); 84 | UpdateLocalStorage(json); 85 | return; 86 | } 87 | console.info('aSyncly: Updating Dossier'); 88 | updating = true; 89 | try { 90 | await UpdateDossier(json ? json.h : null, token); 91 | } 92 | catch (e) { 93 | console.error(e); 94 | } 95 | updating = false; 96 | } 97 | 98 | function UpdateLocalStorage(json) { 99 | const keys = Object.keys(localStorage).filter(key => key.startsWith('Doc_')); 100 | Object.entries(json[2]).forEach(([key, value]) => { 101 | localStorage.setItem(key, typeof value === 'object' ? JSON.stringify(value) : value); 102 | const i = keys.findIndex(k => k === key); 103 | if (i > -1) { 104 | keys.splice(i, 1); 105 | } 106 | }); 107 | keys.forEach(key => localStorage.removeItem(key)); 108 | localStorage.setItem(lastCheckedKey, json[0]); 109 | } 110 | 111 | async function UpdateDossier(hash, token) { 112 | const lastChecked = new Date().getTime(); 113 | const data = Object.entries(localStorage).filter(([key, _]) => key.startsWith('Doc_')).sort((x, y) => x[0] > y[0] ? 1 : y[0] < x[0] ? -1 : 0).reduce((obj, [key, value]) => { 114 | obj[key] = Parse(value); 115 | return obj; 116 | }, {}); 117 | const hashedText = await Hash(JSON.stringify(data)); 118 | if (hashedText === hash) { 119 | localStorage.setItem(lastCheckedKey, lastChecked); 120 | return; 121 | } 122 | console.info('Saved Data', new DOMParser().parseFromString(await (await fetch('https://politicsandwar.com/nation/dossier/', { 123 | method: 'POST', 124 | body: (() => { 125 | const formData = new FormData(); 126 | formData.append('dossier', JSON.stringify([lastChecked, hashedText, data])); 127 | formData.append('update', 'Update'); 128 | formData.append('token', token); 129 | return formData; 130 | })() 131 | })).text(), 'text/html').querySelector('textarea[name="dossier"]').textContent.length); 132 | localStorage.setItem(lastCheckedKey, lastChecked); 133 | } 134 | 135 | async function Hash(text) { 136 | return [...new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text)))].map(bytes => bytes.toString(16)).join(''); 137 | } 138 | 139 | /* Start 140 | -------------------------*/ 141 | globalThis.window.addEventListener('beforeunload', () => updating || undefined); 142 | globalThis.window.addEventListener('unload', () => updating || undefined); 143 | Main(); 144 | function Main() { 145 | // Don't run between 23:55 and 00:05 | inclusive/exclusive. 146 | const date = new Date(); 147 | if ((date.getUTCHours() === 23 && date.getUTCMinutes() >= 55) || (date.getUTCHours() === 0 && date.getUTCMinutes() < 5)) { 148 | return; 149 | } 150 | 151 | const players = parseInt(document.querySelector('#leftcolumn div.sidebar').textContent.split('\n').map(x => x.trim()).filter(x => x.length).pop()); 152 | // Don't run if 800+ players are online. 153 | if (players >= 800) { 154 | return; 155 | } 156 | 157 | const lastChecked = parseInt(localStorage.getItem(lastCheckedKey)) || 0; 158 | // Don't run if there is 500+ players online and has been less than 30mins since last updated. 159 | if (players >= 500 && lastChecked > date.getTime() - 1000 * 60 * 30) { // ms * secs * mins 160 | return; 161 | } 162 | 163 | // Don't run if it's been less than 5mins since last updated. 164 | if (lastChecked > date.getTime() - 1000 * 60 * 5) { 165 | return; 166 | } 167 | 168 | console.info('aSyncly: Syncing...'); 169 | Sync(lastChecked); 170 | } 171 | -------------------------------------------------------------------------------- /static/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import { build, stop } from "esbuild"; 2 | import { denoPlugins } from "@luca/esbuild-deno-loader"; 3 | import { basename } from "@std/path"; 4 | import { extname } from "@std/path"; 5 | 6 | async function esbuild(inPath: string, outPath: string): Promise { 7 | const { errors, warnings } = await build({ 8 | plugins: denoPlugins(), 9 | entryPoints: [inPath], 10 | outfile: outPath, 11 | format: "esm", 12 | bundle: true, 13 | minify: true, 14 | sourcemap: "inline", 15 | }); 16 | errors.forEach((error) => console.error(error)); 17 | warnings.forEach((warning) => console.warn(warning)); 18 | } 19 | 20 | async function createScript(path: string): Promise { 21 | if (extname(path) !== ".ts") { 22 | throw new TypeError( 23 | `Expected a ".ts" type extension. Got ${extname(path)} in ${path}`, 24 | ); 25 | } 26 | 27 | const name = basename(path).slice(0, -3); 28 | const promise = esbuild(path, `tests/${name}.min.js`); 29 | const file = await Deno.create(`tests/${name}.user.js`); 30 | await file.write(new TextEncoder().encode( 31 | await async function (): Promise { 32 | const text = await Deno.readTextFile(path); 33 | const a = text.indexOf("// ==UserScript=="); 34 | if (a == -1) { 35 | throw new SyntaxError( 36 | `Failed to locate "// ==UserScript==" in ${path}`, 37 | ); 38 | } 39 | const b = text.indexOf("// ==/UserScript==", a) + 40 | "// ==/UserScript==".length; 41 | if (b == -1) { 42 | throw new SyntaxError( 43 | `Failed to locate "// ==/UserScript==" in ${path}`, 44 | ); 45 | } 46 | return text.slice(a, b) + "\n'use strict';\n"; 47 | }(), 48 | )); 49 | await promise; 50 | await (await Deno.open(`tests/${name}.min.js`)) 51 | .readable 52 | .pipeTo(file.writable); 53 | await Deno.remove(`tests/${name}.min.js`); 54 | } 55 | 56 | await Deno.remove("tests/", { recursive: true }).catch(() => {}); 57 | await Deno.mkdir("./tests/", { recursive: true }); 58 | 59 | await Promise.allSettled(Deno.args.map((arg) => createScript(arg))); 60 | stop(); 61 | 62 | console.log( 63 | `${ 64 | performance.now().toLocaleString("en-US", { maximumFractionDigits: 2 }) 65 | }ms`, 66 | ); 67 | -------------------------------------------------------------------------------- /ts/main.ts: -------------------------------------------------------------------------------- 1 | document.querySelectorAll('a[href^="#"]').forEach((aTag) => 2 | aTag.addEventListener("click", click) 3 | ); 4 | 5 | function click(this: HTMLAnchorElement, event: MouseEvent): void { 6 | event.preventDefault(); 7 | document.querySelector(this.hash)?.scrollIntoView(); 8 | } 9 | -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | /* Types 2 | -------------------------*/ 3 | export type None = undefined | null; 4 | // deno-lint-ignore no-explicit-any 5 | export type Forced = any; 6 | 7 | /* Enums 8 | -------------------------*/ 9 | export enum Resource { 10 | Money, 11 | Oil, 12 | Coal, 13 | Iron, 14 | Bauxite, 15 | Lead, 16 | Uranium, 17 | Food, 18 | Gasoline, 19 | Steel, 20 | Aluminum, 21 | Munitions, 22 | Credits, 23 | } 24 | 25 | export enum Market { 26 | Oil, 27 | Coal, 28 | Iron, 29 | Bauxite, 30 | Lead, 31 | Uranium, 32 | Food, 33 | Gasoline, 34 | Steel, 35 | Aluminum, 36 | Munitions, 37 | Credits, 38 | } 39 | 40 | /* Functions 41 | -------------------------*/ 42 | export function sleep(ms: number): Promise { 43 | return new Promise((a) => setTimeout(() => a(true), ms)); 44 | } 45 | 46 | export async function waitTilFalse( 47 | func: () => boolean, 48 | delay = 0, 49 | ): Promise { 50 | while (func()) { 51 | await sleep(delay); 52 | } 53 | } 54 | 55 | export function filterMap( 56 | array: T[], 57 | func: (element: T, i: number) => U | None, 58 | ): U[] { 59 | const input = [...array]; 60 | const output: U[] = []; 61 | let i = 0; 62 | for (const element of input) { 63 | const result = func(element, i++); 64 | if (result != undefined) { 65 | output.push(result); 66 | } 67 | } 68 | return output; 69 | } 70 | 71 | export function capitalise(text: string): string { 72 | return filterMap( 73 | text.split(" "), 74 | (word) => 75 | word 76 | ? word[0].toLocaleUpperCase() + 77 | word.slice(1).toLocaleLowerCase() 78 | : null, 79 | ).join(" "); 80 | } 81 | 82 | export function abs(integer: bigint): bigint { 83 | return integer < 0 ? integer * -1n : integer; 84 | } 85 | 86 | export function max(...integers: bigint[]): bigint { 87 | let max = integers.shift() as bigint; 88 | for (let i = 0; i < integers.length; ++i) { 89 | if (max < integers[i]) { 90 | max = integers[i]; 91 | } 92 | } 93 | return max; 94 | } 95 | 96 | export function min(...integers: bigint[]): bigint { 97 | let min = integers.shift() as bigint; 98 | for (let i = 0; i < integers.length; ++i) { 99 | if (integers[i] < min) { 100 | min = integers[i]; 101 | } 102 | } 103 | return min; 104 | } 105 | 106 | export function cusMax(func: (value: T) => U, ...values: T[]): T { 107 | let maxValue = values.shift() as T; 108 | let maxResult = func(maxValue); 109 | for (let i = 0; i < values.length; ++i) { 110 | const result = func(values[i]); 111 | if (maxResult < result) { 112 | maxValue = values[i]; 113 | maxResult = result; 114 | } 115 | } 116 | return maxValue; 117 | } 118 | 119 | export function cusMin(func: (value: T) => U, ...values: T[]): T { 120 | let minValue = values.shift() as T; 121 | let minResult = func(minValue); 122 | for (let i = 0; i < values.length; ++i) { 123 | const result = func(values[i]); 124 | if (result < minResult) { 125 | minValue = values[i]; 126 | minResult = result; 127 | } 128 | } 129 | return minValue; 130 | } 131 | 132 | export function uniqueRandomID(): string { 133 | const char = "abcdefghijklmnopqrstuvwxyz"; 134 | let id: string; 135 | do { 136 | id = ""; 137 | for (let i = 0; i < 50; ++i) { 138 | id += char[Math.floor(Math.random() * 26)]; 139 | } 140 | } while (document.querySelector(`#${id}`)); 141 | return id; 142 | } 143 | 144 | export function endTime(startTime: number): string { 145 | const endTime = performance.now(); 146 | return (endTime - startTime).toLocaleString("en-US", { 147 | maximumFractionDigits: 2, 148 | }) + "ms"; 149 | } 150 | 151 | export function passIfTruthy(x: T | None, func: (x: T) => void): None | T { 152 | if (x) { 153 | func(x); 154 | } 155 | return x; 156 | } 157 | 158 | // deno-lint-ignore no-explicit-any 159 | export function pass(x: T, func: (x: T) => any): T { 160 | func(x); 161 | return x; 162 | } 163 | 164 | export function wrap(x: T, func: (x: T) => U): U { 165 | return func(x); 166 | } 167 | 168 | export async function attemptPromise( 169 | func: () => Promise, 170 | // deno-lint-ignore no-explicit-any 171 | error: ((e: any) => Promise) | None = undefined, 172 | ): Promise { 173 | try { 174 | return await func(); 175 | } catch (e) { 176 | if (error != undefined) { 177 | return await error(e); 178 | } 179 | console.error(e); 180 | } 181 | } 182 | 183 | export function attempt( 184 | func: () => T, 185 | // deno-lint-ignore no-explicit-any 186 | error: ((e: any) => U) | None = undefined, 187 | ): T | U | undefined { 188 | try { 189 | return func(); 190 | } catch (e) { 191 | if (error != undefined) { 192 | return error(e); 193 | } 194 | console.error(e); 195 | } 196 | } 197 | 198 | export function formatDate(date = new Date()): string { 199 | let text = ""; 200 | text += date.getHours().toString().padStart(2, "0"); 201 | text += ":"; 202 | text += date.getMinutes().toString().padStart(2, "0"); 203 | text += " "; 204 | text += date.getDate().toString().padStart(2, "0"); 205 | text += "/"; 206 | text += [ 207 | "Jan", 208 | "Feb", 209 | "Mar", 210 | "Apr", 211 | "May", 212 | "Jun", 213 | "Jul", 214 | "Aug", 215 | "Sep", 216 | "Oct", 217 | "Nov", 218 | "Dec", 219 | ][date.getMonth()]; 220 | text += "/"; 221 | text += date.getFullYear(); 222 | return text; 223 | } 224 | 225 | export function formatNumber(number: number, digits = 2): string { 226 | return number.toLocaleString("en-US", { maximumFractionDigits: digits }); 227 | } 228 | 229 | export function formatBigInt(x: bigint): string { 230 | return `${(x / 100n).toLocaleString("en-US", { maximumFractionDigits: 0 })}.${ 231 | (x % 100n).toString().padStart(2, "0") 232 | }`; 233 | } 234 | --------------------------------------------------------------------------------