From 1e67bd8fa77eca01b0e46b66a3a5bd9838d1ba3e Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Mon, 18 Mar 2024 15:28:05 -0600 Subject: [PATCH] Created dedicated radarr and prowlarr demo images and pushed them up to DockerHub, and updated the script to be a true one liner --- README.md | 30 ++-- build.sh | 6 + docker-compose.yml | 25 +-- managarr-demo.sh | 32 +++- mock-htpc/prowlarr/Definitions/agsvpt.yml | 6 +- .../prowlarr/Definitions/at12project.yml | 2 +- .../prowlarr/Definitions/dasunerwartete.yml | 4 + mock-htpc/prowlarr/Definitions/h-p2p.yml | 142 ++++++++++++++++++ .../prowlarr/Definitions/lilleskyorg.yml | 11 +- .../prowlarr/Definitions/marinetracker.yml | 2 + .../prowlarr/Definitions/oldgreektracker.yml | 34 +++-- mock-htpc/prowlarr/Definitions/sharewood.yml | 2 + mock-htpc/prowlarr/Definitions/wihd.yml | 2 + mock-htpc/prowlarr/logs.db | Bin 417792 -> 421888 bytes mock-htpc/prowlarr/logs.db-shm | Bin 32768 -> 32768 bytes mock-htpc/prowlarr/logs.db-wal | Bin 12392 -> 12392 bytes prowlarr.Dockerfile | 8 + radarr.Dockerfile | 10 ++ 18 files changed, 259 insertions(+), 57 deletions(-) create mode 100755 build.sh create mode 100644 mock-htpc/prowlarr/Definitions/h-p2p.yml create mode 100644 prowlarr.Dockerfile create mode 100644 radarr.Dockerfile diff --git a/README.md b/README.md index 134fb67..44132cb 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,31 @@ To run this demo, simply run the [demo script](./managarr-demo.sh): ./managarr-demo.sh ``` -## Cleanup -This demo will download a handful of docker images. To clean up after this demo, run the following command: - -```shell -docker image rm lscr.io/linuxserver/radarr &&\ - docker image rm lscr.io/linuxserver/prowlarr &&\ - rm -rf /tmp/managarr* -``` - ## Limitations This demo has no download functionality. It is an eventual goal to have a mock API for one of the BitTorrent clients like Transmission to emulate this functionality for a full demo experience. +## Building +To build and push both the [prowlarr](./prowlarr.Dockerfile) and [radarr](./radarr.Dockerfile) images, it is easiest to just use the [build script](./build.sh): + +```shell +./build.sh +``` + +## Running directly with docker compose +If you wish to run the demo directly from the [docker-compose.yml](./docker-compose.yml), +you can either run it simply with + +```shell +docker compose run --rm managarr +``` + +which will use the [default managarr configuration file](./mock-htpc/managarr/config.yml), or you can specify it manually with the `MANAGARR_CONFIG` environment variable: + +```shell +MANAGARR_CONFIG=/tmp/managarr.yml docker compose run --rm managarr +``` + ## Creator * [Alex Clarke](https://github.com/Dark-Alex-17) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d351b58 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +docker build -f radarr.Dockerfile -t darkalex17/radarr-mock:latest . +docker push darkalex17/radarr-mock:latest + +docker build -f prowlarr.Dockerfile -t darkalex17/prowlarr-mock:latest . +docker push darkalex17/prowlarr-mock:latest diff --git a/docker-compose.yml b/docker-compose.yml index 41ff0c8..bb53010 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,32 +1,13 @@ --- services: radarr: - image: lscr.io/linuxserver/radarr:latest + image: darkalex17/radarr-mock:latest container_name: radarr - environment: - - PUID=1000 - - PGID=1000 - - TZ=Etc/UTC - volumes: - - ./mock-htpc/movies:/movies - - ./mock-htpc/films:/films - - ./mock-htpc/downloads:/downloads - - ./mock-htpc/radarr:/config - ports: - - 7878:7878 restart: unless-stopped prowlarr: - image: lscr.io/linuxserver/prowlarr:latest + image: darkalex17/prowlarr-mock:latest container_name: prowlarr - environment: - - PUID=1000 - - PGID=1000 - - TZ=Etc/UTC - volumes: - - ./mock-htpc/prowlarr:/config - ports: - - 9696:9696 restart: unless-stopped managarr: @@ -34,7 +15,7 @@ services: stdin_open: true tty: true volumes: - - ./mock-htpc/managarr:/root/.config/managarr/ + - "${MANAGARR_CONFIG:-./mock-htpc/managarr/config.yml}:/root/.config/managarr/config.yml:ro" depends_on: radarr: condition: service_started diff --git a/managarr-demo.sh b/managarr-demo.sh index 4ede887..20c6b2d 100755 --- a/managarr-demo.sh +++ b/managarr-demo.sh @@ -1,21 +1,37 @@ #!/bin/bash -DEMO_TEMP_DIR=/tmp/managarr-demo +DEMO_TEMP_COMPOSE_FILE=/tmp/docker-compose.yml +DEMO_TEMP_CONFIG_FILE=/tmp/config.yml + +cleanup() { + docker compose -f "$DEMO_TEMP_COMPOSE_FILE" down + + docker rmi -f $(docker images | grep "darkalex17" | awk '{print $3}') + + rm -f "$DEMO_TEMP_COMPOSE_FILE" + rm -f "$DEMO_TEMP_CONFIG_FILE" + rm -f /tmp/managarr-demo.sh +} fail() { result=$? + cleanup if [ "$result" != "0" ]; then - echo "Fail to run the Managarr demo" + echo "Failed to run the Managarr demo" fi exit $result } +main() { + [ -f "$DEMO_TEMP_COMPOSE_FILE" ] || curl https://raw.githubusercontent.com/Dark-Alex-17/managarr-demo/main/docker-compose.yml > "$DEMO_TEMP_COMPOSE_FILE" + [ -f "$DEMO_TEMP_CONFIG_FILE" ] || curl https://raw.githubusercontent.com/Dark-Alex-17/managarr-demo/main/mock-htpc/managarr/config.yml > "$DEMO_TEMP_CONFIG_FILE" + + MANAGARR_CONFIG="$DEMO_TEMP_CONFIG_FILE" docker compose -f "$DEMO_TEMP_COMPOSE_FILE" run --rm managarr + + cleanup +} + trap "fail" EXIT set -e +main -[ -d "$DEMO_TEMP_DIR" ] || git clone git@github.com:Dark-Alex-17/managarr-demo.git "$DEMO_TEMP_DIR" - -docker compose -f "$DEMO_TEMP_DIR/docker-compose.yml" run --rm managarr &&\ - docker compose -f "$DEMO_TEMP_DIR/docker-compose.yml" down &&\ - docker image rm darkalex17/managarr &&\ - rm -rf /tmp/managarr-demo.sh diff --git a/mock-htpc/prowlarr/Definitions/agsvpt.yml b/mock-htpc/prowlarr/Definitions/agsvpt.yml index e540699..2167d9d 100644 --- a/mock-htpc/prowlarr/Definitions/agsvpt.yml +++ b/mock-htpc/prowlarr/Definitions/agsvpt.yml @@ -7,6 +7,7 @@ type: private encoding: UTF-8 requestDelay: 2 links: + - https://www.agsvpt.com/ - https://abroad.agsvpt.com/ caps: @@ -80,9 +81,8 @@ settings: default: "Account retention rules:
  1. Elite User and above will not have their account deleted after parking (in the Control Panel)
  2. Users who do not log in for 400 consecutive days will be disabled.
  3. Users with a parked account will be disabled if they do not log in for 150 consecutive days
  4. Users who have no traffic (i.e., uploading/downloading data is 0) within 7 days of new registration will be disabled
  5. Users with no traffic (i.e. both upload/download data is 0) who do not log in for 30 consecutive days will be disabled.
" login: - path: agsvpt - method: form - form: form[action="takelogin.php"] + path: takelogin.php + method: post inputs: secret: "" username: "{{ .Config.username }}" diff --git a/mock-htpc/prowlarr/Definitions/at12project.yml b/mock-htpc/prowlarr/Definitions/at12project.yml index 2a02540..5495b91 100644 --- a/mock-htpc/prowlarr/Definitions/at12project.yml +++ b/mock-htpc/prowlarr/Definitions/at12project.yml @@ -157,5 +157,5 @@ search: False: 1 # normal True: 2 # double minimumratio: - text: 0.4 + text: 1.0 # json UNIT3D 7.0.3 diff --git a/mock-htpc/prowlarr/Definitions/dasunerwartete.yml b/mock-htpc/prowlarr/Definitions/dasunerwartete.yml index 6338ccf..5a37132 100644 --- a/mock-htpc/prowlarr/Definitions/dasunerwartete.yml +++ b/mock-htpc/prowlarr/Definitions/dasunerwartete.yml @@ -102,6 +102,10 @@ settings: type: info label: Results Per Page default: For best results, change the Anzahl der Torrents beim Durchsuchen: setting to 60 on your Control Panel. The default is 15. + - name: info_activity + type: info + label: Account Inactivity + default: "Inactive accounts will be deactivated after 7 weeks and deleted after another 3 weeks." login: path: login.php diff --git a/mock-htpc/prowlarr/Definitions/h-p2p.yml b/mock-htpc/prowlarr/Definitions/h-p2p.yml new file mode 100644 index 0000000..870b6dc --- /dev/null +++ b/mock-htpc/prowlarr/Definitions/h-p2p.yml @@ -0,0 +1,142 @@ +--- +id: h-p2p +name: H-P2P +description: "H-P2P is a Private Torrent Tracker for OnlyFans XXX" +language: en-US +type: private +encoding: UTF-8 +links: + - https://h-p2p.cam/ + +caps: + categorymappings: + - {id: 6, cat: XXX/WEB-DL, desc: "OnlyFans"} + + modes: + search: [q] + tv-search: [q] + movie-search: [q] + +settings: + - name: apikey + type: text + label: APIKey + - name: info_key + type: info + label: About your API key + default: "Find or Generate a new API Token by accessing your H-P2P account My Security page and clicking on the API Token tab." + - name: freeleech + type: checkbox + label: Search freeleech only + default: false + - name: sort + type: select + label: Sort requested from site + default: created_at + options: + created_at: created + seeders: seeders + size: size + name: title + - name: type + type: select + label: Order requested from site + default: desc + options: + desc: desc + asc: asc + +login: + path: /api/torrents + method: get + error: + - selector: a[href*="/login"] + message: + text: "The API key was not accepted by {{ .Config.sitelink }}." + +search: + paths: + # https://github.com/HDInnovations/UNIT3D-Community-Edition/wiki/Torrent-API-(UNIT3D-v7.0.0) + # https://github.com/HDInnovations/UNIT3D-Community-Edition/blob/master/app/Http/Controllers/API/TorrentController.php#L349 + - path: "/api/torrents/filter" + response: + type: json + + headers: + Authorization: ["Bearer {{ .Config.apikey }}"] + + inputs: + $raw: "{{ range .Categories }}&categories[]={{.}}{{end}}" + name: "{{ .Keywords }}" + free: "{{ if .Config.freeleech }}1{{ else }}{{ end }}" + sortField: "{{ .Config.sort }}" + sortDirection: "{{ .Config.type }}" + perPage: 100 + + keywordsfilters: + - name: re_replace + args: ["\\.", " "] + + rows: + selector: data + attribute: attributes + + fields: + category: + selector: category_id + title: + selector: name + filters: + - name: re_replace + args: ["\\.", " "] + details: + selector: details_link + download: + selector: download_link + infohash: + selector: info_hash + poster: + selector: poster + filters: + - name: replace + args: ["https://via.placeholder.com/90x135", ""] + files: + selector: num_file + seeders: + selector: seeders + leechers: + selector: leechers + grabs: + selector: times_completed + date: + # "created_at": "2021-10-18T00:34:50.000000Z" is returned by Newtonsoft.Json.Linq as 18/10/2021 00:34:50 + selector: created_at + filters: + - name: append + args: " +00:00" # GMT + - name: dateparse + args: "MM/dd/yyyy HH:mm:ss zzz" + size: + selector: size + downloadvolumefactor: + # api returns 0%, 25%, 50%, 75%, 100% + selector: freeleech + case: + 0%: 1 # not free + 25%: 0.75 + 50%: 0.5 + 75%: 0.25 + 100%: 0 # freeleech + "*": 0 # catch errors + uploadvolumefactor: + # api returns 0=false, 1=true + selector: double_upload + case: + 0: 1 # normal + 1: 2 # double + minimumratio: + text: 1.0 + minimumseedtime: + # 7 days (as seconds = 7 x 24 x 60 x 60) + text: 604800 +# json UNIT3D 6.3.0 diff --git a/mock-htpc/prowlarr/Definitions/lilleskyorg.yml b/mock-htpc/prowlarr/Definitions/lilleskyorg.yml index 182f9fe..16c1d3e 100644 --- a/mock-htpc/prowlarr/Definitions/lilleskyorg.yml +++ b/mock-htpc/prowlarr/Definitions/lilleskyorg.yml @@ -10,13 +10,14 @@ links: caps: categorymappings: - - {id: 10, cat: Movies, desc: "MOVIES"} + - {id: 1, cat: Movies, desc: "MOVIES"} - {id: 2, cat: TV, desc: "TV"} - - {id: 3, cat: PC, desc: "APPZ"} + - {id: 3, cat: TV/Foreign, desc: "TV-FOREIGN"} + - {id: 4, cat: PC, desc: "APPS"} - {id: 5, cat: Audio, desc: "MUSIC"} - - {id: 8, cat: XXX, desc: "XXX"} - - {id: 12, cat: Books, desc: "EBOOKS"} - - {id: 9, cat: Console, desc: "GAMES"} + - {id: 6, cat: XXX, desc: "XXX"} + - {id: 7, cat: Console, desc: "GAMES"} + - {id: 8, cat: Books, desc: "EBOOKS"} modes: search: [q] diff --git a/mock-htpc/prowlarr/Definitions/marinetracker.yml b/mock-htpc/prowlarr/Definitions/marinetracker.yml index c2124b6..8e66010 100644 --- a/mock-htpc/prowlarr/Definitions/marinetracker.yml +++ b/mock-htpc/prowlarr/Definitions/marinetracker.yml @@ -144,6 +144,7 @@ caps: - {id: 252, cat: Books, desc: "Yachting, sailing, boating-Projects, design, construction"} - {id: 251, cat: Books, desc: "Diving"} - {id: 177, cat: Books, desc: "Shipmodeling"} + - {id: 352, cat: Books, desc: "Shipmodeling-Paper models"} - {id: 293, cat: Books, desc: "Shipmodeling-Radio controlled models"} - {id: 292, cat: Books, desc: "Shipmodeling-Marine Modelling Magazines"} - {id: 291, cat: Books, desc: "Shipmodeling-Drawings and models of ships (CAD, 3D)"} @@ -159,6 +160,7 @@ caps: - {id: 280, cat: Books, desc: "Marine History-Historic ships"} - {id: 279, cat: Books, desc: "Marine History-Marine vessels"} - {id: 278, cat: Books, desc: "Marine History-Warships"} + - {id: 351, cat: Books, desc: "Marine History-Battleships"} - {id: 259, cat: Books, desc: "Marine History-Shipbuilding history"} - {id: 277, cat: Books, desc: "Marine History-Wars at Sea"} - {id: 276, cat: Books, desc: "Marine History-War at sea (World War I)"} diff --git a/mock-htpc/prowlarr/Definitions/oldgreektracker.yml b/mock-htpc/prowlarr/Definitions/oldgreektracker.yml index 33d4546..f7de5ab 100644 --- a/mock-htpc/prowlarr/Definitions/oldgreektracker.yml +++ b/mock-htpc/prowlarr/Definitions/oldgreektracker.yml @@ -6,6 +6,8 @@ language: el-GR type: private encoding: UTF-8 links: + - https://oldgreektracker.xyz/ +legacylinks: - http://oldgreektracker.xyz/ caps: @@ -134,6 +136,16 @@ login: path: index.php selector: a[href*="/logout.php?logouthash="] +download: + before: + path: takethanks.php + method: post + inputs: + torrentid: "{{ .DownloadUri.Query.id }}" + selectors: + - selector: a[href*="download.php?id="] + attribute: href + search: paths: - path: browse.php @@ -154,28 +166,28 @@ search: order: "{{ if .Config.freeleech }}asc{{ else }}{{ .Config.type }}{{ end }}" rows: - selector: "table.sortable tr:has(a[href*=\"/download-torrent-\"]){{ if .Config.freeleech }}:has(img[src$=\"/freedownload.gif\"]){{ else }}{{ end }}" + selector: "table.sortable tr:has(a[href*=\"/download.php?id=\"]){{ if .Config.freeleech }}:has(img[src$=\"/freedownload.gif\"]){{ else }}{{ end }}" fields: category: - selector: a[href*="/torrent-category-"] + selector: a[href*="/browse.php?category="] attribute: href filters: - - name: regexp - args: /torrent-category-(.+?)/ + - name: querystring + args: category title_default: # is usually abbreaviated - selector: a[href*="/torrent-details-"] + selector: a[href*="/details.php?id="] title: # while still abbreviated, is usually longer than the default selector: div.tooltip-content div optional: true default: "{{ .Result.title_default }}" details: - selector: a[href*="/torrent-details-"] + selector: a[href*="/details.php?id="] attribute: href download: - selector: a[href*="/download-torrent-"] + selector: a[href*="/details.php?id="] attribute: href magnet: selector: a[href^="magnet:?xt="] @@ -184,11 +196,15 @@ search: selector: img[src*="/torrents/images/"] attribute: src date: - selector: td:nth-child(2) > div:last-child + selector: td:nth-child(2) # auto adjusted by site account profile filters: + - name: regexp + args: "(\\d{1,2}(?:st|nd|rd|th):\\w{3,9}:\\d{4} \\d{2}:\\d{2})" + - name: re_replace + args: ["^(\\d{1,2})(?:st|nd|rd|th):", "$1:"] - name: dateparse - args: "dd-MM-yyyy HH:mm" + args: "d:MMMM:yyyy HH:mm" size: selector: td:nth-last-child(5) grabs: diff --git a/mock-htpc/prowlarr/Definitions/sharewood.yml b/mock-htpc/prowlarr/Definitions/sharewood.yml index e6f0f74..434ed18 100644 --- a/mock-htpc/prowlarr/Definitions/sharewood.yml +++ b/mock-htpc/prowlarr/Definitions/sharewood.yml @@ -5,6 +5,8 @@ description: "sharewood is a Semi-Private FRENCH Torrent Tracker for GENERAL" language: fr-FR type: semi-private encoding: UTF-8 +certificates: + - 023A091295E81813D040DFA0FA842DF9892BF0F5 # expired 10-March-2024 note: despite a new CA issued this one still pops up occasionally links: - https://www.sharewood.tv/ diff --git a/mock-htpc/prowlarr/Definitions/wihd.yml b/mock-htpc/prowlarr/Definitions/wihd.yml index beac9da..2f8af45 100644 --- a/mock-htpc/prowlarr/Definitions/wihd.yml +++ b/mock-htpc/prowlarr/Definitions/wihd.yml @@ -200,6 +200,8 @@ search: ":contains(\"Divers\"):contains(\"1080p\")": "5GB" ":contains(\"Divers\"):contains(\"720p\")": "4GB" "*": "" + date: + text: now download: selector: div.download-item > a attribute: href diff --git a/mock-htpc/prowlarr/logs.db b/mock-htpc/prowlarr/logs.db index a1fe2e0777f848d66f30ffe160835ed1871262b6..4a653fc1665bc81e7848023e39bff41a3adc1f75 100644 GIT binary patch delta 2292 zcmai#TWB0r7{_PMnKQddc6N4i*`|nPw+Sjjrf25NbrG^LBwi3S(ny&XjW$Wu&`nH1 zgw{>4nP4AMYp};YC^SBaJ|sqnJ|%f6h!==Y5=4Sj^u-E6AHAHp?5&+O59ejR-}%n@ zf8YPix3F`3;o$f?_n{KUanslGgM4Y@g|)%q7@;FOnnYk6gPd4*>Sum#x?ww71Wr49HJxJ?r!@v1GpE{=;t z%%VTgx9Ah}Hkw8ws2@S$XJJS9P`D_#f-U6WpYVIQ4p-m|d=fqcMR1#)kpuL`s3=u} zdl9p!Vnwr<9&EX=w*W^#vtUMGO|fmJQ8Qh5>_{SaB+=s&m3xoDildp1uCrsh; za!eK5jtjeD2&1TB#m1^>8!Sg>E5b4jTd(N03$v{e4HKIs$s9?@tq9^cxhzr~EaJbsuN@Qrl zlCb^~)gkPZwgw^=NFD_w;%}iT*fv!{*geI7s5Ct*{}!s^SOzA9`4HWkggrb%S8NB{ zSYuLzmV~BTrp2lbV+q|FypX z#l^=){CJucz64lq*zY@=j#=m=Jnc!VW8W#+YOv;My78-!173=b%^*_CT{kpc{K(JU zLGf?GiT-eVlbwgl=jY2alS8w!Pn@4BPk8OYEuw=0GPE@v`V8dZq}N8*KR`x3UiCEH z{mDNlNi%DxDxzk2`t%w)DSC+{_v$d+*aDBl>|gv5<#~_tL+DhbBiJ6tj_&l)WS74vvvf%;v0JHkE*KM z^{CfeL$90TOlE=;slvcJiMmkTP>B0T5y1chl4KJ9ha3*!&O+N6rw-_=lHP>d!%laZB7e;TD4J(~)=qUdmw_+4) delta 232 zcmZp8AlYz0a)LCgJp%(n+C&9=M*EEki|o0+GcfVrV&K2U|8%op!aDwm6BG$WZ=ID)hIdnV!SaMBmV;i z{s&MYjm;18RRlPk_}W1_(kAmXv}{fk_{H1Y)z{wD#|XqsK+FupEZe*KSgSTNyR*%l z&hEh0zp;^-ZThoKECSnCy0Jxb^0095GVnX_WpNa8@NR7U%g&tGS~Fd7CzJ7XpuE8J OEk>+z+Y2Mu`q=?=4@naM diff --git a/mock-htpc/prowlarr/logs.db-shm b/mock-htpc/prowlarr/logs.db-shm index d90439d74415530ba2442883770c4aa4e13d0769..d958e7362c8eae49756f6e364f5fcfc1058aa9e9 100644 GIT binary patch delta 86 zcmZo@U}|V!njj*R&cML%r1;3qbMZGgpM0znKQEIY$n5kZDq;VQ#RQ}$CKxcLZ=ATe PVB>;+Oq)##&v63)KGh~= delta 86 zcmZo@U}|V!njj*R#=yX^XM%O$0+J2sbB?P1=CKxcLZJfBc PV6#!lIqr=M{xJan8hs`C diff --git a/mock-htpc/prowlarr/logs.db-wal b/mock-htpc/prowlarr/logs.db-wal index a8215d6c741d357ff99528b3c2834e83bf5018dd..43eda9353a7224f5748cc6b81239795a2edbb2b4 100644 GIT binary patch literal 12392 zcmeI1TWl0n7{`a1-RIz$6MgW(2aQIJ4?dWv2@fjXFNk+VK~X_L#G;@m7pthK_)W1(V!!`;PO^YE@;U#pe3 zm7gl#SzcAXxXj8kOZ!UeOAnTAE)A7VE1gu*i`$FO6z?itTfC&mi?a&jg^h*PgA3i*v_k z4`nxJ*JN+Y2H6X;C|l~-)A3rz{T=O&YDaI!{LJCZ*36TcJ2Io0Wf_|3O7Bg-nSLOB zQ+hDnmp&FOc668|uVq{TlBT2Dm%u^9W*o-6L8FM2EL@X&8rG$INoLFLZEYTfJAj=`#VGO*? zQW3)87)i%7j*TE}mmo_b%%u%BW~vCmZONGQj4m}n5S3u#m{_8$BB;klctQ7`G6#7qx-V zV~H8DgceQM7Ilyyn0oqQ6+vu_L3B_5B#^Wi=Lp&0YbFlHHir|i*4g(usC<#G`fbCe{ts-n8CC1FtcSWay zEJhhYkn86v69f}D!obOAvBXX_fiOa_6vkqSPh*K4vBW2_#P+EKa$K8R5{@_t8=?NO zieS=)g$;Uan=0uM8%%;`urq8`nh+e$6_DejSmMK2VvCZn;dp{0P~WU3!rDU*exOW+ zAr_|czS0DR%?FMJ{XHc?Y{J9EvMH8$S4B`bB#{8Y-iahAEI3$8l0qCCV~GvZ5Qv6d z`gSBiU}kWb1L=DDTPi|^-65cV)<+Q1rObjvYzBw1{^kkBlHVtSiR~Co5J+M;M)fx$ zEf8TbI5cbuQM?{Yyrv?k!=cZiDz8Qp9A+Dse!@Nf6%}C(6eF{TD z*x>2UsRw#n+2X~81Mn(&5(N?k5(N?k5(N?k5(N?k5(N?k5(WNM1s1pGWwix;^EIuzZ&fX* z)d$UyO08~9cQe%M*IWK@bxEUfW_`e~wC85Ev-_5S1$Z-DTV4rje%-9K%+}D#V8UX* zKU%k}NSl*)N$1vG!W( z9wzwreO%S<&T1>B2-XTJ&7j(EfsbmdW%dTm;gfsKz@L!OoRIME`@E<| z!q87xXKDsR&1$8;We)gFGpMvyw5FB_{eI2X>yM07>ivDIs>@fx-FR4+{`Rq1?fi*U zminXSFkG5f>!EGG?wUhE&~STtE_L0@T!%Y+YV^pOyr7%DDvR4Q|4Ne{b)k23lMb}I Q{y_sx7@oWboIccF0p4c|w*UYD literal 12392 zcmeI1U2Ggz6~~vcYVU`4w?b6V2b3ELrLN?R@11++&de4or2a?@X$f}t=vJ0GnZ3In zx}Mq2%n~OQRY_!!ii98!R3IMm&<7rP=tD(Y8VVFBZBq&q3M9~yrXRG>mH;h*wh^4! z^}5E{>rq1cP&6ZXG<)~AXa47Z?mc7OF>V<=l-YXveVNRqnfN&U^wHXh<43+?ec`!R z4=cBZFm%bDJOhX}a`*l2SZXys!9;;(_9) zioE!q;a7$q8@^-sOT*3K4-bzF4;G#&++R3axTP>(xT}(ciOG7UXJv{Wyq0bMAp^2f*ga00UV({L< zuYsWSNF7KWNF7KW_d8vO0tU^(^$w^gtQAQXhVT41BKg$Si8PsAB<4;n;&@{sYOJ9%? zx?!4xu&VNW|5UKbDAO?H`bSv^jcAnUaFgc}iD%^mCK$s~cqWl}I*~Y?NIaEDoI0Pt zRz>HkX;|E(Fw_4aBbcef!UjK{Jm1o83}%97&>5bPhA=ocIN0&~MB;ae#7QZkbIK{y z{)C)}YY%hqx6)J?W^pRNk%pkqd^Bh-zm^i3uJL%WJf2AWN=8tV!iogLe%Y6xu;5@V znG|$9mPq_!0|L`{mj1jip;^!enFZ-qm7mE7GoBp=%%4a55N3rk6%x@Ig=PKIO#|E4 zwPV~Nut^Lk+arA=Xokw*p`lYyaXgWDSVmBb!#szoJk+1yDyNk4xaU78BXrHwajf(w zeF$D58Z;LQ5LD%Xz69o~r7?IafQ}!_2*xZ%4e;wn{RyU;j8JaCiu|FBASPxwuG{@m z!Y~=-5aS0@0{kKbbbMb<7={r)@V_S|7-P_otIBD z!ApjYXUZQceL8znwg{K$kvfn%kUEe$kUEe$@Xm2y+w$kKnJcC?XEK*fExD2FHPE8% zdg?~|FB85O`c1LD)td3@zP)^NHuI6G?GOP*nyzCO`#h`#e?cA9H2L z<{H-t{LrsQ>P|n5f28hm>mqU&MSPDhwwFI!$Q&k9n~uyJ-1^?_9uh&|2grAvb`Xdf z+PB~~1!@JpBf>CF!u4umNd%}aL`_8tQPc`6V`K2vBM@HHu$%VMel_xY1EJblXl--o zIN9Pb-Z|@?v(`DsCjzHX*nzV!9z{gs1~sfLP1|dX<1L{dMB{F4iyzcPFkTa(Q$f>y z+p8hRZbi<5jcV;6R%N#%e-Zp~P}6U?-YBy7!cXCwb$0*PR@HQrT@OkJRZ&-2b!y2Gt#yM?xVqa|{pLbN<`F`6gf2+Ns-RhMQ(J`5 z4&jNwhB~M_MAX&OhR+~=+7ISL)NYMj?Zf1b&6prAWdHnwnG=v7U+i7!giZo459GKyw)+d&AO zsMAw=vQaA@3@%b8VHYKHR>0Z_ML06u_M96u(9jU}-lkZY;S&qcyyt*%V2O>QxlY5H z2;4{nt{s;Eb$eKEXcYBUn;Xsw2YTbhr2{((7t|J3d(fJewXQo&#U1|O?DBzJ=8!R^ z!{eo!&6@5$5?)zB*YD{z^&Oq2K9B0~T-YOmMfheO-Hc`?JJifIlY3`smFrhJ_xvdO zXuTeasDkh)+T|{~a0R=eqi6!&k2n0l6(IzGEqWNzU@s&-lg_o)9qF7Yo