From 9e96d00631653e9d846afb9978f63038c44bfc8b Mon Sep 17 00:00:00 2001 From: zakary <123845855+zakkarry@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:02:55 -0500 Subject: [PATCH] update(docs): revamped and expanded documentation (#12) tips and qbit-multi-command Rearranged the structure of the nav-bar, addressed reviews, and fixed some typos. fixed punctuation/grammar, clarifications based on comments, and expanded some sections. update(ui): minor ui changes to docs --- .gitignore | 3 + docs/basics/daemon.md | 263 ++++++++++++++++------- docs/basics/faq-troubleshooting.md | 94 +++++++++ docs/basics/getting-started.md | 46 ++--- docs/{reference => basics}/options.md | 286 ++++++++++++++++++++++++-- docs/reference/api.md | 34 +-- docs/reference/architecture.md | 24 ++- docs/reference/faq-troubleshooting.md | 17 -- docs/reference/utils.md | 18 +- docs/tutorials/data-based-matching.md | 34 +-- docs/tutorials/injection.md | 78 +++---- docs/tutorials/unraid.md | 25 +-- docusaurus.config.js | 22 +- src/css/custom.css | 108 ++++++++-- src/pages/index.js | 58 +++--- 15 files changed, 830 insertions(+), 280 deletions(-) create mode 100644 docs/basics/faq-troubleshooting.md rename docs/{reference => basics}/options.md (69%) delete mode 100644 docs/reference/faq-troubleshooting.md diff --git a/.gitignore b/.gitignore index b2d6de3..fa84d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.frontmatter/database/pinnedItemsDb.json +.frontmatter/database/taxonomyDb.json +frontmatter.json diff --git a/docs/basics/daemon.md b/docs/basics/daemon.md index e29631f..c92ab26 100644 --- a/docs/basics/daemon.md +++ b/docs/basics/daemon.md @@ -4,23 +4,26 @@ sidebar_position: 1 title: Daemon Mode --- -Daemon Mode lets you harness the full power of `cross-seed`, by: - -- instantly searching for cross-seeds for finished downloads -- watching for new releases: - - scanning RSS feeds periodically - - listening for new release announces and snatching them if you already - have the data -- Running batch searches on your full collection of torrents periodically +Daemon Mode lets you harness the full power of `cross-seed` by continuously running +and enabling the following features: + +- instantly searching for cross-seeds for finished downloads + - using your torrent client using `webhook` end point + - using a third-party script to trigger from an Arr's import event ([cross-seed Arr Import script](https://github.com/bakerboy448/StarrScripts#xseedsh)) +- watching for new releases: + - scanning RSS feeds periodically (`rssCadence`) for matching content + - listening for new release announces and snatching them if you already + have the data (e.g. [autobrr](https://autobrr.com/) -> `announce` API endpoint) +- Running batch searches on your full collection of torrents periodically (`searchCadence`) :::tip -In theory, after you run a full-collection search for the first time, just the -first 2 features should be able to find all cross-seeds as soon as possible. -However there are applications for the third feature as well. If improvements in -the matching algorithm are made to `cross-seed`, or your daemon is down for an -amount of time, running searches very infrequently will find cross-seeds which -fell through the cracks. +In theory, after you run a full-collection search for the first time, the first two +features should be able to find all cross-seeds as soon as possible. However there +are applications for the third feature as well. If improvements in the matching +algorithm are made to `cross-seed`, or your daemon is down for an amount of time, +running searches very infrequently will find cross-seeds which fell through the +cracks. ::: @@ -30,8 +33,9 @@ finished downloading, and ways to watch for new releases. :::danger -`cross-seed` does _not_ have API auth. **Do not expose its port to the -internet.** +`cross-seed` does _not_ have API auth. + +**Do not expose its port to untrusted networks (such as the Internet).** ::: @@ -46,15 +50,15 @@ cross-seed daemon However, that's not very sustainable. -- If you run `cross-seed` on a server that you use `ssh` to log into, then - `cross-seed` will stop whenever your `ssh` session closes. -- If the server restarts, then you'll have to start `cross-seed` manually. +- If you run `cross-seed` on a server that you use `ssh` to log into, then + `cross-seed` will stop whenever your `ssh` session closes. +- If the server restarts, then you'll have to start `cross-seed` manually. Below are a few ways you can set up `cross-seed daemon` to run on its own: -- [Docker](#docker) -- [`systemd`](#systemd-linux) -- [`screen`](#screen) +- [Docker](#docker) +- [`systemd`](#systemd-linux) +- [`screen`](#screen) ### Docker @@ -65,18 +69,19 @@ service: ```yaml version: "2.1" services: - cross-seed: - image: crossseed/cross-seed - container_name: cross-seed - user: 1000:1000 # optional but recommended - ports: - - "2468:2468" # you'll need this if your torrent client runs outside of Docker - volumes: - - /path/to/config/folder:/config - - /path/to/rtorrent_sess:/torrents:ro # note that this volume can and should be mounted read-only - - /path/to/output/folder:/output - command: daemon # this enables the daemon - restart: unless-stopped + cross-seed: + image: crossseed/cross-seed + container_name: cross-seed + user: 1000:1000 # optional but recommended + ports: + - "2468:2468" # you'll need this if your torrent client runs outside of Docker + volumes: + - /path/to/config/folder:/config + - /path/to/torrent_dir:/torrents:ro # your torrent clients .torrent cache, can and should be mounted read-only (e.g. qbit: `BT_Backup` | deluge: `state` | transmission: `transmission/torrents` | rtorrent: session dir from `.rtorrent.rc`) + - /path/to/output/folder:/cross-seeds + - /path/to/torrent/data:/data # OPTIONAL!!! this is dataDir path (for data-based matching) - will need to mirror your torrent client's path (like Arr's do) + command: daemon # this enables the daemon, change to search to specifically run a search ONLY + restart: unless-stopped ``` After that, you can use the following commands to control it: @@ -100,14 +105,15 @@ touch /etc/systemd/system/cross-seed.service ``` Open the file in your favorite editor, and paste the following code in. You'll -want to customize the following variables: +want to customize the following variables and ensure proper permissions are set +to allow this user/group to read/write/execute appropriately: -- `{user}`: your user, or another user if you want to create a separate user - for `cross-seed` -- `{group}`: your group, or another group if you want to create a separate - group for `cross-seed` -- `/path/to/node`: run the command `which node` in your terminal, then paste - the output here. +- `{user}`: your user, or another user if you want to create a separate user + for `cross-seed` +- `{group}`: your group, or another group if you want to create a separate + group for `cross-seed` +- `/path/to/node`: run the command `which node` in your terminal, then paste + the output here. ```unit file (systemd) [Unit] @@ -168,63 +174,131 @@ The most powerful feature of Daemon Mode is the ability to search for cross-seeds as soon as a torrent finishes downloading. However, it requires some manual setup. +:::info +If you plan on utilizing the `path` webhook call, you will need to configure data-based searches +in you config file. + +[**Data-Based Setup**](./tutorials/data-based-matching#setup) + +::: + ### rTorrent For rTorrent, you'll have to edit your `.rtorrent.rc` file. 1. `cd` to the directory where `.rtorrent.rc` lives. + 2. Create a file called `rtorrent-cross-seed.sh`. It should contain the following contents: ```shell #!/bin/sh - curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=$1" + # curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=$1" + curl -XPOST http://localhost:2468/api/webhook --data-urlencode "infoHash=$2" + # curl -XPOST http://localhost:2468/api/webhook --data-urlencode "path=$3" ``` -:::tip Docker users + :::tip Docker -You can use `http://cross-seed:2468` instead of `http://localhost:2468` with -Docker networks. + You can use `http://cross-seed:2468` instead of `http://localhost:2468` with + Docker networks. `localhost` will not work for Docker. You will need to use your + host's IP (e.g. 192.x or 10.x) if not using custom Docker networks. -::: + ::: + :::tip + You will need to pick a method of search, **infoHash is recommended** - but requires your session directory from `.rtorrent.rc` to + be mounted (Docker) and/or set to `torrenDir` in the config or it will not function properly. + ::: + +3. Uncomment/Comment the appropriate lines to decide how you wish to use search. -3. Run the following command (this will give rTorrent permission to execute + - The hastag/pound-sign (`#`) is used to "comment" the line - commented lines + will not be executed. + +4. Run the following command (this will give rTorrent permission to execute your script): ```shell chmod +x rtorrent-cross-seed.sh ``` -4. Run the following command (this will tell rTorrent to execute your script : +5. Run the following commands (this will add variables and tell rTorrent to execute your script: ```shell - echo 'method.set_key=event.download.finished,cross_seed,"execute={'`pwd`/rtorrent-cross-seed.sh',$d.name=}"' >> .rtorrent.rc + echo 'method.insert=d.data_path,simple,"if=(d.is_multi_file),(cat,(d.directory),/),(cat,(d.directory),/,(d.name))"' >> .rtorrent.rc + echo 'method.set_key=event.download.finished,cross_seed,"execute={'`pwd`/rtorrent-cross-seed.sh',$d.name=,$d.hash=,$d.data_path}"' >> .rtorrent.rc ``` ### qBittorrent 1. In the **qBittorrent** Web UI, navigate to Tools > Options > Downloads. -2. Check the **Run external program on torrent completion** box and enter the - following in the box: - ```shell - curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=%N" - ``` +2. Check the **Run external program on torrent completion** box and enter one of + the following in the box: + :::tip + You will need to pick a method of search, **infoHash is recommended** - but requires the `BT_Backup` + folder from qBittorrent to be mounted (Docker) and/or set to `torrenDir` in the config or it will not + function properly. + + ::: + + **Name Based:** + + ```shell + curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=%N" + ``` + + **InfoHash Based:** + + ```shell + curl -XPOST http://localhost:2468/api/webhook --data-urlencode "infoHash=%I" + ``` + + **Data Based:** + + ```shell + curl -XPOST http://localhost:2468/api/webhook --data-urlencode "path=%F" + ``` + +:::tip Docker +You can use `http://cross-seed:2468` instead of `http://localhost:2468` with +Docker networks. `localhost` will not work for Docker. You will need to use your +host's IP (e.g. 192.x or 10.x) if not using custom docker networks. + +::: + +3. Click "Save". :::tip If you are already using the **Run external program on torrent completion** box, -you should create a shell script with your commands: +you should create a shell script with your preferred commands and parameters. + +::: + +#### Multiple Commands + +If you're already using the following in the **Run external program on torrent completion** box + +```shell +oldcommand %N +``` + +You can add infoHash searching using the following script: ```shell #!/bin/bash oldcommand ${1} - curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=${1}" + curl -XPOST http://localhost:2468/api/webhook --data-urlencode "infoHash=${2}" ``` -Then use the following in qBittorrent: +Then add this in qBittorrent's settings to execute the script: + ```shell - /bin/bash ./qBittorrent/yourscriptname.sh "%N" + /bin/bash ./qBittorrent/yourscriptname.sh "%N" "%I" ``` +:::note + +You may need to adjust the variables above that qBittorrent sends to the script. ::: @@ -239,12 +313,17 @@ Then use the following in qBittorrent: curl -XPOST http://localhost:2468/api/webhook --data-urlencode "infoHash=$TR_TORRENT_HASH" ``` -:::tip Docker users + :::tip Docker -You can use `http://cross-seed:2468` instead of `http://localhost:2468` with -Docker networks. + You can use `http://cross-seed:2468` instead of `http://localhost:2468` with + Docker networks. `localhost` will not work for Docker. You will need to use your + host's IP (e.g. 192.x or 10.x) if not using custom docker networks. -::: + ::: + :::tip + `cross-seed` requires your `torrents` directory from `/.config/transmission` be mounted (Docker) + and/or set to `torrenDir` in the config or it will not function properly. + ::: 3. Run the following command (this will give Transmission permission to execute your script): @@ -253,7 +332,11 @@ Docker networks. chmod +x transmission-cross-seed.sh ``` -4. In the settings of Transmission: call script when download completes: `sh ./transmission-cross-seed.sh` +4. In the settings of Transmission set it to call the script when download completes: + + ```shell + sh ./transmission-cross-seed.sh + ``` ### Deluge @@ -261,33 +344,52 @@ Docker networks. following contents: ```shell - #!/bin/sh - curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=$2" + #!/bin/bash + torrentid=$1 + torrentname=$2 + torrentpath=$3 + # curl -XPOST http://localhost:2468/api/webhook --data-urlencode "name=$torrentname" + curl -XPOST http://localhost:2468/api/webhook --data-urlencode "infoHash=$torrentid" + # curl -XPOST http://localhost:2468/api/webhook --data-urlencode "path=$torrentpath/$torrentname" ``` -:::tip Docker users + :::tip Docker -You can use `http://cross-seed:2468` instead of `http://localhost:2468` with -Docker networks. + You can use `http://cross-seed:2468` instead of `http://localhost:2468` with Docker networks. + `localhost` will not work for Docker. You will need to use your host's IP (e.g. 192.x or 10.x) + if not using custom docker networks. -::: + ::: + + :::tip + You will need to pick a method of search, **infoHash is recommended** - but requires the `state` + folder from Deluge to be mounted (docker) and/or set to `torrenDir` in the config or it will not + function properly. -2. Run the following command (this will give Deluge permission to execute your + ::: + +2. Uncomment/Comment the appropriate lines to decide how you wish to use search. + + - The hastag/pound-sign (`#`) is used to "comment" the line - commented lines + will not be executed. + +3. Run the following command (this will give Deluge permission to execute your script): ```shell chmod +x deluge-cross-seed.sh ``` -3. In the settings of **Deluge**: - * Enable the Execute plugin - * Under **Add Command** select event of **Torrent Complete** and input the Command: `sh /path/to/deluge-cross-seed.sh` - * Press **Add** and **Apply** +4. In the settings of **Deluge**: + - Enable the Execute plugin + - Under **Add Command** select event of **Torrent Complete** and input the Command: `sh /path/to/deluge-cross-seed.sh` + - Press **Add** and **Apply** + - Restart your Deluge client/daemon (this is required to hook torrent completion calls) ## Set up RSS Setting up RSS is very easy. Just open your config file, and set the -[`rssCadence`](../reference/options#rsscadence) option. I recommend 10 minutes: +[`rssCadence`](../basics/options#rsscadence) option. I recommend 10 minutes: ```js rssCadence: "10 minutes", @@ -296,17 +398,24 @@ rssCadence: "10 minutes", ## Set up periodic searches Setting up periodic searches is very easy. Just open your config file, and set -the [`searchCadence`](../reference/options#searchcadence) option. I recommend 26 +the [`searchCadence`](../basics/options#searchcadence) option. I recommend 26 weeks (biannual): ```js searchCadence: "26 weeks", ``` -You can also combine `searchCadence` with `excludeRecentSearch` and run it more +You can also combine `searchCadence` with `excludeRecentSearch` and `excludeOlder` and run it more frequently for a smoother load: +- `excludeRecentSearch` will exclude any torrents searched for **from the current moment back the specified time**. + +- `excludeOlder` will exclude any torrents that were first discovered for cross-seeding a **longer time ago than the specified time**. + ```js searchCadence: "1 week", excludeRecentSearch: "26 weeks", +excludeOlder: "52 weeks", ``` + +This will search once a week for any torrents that cross-seed first searched less than a year ago, and have not been searched in the last 26 weeks. diff --git a/docs/basics/faq-troubleshooting.md b/docs/basics/faq-troubleshooting.md new file mode 100644 index 0000000..3cdea53 --- /dev/null +++ b/docs/basics/faq-troubleshooting.md @@ -0,0 +1,94 @@ +# FAQ & Troubleshooting + +### What can I do about `error parsing torrent at http://…`? + +This means that the Prowlarr/Jackett download link didn't resolve to a torrent file. It's +possible you got rate-limited so you might want to try again in a day or more. +Otherwise, just ignore it. There's nothing cross-seed will be able to do to fix +it. + +### rtorrent injected torrents don't check (or start at all) until force rechecked + +Remove any `stop_untied=` schedules from your .rtorrent.rc. + +Commonly: `schedule2 = untied_directory, 5, 5, (cat,"stop_untied=",(cfg.watch),"*.torrent")` + +### Failed to inject, saving instead. + +Best way to start troubleshooting this is to check the `verbose.\*.log` and find this specific event. + +You will be able to see the circumstances around the failure, and start investigating why this occurred. + +### My data-based results are paused at 100% after injection + +This is by design and due to the way data-based searches function. This is done to prevent automatically downloading +missing files or files that failed rechecking. Until there is a way to guarentee you won't end up downloading +extra (not cross-seeding) this is the best solution. + +:::tip +Consider using `infoHash` if you are racing to prevent mismatches. +::: + +### My data-based search is searching torrent files! + +Torrent files in `torrentDir` take precedence over data files with the same name and are de-duplicated. If you need to search +specifically the `dataDirs`, use an empty directory for `torrentDir` temporarily. This will strictly search the data. + +### `Error: ENOENT: no such file or directory` + +This is usually caused by broken symlinks in, or files being moved or deleted from, your `dataDirs` during a data-based search. +Check the `/logs/verbose.\*.log` files for the file causing this. + +:::tip +If you do not link files within your `dataDirs` or have them outside of the `maxDataDepth` visibility, this is preventable. + +::: + +### What `linkType` should I use? (data-based searching) + +Your options are `"hardlink"` or `"symlink"`. These operate in seperate ways, and depending on your workflow you should choose appropriately. This is a brief description, however +a more in depth guide is available at [Trash's Hardlinking Guide](https://trash-guides.info/Hardlinks/Hardlinks-and-Instant-Moves/). + +- symlinks are a "shortcut" of sorts, pointing at the original file from a new location. This can be used across mounts (docker) or partitions/drives, and does not cost you any extra space. The only possible issue is that if the original file is deleted (when you remove a torrent,) the torrents in your client using the `symlink` will "break" and you will receive errors. If this sounds like a hassle, consider reading further about hardlinks. + +- hardlinks are a tricky thing for someone not familiar, but are worth understanding. They are a "direct line" to the actual data on the disk and are indistinguishable from the original to the file system. They do not require any space, but until all references are deleted, the file will exist on the disk. These linktypes are only possible on the same partition/disk/mount, and you will need to have your linkDir set to the same mount (docker) or partition. This is the best approach if you do not always keep the source torrent in your client (due to them being deleted from the tracker) - which would then break symlinks and cross-seeds. This is the approach commonly used in Arr's on import. + +:::tip +If you are using qBittorrent, consider checking out [qbit_manage](https://github.com/StuffAnThings/qbit_manage) to manage your hardlinks eligible for deletion. + +::: + +### Searches aren't working from VPN'd torrent client + +If you are running your searches on completion from a VPN'd torrent client, it is likely you will need to set up split tunneling. You will not be able to access your local instance (which should not be publicly exposed) from the VPN due to localhost/NAT not being accessible from the VPN. If you are running a VPN'd torrent client in docker, you can use your cross-seed's container IP or name (if using custom docker networks). + +### Searching media libraries vs. torrent data (data-based searching) + +You can search both your media libraries (Arr/Plex) and actual torrent data (downloaded files). If you are using the media libraries with renamed files, you will need to use `matchMode: "risky"` in your configuration file to allow cross-seed some leeway in its matching process. `"risky"` `matchMode` is not recommended to be used without skipRecheck set to false, as it could result in more false positives than `"safe"`. + +- Due to the way data-based searching works, risky matching only matches renamed files if they are a single-file torrent. As TV libraries often include renamed files, data-based matching will not be able to pick up matches on multi-file torrents (such as season packs). + +### My season packs are cross-seeding individual episodes! + +You can use `includeSingleEpisodes`, which expands from `includeEpisodes`. If you wish to search for season packs as a whole and individual episodes _not_ from a season pack, you will need to set `includeEpisodes` to false, and `includeSingleEpisodes` to true. Both options would be best utilized with `includeNonVideos` set to true. + +### Why do I see `filetree is different` in my logs? + +This is a result of the matching algorithm used by `cross-seed`, and is most commonly associated with a few scenarios. These include the presence of additional .nfo/.srt files in a torrent, differences in the organization of files (one torrent having a folder while the potential match does not, or vice versa), and discrepancies in the filenames within the torrent. + +:::tip +You can utilize the [`cross-seed diff`](../reference/utils#cross-seed-diff) command to compare the torrents. +::: + +### My tracker is mad at me for snatching 1000000 .torrent files! + +`cross-seed` currently searches for potential matches and compares the contents and size of files within, it is possible you are running searches too often, are not keeping the torrent_cache folder, or are improperly utilizing [**autobrr**](https://autobrr.com/). + +We try to reduce unnecessary snatches of .torrent files as much as possible, but because we need to compare files inside the torrent as well as their sizes, it is sometimes unavoidable. + +### Recommended Overall Improvements + +- You can use the `announce` endpoint instead of `webhook` to match what cross-seed already knows about your available media. +- If using [**autobrr**](https://autobrr.com/), consider setting up filters with [**omegabrr**](https://github.com/autobrr/omegabrr) to minimize calls to cross-seed +- Adjust `excludeRecentSearch` `excludeOlder` and `searchCadence` to reduce searching +- If your cross-seed runs 24/7, consider reducing or eliminating searching. RSS is capable of catching all releases if 24/7. diff --git a/docs/basics/getting-started.md b/docs/basics/getting-started.md index fe4f747..58dfa6b 100644 --- a/docs/basics/getting-started.md +++ b/docs/basics/getting-started.md @@ -45,14 +45,16 @@ Docker users can skip ahead to [Scaling Up](#with-docker). ::: -To get started, we'll use the following command: +To get started, you can use CLI. + +For CLI commands, a basic example is below: ```shell # one liner -cross-seed search --torrent-dir /path/to/dir/with/torrent/files --output-dir . --torznab https://localhost/prowlarr/1/api?apikey=12345 +cross-seed search --torrent-dir /path/to/dir/with/.torrent/files --output-dir . --torznab https://localhost/prowlarr/1/api?apikey=12345 # readable cross-seed search \ - --torrent-dir /path/to/dir/with/torrent/files \ + --torrent-dir /path/to/dir/with/torrent/files \ # folder containing .torrent files -o . \ # any directory, cross-seed will put its output torrents here --torznab https://localhost/prowlarr/1/api?apikey=12345 # any valid torznab link, separated by spaces ``` @@ -69,12 +71,17 @@ torrents that look like single episodes. Use these command line flags to override the default: `--include-episodes --include-non-videos` +These command line arguments are always configurable in the config.js file (generated with gen-config.) +You won't have to specify them if they are set unless you wish to override the settings temporarily. + +Below we will cover the configuration process. + ::: ## Scaling Up -Searching like above is nice, but it's pretty manual and not very useful in -practice. In this section we'll set up a configuration file. A configuration +Searching like the above is nice, but it's pretty manual and not very useful in +practice. In this section, we'll set up a configuration file. A configuration file is not necessary to use cross-seed, but it makes things a lot easier. ### without Docker @@ -95,13 +102,13 @@ in your editor of choice, and start editing. The config file uses JavaScript syntax. Each option in the config (and each element in a list) must be separated by a comma, and make sure to wrap your -strings in "quotation marks". +strings in "quotation marks" or array (multiple strings) objects in []'s ::: -The only **required** options are [`torznab`](../reference/options.md#torznab), -[`torrentDir`](../reference/options.md#torrentdir), and -[`outputDir`](../reference/options.md#outputdir) (see links for details). Once +The only **required** options are [`torznab`](../basics/options.md#torznab), +[`torrentDir`](../basics/options.md#torrentdir), and +[`outputDir`](../basics/options.md#outputdir) (see links for details). Once you've configured those, you can try running the app with ```shell @@ -116,14 +123,13 @@ of your torrent directory! #### Set up your container With Docker, any paths that you would provide as command-line arguments or in -the config file should stay hardcoded, and instead you can mount volumes to the +the config file should stay hardcoded. Instead, you can mount volumes to the Docker container. - `/config` - config dir (this follows a common Docker convention) - `/torrents` - your torrent input dir (usually set to your rtorrent session dir, or your qBittorrent BT_backup dir) - `/cross-seeds` - the output dir. People sometimes point this at a watch folder. If you use - [autotorrent2][at2] you can set it up to read from this folder. Create or open your existing `docker-compose.yml` file and add the `cross-seed` service: @@ -139,6 +145,8 @@ services: - /path/to/config/folder:/config - /path/to/rtorrent_sess:/torrents:ro # note that this volume can and should be mounted read-only - /path/to/output/folder:/cross-seeds + - /path/to/torrent/data:/data # this is optional dataDir path (for data-based matching) - will need to mirror your torrent client's path (like Arr's do) + command: search ``` @@ -147,7 +155,7 @@ services: When you run the container the first time, it will create a config file at `/config/config.js`. Open this file in your favorite editor. Since you've already configured your volume mappings, the only required config option is -[`torznab`](../reference/options.md#torznab). Once you've got that set up, you +[`torznab`](../basics/options.md#torznab). Once you've got that setup, you can run your Docker container and it should get started running through a full scan of your torrent directory! @@ -155,16 +163,8 @@ scan of your torrent directory! `cross-seed` has two subcommands: `search` and `daemon`. -- `search` (used above) will scan each torrent you provide and look for +- `search` (used above) will scan each torrent and dataDir (optional) you provide and look for cross-seeds, then exit. -- `daemon` will run forever, and can be configured to run searches - periodically, watch RSS, and search for newly finished downloads. - -If you're satisfied by just running `cross-seed search` every once in a while, -and then using [`autotorrent2`][at2] to inject these into your client, then -you're done! - -If you want to learn more about fully-automatic cross-seeding, keep reading. - -[at2]: https://github.com/JohnDoee/autotorrent2 +- `daemon` will run indefinitely. It can be configured to run searches periodically, watch RSS, + and be triggered to search for newly finished downloads. diff --git a/docs/reference/options.md b/docs/basics/options.md similarity index 69% rename from docs/reference/options.md rename to docs/basics/options.md index 8433241..cc6841b 100644 --- a/docs/reference/options.md +++ b/docs/basics/options.md @@ -11,7 +11,7 @@ CLI > config file > defaults ``` If you specify an option both on the command line and in the config file, the -command line value will override the config file value. +command line value will override the config file's value. ## Options on the command line @@ -38,8 +38,8 @@ long-form option name as the `kebab-cased` variant. For example, the `cross-seed` will look for a configuration file at -- **Mac**/**Linux**/**Unix**: `~/.cross-seed/config.js` -- **Windows**: `AppData\Local\cross-seed\config.js` +- **Mac**/**Linux**/**Unix**: `~/.cross-seed/config.js` +- **Windows**: `AppData\Local\cross-seed\config.js` :::tip @@ -61,9 +61,10 @@ configuration. The configuration file uses JavaScript syntax, which means: -- Array/multi options must be enclosed in \[brackets\]. -- Strings must be enclosed in "quotation" marks. -- Array elements and options must be separated by commas. +- Array/multi options must be enclosed in \[brackets\]. +- Strings must be enclosed in "quotation" marks. +- Array elements and options must be separated by commas. +- **Windows users will need to use \\\\ for paths. (e.g. c:\\\\torrents)** ::: @@ -75,7 +76,11 @@ The configuration file uses JavaScript syntax, which means: | [`torznab`](#torznab) | **Required** | | [`torrentDir`](#torrentdir) | **Required** | | [`outputDir`](#outputdir) | **Required** | +| [`dataDirs`](#datadir) | | +| [`dataCategory`](#datacategory) | | +| [`linkDir`](#linkdir) | | | [`includeEpisodes`](#includeepisodes) | | +| [`includeSingleEpisodes`](#includesingleepisodes) | | | [`includeNonVideos`](#includenonvideos) | | | [`fuzzySizeThreshold`](#fuzzysizethreshold) | | | [`excludeOlder`](#excludeolder) | | @@ -83,6 +88,7 @@ The configuration file uses JavaScript syntax, which means: | [`action`](#action) | | | [`rtorrentRpcUrl`](#rtorrentrpcurl) | | | [`qbittorrentUrl`](#qbittorrenturl) | | +| [`delugeRpcUrl`](#delugerpcurl) | | | [`notificationWebhookUrl`](#notificationwebhookurl) | | ## Options used in `cross-seed daemon` @@ -93,7 +99,11 @@ The configuration file uses JavaScript syntax, which means: | [`torznab`](#torznab) | **Required** | | [`torrentDir`](#torrentDir) | **Required** | | [`outputDir`](#outputDir) | **Required** | -| [`includeEpisodes`](#includeEpisodes) | | +| [`dataDirs`](#datadir) | | +| [`dataCategory`](#datacategory) | | +| [`linkDir`](#linkdir) | | +| [`includeEpisodes`](#includeepisodes) | | +| [`includeSingleEpisodes`](#includesingleepisodes) | | | [`includeNonVideos`](#includeNonVideos) | | | [`fuzzySizeThreshold`](#fuzzySizeThreshold) | | | [`excludeOlder`](#excludeOlder) | | @@ -108,7 +118,7 @@ The configuration file uses JavaScript syntax, which means: ## All options -### `delay` +### `delay`\* | Config file name | CLI short form | CLI Long form | Format | Default | | ---------------- | -------------- | ----------------- | ------------------ | ------- | @@ -144,6 +154,8 @@ delay: 20, List of Torznab URLs. You can use Jackett, Prowlarr, or indexer built-in Torznab implementations. +This entry **MUST** be wrapped in []'s and strings inside wrapped with quotes and separated by commas. + :::caution ``` @@ -199,7 +211,7 @@ torznab: ["http://jackett:9117/api/v2.0/indexers/oink/results/torznab/api?apikey Point this at a directory containing torrent files. If you don't know where your torrent client stores its files, the table below might help. -:::caution Docker users +:::caution Docker Leave the `torrentDir` as `/torrents` and use Docker to map your directory to `/torrents`. @@ -209,7 +221,7 @@ Leave the `torrentDir` as `/torrents` and use Docker to map your directory to | Client | Linux | Windows | Mac | | ---------------- | ---------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------- | | **rTorrent** | your session directory as configured in .rtorrent.rc | your session directory as configured in .rtorrent.rc | your session directory as configured in .rtorrent.rc | -| **Deluge** | `/home//.config/deluge/state` | Unknown (please submit a [PR][pr]!) | Unknown (please submit a [PR][pr]!) | +| **Deluge** | `/home//.config/deluge/state` | %APPDATA%\deluge\state | Currently Not Supported Officially | | **Transmission** | `/home//.config/transmission/torrents` | Unknown (please submit a [PR][pr]!) | Unknown (please submit a [PR][pr]!) | | **qBittorrent** | `/home//.local/share/data/qBittorrent/BT_backup` | `C:\Users\\AppData\Local\qBittorrent\BT_backup` | `~/Library/Application Support/qBittorrent/BT_backup` | @@ -235,10 +247,10 @@ torrentDir: "/torrents", | `outputDir` | `-o ` | `--output-dir ` | `string` | | `cross-seed` will store the torrent files it finds in this directory. If you use -[Injection](../tutorials/injection) with **rTorrent**, you'll need to make sure -rTorrent has access to this path also. +[Injection](../tutorials/injection) you'll need to make sure that the client has +access to this path also. -:::caution Docker users +:::caution Docker Leave the `outputDir` as `/cross-seeds` and use Docker to map your directory to `/cross-seeds`. @@ -262,6 +274,160 @@ outputDir: "/tmp/output", outputDir: ".", ``` +### `dataDirs` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ---------------- | ----------------------- | ----------------------- | ----------- | ------- | +| `dataDirs` | `--data-dirs ` | `--data-dirs ` | `string(s)` | | + +`cross-seed` will search the paths provided (separated by spaces). If you use +[Injection](../tutorials/injection) `cross-seed` will use the specified linkType +to create a link to the original file in the linkDir during data-based searchs where it cannot +find a associated torrent file. + +:::caution Docker + +You will need to mount the volume for cross-seed to have access to the data and linkDir. + +::: + +#### `dataDirs` Examples (CLI) + +```shell +cross-seed search --data-dirs /data/torrents/completed +``` + +#### `dataDirs` Examples (Config file) + +```js +dataDir: ["/data/torrents/completed"], + +dataDir: ["/data/torrents/completed", "/media/library/movies"], + +``` + +### `dataCategory` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ---------------- | ---------------------------- | ------------- | -------- | ------- | +| `dataCategory` | `--data-category ` | N/A | `string` | | + +`cross-seed` will, when performing data-based searches with [injection](../tutorials/injection), +use this category for all injected torrents. + +:::caution Docker + +You will need to mount the volume for cross-seed to have access to the data and linkDir. +::: + +#### `dataCategory` Examples (CLI) + +```shell +cross-seed search --data-category category +``` + +#### `dataCategory` Examples (Config file) + +```js +dataCategory: "Category1", + +``` + +### `linkDir` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ---------------- | ------------------ | ------------------ | -------- | ------- | +| `linkDir` | `--link-dir ` | `--link-dir ` | `string` | | + +`cross-seed` will link (symlink/hardlink) in the path provided. If you use +[Injection](../tutorials/injection) `cross-seed` will use the specified linkType +to create a link to the original file in the linkDir during data-based searchs where it cannot +find a associated torrent file. + +:::caution Docker + +You will need to mount the volume for cross-seed to have access to the dataDir and linkDir. + +::: + +#### `linkDir` Examples (CLI) + +```shell +cross-seed search --linkDir /data/torrents/xseeds +``` + +#### `linkDir` Examples (Config file) + +```js +linkDir: "/links", + +linkDir: "/data/torrents/links", + +``` + +### `linkType` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ---------------- | -------------------- | -------------------- | -------- | ------- | +| `linkType` | `--link-type ` | `--link-type ` | `string` | | + +`cross-seed` will link (symlink/hardlink) in the method provided. If you use +[Injection](../tutorials/injection) `cross-seed` will use the specified linkType +to create a link to the original file in the linkDir during data-based searchs where it cannot +find a associated torrent file. + +Valid methods for linkType are `symlink` and `hardlink`. + +:::caution Docker + +You will need to mount the volume for cross-seed to have access to the dataDir and linkDir. +::: + +#### `linkType` Examples (CLI) + +```shell +cross-seed search --linkType hardlink +``` + +#### `linkType` Examples (Config file) + +```js +linkType: "hardlink", + +linkType: "symlink", + +``` + +### `matchMode` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ---------------- | --------------------- | --------------------- | -------------- | ------- | +| `matchMode` | `--match-mode ` | `--match-mode ` | `safe`/`risky` | `safe` | + +`cross-seed` uses two types of matching algorithms, `safe` and `risky`. `safe` is the default and matches +based on name and size, while `risky` matches strictly on size. For media library searches `risky` is necessary +due to the renaming of files. + +:::tip +Using skipRecheck in conjunction with `risky` is not recommended, and could result in you downloading rather than cross-seeding properly. +**Proceed with caution!** +::: + +#### `matchMode` Examples (CLI) + +```shell +cross-seed search --match-mode risky +cross-seed search --match-mode safe +``` + +#### `matchMode` Examples (Config file) + +```js +matchMode: "risky", + +matchMode: "safe", +``` + ### `includeEpisodes` | Config file name | CLI short form | CLI long form | Format | Default | @@ -269,13 +435,14 @@ outputDir: ".", | `includeEpisodes` | `-e` | `--include-episodes` | `boolean` | `false` | Set this to `true` to include single episode torrents in the search (which are -ignored by default). +ignored by default). This will include episodes present inside season packs for data-based +searches. #### `includeEpisodes` Examples (CLI) ```shell -cross-seed search -e # will include episodes -cross-seed search --include-episodes # will include episodes +cross-seed search -e # will include all episodes +cross-seed search --include-episodes # will include all episodes cross-seed search --no-include-episodes # will not include episodes cross-seed search # will not include episodes ``` @@ -288,6 +455,31 @@ includeEpisodes: true, includeEpisodes: false, ``` +### `includeSingleEpisodes` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ----------------------- | -------------- | --------------------------- | --------- | ------- | +| `includeSingleEpisodes` | `N/A` | `--include-single-episodes` | `boolean` | `false` | + +Set this to `true` to include single episode torrents in the search (which are +ignored by default). This will include episodes present inside season packs for data-based +searches. + +#### `includeSingleEpisodes` Examples (CLI) + +```shell +cross-seed search --include-single-episodes # will include single episodes not from season pack +cross-seed search # will not include episodes +``` + +#### `includeSingleEpisodes` Examples (Config file) + +```js +includeSingleEpisodes: true, + +includeSingleEpisodes: false, +``` + ### `includeNonVideos` | Config file name | CLI short form | CLI long form | Format | Default | @@ -405,7 +597,7 @@ excludeRecentSearch: "1 day", excludeRecentSearch: "2 weeks", ``` -### `action` +### `action`\* | Config file name | CLI short form | CLI long form | Format | Default | | ---------------- | ------------------ | ------------------------ | --------------- | ------- | @@ -493,6 +685,62 @@ qbittorrentUrl: "http://qbittorrent:8080/qbittorrent", qbittorrentUrl: "http://user:pass@localhost:8080", ``` +### `transmissionRpcUrl` + +| Config file name | CLI short form | CLI long form | Format | Default | +| -------------------- | -------------- | ----------------------------- | ------ | ------- | +| `transmissionRpcUrl` | | `--transmissionrpc-url ` | URL | | + +The url of your **Transmission** RPC Interface. Only relevant with +[Injection](../tutorials/injection). + +**Transmission** doesn't use HTTP Basic/Digest Auth, but you can provide your +**Transmission** credentials at the beginning of the URL like so: +`http://username:password@localhost:9091/transmission/rpc` + +#### `transmissionRpcUrl` Examples (CLI) + +```shell +cross-seed search --transmissionrpc-url http://transmission:8080/transmission/rpc +cross-seed search --transmissionrpc-url http://user:pass@localhost:8080 +``` + +#### `transmissionRpcUrl` Examples (Config file) + +```js +transmissionRpcUrl: "http://transmission:8080/transmission/rpc", + +transmissionRpcUrl: "http://username:password@localhost:9091/transmission/rpc", +``` + +### `delugeRpcUrl` + +| Config file name | CLI short form | CLI long form | Format | Default | +| ---------------- | -------------- | ----------------------- | ------ | ------- | +| `delugeRpcUrl` | | `--delugerpc-url ` | URL | | + +The url of your **Deluge** JSON-RPC Interface. Only relevant with +[Injection](../tutorials/injection). + +**Deluge** doesn't use HTTP Basic/Digest Auth, but you can provide your +**Deluge** password at the beginning of the URL like so: +`http://:password@localhost:8112/json` + +#### `delugeRpcUrl` Examples (CLI) + +```shell +cross-seed search --delugerpc-url http://deluge:8112/json +cross-seed search --delugerpc-url http://:pass@localhost:8112/json +``` + +#### `delugeRpcUrl` Examples (Config file) + +```js +delugeRpcUrl: "http://deluge:8112/json", + +delugeRpcUrl: "http://:pass@localhost:8112/json", +``` + ### `notificationWebhookUrl` | Config file name | CLI short form | CLI long form | Format | Default | @@ -519,7 +767,7 @@ Content-Type: application/json } ``` -Currently, cross-seed only sends the "RESULTS" event. In the future it may send +Currently, cross-seed only sends the "RESULTS" and "TEST" events. In the future it may send more. This payload supports both [**apprise**](https://github.com/caronc/apprise-api) and [**Notifiarr**](https://github.com/Notifiarr/Notifiarr). @@ -536,7 +784,7 @@ cross-seed daemon --notification-webhook-url http://apprise:8000/notify notificationWebhookUrl: "http://apprise:8000/notify", ``` -### `port` +### `port`\* | Config file name | CLI short form | CLI long form | Format | Default | | ---------------- | -------------- | --------------- | -------- | ------- | diff --git a/docs/reference/api.md b/docs/reference/api.md index 4a1327e..eb5a796 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -1,23 +1,30 @@ # HTTP API `cross-seed` has an HTTP API as part of [Daemon Mode](../basics/daemon.md). When -you run `cross-seed daemon`, the app starts an HTTP server, listening on port -2468 (configurable with the [`port`](options#port) option). +you run the `cross-seed daemon` command, the app starts an HTTP server, listening +on port 2468 (configurable with the [`port`](options#port) option). :::danger -`cross-seed` does _not_ have API auth. **Do not expose its port to the -internet.** +`cross-seed` does _not_ have API auth. + +**Do not expose its port to untrusted networks (such as the Internet)** ::: ## POST `/api/webhook` This endpoint invokes a search, on all configured trackers, for a specific -torrent name. You can provide the name directly, or you can provide an infoHash, -which `cross-seed` will use to look up the name in your -[`torrentDir`](options#torrentdir). It will respond with `204 No Content` once -it has received your request successfully. +torrent name, infoHash, or torrent data. You can provide the name directly, +or you can provide an infoHash or path to search for, which `cross-seed` will +use to either look up the torrent in your [`torrentDir`](options#torrentdir) or parse the +filename directly. It will respond with `204 No Content` once it has received your +request successfully. + +:::tip +Searches that match a torrent file always take precedence, even in data-based searching. + +::: ### Supported formats @@ -31,9 +38,10 @@ it has received your request successfully. ```js POST /api/webhook { - // one of { name, infoHash } is required + // one of { name, infoHash, path } is required name: "", infoHash: "", + path: "/path/to/torrent/file.mkv", outputDir: "/path/to/output/dir", // optional } ``` @@ -54,15 +62,15 @@ curl -XPOST http://localhost:2468/api/webhook \ ## POST `/api/announce` (experimental) -Use this endpoint to feed announces into cross-seed. For each announce, -cross-seed will check if the given torrent name matches any torrents you already +Use this endpoint to feed announces into cross-seed. For each `announce`, +`cross-seed` will check if the provided search criteria match any torrents you already have. If found, it will run the matching algorithm to verify that the torrents do match, and download/inject the announced torrent. :::tip -This is a real-time alternative to scanning RSS feeds. Consider turning RSS scan -off if you set up this feature. +This is a real-time alternative to scanning RSS feeds. Consider turning the RSS +scan off (`rssCadence: null,`) if you set up this feature. ::: diff --git a/docs/reference/architecture.md b/docs/reference/architecture.md index aab9fad..883ce7d 100644 --- a/docs/reference/architecture.md +++ b/docs/reference/architecture.md @@ -51,11 +51,11 @@ checkNewCandidateMatch --> getTorrentByFuzzyName - `cross-seed search` - `POST /api/search` -The search pipeline takes an owned torrent, parses its name, then searches for +The search pipeline takes an owned torrent, parses its name, and then searches for its parsed name on all of your Torznab indexers. After that, it's given a list -of candidates, which then all run through the -[**matching algorithm**](#matching-algorithm) against the owned torrent. Any -resulting matches will then run through the configured [**action**](#actions). +of candidates, which then all run through the [**matching algorithm**](#matching-algorithm) +against the owned torrent. Any resulting matches will then run through the configured +[**action**](#actions). ### RSS pipeline @@ -72,7 +72,15 @@ run through the configured [**action**](#actions). ## Prefiltering -TODO +Prefiltering occurs during the startup of `cross-seed`. This will index all the .torrent files and any +`dataDirs` you may have added. + +- If you're using injection, the existence of any .torrent files implies their + presence in the client. If the torrent is not present, it will fail injection and save instead. + +- Your torrentDir should not contain torrent files that are not present in your client. + +- Prefiltering de-duplicates, multiple files with the same torrent name will not be searched multiple times. ## Matching algorithm @@ -80,8 +88,8 @@ TODO ## Actions -There are three things that can happen during the `action` phase: +Three things can happen during the `action` phase: - link data files (if data-based) -- inject matched torrent file -- save matched torrent file +- inject matching torrent file +- save matching torrent file diff --git a/docs/reference/faq-troubleshooting.md b/docs/reference/faq-troubleshooting.md deleted file mode 100644 index da8c4d8..0000000 --- a/docs/reference/faq-troubleshooting.md +++ /dev/null @@ -1,17 +0,0 @@ -# FAQ & Troubleshooting - -## What can I do about `error parsing torrent at http://…`? - -This means that the jacket download link didn't resolve to a torrent file. It's -possible you got rate-limited so you might want to try again in a day or more. -Otherwise, just ignore it. There's nothing cross-seed will be able to do to fix -it. - -## rtorrent injected torrents don't check (or start at all) until force rechecked -Remove any `stop_untied=` schedules from your .rtorrent.rc. - -Commonly: `schedule2 = untied_directory, 5, 5, (cat,"stop_untied=",(cfg.watch),"*.torrent")` - -## Failed to inject, saving instead. - -TODO diff --git a/docs/reference/utils.md b/docs/reference/utils.md index 372c15f..a24d516 100644 --- a/docs/reference/utils.md +++ b/docs/reference/utils.md @@ -1,11 +1,11 @@ The `cross-seed` app has several subcommand utilities. Some of these can help you debug your system, or help you find more information to file a bug report. -## `cross-seed gen-config` +### `cross-seed gen-config` Generate an empty config file in its proper location. -### Options +#### Options | short form | long form | note | | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------ | @@ -18,35 +18,35 @@ cross-seed gen-config cross-seed gen-config -d ``` -## `cross-seed clear-cache` +### `cross-seed clear-cache` Clear the cache of previous decisions. -## `cross-seed test-notification` +### `cross-seed test-notification` Send a notification to the specified URL. -### Usage +#### Usage ```shell cross-seed test-notification ``` -## `cross-seed diff` +### `cross-seed diff` See if and why two torrents pass the matching algorithm. -### Usage +#### Usage ```shell cross-seed diff ``` -## `cross-seed tree` +### `cross-seed tree` Check a torrent's file tree from `cross-seed`'s perspective. -### Usage +#### Usage ```shell cross-seed tree file.torrent diff --git a/docs/tutorials/data-based-matching.md b/docs/tutorials/data-based-matching.md index 76ce667..17b807c 100644 --- a/docs/tutorials/data-based-matching.md +++ b/docs/tutorials/data-based-matching.md @@ -1,35 +1,40 @@ --- id: data-based-matching sidebar_position: 2 -title: Data Based Matching +title: Data-Based Matching --- ## Why? -Before data based matching, cross-seed relied on having torrent files to be able to search for cross seeds. In this method, it analyzes the list of files stored in the `.torrent` file and searches your trackers for similar files. If the files found from the remote torrent (candidate) match the local torrent (searchee), this is a compatible cross-seed. To determine if the files match, the files in the candidate must be a subset of the searchee, and contain the exact same name and size as those in the searchee. +Before data-based matching, cross-seed relies on having torrent files to be able to search for cross-seeds. In this method, it analyzes the list of files stored in the `.torrent` file and searches your trackers for similar files. If the files found from the remote torrent (candidate) match the local torrent (searchee), this is a compatible cross-seed. To determine if the files match, the files in the candidate must be a subset of the searchee and contain the exact name and size as those in the searchee. + +Data-based matching allows cross seed to not require the `.torrent` file, and instead to look at actual files to search for matches. This is great if you have the actual files to cross-seed, but not the `.torrent` files (think usenet). It still generates a searchee, but the method by which it generates is different. For one, `.torrent`-based searchees are limited to the top-level directory if a torrent file contains a folder. Data-based searching can create this same searchee, but can also traverse into the folder to generate a searchee from an individual file within the folder. This allows cross-seed to take a searchee with a single nested file: -Data based matching allows cross seed to not require the `.torrent` file, and instead to look at actual files to search for matches. This is great if you have the actual files to cross-seed, but not the `.torrent` files (think usenet). It still generates a searchee, but the method by which it generates is different. For one, `.torrent` based searchees are limited to the top level directory if a torrent file contains a folder. Data based searching can create this same searchee, but can also traverse into the folder to generate a searchee from an individual file within the folder. This allows cross-seed to take a searchee with a single nested file: ``` torrent_name/ ├─ torrent_file.mkv ``` + and separately match a single file torrent for `torrent_file.mkv` -Due to using file system links, data based matching also opens the door for "risky" matching where the name of the searchee can be ignored and only file size is used, with a couple of caveats: 1) false positives are possible with this method, so it is recommended that you have cross-seed recheck torrents added during risky matching, and 2) risky matching only finds extra results for single-file searchees. +Due to using file system links, data-based matching also opens the door for "risky" matching where the name of the searchee can be ignored and only file size is used, with a couple of caveats: 1) false positives are possible with this method, so it is recommended that you have cross-seed recheck torrents added during risky matching, and 2) risky matching only finds extra results for single-file searchees. ## Setup -Data based matching adds a few parameters: +Data-based matching adds a few parameters: `dataDirs`: This specifies what directories cross-seed should generate searchees from. They are specified as an array, for example `["/data/torrents/", "/data/media/"]` similar to the existing `torznab` parameter. -`linkDir`: This specifies where you want your links cross-seed generates to be placed. While technically possible to specify the same directory as your dataDirs, this is not recommended and could result in bad interactions between existing and new files. Instead it should be a separate directory visible to both your torrent client and cross-seed. +`linkDir`: This specifies where you want your links cross-seed generates to be placed. While technically possible to specify the same directory as your `dataDirs`, this is not recommended and could result in bad interactions between existing and new files. Instead, it should be a separate directory visible to both your torrent client and cross-seed. -`linkType`: Either `hardlink` or `symlink`. We recommend symlinks, as if a source is removed for a hardlink, the new link still takes up space which is probably not desireable. If a symlink's source is removed, it's merely a broken symlink taking up basically no space on your file system. However, hardlinks are more flexible if you have a solution to this. For hardlinks, only the linkDir will need to be visible (with a matching path mapping) to your torrent client, whereas for symlinks both the linkDir and all specified dataDirs need to be visible with matching path mappings to your torrent client. +`linkType`: Either `"hardlink"` or `"symlink"`. [(**See FAQ for details**)](../basics/faq-troubleshooting#what-linktype-should-i-use-data-based-searching). -`matchMode`: Either `safe` or `risky` for the time being. As explained above, `risky` matches only using file sizes. `safe` uses the existing method of name + file sizes. +- `symlink` (Recommended): If a source is removed for a `hardlink`, the new link still takes up space which may not be desirable. If a symlink's source is removed, it's merely a broken symlink taking up almost no space on your file system. +- `hardlink`: These can be more flexible. For `hardlink`s, only the linkDir will need to be visible (with a matching path mapping) to your torrent client, whereas for `symlink` both the `linkDir` and all specified `dataDirs` need to be visible with matching path mappings to your torrent client. -`skipRecheck`: Currently only works in qBittorrent. If set to false, cross-seed will inject the torrent as paused and tell qBitorrent to recheck the torrent contents. This is recommended with `matchMode: "risky"` due to potential false positives. As of 5.0.2 this applies to all torrents added, not just those found by data based matching. This will change in a later revision. +`matchMode`: Either `"safe"` or `"risky"` for the time being. As explained above, `"risky"` matching will only use file size. `"safe"` uses the existing method of name + file sizes. + +`skipRecheck`: Currently only works in qBittorrent and Deluge. If set to false, cross-seed will inject the torrent as paused and tell qBitorrent to recheck the torrent contents. This is recommended with `matchMode: "risky"` due to potential false positives. As of 5.0.2, this applies to all torrents added, not just those found by data-based matching. This will change in a later revision. `maxDataDepth`: Determines how deep to traverse the file tree for generating searches. If you specify a dataDir of `/data/torrents`, the depth is as follows: @@ -41,18 +46,19 @@ data/ │ | ├─ torrent_subfolder # 2 │ | | ├─ torrent_item.mkv # 3 ``` + Be careful setting this to a higher value than 2 (if the dataDir is your torrents folder), else it might generate a larger than intended number of searchees that will not realistically get many matches. ## Why linking? -Linking (and thus the linkDir) allows cross-seed to add torrents which do not have the same name or structure as the original torrent. While qBittorrent supports renaming of files within the torrent, this is not supported on all clients so linking is generally a more robust solution. +Linking (and thus the linkDir) allows cross-seed to add torrents that do not have the same name or structure as the original torrent. While qBittorrent supports renaming of files within the torrent, this is not supported on all clients so linking is generally a more robust solution. -*All* matches found will be linked into the linkDir, even perfect matches. If a file already exists in the linkDir with the same name, cross-seed will not overwrite the file. This means that using the same linkDir as dataDir *should* be possible but is not something we encourage. +_All_ matches found will be linked into the linkDir, even perfect matches. If a file already exists in the linkDir with the same name, cross-seed will not overwrite the file. This means that using the same linkDir as dataDir _should_ be possible but is not something we encourage. -Eventually this can be taken further than it is now. Cross-seed could eventually make per-match directories, allowing for partial matches of torrents without the risk colliding files (no need to worry about differing nfos between two torrents). +Eventually, this can be taken further than it is now. Cross-seed could eventually make per-match directories, allowing for partial matches of torrents without the risk of colliding files (no need to worry about differing nfos between two torrents). -Links also allow us to "normalize" the depth of matches. If a match is found at dataDepth 2, it can be linked up to be at depth 1 in the link dir, so that all your files found with data based matching have the same save path, matching the linkDir. This will let it play nicer with autoTMM if your setup heavily involves that. +Links also allow us to "normalize" the depth of matches. If a match is found at dataDepth 2, it can be linked up to be at depth 1 in the linkDir, so that all your files found with data-based matching have the same save path, matching the linkDir. This will let it play nicer with autoTMM if your setup heavily involves that. ## Daemon mode -Data based matching does not support RSS but does allow you to hit the cross-seed endpoint with a path to use for data based searching the same way you use the existing `name` parameter (but `path` instead), for example from a *arr post import script. +Data-based matching does not support RSS but does allow you to hit the cross-seed endpoint with a path to use for data-based searching the same way you use the existing `name` parameter (but `path` instead), for example from an [Arr Import script](https://github.com/bakerboy448/StarrScripts#xseedsh). diff --git a/docs/tutorials/injection.md b/docs/tutorials/injection.md index cb75436..bc6ba6e 100644 --- a/docs/tutorials/injection.md +++ b/docs/tutorials/injection.md @@ -4,29 +4,28 @@ sidebar_position: 1 title: Direct Client Injection --- -If you use **rTorrent** or **qBittorrent**, `cross-seed` can inject the torrents -it finds directly into your torrent client. This satisfies most simple use -cases. For more complex cases, -[**autotorrent2**](https://github.com/JohnDoee/autotorrent2) or -[**qbit_manage**](https://github.com/StuffAnThings/qbit_manage) is recommended. +If you use **rTorrent**, **Transmission**, **Deluge**, or **qBittorrent**, `cross-seed` +can inject the torrents it finds directly into your torrent client. This satisfies most +simple use cases. For more complex cases, [**autotorrent2**](https://github.com/JohnDoee/autotorrent2) +or [**qbit_manage**](https://github.com/StuffAnThings/qbit_manage) is recommended. -## `rTorrent` setup +### `rTorrent` setup `cross-seed` will inject torrents into **rTorrent** with a `cross-seed` label. 1. Make sure **rTorrent** has access to your `outputDir` (if Docker, make sure they're mapped to the same path). 2. Edit your config file: - 1. Set your [`action`](../reference/options#action) option to `inject`. - 2. Set your [`rtorrentRpcUrl`](../reference/options#rtorrentrpcurl) option. - It should look like an `http` url that looks like - `http://user:pass@localhost:8080/rutorrent/RPC2` (if you have ruTorrent - installed). See the [reference](../reference/options#rtorrentrpcurl) for - more details. + 1. Set your [`action`](../basics/options#action) option to `inject`. + 2. Set your [`rtorrentRpcUrl`](../basics/options#rtorrentrpcurl) option. + It should look like an `http` url that looks like + `http://user:pass@localhost:8080/rutorrent/RPC2` (if you have ruTorrent + installed). See the [reference](../basics/options#rtorrentrpcurl) for + more details. 3. Start or restart `cross-seed`. The logs at startup will tell you if `cross-seed` was able to connect to rTorrent. -:::tip Docker users +:::tip Docker In order for `cross-seed` to prove to **rTorrent** that a torrent is completed, it must check the modification timestamps of all the torrent's files. @@ -36,7 +35,7 @@ directories** of your torrents, mapped to the same path as **rTorrent**. ::: -## `qBittorrent` setup +### `qBittorrent` setup :::caution @@ -45,39 +44,48 @@ Injection will work best if you use the `Original` content layout. ::: 2. Edit your config file: - 1. Set your [`action`](../reference/options#action) option to `inject`. - 2. Set your [`qbittorrentUrl`](../reference/options#qbittorrenturl) option. - It should look like an `http` url that looks like - `http://user:pass@localhost:8080(/qbittorrent)` See the - [reference](../reference/options#qbittorrenturl) for more details. - -:::caution Sonarr users - -There is a potential problem with Season Packs and using Sonarr On Download/Upgrade OR Qbit Download -Complete or search/rss/announce race conditions to trigger cross-seed if you use inject -with `cross-seed`, **qBittorrent**, and **Sonarr**, -where new cross-seeds will be added with the Sonarr category, and then get stuck + 1. Set your [`action`](../basics/options#action) option to `inject`. + 2. Set your [`qbittorrentUrl`](../basics/options#qbittorrenturl) option. + It should look like an `http` url that looks like + `http://user:pass@localhost:8080/qbittorrent` See the + [reference](../basics/options#qbittorrenturl) for more details. + +:::caution Arr Users + +There is a potential problem with duplication of imports using an Arr On Download/Upgrad, Qbit Download +Complete, or search/rss/announce race conditions to trigger cross-seed if you use inject with `cross-seed`, +**qBittorrent**, and an **Arr** where new cross-seeds will be added with the Arr category, and then get stuck in Sonarr's import queue. The workaround is to enable the `duplicateCategories` option which will append your category with `.cross-seed` and: -- you don't use separate **pre/post import categories** in Sonarr OR -- Sonarr's **pre/post import categories** have the same **save path** in +- you don't use separate **pre/post import categories** in your Arr OR +- your Arr's **pre/post import categories** have the same **save path** in qBittorrent. ::: -## `Transmission` setup +### `Transmission` setup + +1. Edit your config file: + 1. Set your [`action`](../basics/options#action) option to `inject`. + 2. Set your [`transmissionRpcUrl`](../basics/options#rtorrentrpcurl) option. + It should look like an `http` url that looks like + `http://user:pass@localhost:9091/transmission/rpc` +2. Start or restart `cross-seed`. The logs at startup will tell you if + `cross-seed` was able to connect to Transmission. + +### `Deluge` setup :::caution -Transmission is for now only available on the `next` branch +Deluge is a work in progress at the moment, but is available for now on the `deluge` branch ::: 1. Edit your config file: - 1. Set your [`action`](../reference/options#action) option to `inject`. - 2. Set your [`transmissionRpcUrl`](../reference/options#rtorrentrpcurl) option. - It should look like an `http` url that looks like - `http://user:pass@localhost:9091/transmission/rpc` + 1. Set your [`action`](../basics/options#action) option to `inject`. + 2. Set your [`delugenRpcUrl`](../basics/options#delugerpcurl) option. + It should look like an `http` url that looks like + `http://:pass@localhost:8112/json` 2. Start or restart `cross-seed`. The logs at startup will tell you if - `cross-seed` was able to connect to Transmission. + `cross-seed` was able to connect to Deluge. diff --git a/docs/tutorials/unraid.md b/docs/tutorials/unraid.md index dac0eba..1b67dda 100644 --- a/docs/tutorials/unraid.md +++ b/docs/tutorials/unraid.md @@ -14,20 +14,21 @@ app. Once the app is installed, go to the **App** tab and search for ### Volume Mappings -Cross-seed needs to access 3 directories: an input directory with torrent files, -an output directory for cross-seed to save new torrent files, and a config +Cross-seed **needs** to access 3 directories: an input directory with torrent files, +an output directory for cross-seed to save new torrent files (for `action: 'save',`), and a config directory. -| Config Type | Name | Container Path | Host Path | Access Mode | -| ----------- | ------ | -------------- | ----------------------------------- | ----------- | -| Path | Config | /config | /mnt/user/appdata/cross-seed | Read/Write | -| Path | Input | /torrents | /path/to/torrent/client/session/dir | Read Only | -| Path | Output | /output | /path/to/torrent/client/watch/dir | Read/Write | +| Config Type | Name | Container Path | Host Path | Access Mode | +| --------------- | ------ | ------------------------------------ | ----------------------------------- | ----------- | +| Path | Config | /config | /mnt/user/appdata/cross-seed | Read/Write | +| Path | Input | /torrents | /path/to/torrent/client/session/dir | Read Only | +| Path | Output | /output | /path/to/torrent/client/watch/dir | Read/Write | +| Path (Optional) | Data | /torrent/client/path/to/torrent/data | /path/to/torrent/client/data | Read/Write | ### Port Mappings -Cross-seed can listen for HTTP requests for when a torrent finishes downloading -or you receive an announce. +`cross-seed` can listen for an HTTP request from when a torrent finishes downloading +or you receive an `announce`. :::tip @@ -73,8 +74,8 @@ info: [server] Server is running on port 2468, ^C to stop. ## Automation/Scheduling -`cross-seed` runs in [Daemon mode](../basics/daemon.md) by default on Unraid. +`cross-seed` runs in [Daemon mode](../basics/daemon.md) by default on Unraid. -If you would like to schedule a periodic scan of your library, set the [`searchCadence option`](../reference/options.md#searchcadence). -If you would like to set up RSS scans, set the [`rssCadence option`](../reference/options.md#rsscadence). +If you would like to schedule a periodic scan of your library, set the [`searchCadence option`](../basics/options.md#searchcadence). +If you would like to set up RSS scans, set the [`rssCadence option`](../basics/options.md#rsscadence). If you would like to automatically check for cross-seeds when a download finishes, learn how to [set up automatic searches for finished downloads](../basics/daemon#set-up-automatic-searches-for-finished-downloads). diff --git a/docusaurus.config.js b/docusaurus.config.js index 8b30a18..c3ea892 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -59,10 +59,9 @@ const config = { ({ navbar: { title: "cross-seed", - // logo: { - // alt: "cross-seed logo", - // src: "img/logo.svg", - // }, + logo: { + src: "img/cross-seed.svg", + }, items: [ { type: "doc", @@ -71,8 +70,15 @@ const config = { label: "Tutorial", }, { - href: "https://github.com/cross-seed/cross-seed", + to: "https://discord.gg/jpbUFzS5Wb", + label: "Discord", + className: "discord-link", + position: "right", + }, + { + to: "https://github.com/cross-seed/cross-seed", label: "GitHub", + className: "github-link", position: "right", }, ], @@ -94,7 +100,8 @@ const config = { items: [ { label: "Discord", - href: "https://discord.gg/jpbUFzS5Wb", + to: "https://discord.gg/jpbUFzS5Wb", + //className: "discord-link", }, ], }, @@ -103,7 +110,8 @@ const config = { items: [ { label: "GitHub", - href: "https://github.com/cross-seed/cross-seed", + to: "https://github.com/cross-seed/cross-seed", + //className: "github-link", }, ], }, diff --git a/src/css/custom.css b/src/css/custom.css index d7585b7..8774c0d 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -6,25 +6,99 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #d73833; - --ifm-color-primary-dark: #cc2e28; - --ifm-color-primary-darker: #bb2a25; - --ifm-color-primary-darkest: #aa2622; - --ifm-color-primary-light: #da4944; - --ifm-color-primary-lighter: #dd5955; - --ifm-color-primary-lightest: #e16a66; - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + --ifm-color-primary: #d73833; + --ifm-color-primary-dark: #cc2e28; + --ifm-color-primary-darker: #bb2a25; + --ifm-color-primary-darkest: #aa2622; + --ifm-color-primary-light: #da4944; + --ifm-color-primary-lighter: #dd5955; + --ifm-color-primary-lightest: #e16a66; + --ifm-code-font-size: 95%; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme="dark"] { - --ifm-color-primary: #dd5955; - --ifm-color-primary-dark: #da4944; - --ifm-color-primary-darker: #d73833; - --ifm-color-primary-darkest: #cc2e28; - --ifm-color-primary-light: #e16a66; - --ifm-color-primary-lighter: #e47a77; - --ifm-color-primary-lightest: #e78b88; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --ifm-color-primary: #dd5955; + --ifm-color-primary-dark: #da4944; + --ifm-color-primary-darker: #d73833; + --ifm-color-primary-darkest: #cc2e28; + --ifm-color-primary-light: #e16a66; + --ifm-color-primary-lighter: #e47a77; + --ifm-color-primary-lightest: #e78b88; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); +} +html[data-theme="dark"] .github-link { + align-items: center; + display: flex; +} +html[data-theme="dark"] .github-link:before { + align-self: center; + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSIjZTNlM2UzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04IDBDMy41OCAwIDAgMy41OCAwIDhjMCAzLjU0IDIuMjkgNi41MyA1LjQ3IDcuNTkuNC4wNy41NS0uMTcuNTUtLjM4IDAtLjE5LS4wMS0uODItLjAxLTEuNDktMi4wMS4zNy0yLjUzLS40OS0yLjY5LS45NC0uMDktLjIzLS40OC0uOTQtLjgyLTEuMTMtLjI4LS4xNS0uNjgtLjUyLS4wMS0uNTMuNjMtLjAxIDEuMDguNTggMS4yMy44Mi43MiAxLjIxIDEuODcuODcgMi4zMy42Ni4wNy0uNTIuMjgtLjg3LjUxLTEuMDctMS43OC0uMi0zLjY0LS44OS0zLjY0LTMuOTUgMC0uODcuMzEtMS41OS44Mi0yLjE1LS4wOC0uMi0uMzYtMS4wMi4wOC0yLjEyIDAgMCAuNjctLjIxIDIuMi44Mi42NC0uMTggMS4zMi0uMjcgMi0uMjcuNjggMCAxLjM2LjA5IDIgLjI3IDEuNTMtMS4wNCAyLjItLjgyIDIuMi0uODIuNDQgMS4xLjE2IDEuOTIuMDggMi4xMi41MS41Ni44MiAxLjI3LjgyIDIuMTUgMCAzLjA3LTEuODcgMy43NS0zLjY1IDMuOTUuMjkuMjUuNTQuNzMuNTQgMS40OCAwIDEuMDctLjAxIDEuOTMtLjAxIDIuMiAwIC4yMS4xNS40Ni41NS4zOEE4LjAxMyA4LjAxMyAwIDAwMTYgOGMwLTQuNDItMy41OC04LTgtOHoiLz48L3N2Zz4=") + 0 0 / contain; + content: ""; + display: inline-flex; + height: 24px; + width: 24px; + margin-right: 0.5rem; + color: var(--ifm-navbar-link-color); +} +html[data-theme="dark"] .github-link:hover:before { + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSIjZDczODMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04IDBDMy41OCAwIDAgMy41OCAwIDhjMCAzLjU0IDIuMjkgNi41MyA1LjQ3IDcuNTkuNC4wNy41NS0uMTcuNTUtLjM4IDAtLjE5LS4wMS0uODItLjAxLTEuNDktMi4wMS4zNy0yLjUzLS40OS0yLjY5LS45NC0uMDktLjIzLS40OC0uOTQtLjgyLTEuMTMtLjI4LS4xNS0uNjgtLjUyLS4wMS0uNTMuNjMtLjAxIDEuMDguNTggMS4yMy44Mi43MiAxLjIxIDEuODcuODcgMi4zMy42Ni4wNy0uNTIuMjgtLjg3LjUxLTEuMDctMS43OC0uMi0zLjY0LS44OS0zLjY0LTMuOTUgMC0uODcuMzEtMS41OS44Mi0yLjE1LS4wOC0uMi0uMzYtMS4wMi4wOC0yLjEyIDAgMCAuNjctLjIxIDIuMi44Mi42NC0uMTggMS4zMi0uMjcgMi0uMjcuNjggMCAxLjM2LjA5IDIgLjI3IDEuNTMtMS4wNCAyLjItLjgyIDIuMi0uODIuNDQgMS4xLjE2IDEuOTIuMDggMi4xMi41MS41Ni44MiAxLjI3LjgyIDIuMTUgMCAzLjA3LTEuODcgMy43NS0zLjY1IDMuOTUuMjkuMjUuNTQuNzMuNTQgMS40OCAwIDEuMDctLjAxIDEuOTMtLjAxIDIuMiAwIC4yMS4xNS40Ni41NS4zOEE4LjAxMyA4LjAxMyAwIDAwMTYgOGMwLTQuNDItMy41OC04LTgtOHoiLz48L3N2Zz4=") + 0 0 / contain; +} +html[data-theme="light"] .github-link { + align-items: center; + display: flex; +} +html[data-theme="light"] .github-link:before { + align-self: center; + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSIjMWMxZTIxIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04IDBDMy41OCAwIDAgMy41OCAwIDhjMCAzLjU0IDIuMjkgNi41MyA1LjQ3IDcuNTkuNC4wNy41NS0uMTcuNTUtLjM4IDAtLjE5LS4wMS0uODItLjAxLTEuNDktMi4wMS4zNy0yLjUzLS40OS0yLjY5LS45NC0uMDktLjIzLS40OC0uOTQtLjgyLTEuMTMtLjI4LS4xNS0uNjgtLjUyLS4wMS0uNTMuNjMtLjAxIDEuMDguNTggMS4yMy44Mi43MiAxLjIxIDEuODcuODcgMi4zMy42Ni4wNy0uNTIuMjgtLjg3LjUxLTEuMDctMS43OC0uMi0zLjY0LS44OS0zLjY0LTMuOTUgMC0uODcuMzEtMS41OS44Mi0yLjE1LS4wOC0uMi0uMzYtMS4wMi4wOC0yLjEyIDAgMCAuNjctLjIxIDIuMi44Mi42NC0uMTggMS4zMi0uMjcgMi0uMjcuNjggMCAxLjM2LjA5IDIgLjI3IDEuNTMtMS4wNCAyLjItLjgyIDIuMi0uODIuNDQgMS4xLjE2IDEuOTIuMDggMi4xMi41MS41Ni44MiAxLjI3LjgyIDIuMTUgMCAzLjA3LTEuODcgMy43NS0zLjY1IDMuOTUuMjkuMjUuNTQuNzMuNTQgMS40OCAwIDEuMDctLjAxIDEuOTMtLjAxIDIuMiAwIC4yMS4xNS40Ni41NS4zOEE4LjAxMyA4LjAxMyAwIDAwMTYgOGMwLTQuNDItMy41OC04LTgtOHoiLz48L3N2Zz4=") + 0 0 / contain; + content: ""; + display: inline-flex; + height: 24px; + width: 24px; + margin-right: 0.5rem; + color: var(--ifm-navbar-link-color); +} +html[data-theme="light"] .github-link:hover:before { + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSIjZDczODMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04IDBDMy41OCAwIDAgMy41OCAwIDhjMCAzLjU0IDIuMjkgNi41MyA1LjQ3IDcuNTkuNC4wNy41NS0uMTcuNTUtLjM4IDAtLjE5LS4wMS0uODItLjAxLTEuNDktMi4wMS4zNy0yLjUzLS40OS0yLjY5LS45NC0uMDktLjIzLS40OC0uOTQtLjgyLTEuMTMtLjI4LS4xNS0uNjgtLjUyLS4wMS0uNTMuNjMtLjAxIDEuMDguNTggMS4yMy44Mi43MiAxLjIxIDEuODcuODcgMi4zMy42Ni4wNy0uNTIuMjgtLjg3LjUxLTEuMDctMS43OC0uMi0zLjY0LS44OS0zLjY0LTMuOTUgMC0uODcuMzEtMS41OS44Mi0yLjE1LS4wOC0uMi0uMzYtMS4wMi4wOC0yLjEyIDAgMCAuNjctLjIxIDIuMi44Mi42NC0uMTggMS4zMi0uMjcgMi0uMjcuNjggMCAxLjM2LjA5IDIgLjI3IDEuNTMtMS4wNCAyLjItLjgyIDIuMi0uODIuNDQgMS4xLjE2IDEuOTIuMDggMi4xMi41MS41Ni44MiAxLjI3LjgyIDIuMTUgMCAzLjA3LTEuODcgMy43NS0zLjY1IDMuOTUuMjkuMjUuNTQuNzMuNTQgMS40OCAwIDEuMDctLjAxIDEuOTMtLjAxIDIuMiAwIC4yMS4xNS40Ni41NS4zOEE4LjAxMyA4LjAxMyAwIDAwMTYgOGMwLTQuNDItMy41OC04LTgtOHoiLz48L3N2Zz4=") + 0 0 / contain; +} +html[data-theme="dark"] .discord-link { + align-items: center; + display: flex; +} +html[data-theme="dark"] .discord-link:before { + align-self: center; + background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI0cHgiIHdpZHRoPSIyNHB4IiB2aWV3Qm94PSIwIDAgNzEgNTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYwLjEwNDUgNC44OTc4QzU1LjU3OTIgMi44MjE0IDUwLjcyNjUgMS4yOTE2IDQ1LjY1MjcgMC40MTU0MkM0NS41NjAzIDAuMzk4NTEgNDUuNDY4IDAuNDQwNzY5IDQ1LjQyMDQgMC41MjUyODlDNDQuNzk2MyAxLjYzNTMgNDQuMTA1IDMuMDgzNCA0My42MjA5IDQuMjIxNkMzOC4xNjM3IDMuNDA0NiAzMi43MzQ1IDMuNDA0NiAyNy4zODkyIDQuMjIxNkMyNi45MDUgMy4wNTgxIDI2LjE4ODYgMS42MzUzIDI1LjU2MTcgMC41MjUyODlDMjUuNTE0MSAwLjQ0MzU4OSAyNS40MjE4IDAuNDAxMzMgMjUuMzI5NCAwLjQxNTQyQzIwLjI1ODQgMS4yODg4IDE1LjQwNTcgMi44MTg2IDEwLjg3NzYgNC44OTc4QzEwLjgzODQgNC45MTQ3IDEwLjgwNDggNC45NDI5IDEwLjc4MjUgNC45Nzk1QzEuNTc3OTUgMTguNzMwOSAtMC45NDM1NjEgMzIuMTQ0MyAwLjI5MzQwOCA0NS4zOTE0QzAuMjk5MDA1IDQ1LjQ1NjIgMC4zMzUzODYgNDUuNTE4MiAwLjM4NTc2MSA0NS41NTc2QzYuNDU4NjYgNTAuMDE3NCAxMi4zNDEzIDUyLjcyNDkgMTguMTE0NyA1NC41MTk1QzE4LjIwNzEgNTQuNTQ3NyAxOC4zMDUgNTQuNTEzOSAxOC4zNjM4IDU0LjQzNzhDMTkuNzI5NSA1Mi41NzI4IDIwLjk0NjkgNTAuNjA2MyAyMS45OTA3IDQ4LjUzODNDMjIuMDUyMyA0OC40MTcyIDIxLjk5MzUgNDguMjczNSAyMS44Njc2IDQ4LjIyNTZDMTkuOTM2NiA0Ny40OTMxIDE4LjA5NzkgNDYuNiAxNi4zMjkyIDQ1LjU4NThDMTYuMTg5MyA0NS41MDQxIDE2LjE3ODEgNDUuMzA0IDE2LjMwNjggNDUuMjA4MkMxNi42NzkgNDQuOTI5MyAxNy4wNTEzIDQ0LjYzOTEgMTcuNDA2NyA0NC4zNDYxQzE3LjQ3MSA0NC4yOTI2IDE3LjU2MDYgNDQuMjgxMyAxNy42MzYyIDQ0LjMxNTFDMjkuMjU1OCA0OS42MjAyIDQxLjgzNTQgNDkuNjIwMiA1My4zMTc5IDQ0LjMxNTFDNTMuMzkzNSA0NC4yNzg1IDUzLjQ4MzEgNDQuMjg5OCA1My41NTAyIDQ0LjM0MzNDNTMuOTA1NyA0NC42MzYzIDU0LjI3NzkgNDQuOTI5MyA1NC42NTI5IDQ1LjIwODJDNTQuNzgxNiA0NS4zMDQgNTQuNzczMiA0NS41MDQxIDU0LjYzMzMgNDUuNTg1OEM1Mi44NjQ2IDQ2LjYxOTcgNTEuMDI1OSA0Ny40OTMxIDQ5LjA5MjEgNDguMjIyOEM0OC45NjYyIDQ4LjI3MDcgNDguOTEwMiA0OC40MTcyIDQ4Ljk3MTggNDguNTM4M0M1MC4wMzggNTAuNjAzNCA1MS4yNTU0IDUyLjU2OTkgNTIuNTk1OSA1NC40MzVDNTIuNjUxOSA1NC41MTM5IDUyLjc1MjYgNTQuNTQ3NyA1Mi44NDUgNTQuNTE5NUM1OC42NDY0IDUyLjcyNDkgNjQuNTI5IDUwLjAxNzQgNzAuNjAxOSA0NS41NTc2QzcwLjY1NTEgNDUuNTE4MiA3MC42ODg3IDQ1LjQ1OSA3MC42OTQzIDQ1LjM5NDJDNzIuMTc0NyAzMC4wNzkxIDY4LjIxNDcgMTYuNzc1NyA2MC4xOTY4IDQuOTgyM0M2MC4xNzcyIDQuOTQyOSA2MC4xNDM3IDQuOTE0NyA2MC4xMDQ1IDQuODk3OFpNMjMuNzI1OSAzNy4zMjUzQzIwLjIyNzYgMzcuMzI1MyAxNy4zNDUxIDM0LjExMzYgMTcuMzQ1MSAzMC4xNjkzQzE3LjM0NTEgMjYuMjI1IDIwLjE3MTcgMjMuMDEzMyAyMy43MjU5IDIzLjAxMzNDMjcuMzA4IDIzLjAxMzMgMzAuMTYyNiAyNi4yNTMyIDMwLjEwNjYgMzAuMTY5M0MzMC4xMDY2IDM0LjExMzYgMjcuMjggMzcuMzI1MyAyMy43MjU5IDM3LjMyNTNaTTQ3LjMxNzggMzcuMzI1M0M0My44MTk2IDM3LjMyNTMgNDAuOTM3MSAzNC4xMTM2IDQwLjkzNzEgMzAuMTY5M0M0MC45MzcxIDI2LjIyNSA0My43NjM2IDIzLjAxMzMgNDcuMzE3OCAyMy4wMTMzQzUwLjkgMjMuMDEzMyA1My43NTQ1IDI2LjI1MzIgNTMuNjk4NiAzMC4xNjkzQzUzLjY5ODYgMzQuMTEzNiA1MC45IDM3LjMyNTMgNDcuMzE3OCAzNy4zMjUzWiIgZmlsbD0iI2UzZTNlMyIvPjwvc3ZnPg==") + 0 0 / contain; + content: ""; + display: inline-flex; + height: 24px; + width: 24px; + margin-right: 0.5rem; +} +html[data-theme="dark"] .discord-link:hover:before { + background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI0cHgiIHdpZHRoPSIyNHB4IiB2aWV3Qm94PSIwIDAgNzEgNTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYwLjEwNDUgNC44OTc4QzU1LjU3OTIgMi44MjE0IDUwLjcyNjUgMS4yOTE2IDQ1LjY1MjcgMC40MTU0MkM0NS41NjAzIDAuMzk4NTEgNDUuNDY4IDAuNDQwNzY5IDQ1LjQyMDQgMC41MjUyODlDNDQuNzk2MyAxLjYzNTMgNDQuMTA1IDMuMDgzNCA0My42MjA5IDQuMjIxNkMzOC4xNjM3IDMuNDA0NiAzMi43MzQ1IDMuNDA0NiAyNy4zODkyIDQuMjIxNkMyNi45MDUgMy4wNTgxIDI2LjE4ODYgMS42MzUzIDI1LjU2MTcgMC41MjUyODlDMjUuNTE0MSAwLjQ0MzU4OSAyNS40MjE4IDAuNDAxMzMgMjUuMzI5NCAwLjQxNTQyQzIwLjI1ODQgMS4yODg4IDE1LjQwNTcgMi44MTg2IDEwLjg3NzYgNC44OTc4QzEwLjgzODQgNC45MTQ3IDEwLjgwNDggNC45NDI5IDEwLjc4MjUgNC45Nzk1QzEuNTc3OTUgMTguNzMwOSAtMC45NDM1NjEgMzIuMTQ0MyAwLjI5MzQwOCA0NS4zOTE0QzAuMjk5MDA1IDQ1LjQ1NjIgMC4zMzUzODYgNDUuNTE4MiAwLjM4NTc2MSA0NS41NTc2QzYuNDU4NjYgNTAuMDE3NCAxMi4zNDEzIDUyLjcyNDkgMTguMTE0NyA1NC41MTk1QzE4LjIwNzEgNTQuNTQ3NyAxOC4zMDUgNTQuNTEzOSAxOC4zNjM4IDU0LjQzNzhDMTkuNzI5NSA1Mi41NzI4IDIwLjk0NjkgNTAuNjA2MyAyMS45OTA3IDQ4LjUzODNDMjIuMDUyMyA0OC40MTcyIDIxLjk5MzUgNDguMjczNSAyMS44Njc2IDQ4LjIyNTZDMTkuOTM2NiA0Ny40OTMxIDE4LjA5NzkgNDYuNiAxNi4zMjkyIDQ1LjU4NThDMTYuMTg5MyA0NS41MDQxIDE2LjE3ODEgNDUuMzA0IDE2LjMwNjggNDUuMjA4MkMxNi42NzkgNDQuOTI5MyAxNy4wNTEzIDQ0LjYzOTEgMTcuNDA2NyA0NC4zNDYxQzE3LjQ3MSA0NC4yOTI2IDE3LjU2MDYgNDQuMjgxMyAxNy42MzYyIDQ0LjMxNTFDMjkuMjU1OCA0OS42MjAyIDQxLjgzNTQgNDkuNjIwMiA1My4zMTc5IDQ0LjMxNTFDNTMuMzkzNSA0NC4yNzg1IDUzLjQ4MzEgNDQuMjg5OCA1My41NTAyIDQ0LjM0MzNDNTMuOTA1NyA0NC42MzYzIDU0LjI3NzkgNDQuOTI5MyA1NC42NTI5IDQ1LjIwODJDNTQuNzgxNiA0NS4zMDQgNTQuNzczMiA0NS41MDQxIDU0LjYzMzMgNDUuNTg1OEM1Mi44NjQ2IDQ2LjYxOTcgNTEuMDI1OSA0Ny40OTMxIDQ5LjA5MjEgNDguMjIyOEM0OC45NjYyIDQ4LjI3MDcgNDguOTEwMiA0OC40MTcyIDQ4Ljk3MTggNDguNTM4M0M1MC4wMzggNTAuNjAzNCA1MS4yNTU0IDUyLjU2OTkgNTIuNTk1OSA1NC40MzVDNTIuNjUxOSA1NC41MTM5IDUyLjc1MjYgNTQuNTQ3NyA1Mi44NDUgNTQuNTE5NUM1OC42NDY0IDUyLjcyNDkgNjQuNTI5IDUwLjAxNzQgNzAuNjAxOSA0NS41NTc2QzcwLjY1NTEgNDUuNTE4MiA3MC42ODg3IDQ1LjQ1OSA3MC42OTQzIDQ1LjM5NDJDNzIuMTc0NyAzMC4wNzkxIDY4LjIxNDcgMTYuNzc1NyA2MC4xOTY4IDQuOTgyM0M2MC4xNzcyIDQuOTQyOSA2MC4xNDM3IDQuOTE0NyA2MC4xMDQ1IDQuODk3OFpNMjMuNzI1OSAzNy4zMjUzQzIwLjIyNzYgMzcuMzI1MyAxNy4zNDUxIDM0LjExMzYgMTcuMzQ1MSAzMC4xNjkzQzE3LjM0NTEgMjYuMjI1IDIwLjE3MTcgMjMuMDEzMyAyMy43MjU5IDIzLjAxMzNDMjcuMzA4IDIzLjAxMzMgMzAuMTYyNiAyNi4yNTMyIDMwLjEwNjYgMzAuMTY5M0MzMC4xMDY2IDM0LjExMzYgMjcuMjggMzcuMzI1MyAyMy43MjU5IDM3LjMyNTNaTTQ3LjMxNzggMzcuMzI1M0M0My44MTk2IDM3LjMyNTMgNDAuOTM3MSAzNC4xMTM2IDQwLjkzNzEgMzAuMTY5M0M0MC45MzcxIDI2LjIyNSA0My43NjM2IDIzLjAxMzMgNDcuMzE3OCAyMy4wMTMzQzUwLjkgMjMuMDEzMyA1My43NTQ1IDI2LjI1MzIgNTMuNjk4NiAzMC4xNjkzQzUzLjY5ODYgMzQuMTEzNiA1MC45IDM3LjMyNTMgNDcuMzE3OCAzNy4zMjUzWiIgZmlsbD0iI2Q3MzgzMyIvPjwvc3ZnPg==") + 0 0 / contain; +} +html[data-theme="light"] .discord-link { + align-items: center; + display: flex; +} +html[data-theme="light"] .discord-link:before { + align-self: center; + background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI0cHgiIHdpZHRoPSIyNHB4IiB2aWV3Qm94PSIwIDAgNzEgNTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYwLjEwNDUgNC44OTc4QzU1LjU3OTIgMi44MjE0IDUwLjcyNjUgMS4yOTE2IDQ1LjY1MjcgMC40MTU0MkM0NS41NjAzIDAuMzk4NTEgNDUuNDY4IDAuNDQwNzY5IDQ1LjQyMDQgMC41MjUyODlDNDQuNzk2MyAxLjYzNTMgNDQuMTA1IDMuMDgzNCA0My42MjA5IDQuMjIxNkMzOC4xNjM3IDMuNDA0NiAzMi43MzQ1IDMuNDA0NiAyNy4zODkyIDQuMjIxNkMyNi45MDUgMy4wNTgxIDI2LjE4ODYgMS42MzUzIDI1LjU2MTcgMC41MjUyODlDMjUuNTE0MSAwLjQ0MzU4OSAyNS40MjE4IDAuNDAxMzMgMjUuMzI5NCAwLjQxNTQyQzIwLjI1ODQgMS4yODg4IDE1LjQwNTcgMi44MTg2IDEwLjg3NzYgNC44OTc4QzEwLjgzODQgNC45MTQ3IDEwLjgwNDggNC45NDI5IDEwLjc4MjUgNC45Nzk1QzEuNTc3OTUgMTguNzMwOSAtMC45NDM1NjEgMzIuMTQ0MyAwLjI5MzQwOCA0NS4zOTE0QzAuMjk5MDA1IDQ1LjQ1NjIgMC4zMzUzODYgNDUuNTE4MiAwLjM4NTc2MSA0NS41NTc2QzYuNDU4NjYgNTAuMDE3NCAxMi4zNDEzIDUyLjcyNDkgMTguMTE0NyA1NC41MTk1QzE4LjIwNzEgNTQuNTQ3NyAxOC4zMDUgNTQuNTEzOSAxOC4zNjM4IDU0LjQzNzhDMTkuNzI5NSA1Mi41NzI4IDIwLjk0NjkgNTAuNjA2MyAyMS45OTA3IDQ4LjUzODNDMjIuMDUyMyA0OC40MTcyIDIxLjk5MzUgNDguMjczNSAyMS44Njc2IDQ4LjIyNTZDMTkuOTM2NiA0Ny40OTMxIDE4LjA5NzkgNDYuNiAxNi4zMjkyIDQ1LjU4NThDMTYuMTg5MyA0NS41MDQxIDE2LjE3ODEgNDUuMzA0IDE2LjMwNjggNDUuMjA4MkMxNi42NzkgNDQuOTI5MyAxNy4wNTEzIDQ0LjYzOTEgMTcuNDA2NyA0NC4zNDYxQzE3LjQ3MSA0NC4yOTI2IDE3LjU2MDYgNDQuMjgxMyAxNy42MzYyIDQ0LjMxNTFDMjkuMjU1OCA0OS42MjAyIDQxLjgzNTQgNDkuNjIwMiA1My4zMTc5IDQ0LjMxNTFDNTMuMzkzNSA0NC4yNzg1IDUzLjQ4MzEgNDQuMjg5OCA1My41NTAyIDQ0LjM0MzNDNTMuOTA1NyA0NC42MzYzIDU0LjI3NzkgNDQuOTI5MyA1NC42NTI5IDQ1LjIwODJDNTQuNzgxNiA0NS4zMDQgNTQuNzczMiA0NS41MDQxIDU0LjYzMzMgNDUuNTg1OEM1Mi44NjQ2IDQ2LjYxOTcgNTEuMDI1OSA0Ny40OTMxIDQ5LjA5MjEgNDguMjIyOEM0OC45NjYyIDQ4LjI3MDcgNDguOTEwMiA0OC40MTcyIDQ4Ljk3MTggNDguNTM4M0M1MC4wMzggNTAuNjAzNCA1MS4yNTU0IDUyLjU2OTkgNTIuNTk1OSA1NC40MzVDNTIuNjUxOSA1NC41MTM5IDUyLjc1MjYgNTQuNTQ3NyA1Mi44NDUgNTQuNTE5NUM1OC42NDY0IDUyLjcyNDkgNjQuNTI5IDUwLjAxNzQgNzAuNjAxOSA0NS41NTc2QzcwLjY1NTEgNDUuNTE4MiA3MC42ODg3IDQ1LjQ1OSA3MC42OTQzIDQ1LjM5NDJDNzIuMTc0NyAzMC4wNzkxIDY4LjIxNDcgMTYuNzc1NyA2MC4xOTY4IDQuOTgyM0M2MC4xNzcyIDQuOTQyOSA2MC4xNDM3IDQuOTE0NyA2MC4xMDQ1IDQuODk3OFpNMjMuNzI1OSAzNy4zMjUzQzIwLjIyNzYgMzcuMzI1MyAxNy4zNDUxIDM0LjExMzYgMTcuMzQ1MSAzMC4xNjkzQzE3LjM0NTEgMjYuMjI1IDIwLjE3MTcgMjMuMDEzMyAyMy43MjU5IDIzLjAxMzNDMjcuMzA4IDIzLjAxMzMgMzAuMTYyNiAyNi4yNTMyIDMwLjEwNjYgMzAuMTY5M0MzMC4xMDY2IDM0LjExMzYgMjcuMjggMzcuMzI1MyAyMy43MjU5IDM3LjMyNTNaTTQ3LjMxNzggMzcuMzI1M0M0My44MTk2IDM3LjMyNTMgNDAuOTM3MSAzNC4xMTM2IDQwLjkzNzEgMzAuMTY5M0M0MC45MzcxIDI2LjIyNSA0My43NjM2IDIzLjAxMzMgNDcuMzE3OCAyMy4wMTMzQzUwLjkgMjMuMDEzMyA1My43NTQ1IDI2LjI1MzIgNTMuNjk4NiAzMC4xNjkzQzUzLjY5ODYgMzQuMTEzNiA1MC45IDM3LjMyNTMgNDcuMzE3OCAzNy4zMjUzWiIgZmlsbD0iIzFjMWUyMSIvPjwvc3ZnPg==") + 0 0 / contain; + content: ""; + display: inline-flex; + height: 24px; + width: 24px; + margin-right: 0.5rem; +} +html[data-theme="light"] .discord-link:hover:before { + background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI0cHgiIHdpZHRoPSIyNHB4IiB2aWV3Qm94PSIwIDAgNzEgNTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYwLjEwNDUgNC44OTc4QzU1LjU3OTIgMi44MjE0IDUwLjcyNjUgMS4yOTE2IDQ1LjY1MjcgMC40MTU0MkM0NS41NjAzIDAuMzk4NTEgNDUuNDY4IDAuNDQwNzY5IDQ1LjQyMDQgMC41MjUyODlDNDQuNzk2MyAxLjYzNTMgNDQuMTA1IDMuMDgzNCA0My42MjA5IDQuMjIxNkMzOC4xNjM3IDMuNDA0NiAzMi43MzQ1IDMuNDA0NiAyNy4zODkyIDQuMjIxNkMyNi45MDUgMy4wNTgxIDI2LjE4ODYgMS42MzUzIDI1LjU2MTcgMC41MjUyODlDMjUuNTE0MSAwLjQ0MzU4OSAyNS40MjE4IDAuNDAxMzMgMjUuMzI5NCAwLjQxNTQyQzIwLjI1ODQgMS4yODg4IDE1LjQwNTcgMi44MTg2IDEwLjg3NzYgNC44OTc4QzEwLjgzODQgNC45MTQ3IDEwLjgwNDggNC45NDI5IDEwLjc4MjUgNC45Nzk1QzEuNTc3OTUgMTguNzMwOSAtMC45NDM1NjEgMzIuMTQ0MyAwLjI5MzQwOCA0NS4zOTE0QzAuMjk5MDA1IDQ1LjQ1NjIgMC4zMzUzODYgNDUuNTE4MiAwLjM4NTc2MSA0NS41NTc2QzYuNDU4NjYgNTAuMDE3NCAxMi4zNDEzIDUyLjcyNDkgMTguMTE0NyA1NC41MTk1QzE4LjIwNzEgNTQuNTQ3NyAxOC4zMDUgNTQuNTEzOSAxOC4zNjM4IDU0LjQzNzhDMTkuNzI5NSA1Mi41NzI4IDIwLjk0NjkgNTAuNjA2MyAyMS45OTA3IDQ4LjUzODNDMjIuMDUyMyA0OC40MTcyIDIxLjk5MzUgNDguMjczNSAyMS44Njc2IDQ4LjIyNTZDMTkuOTM2NiA0Ny40OTMxIDE4LjA5NzkgNDYuNiAxNi4zMjkyIDQ1LjU4NThDMTYuMTg5MyA0NS41MDQxIDE2LjE3ODEgNDUuMzA0IDE2LjMwNjggNDUuMjA4MkMxNi42NzkgNDQuOTI5MyAxNy4wNTEzIDQ0LjYzOTEgMTcuNDA2NyA0NC4zNDYxQzE3LjQ3MSA0NC4yOTI2IDE3LjU2MDYgNDQuMjgxMyAxNy42MzYyIDQ0LjMxNTFDMjkuMjU1OCA0OS42MjAyIDQxLjgzNTQgNDkuNjIwMiA1My4zMTc5IDQ0LjMxNTFDNTMuMzkzNSA0NC4yNzg1IDUzLjQ4MzEgNDQuMjg5OCA1My41NTAyIDQ0LjM0MzNDNTMuOTA1NyA0NC42MzYzIDU0LjI3NzkgNDQuOTI5MyA1NC42NTI5IDQ1LjIwODJDNTQuNzgxNiA0NS4zMDQgNTQuNzczMiA0NS41MDQxIDU0LjYzMzMgNDUuNTg1OEM1Mi44NjQ2IDQ2LjYxOTcgNTEuMDI1OSA0Ny40OTMxIDQ5LjA5MjEgNDguMjIyOEM0OC45NjYyIDQ4LjI3MDcgNDguOTEwMiA0OC40MTcyIDQ4Ljk3MTggNDguNTM4M0M1MC4wMzggNTAuNjAzNCA1MS4yNTU0IDUyLjU2OTkgNTIuNTk1OSA1NC40MzVDNTIuNjUxOSA1NC41MTM5IDUyLjc1MjYgNTQuNTQ3NyA1Mi44NDUgNTQuNTE5NUM1OC42NDY0IDUyLjcyNDkgNjQuNTI5IDUwLjAxNzQgNzAuNjAxOSA0NS41NTc2QzcwLjY1NTEgNDUuNTE4MiA3MC42ODg3IDQ1LjQ1OSA3MC42OTQzIDQ1LjM5NDJDNzIuMTc0NyAzMC4wNzkxIDY4LjIxNDcgMTYuNzc1NyA2MC4xOTY4IDQuOTgyM0M2MC4xNzcyIDQuOTQyOSA2MC4xNDM3IDQuOTE0NyA2MC4xMDQ1IDQuODk3OFpNMjMuNzI1OSAzNy4zMjUzQzIwLjIyNzYgMzcuMzI1MyAxNy4zNDUxIDM0LjExMzYgMTcuMzQ1MSAzMC4xNjkzQzE3LjM0NTEgMjYuMjI1IDIwLjE3MTcgMjMuMDEzMyAyMy43MjU5IDIzLjAxMzNDMjcuMzA4IDIzLjAxMzMgMzAuMTYyNiAyNi4yNTMyIDMwLjEwNjYgMzAuMTY5M0MzMC4xMDY2IDM0LjExMzYgMjcuMjggMzcuMzI1MyAyMy43MjU5IDM3LjMyNTNaTTQ3LjMxNzggMzcuMzI1M0M0My44MTk2IDM3LjMyNTMgNDAuOTM3MSAzNC4xMTM2IDQwLjkzNzEgMzAuMTY5M0M0MC45MzcxIDI2LjIyNSA0My43NjM2IDIzLjAxMzMgNDcuMzE3OCAyMy4wMTMzQzUwLjkgMjMuMDEzMyA1My43NTQ1IDI2LjI1MzIgNTMuNjk4NiAzMC4xNjkzQzUzLjY5ODYgMzQuMTEzNiA1MC45IDM3LjMyNTMgNDcuMzE3OCAzNy4zMjUzWiIgZmlsbD0iI2Q3MzgzMyIvPjwvc3ZnPg==") + 0 0 / contain; } diff --git a/src/pages/index.js b/src/pages/index.js index e6b103e..057ac64 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -7,36 +7,36 @@ import styles from "./index.module.css"; import HomepageFeatures from "@site/src/components/HomepageFeatures"; function HomepageHeader() { - const { siteConfig } = useDocusaurusContext(); - return ( -
-
-

{siteConfig.title}

-

{siteConfig.tagline}

-
- - cross-seed Tutorial - 5 minutes ⏱️ - -
-
-
- ); + const { siteConfig } = useDocusaurusContext(); + return ( +
+
+

{siteConfig.title}

+

{siteConfig.tagline}

+
+ + 🌱 Get Seeding 🌱 + +
+
+
+ ); } export default function Home() { - const { siteConfig } = useDocusaurusContext(); - return ( - - -
- -
-
- ); + const { siteConfig } = useDocusaurusContext(); + return ( + + +
+ +
+
+ ); }