├── .gitignore ├── LICENSE.md ├── README.md ├── anonfiles-upload.sh ├── config ├── .gitignore ├── fansarchive.sample.js └── lolisafe.sample.js ├── fansly-stream-capture.sh ├── ffmpeg-volume-mute.php ├── gofile-folder-upload.sh ├── gofile-guest-account.sh ├── gofile-single-upload.sh ├── index.php ├── lolisafe-create-albums.js ├── lolisafe-search-albums.js ├── lolisafe-upload-folder.sh ├── megatools-copy.sh ├── megatools-mkdir.sh ├── megatools-reg.sh ├── ofans-party-dl.sh ├── onlyfans-backup-config.sh ├── onlyfans-create-profile.sh ├── onlyfans-sort-creator-clipboard-list.js ├── onlyfans-switch-config.sh ├── output └── .gitignore ├── package-lock.json ├── package.json ├── pixeldrain-upload.sh ├── replace-bbcodes.js ├── ss-minio.sh ├── twitch-founders.sh ├── upload-minio.sh └── vcs-folder.sh /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | > Copyright (c) 2020 Marcus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # marcus-scripts 2 | 3 | My personal scripts for random tools. 4 | 5 | Some of these scripts are used via CLI, others are used via a webpage (currently only one: `ffmpeg-volume-mute.php`). 6 | 7 | ## CLI scripts 8 | 9 | ### Bash / SH 10 | 11 | Most Bash scripts are meant to be helper scripts for using other binaries, such as [megatools][megatools]. 12 | 13 | ### JavaScript / node.js 14 | 15 | For `.js` scripts you'll need [node.js/npm](https://nodejs.org/) 16 | Run `npm install` in the project directory to install the required dependencies 17 | 18 | ### PHP 19 | 20 | As of right now PHP scripts don't have dependencies (besides `php` itself). 21 | If they ever do, make sure to install [Composer](https://getcomposer.org/) and run `composer install` in the project directory. 22 | If you're unsure: If there's a `composer.json` file in the project directory, get Composer and run the command. Unless `composer.json` exists at all in this project, Composer won't be necessary. 23 | 24 | ## What da script do 25 | 26 | ### ffmpeg-volume-mute.php 27 | 28 | Generates an `ffmpeg` command where you can manually mute parts of a video. 29 | This is meant to be a webpage, not a CLI script. 30 | 31 | ### lolisafe-create-albums.js 32 | 33 | Node script that creates albums based on a list (JavaScript array). 34 | Each album name is listed and when every album is created. By default, a Markdown-formatted list is also printed to the console. See `printFormat` in [config/lolisafe.sample.js](./config/lolisafe.sample.js). 35 | 36 | ### megatools-mkdir.sh 37 | 38 | Bash script for [megatools][megatools]. 39 | 40 | Creates a directory in your Mega account, based on the input directory. 41 | There's an optional prompt after creating directory to copy (upload) the local input directory to the corresponding directory on your Mega account. 42 | 43 | ### megatools-reg.sh 44 | 45 | Bash script for [megatools][megatools]. 46 | 47 | Makes it easier to register and verify a Mega account. I use this for creating burner accounts on Mega, using [Sharklasers temporary emails][sharklasers]. 48 | 49 | ### onlyfans-switch-config.sh 50 | 51 | Bash helper script to be used with [DIGITALCRIMINAL/OnlyFans][OnlyFans]. 52 | 53 | Makes it easier to toggle between "manual" and "automatic" configs. 54 | "Manual" is meant to have config options that make you pick everything: What account to log in as, what creators to rip etc. 55 | "Automatic" is supposed to be fully automatic. I essentially set my automatic configuration to log into every OF account, rip all the creators and set the `loop_timeout` to 15 minutes (so it does the process again, 15 minutes after finishing the last one). 56 | 57 | A longer description of the script is documented via comments at the top of the script itself. 58 | 59 | ### replace-bbcodes.js 60 | 61 | Node script for reading BB Code formatted forum posts from clipboard and then converting it into Discord-Markdown and inserting the new Discord-Markdown formatted post into clipboard instead. 62 | 63 | ### ss-minio.sh 64 | 65 | Bash script for uploading screenshots to my Minio server. 66 | The script itself has comments that describe requirements and such. 67 | 68 | ### upload-minio.sh 69 | 70 | Bash script similar to `ss-minio.sh`. 71 | Not sure what the difference actually is, all I know is that `ss-minio` is tied to my screenshot handling and `upload-minio` is for uploading other files via Thunar (my file manager) or the CLI. 72 | 73 | ### vcs-folder.sh 74 | 75 | Stupid simple Bash script for creating thumbnails of all video files (mp4, mov & mkv) in a folder using [vcs][vcs]. 76 | 77 | [megatools]: https://megatools.megous.com/ 78 | [OnlyFans]: https://github.com/DIGITALCRIMINAL/OnlyFans 79 | [sharklasers]: https://sharklasers.com/ 80 | [vcs]: https://p.outlyer.net/vcs -------------------------------------------------------------------------------- /anonfiles-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` - Used for uploading 5 | # - `jq` - Used for extracting the file URL from the API 6 | # - `date` - Used for logging and data filenames in `output` 7 | # 8 | # For Debian/Ubuntu: `apt install curl jq` (as root/sudo) 9 | 10 | usage() 11 | { 12 | cat << EOF 13 | usage: $0 [FileName] 14 | 15 | Uploads files to Anonfiles 16 | 17 | OPTIONS: 18 | -h Show this message 19 | -s Short format output: "\$FILENAME => \$URL". Short format will still display errors. 20 | -b Short format only: Wrap URL and filename into "list-format" BBCode, you know... for sharing. 21 | Example: \`[*][URL='https://anonfiles.com/filename-blah.mp4']filename-blah.mp4[/URL]\` 22 | EOF 23 | } 24 | 25 | # Print usage when no parameters specified. 26 | if [[ -z "$@" ]]; then 27 | usage 28 | exit 0 29 | fi 30 | 31 | SHORT_FORMAT=0; 32 | BBCODE_FORMAT=0; 33 | 34 | while getopts "hsb" opt; do 35 | case $opt in 36 | h) 37 | usage 38 | exit 0 39 | ;; 40 | s) 41 | SHORT_FORMAT=1; 42 | ;; 43 | b) 44 | BBCODE_FORMAT=1; 45 | ;; 46 | \?) 47 | echo "Invalid option: -$OPTARG" >&2 48 | exit 1 49 | ;; 50 | :) 51 | echo "Option -$OPTARG requires an argument." >&2 52 | exit 1 53 | ;; 54 | esac 55 | done 56 | 57 | SCRIPT_DIR="$(dirname "$(readlink "$0")")"; 58 | 59 | shift $((OPTIND - 1)); 60 | 61 | DATE="$(date +"%Y%m%d_%H%I%S")"; 62 | RESPONSE_FILE="${SCRIPT_DIR}/output/${DATE}_anonfiles.json"; 63 | 64 | FILE_NAME="$@"; 65 | 66 | if [[ $SHORT_FORMAT == 0 ]]; then 67 | echo "Uploading file: ${FILE_NAME}"; 68 | fi 69 | 70 | if [[ $SHORT_FORMAT == 1 ]]; then 71 | curl -s -X POST -F "file=@\"${FILE_NAME}\"" "https://api.anonfiles.com/upload" -o "${RESPONSE_FILE}"; 72 | else 73 | curl --progress-bar -X POST -F "file=@\"${FILE_NAME}\"" "https://api.anonfiles.com/upload" -o "${RESPONSE_FILE}" | tee; 74 | fi 75 | 76 | if [[ $SHORT_FORMAT == 0 ]]; then 77 | echo "Saving Anonfiles API response for ${FILE_NAME} to: ${RESPONSE_FILE}"; 78 | fi 79 | 80 | RESPONSE="$(cat "${RESPONSE_FILE}")"; 81 | STATUS="$(jq -r '.status' <<< "${RESPONSE}")"; 82 | 83 | # Ignore short format on errors 84 | if [[ $STATUS == "false" ]]; then 85 | echo "File upload failed"; 86 | jq -r '.error' <<< "${RESPONSE}"; 87 | exit 1; 88 | fi 89 | 90 | RESPONSE="$(cat "${RESPONSE_FILE}")"; 91 | FILESIZE="$(jq -r '.data.file.metadata.size.readable' <<< "${RESPONSE}")"; 92 | FILE_URL="$(jq -r '.data.file.url.full' <<< "${RESPONSE}")"; 93 | 94 | if [[ $SHORT_FORMAT == 1 ]]; then 95 | if [[ $BBCODE_FORMAT == 1 ]]; then 96 | echo "[*][URL='${FILE_URL}']${FILE_NAME}[/URL]"; 97 | else 98 | echo "${FILE_NAME} => ${FILE_URL}"; 99 | fi 100 | 101 | exit 0; 102 | fi 103 | 104 | echo "File uploaded (${FILESIZE}): ${FILE_URL}"; -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.sample.* 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /config/fansarchive.sample.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | token: '', 3 | }; -------------------------------------------------------------------------------- /config/lolisafe.sample.js: -------------------------------------------------------------------------------- 1 | /** 2 | * !! COPY/RENAME THIS FILE 3 | * 4 | * Make sure to copy and rename this file to `lolisafe.js` 5 | * Or else the `lolisafe-create-albums.js` script will fail. 6 | */ 7 | module.exports = { 8 | /** 9 | * Token used for API requests 10 | */ 11 | TOKEN: process.env.CD_TOKEN, 12 | 13 | /** 14 | * Prefix of every album name 15 | */ 16 | prefix: '[Prefix] CreatorName - ', 17 | 18 | /** 19 | * List of album names (added after prefix) 20 | */ 21 | albums: [], 22 | 23 | /** 24 | * Base URL for API/album links 25 | * Technically this should be compatible with any other hosted versions 26 | * of https://github.com/BobbyWibowo/lolisafe 27 | */ 28 | baseUrl: 'https://cyberdrop.me', 29 | 30 | /** 31 | * Print out each created album with the following format. 32 | * Available formats: 33 | * - {name} (Album name) 34 | * - {base_url} 35 | * - {slug} 36 | * - {id} 37 | * - {files} (number of files) 38 | */ 39 | printFormat: '- [{name}]({base_url}/a/{slug})', 40 | 41 | /** 42 | * Similar to `printFormat`, except for the search albums script. 43 | * 44 | * `searchCopyFormat` is the format used for copying a list of albums to the clipboard. 45 | * `searchPrintFormat` is the format used for printing the list to console. 46 | */ 47 | searchCopyFormat: '{base_url}/a/{slug}', 48 | searchPrintFormat: '- {name}: {base_url}/a/{slug}', 49 | }; -------------------------------------------------------------------------------- /fansly-stream-capture.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Things to install if you don't already have it: 4 | # - curl 5 | # - ffmpeg 6 | # - jq 7 | 8 | # All three values below can also be set as environment variables, or before you call the script. 9 | # Example: `FANSLY_TOKEN="your-token-here" USER_AGENT="your-user-agent-here" ./fansly-stream-capture.sh Kati3kat` 10 | 11 | # You can get the token by opening the browser console and pasting this: `copy(JSON.parse(localStorage.session_active_session).token);` 12 | # It will copy your Fansly token to the clipboard. 13 | # Note that you need to be logged into an account. Some Fansly creators may also restrict streams for certain subscription tiers, followers etc. 14 | # FANSLY_TOKEN="" 15 | 16 | # You can either just Google for "what is my user agent", or use this in the browser console: `copy(navigator.userAgent);` 17 | # USER_AGENT="" 18 | 19 | # The initial path where livestreams will be stored to 20 | # The resulting path will be something like: $BASE_PATH/$USERNAME/$DATE_$ACCOUNTID_$STREAMID_$TIMESTAMP.ts 21 | # If not set, it will default to the current working directory. 22 | # BASE_PATH="/home/marcus/media/fansly-livestreams" 23 | 24 | ################################################################################# 25 | ################################################################################# 26 | ## Unless you know what you are doing, don't change anything below this block. ## 27 | ################################################################################# 28 | ################################################################################# 29 | 30 | if [[ -z "$FANSLY_TOKEN" ]]; then 31 | echo "Missing Fansly token (\`\$FANSLY_TOKEN\`)"; 32 | exit 1; 33 | fi 34 | 35 | if [[ -z "$USER_AGENT" ]]; then 36 | echo "Missing user agent (\`\$USER_AGENT\`)"; 37 | exit 1; 38 | fi 39 | 40 | if [[ -z "$BASE_PATH" ]]; then 41 | echo "Base path not set. Defaulting to $(pwd)"; 42 | BASE_PATH="$(pwd)"; 43 | fi 44 | 45 | function request() 46 | { 47 | curl -H "User-Agent: ${USER_AGENT}" -H "Referer: https://fansly.com/" -H "Authorization: ${FANSLY_TOKEN}" -fsSL "${1}" 48 | } 49 | 50 | if [[ -z "$1" ]]; then 51 | echo "Missing username (first parameter)"; 52 | exit 1; 53 | fi 54 | 55 | # The username of the stream you're trying to capture. `${1}` means it gets defined when you run the script. 56 | # Example: `bash fansly-stream-capture.sh badbitch69420xd` 57 | # Would result in: `USERNAME="badbitch69420xd"` 58 | USERNAME="${1}"; 59 | ACCOUNT_ID="$(request "https://apiv3.fansly.com/api/v1/account?usernames=${USERNAME}" | jq -r .response[0].id)"; 60 | STREAM="$(request "https://apiv3.fansly.com/api/v1/streaming/channel/${ACCOUNT_ID}")"; 61 | 62 | if [[ $? -ne 0 ]]; then 63 | echo "Error occurred getting data from Fansly API. Make sure your token/user agent is valid"; 64 | exit 1; 65 | fi 66 | 67 | PLAYBACK_URL="$(echo "${STREAM}" | jq -r .response.stream.playbackUrl)"; 68 | 69 | # Stream not live. 70 | NOW="$(date "+%Y-%m-%d %H:%M:%S")"; 71 | if [[ "${PLAYBACK_URL}" == "null" ]]; then 72 | echo "[${NOW}] ${USERNAME} is not live."; 73 | exit 0; 74 | fi 75 | 76 | STREAM_ID="$(echo "${STREAM}" | jq -r .response.id)"; 77 | STREAM_START="$(echo "${STREAM}" | jq -r .response.stream.startedAt)"; 78 | STREAM_START=$(( STREAM_START / 1000 )); 79 | 80 | STREAM_DATE="$(date -d "@${STREAM_START}" "+%Y-%m-%d")"; 81 | NOW="$(date "+%s")"; 82 | 83 | mkdir -p "${BASE_PATH}/${USERNAME}"; 84 | # Append `-report` to this command if you want debug logs 85 | ffmpeg -v warning -hide_banner -stats -user_agent "${USER_AGENT}" -http_persistent 0 -i "${PLAYBACK_URL}" -c copy "${BASE_PATH}/${USERNAME}/${STREAM_DATE}_${ACCOUNT_ID}_${STREAM_ID}_${NOW}.ts" 86 | 87 | # In some cases I've noticed that ffmpeg will just detect the stream as offline (playlist ended) for reasons. 88 | # ffmpeg will quit with a successfuly exit code (0), so in those case, I simply trigger the script again. 89 | # When the stream _actually_ goes offline, the script will stop triggering itself as ffmpeg will exit with a non-zero code. 90 | if [[ $? -eq 0 ]]; then 91 | $0 "${USERNAME}"; 92 | fi -------------------------------------------------------------------------------- /ffmpeg-volume-mute.php: -------------------------------------------------------------------------------- 1 | $start, 'end' => $end]; 78 | } 79 | 80 | usort($convertedTimestamps, function($a, $b) { 81 | return $a['start'] - $b['start']; 82 | }); 83 | 84 | return generateCommand($filename, $convertedTimestamps, $output); 85 | } 86 | 87 | $command = null; 88 | if (isset($_POST['filename'], $_POST['timestamps'], $_POST['output'])) 89 | { 90 | $filename = $_POST['filename']; 91 | $timestamps = $_POST['timestamps']; 92 | $output = $_POST['output']; 93 | 94 | $command = convertValues($filename, $timestamps, $output); 95 | } 96 | ?> 97 | 98 | 99 | 100 | 101 | ffmpeg command for muting audio 102 | 103 | 104 | 105 | 106 |
107 |
108 |

ffmpeg command for muting audio:

109 |
110 | A bit of a 'niche' page I guess. I needed something to generate an ffmpeg command that did the following: 111 |
112 |
113 | Took a list of timestamps (HH:MM:SS) and converted each timestamp into seconds 114 | Placed the converted seconds into the proper fields for this audiofilter parameter: "volume=enable='between(t,5,10)':volume=0" - This would mute from 00:00:05 to 00:00:10. 115 | Generated a full ffmpeg command with filename and the correct video/audio codec. 116 | Video codec should be copied, audio codec can't be copied since we're applying audiofilters. For this I use aac. 117 |
118 |
119 |
120 | 121 |
122 | 123 | 126 |
127 | Your converted command:
128 | 129 |
130 | 133 | 134 |
135 | 136 |
137 |

Input your details:

138 |
139 |
140 |
141 | 142 | 143 | The input filename 144 |
145 | 146 |
147 |
148 | 149 | 156 |
157 |
158 | 159 |
160 | 161 | 162 | The filename of the output file. By default it's just changed to include MutedAudio before .mp4. If you're not using mp4, then this is practically required. 163 |
164 | 165 | 166 |
167 |
168 |
169 |
170 | 171 | -------------------------------------------------------------------------------- /gofile-folder-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` 5 | # - `findutils` - Typically pre-installed on Debian/Ubuntu 6 | # - `jq` 7 | # 8 | # For Debian/Ubuntu: `apt install curl jq findutils` (as root/sudo) 9 | # 10 | # REQUIRED: 11 | # - Set the `GOFILE_ACCESS_TOKEN` environment variable, or specify \`-g\` for guest account 12 | # - Uses the `gofile-single-upload.sh` script in this repository. It's easiest if you just clone it. Alternatively, override the `$GOFILE_UPLOAD` variable below. 13 | # 14 | # If you clone the repository, you can still use symlinks to shortcut `gofile-folder-upload.sh` to for instance "gofile-folder". 15 | # A symlink will still resolve to the "marcus-scripts" directory and thus the "gofile-single-upload" script can easily be picked up. 16 | # 17 | # OPTIONAL: 18 | # - Set the `GOFILE_PARENT_FOLDER` environment variable as the default parent ID. `-p` can still be used to override the environment variable. 19 | 20 | SCRIPT_DIR="$( cd "$( dirname $( realpath "${BASH_SOURCE}" ) )" &> /dev/null && pwd )"; 21 | GOFILE_UPLOAD="${SCRIPT_DIR}/gofile-single-upload.sh"; 22 | GOFILE_GUEST_ACCOUNT="${SCRIPT_DIR}/gofile-guest-account.sh"; 23 | GOFILE_ZONE=""; 24 | 25 | usage() 26 | { 27 | cat << EOF 28 | usage: $0 [FolderName] 29 | 30 | Creates a folder based on the input folder names. 31 | Uploads files from input folder to the created Gofile folder. 32 | 33 | OPTIONS: 34 | -p Parent folder ID. 35 | 36 | -g Use guest account. Environment variable \`GOFILE_PARENT_FOLDER\` will be ignored if -g is specified. 37 | Requires the \`gofile-guest-account.sh\` script. 38 | 39 | -z What zone (geographical region) the Gofile server should reside in for upload. 40 | At the time of writing, valid values are \`eu\` (Europe) or \`na\` (North America). Check the Gofile API documentation updated options: https://gofile.io/api 41 | If not specified, all zones will be considered. 42 | If an invalid zone is specified, the Gofile API will return servers from all zones. 43 | EOF 44 | } 45 | 46 | newGofileServer() { 47 | # The sorting of the `.data.servers` array doesn't seem consistent, so picking the first server in the response is *probably* fine. 48 | # Not sure if it's sorted by load or just random. 49 | GOFILE_SERVER="$(curl -fsSL "https://api.gofile.io/servers?zone=${GOFILE_ZONE}" | jq -r '.data.servers[0].name')"; 50 | } 51 | 52 | # Print usage when no parameters specified. 53 | if [[ -z "$@" ]]; then 54 | usage 55 | exit 0 56 | fi 57 | 58 | PARENT_FOLDER="${GOFILE_PARENT_FOLDER}"; 59 | 60 | while getopts "hp:gz:" opt; do 61 | case $opt in 62 | h) 63 | usage 64 | exit 0 65 | ;; 66 | p) 67 | if [[ ! -z "${PARENT_FOLDER}" ]]; then 68 | echo "Overriding parent folder ID from environment variable - Previous parent folder ID: ${GOFILE_PARENT_FOLDER}"; 69 | fi 70 | 71 | PARENT_FOLDER="${OPTARG}"; 72 | echo "Parent folder ID specified: ${PARENT_FOLDER}"; 73 | ;; 74 | g) 75 | GOFILE_ACCESS_TOKEN="$(eval "${GOFILE_GUEST_ACCOUNT}")"; 76 | # Parent folder will be automatically retrieved using guest account's token. 77 | PARENT_FOLDER=""; 78 | 79 | echo "Using guest account - Token: ${GOFILE_ACCESS_TOKEN}"; 80 | ;; 81 | z) 82 | GOFILE_ZONE="${OPTARG}"; 83 | echo "Forcing Gofile zone: ${GOFILE_ZONE}"; 84 | ;; 85 | \?) 86 | echo "Invalid option: -$OPTARG" >&2 87 | exit 1 88 | ;; 89 | :) 90 | echo "Option -$OPTARG requires an argument." >&2 91 | exit 1 92 | ;; 93 | esac 94 | done 95 | 96 | shift $((OPTIND - 1)); 97 | 98 | if [[ -z "${GOFILE_ACCESS_TOKEN}" ]]; then 99 | echo "Make sure to set the \`GOFILE_ACCESS_TOKEN\` environment variable."; 100 | exit 1; 101 | fi 102 | 103 | 104 | # No parent folder ID specified, so we use the account's root folder. 105 | if [[ -z "${PARENT_FOLDER}" ]]; then 106 | # I really don't understand why this is even an endpoint that's necessary, 107 | # but without knowing the account ID beforehand, we can't get account details... for whatever reason. 108 | # In the past, Gofile simply gave us the account details based on the API token. 109 | ACCOUNT_ID="$(curl -fsSL "https://api.gofile.io/accounts/getid?token=${GOFILE_ACCESS_TOKEN}" | jq -r '.data.id')"; 110 | 111 | PARENT_FOLDER="$(curl -fsSL "https://api.gofile.io/accounts/${ACCOUNT_ID}?token=${GOFILE_ACCESS_TOKEN}" | jq -r '.data.rootFolder')"; 112 | echo "Parent folder set to root folder: ${PARENT_FOLDER}"; 113 | fi 114 | 115 | UPLOAD_FOLDER="$(realpath "$@")"; 116 | if [[ ! -d "${UPLOAD_FOLDER}" ]]; then 117 | echo "Folder does not exist: ${UPLOAD_FOLDER}"; 118 | exit 1; 119 | fi 120 | 121 | FOLDER_NAME="$(basename -- "${UPLOAD_FOLDER}")"; 122 | CREATED_FOLDER="$(curl -s -X POST "https://api.gofile.io/contents/createFolder" --data-raw "parentFolderId=${PARENT_FOLDER}&token=${GOFILE_ACCESS_TOKEN}&folderName=${FOLDER_NAME}")"; 123 | 124 | STATUS="$(echo "${CREATED_FOLDER}" | jq -r .status)"; 125 | 126 | echo -e "Created folder with status: ${STATUS}\n"; 127 | 128 | if [[ "${STATUS}" != "ok" ]]; then 129 | echo "An error occurred creating Gofile folder: ${FOLDER_NAME}"; 130 | jq '.' <<< $CREATED_FOLDER; 131 | exit 1; 132 | fi 133 | 134 | FOLDER_ID="$(echo "${CREATED_FOLDER}" | jq -r .data.id)"; 135 | FOLDER_CODE="$(echo "${CREATED_FOLDER}" | jq -r .data.code)"; 136 | 137 | # Set folder to public 138 | FOLDER_UPDATE="$(curl -s -X PUT "https://api.gofile.io/contents/${FOLDER_ID}/setOption" --data-raw "token=${GOFILE_ACCESS_TOKEN}&attribute=public&attributeValue=true")"; 139 | 140 | echo "Set Gofile folder ID ${FOLDER_ID} to be public"; 141 | 142 | # Navigate to directory. 143 | OLD_DIR="$(pwd)"; 144 | cd "${UPLOAD_FOLDER}"; 145 | FILES="$(find "." -maxdepth 1 -type f)"; 146 | 147 | OIFS="$IFS"; 148 | IFS=$'\n'; 149 | INCREMENT=0; 150 | 151 | newGofileServer; 152 | 153 | LAST_UPLOAD=$(date +%s); 154 | for file in $FILES; 155 | do 156 | file="$(basename -- "$file")"; 157 | $GOFILE_UPLOAD -f "${FOLDER_ID}" -s "${GOFILE_SERVER}" "${file}"; 158 | INCREMENT=$((INCREMENT+1)); 159 | NOW=$(date +%s); 160 | 161 | if [[ $LAST_UPLOAD -ne 0 ]]; then 162 | LAST_UPLOAD_DIFF=$((NOW-LAST_UPLOAD)); 163 | 164 | # Check if previous upload was over a minute ago 165 | # If it is, we assume that the file that was *just* uploaded is a big file 166 | # So let's be nice and fetch a new Gofile server to "load balance" 167 | # Probably doesn't matter much at Gofile's scale though 168 | if [[ $LAST_UPLOAD_DIFF -gt 60 ]]; then 169 | INCREMENT=0; 170 | newGofileServer; 171 | fi 172 | fi 173 | 174 | LAST_UPLOAD=$NOW; 175 | 176 | # To avoid too many rate limits when uploading a lot of files, we get a new Gofile server every 6 files. 177 | # Usually it's not necessary to get a new server, but let's help Gofile balance files out :) 178 | if [[ $INCREMENT -ge 6 ]]; then 179 | INCREMENT=0; 180 | newGofileServer; 181 | fi 182 | done 183 | 184 | IFS="$OIFS"; 185 | cd "${OLD_DIR}"; 186 | 187 | echo -e "\nFiles in ${FOLDER_NAME} uploaded to the following URL: https://gofile.io/d/${FOLDER_CODE}"; -------------------------------------------------------------------------------- /gofile-guest-account.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` 5 | # - `jq` 6 | # 7 | # For Debian/Ubuntu: `apt install curl jq` (as root/sudo) 8 | 9 | VERBOSE=0; 10 | USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"; 11 | 12 | usage() 13 | { 14 | cat << EOF 15 | usage: $0 [options] 16 | 17 | Creates a temporary Gofile guest account. Useful for "fire and forget" uploads. 18 | By default only echoes out the Gofile account token that can be used for API requests and uploads. 19 | 20 | Saves JSON into the \`output\` directory in the script folder. 21 | 22 | OPTIONS: 23 | -h Print help/usage information 24 | -v Non-silent cURL requests and JSON dumps (verbose) 25 | -u Use a custom user agent - Default: ${USER_AGENT} 26 | EOF 27 | } 28 | 29 | while getopts "hvu:" opt; do 30 | case $opt in 31 | h) 32 | usage 33 | exit 0 34 | ;; 35 | v) 36 | VERBOSE=1; 37 | ;; 38 | u) 39 | USER_AGENT="${OPTARG}"; 40 | ;; 41 | \?) 42 | echo "Invalid option: -$OPTARG" >&2 43 | exit 1 44 | ;; 45 | :) 46 | echo "Option -$OPTARG requires an argument." >&2 47 | exit 1 48 | ;; 49 | esac 50 | done 51 | 52 | shift $((OPTIND - 1)); 53 | 54 | CURL_CMD="curl -fsSL"; 55 | 56 | if [[ $VERBOSE == 1 ]]; then 57 | CURL_CMD="curl -fSL"; 58 | fi 59 | 60 | CURL_CMD="${CURL_CMD} -H 'User-Agent: ${USER_AGENT}'"; 61 | CREATE_ACCOUNT=$(eval "${CURL_CMD} -X POST https://api.gofile.io/accounts"); 62 | 63 | if [[ $VERBOSE == 1 ]]; then 64 | jq . <<< "${CREATE_ACCOUNT}"; 65 | fi 66 | 67 | STATUS="$(jq -r .status <<< "${CREATE_ACCOUNT}")"; 68 | 69 | # If unsuccessful, dump the whole JSON and exit with non-zero. 70 | if [[ "${STATUS}" != "ok" ]]; then 71 | jq . <<< "${CREATE_ACCOUNT}"; 72 | exit 1; 73 | fi 74 | 75 | TOKEN="$(jq -r .data.token <<< "${CREATE_ACCOUNT}")"; 76 | 77 | # Add trailing newline for verbose output 78 | if [[ $VERBOSE == 1 ]]; then 79 | echo $TOKEN; 80 | exit 0; 81 | fi 82 | 83 | echo -n $TOKEN; -------------------------------------------------------------------------------- /gofile-single-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` 5 | # - `jq` 6 | # 7 | # For Debian/Ubuntu: `apt install curl jq` (as root/sudo) 8 | # 9 | # REQUIRED: 10 | # Set the `GOFILE_ACCESS_TOKEN` environment variable. 11 | 12 | usage() 13 | { 14 | cat << EOF 15 | usage: $0 [FileName] 16 | 17 | Uploads files to Gofile 18 | 19 | OPTIONS: 20 | -f Folder ID. Used for uploading files to an existing upload/folder. 21 | -s What server to use (e.g. 'srv-store8'). Used in the format of https://srv-store8.gofile.io 22 | -h Show this message 23 | EOF 24 | } 25 | 26 | # Print usage when no parameters specified. 27 | if [[ -z "$@" ]]; then 28 | usage 29 | exit 0 30 | fi 31 | 32 | GOFILE_SERVER=""; 33 | FOLDER_ID="" 34 | 35 | while getopts "hs:f:" opt; do 36 | case $opt in 37 | h) 38 | usage 39 | exit 0 40 | ;; 41 | s) 42 | GOFILE_SERVER="${OPTARG}"; 43 | echo "Gofile server specified: ${GOFILE_SERVER}"; 44 | ;; 45 | f) 46 | FOLDER_ID="${OPTARG}"; 47 | echo "Folder ID specified: ${FOLDER_ID}"; 48 | ;; 49 | \?) 50 | echo "Invalid option: -$OPTARG" >&2 51 | exit 1 52 | ;; 53 | :) 54 | echo "Option -$OPTARG requires an argument." >&2 55 | exit 1 56 | ;; 57 | esac 58 | done 59 | 60 | if [[ -z "${GOFILE_ACCESS_TOKEN}" ]]; then 61 | echo "Make sure to set the \`GOFILE_ACCESS_TOKEN\` environment variable."; 62 | exit 1; 63 | fi 64 | 65 | if [[ -z "${GOFILE_SERVER}" ]]; then 66 | echo "No Gofile server specified. Requesting new one"; 67 | GOFILE_SERVER_DETAILS="$(curl -fsSL "https://api.gofile.io/servers?zone=${GOFILE_ZONE}" | jq -r '.data.servers[0]')"; 68 | GOFILE_SERVER="$(echo "${GOFILE_SERVER_DETAILS}" | jq -r .name)"; 69 | GOFILE_ZONE="$(echo "${GOFILE_SERVER_DETAILS}" | jq -r .zone)"; 70 | 71 | if [[ -z "${GOFILE_SERVER}" || "${GOFILE_SERVER}" == "null" ]]; then 72 | echo "Unable to get Gofile server from API. Exiting."; 73 | exit 1; 74 | fi 75 | 76 | echo "Gofile server set to: ${GOFILE_SERVER} | Zone: ${GOFILE_ZONE}"; 77 | fi 78 | 79 | # If no folder ID is specified, we either: 80 | # - Use the root folder 81 | # - Prompt the user to create one. 82 | # 83 | # At the moment it will just default to use the root folder. 84 | # 85 | # Eventually I'll add a flag or something to create a folder, 86 | # which allows for specifying a folder name, of course. 87 | if [[ -z "${FOLDER_ID}" ]]; then 88 | ACCOUNT_ID="$(curl -fsSL "https://api.gofile.io/accounts/getid?token=${GOFILE_ACCESS_TOKEN}" | jq -r '.data.id')"; 89 | ROOT_FOLDER_ID="$(curl -fsSL "https://api.gofile.io/accounts/${ACCOUNT_ID}?token=${GOFILE_ACCESS_TOKEN}" | jq -r '.data.rootFolder')"; 90 | 91 | FOLDER_ID="${ROOT_FOLDER_ID}"; 92 | fi 93 | 94 | shift $((OPTIND - 1)); 95 | 96 | FILE_NAME="$@"; 97 | curl --progress-bar -X POST -F folderId="${FOLDER_ID}" -F token="${GOFILE_ACCESS_TOKEN}" -F "file=@\"${FILE_NAME}\"" "https://${GOFILE_SERVER}.gofile.io/contents/uploadfile" | tee; 98 | 99 | # Just to add spacing 100 | echo; -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Marcus - Scripts 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Scripts:

13 |
14 | Welcome to my page dedicated for providing basic scripts to do stuff. 15 | 18 |
19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /lolisafe-create-albums.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const config = require('./config/lolisafe'); 3 | const signale = require('signale'); 4 | 5 | const token = config.TOKEN; 6 | 7 | /** 8 | * Configure signale logger with custom settings 9 | */ 10 | signale.config({ 11 | displayDate: true, 12 | displayTimestamp: true, 13 | }); 14 | 15 | (async() => { 16 | const client = new axios.create({ 17 | baseURL: config.baseUrl, 18 | responseType: 'json', 19 | headers: { 20 | token, 21 | }, 22 | }); 23 | 24 | const body = { 25 | name: '', 26 | description: '', 27 | download: true, 28 | public: true, 29 | }; 30 | 31 | config.albums.sort() 32 | const albumNames = config.albums; 33 | 34 | const albumIds = []; 35 | for (const album of albumNames) 36 | { 37 | body.name = config.prefix + album; 38 | const response = await client.post('/api/albums', body); 39 | const data = response.data; 40 | 41 | if (data.success) 42 | { 43 | signale.info(`Successfully created album with name: ${body.name} - ID: ${data.id}`); 44 | albumIds.push(data.id); 45 | continue; 46 | } 47 | 48 | signale.error(data); 49 | } 50 | 51 | signale.info('Created the following albums:'); 52 | signale.info(albumNames); 53 | 54 | const getAlbums = await client.get('/api/albums'); 55 | const data = getAlbums.data; 56 | 57 | if (!data.success) 58 | { 59 | signale.error('Unable to retrieve albums'); 60 | signale.error(data); 61 | return; 62 | } 63 | 64 | /** 65 | * Only list those albums that we just created. 66 | */ 67 | const albums = data.albums.filter((album) => { 68 | return albumIds.includes(album.id); 69 | }); 70 | 71 | /** 72 | * For reference, each album object looks like: 73 | * { 74 | * "id": 1, 75 | * "name": "Album name", 76 | * "timestamp": 0, 77 | * "identifier": "ABCDEF12", 78 | * "editedAt": 123, 79 | * "download": true, 80 | * "public": true, 81 | * "description": "Description", 82 | * "files": 1337 83 | * } 84 | */ 85 | const format = config.printFormat; 86 | const printList = []; 87 | for (const album of albums) 88 | { 89 | const {name, id, description, files} = album; 90 | const slug = album.identifier; 91 | const templates = { 92 | base_url: config.baseUrl, 93 | name, 94 | id, 95 | description, 96 | files, 97 | slug, 98 | }; 99 | 100 | let albumFormat = format; 101 | for (const key in templates) 102 | { 103 | const reg = new RegExp(`{${key}}`, 'g'); 104 | albumFormat = albumFormat.replace(reg, templates[key]); 105 | } 106 | 107 | printList.push(albumFormat); 108 | } 109 | 110 | signale.info(`Formatted print list:\n${printList.join('\n')}`); 111 | })(); -------------------------------------------------------------------------------- /lolisafe-search-albums.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const axios = require('axios'); 3 | const config = require('./config/lolisafe'); 4 | const meow = require('meow'); 5 | const clipboardy = require('clipboardy'); 6 | 7 | const token = config.TOKEN; 8 | 9 | 10 | const cli = meow(` 11 | Usage 12 | $ lolisafe-search-albums 13 | 14 | Options 15 | --sensitive, -s Do a case sensitive search instead 16 | 17 | Examples 18 | - Only searches album names including "MEMES HERE" in all caps 19 | $ lolisafe-search-albums MEMES HERE -s 20 | `, { 21 | flags: { 22 | sensitive: { 23 | type: 'boolean', 24 | alias: 's' 25 | } 26 | } 27 | }); 28 | 29 | 30 | (async() => { 31 | let search = cli.input.join(' ').trim(); 32 | if (search.length === 0) { 33 | console.error('No search text provided'); 34 | return; 35 | } 36 | 37 | const sensitive = cli.flags.sensitive; 38 | if (!sensitive) { 39 | search = search.toLowerCase(); 40 | } 41 | else { 42 | console.info('--sensitive flag provided - doing case sensitive search'); 43 | } 44 | 45 | const client = new axios.create({ 46 | baseURL: config.baseUrl, 47 | responseType: 'json', 48 | headers: { 49 | token, 50 | }, 51 | }); 52 | 53 | const response = await client.get('/api/albums'); 54 | const data = response.data; 55 | let albums = data.albums.filter((album) => { 56 | let albumName = album.name; 57 | 58 | if (!sensitive) { 59 | albumName = albumName.toLowerCase(); 60 | } 61 | 62 | return albumName.includes(search); 63 | }); 64 | 65 | albums = albums.sort((a, b) => { 66 | if (a.name < b.name) { 67 | return -1; 68 | } 69 | 70 | if (b.name < a.name) { 71 | return 1; 72 | } 73 | 74 | return 0; 75 | }) 76 | 77 | /** 78 | * Yoinked from the create albums script. 79 | */ 80 | const printFormat = config.searchPrintFormat || config.printFormat; 81 | const copyFormat = config.searchCopyFormat; 82 | const printList = []; 83 | const copyList = []; 84 | for (const album of albums) 85 | { 86 | const {name, id, description, files} = album; 87 | const slug = album.identifier; 88 | const templates = { 89 | base_url: data.baseUrl || config.baseUrl, 90 | name, 91 | id, 92 | description, 93 | files, 94 | slug, 95 | }; 96 | 97 | let albumPrintFormat = printFormat; 98 | let albumCopyFormat = copyFormat; 99 | for (const key in templates) 100 | { 101 | const reg = new RegExp(`{${key}}`, 'g'); 102 | albumPrintFormat = albumPrintFormat.replace(reg, templates[key]); 103 | albumCopyFormat = albumCopyFormat.replace(reg, templates[key]); 104 | } 105 | 106 | printList.push(albumPrintFormat); 107 | copyList.push(albumCopyFormat); 108 | } 109 | 110 | const print = printList.join('\n'); 111 | console.log(print); 112 | 113 | const copyText = copyList.join('\n'); 114 | await clipboardy.write(copyText); 115 | console.log('Written to clipboard.'); 116 | })(); -------------------------------------------------------------------------------- /lolisafe-upload-folder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` 5 | # - `jq` 6 | 7 | # Base URL for Lolisafe instance. 8 | # Resulting URLs will be something like: ${BASE_URL}/api/albums 9 | # For example: https://cyberdrop.me/api/albums 10 | # BASE_URL="https://cyberdrop.me"; 11 | # Alternatively use an environment variable (like I do). 12 | BASE_URL="${CYBERDROP_URL}"; 13 | UPLOAD_FOLDER="$(realpath "$@")"; 14 | 15 | if [[ ! -d "${UPLOAD_FOLDER}" ]]; then 16 | echo "Folder does not exist: ${UPLOAD_FOLDER}"; 17 | exit 1; 18 | fi 19 | 20 | # API token used for creating album and whatever. 21 | # The token can be retrieved from the "Dashboard" page and then "Manage your token" 22 | # The default setting expects the token to be set in the environment variable: `CYBERDROP_TOKEN`. 23 | # Change this as you see fit. 24 | TOKEN="${CYBERDROP_TOKEN}"; 25 | ALBUM_NAME="$(basename -- "${UPLOAD_FOLDER}")"; 26 | 27 | ALBUM_JSON='{"name": "", "description": "", "public": true, "download": true}'; 28 | ALBUM_JSON="$(echo "${ALBUM_JSON}" | jq --arg name "${ALBUM_NAME}" '.name = $name')"; 29 | 30 | echo "Creating album: ${ALBUM_NAME}"; 31 | 32 | ALBUMS_URL="${BASE_URL}/api/albums"; 33 | CREATE_ALBUM="$(curl -fsSL -X POST -H "Content-Type: application/json" -H "token: ${TOKEN}" --data "${ALBUM_JSON}" "${ALBUMS_URL}")"; 34 | 35 | ALBUM_ID="$(echo "${CREATE_ALBUM}" | jq -r '.id')"; 36 | 37 | if [[ "${ALBUM_ID}" == "null" ]]; then 38 | echo "Error occurred creating album:"; 39 | echo "${CREATE_ALBUM}" | jq; 40 | exit 1; 41 | fi 42 | 43 | # Navigate to directory. 44 | OLD_DIR="$(pwd)"; 45 | cd "${UPLOAD_FOLDER}"; 46 | 47 | FILES="$(find . -maxdepth 1 -type f)"; 48 | UPLOAD_FILE_URL="${BASE_URL}/api/upload"; 49 | 50 | OIFS="$IFS"; 51 | IFS=$'\n'; 52 | for file in $FILES; 53 | do 54 | file="$(basename -- "$file")"; 55 | POST_FILE="$(curl -fsSL -H "token: ${TOKEN}" -H "albumid: ${ALBUM_ID}" -F files[]=@\"${file}\" "${UPLOAD_FILE_URL}")" 56 | STATUS="$(echo "${POST_FILE}" | jq '.success')"; 57 | 58 | if [[ "${STATUS}" == "false" ]]; then 59 | echo "Unable to upload file: ${file}"; 60 | echo "${POST_FILE}" | jq; 61 | continue; 62 | fi 63 | 64 | FINAL_URL="$(echo "${POST_FILE}" | jq -r '.files[].url')"; 65 | echo "File ${file} upload to URL: ${FINAL_URL}"; 66 | done 67 | 68 | # Check if there's a ZIP file named the same as the folder name 69 | # If so, attempt to upload this after uploading all the other files. 70 | # TODO: Move file uploading to its own function, so it's less copy/paste. 71 | ZIP_FILE="../${ALBUM_NAME}.zip"; 72 | if test -f "${ZIP_FILE}"; then 73 | echo "ZIP file detected. Attempting to upload: ${ZIP_FILE}"; 74 | 75 | file="${ZIP_FILE}"; 76 | POST_FILE="$(curl -fsSL -H "token: ${TOKEN}" -H "albumid: ${ALBUM_ID}" -F files[]=@\"${file}\" "${UPLOAD_FILE_URL}")" 77 | STATUS="$(echo "${POST_FILE}" | jq '.success')"; 78 | 79 | if [[ "${STATUS}" == "false" ]]; then 80 | echo "Unable to upload file: ${file}"; 81 | echo "${POST_FILE}" | jq; 82 | continue; 83 | fi 84 | 85 | FINAL_URL="$(echo "${POST_FILE}" | jq -r '.files[].url')"; 86 | echo "File ${file} upload to URL: ${FINAL_URL}"; 87 | else 88 | echo "ZIP file not found: ${ZIP_FILE}"; 89 | echo "No attempt to upload ZIP file will be made."; 90 | fi 91 | 92 | IFS="$OIFS"; 93 | cd "${OLD_DIR}"; 94 | 95 | echo "Retrieving album list from API and attempting to extract album information."; 96 | # Generate a timestamp that should hopefully bypass the API cache, if any? 97 | DATE_TS="$(date +%s)"; 98 | # Retrieve albums 99 | GET_ALBUMS="$(curl -fsSL -H "token: ${TOKEN}" "${ALBUMS_URL}?${DATE_TS}")"; 100 | # Filter by ID 101 | ALBUM_INFO="$(echo "${GET_ALBUMS}" | jq --arg albumId "${ALBUM_ID}" '.albums[] | select(.id == ($albumId | tonumber))')"; 102 | 103 | if [[ "${ALBUM_INFO}" == "" ]]; then 104 | echo "Unless you saw other errors, album was created and files uploaded successfully." 105 | echo "However, the script could not extract information about the new album from the API - potentially due to cache. Otherwise it'd display the URL here."; 106 | else 107 | # Extract the actual name, in case of any filtering/stripping etc. 108 | ALBUM_NAME="$(echo "${ALBUM_INFO}" | jq -r '.name')"; 109 | # Extract the album slug used for the URL. 110 | ALBUM_SLUG="$(echo "${ALBUM_INFO}" | jq -r '.identifier')"; 111 | # Extract the `homeDomain` that CyberDrop uses, but may not be available on other Lolisafe instances. 112 | HOME_URL="$(echo "${GET_ALBUMS}" | jq -r '.homeDomain')"; 113 | 114 | # Fallback to base URL if `homeDomain` is not set. 115 | if [[ "${HOME_URL}" == "" ]]; then 116 | HOME_URL="${BASE_URL}"; 117 | fi 118 | 119 | echo "Upload complete for album: ${ALBUM_NAME}"; 120 | echo "Album URL: ${HOME_URL}/a/${ALBUM_SLUG}"; 121 | fi -------------------------------------------------------------------------------- /megatools-copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FULL_PATH="$(realpath "$@")"; 4 | FOLDER_NAME=$(basename -- "${FULL_PATH}"); 5 | 6 | if [[ -z "${FOLDER_NAME}" ]]; then 7 | echo "Please specify folder name."; 8 | exit 0 9 | fi 10 | 11 | # Print out the account email address used 12 | MEGA_CONFIG="$HOME/.megarc"; 13 | if [[ -f "${MEGA_CONFIG}" ]]; then 14 | MEGA_USERNAME=`sed -n 's/^Username = \(.*\)/\1/p' < ${MEGA_CONFIG}`; 15 | echo "Using account: ${MEGA_USERNAME}"; 16 | fi 17 | 18 | MEGA_FOLDER="/Root/${FOLDER_NAME}"; 19 | echo "Remote: ${MEGA_FOLDER}"; 20 | echo "Local: ${FULL_PATH}"; 21 | 22 | megatools copy --remote "${MEGA_FOLDER}" --local "${FULL_PATH}"; -------------------------------------------------------------------------------- /megatools-mkdir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FULL_PATH="$(realpath "$@")"; 4 | FOLDER_NAME=$(basename -- "${FULL_PATH}"); 5 | 6 | if [[ -z "${FOLDER_NAME}" ]]; then 7 | echo "Please specify folder name."; 8 | exit 0 9 | fi 10 | 11 | MEGA_FOLDER="/Root/${FOLDER_NAME}"; 12 | 13 | # Print out the account email address used 14 | MEGA_CONFIG="$HOME/.megarc"; 15 | if [[ -f "${MEGA_CONFIG}" ]]; then 16 | MEGA_USERNAME=`sed -n 's/^Username = \(.*\)/\1/p' < ${MEGA_CONFIG}`; 17 | echo "Using account: ${MEGA_USERNAME}"; 18 | fi 19 | 20 | echo "Creating remote folder: ${MEGA_FOLDER}"; 21 | megatools mkdir "${MEGA_FOLDER}"; 22 | 23 | echo "* Copying:" 24 | echo "Remote: ${MEGA_FOLDER}" 25 | echo "Local: ${FULL_PATH}" 26 | 27 | megatools copy --remote "${MEGA_FOLDER}" --local "${FULL_PATH}"; -------------------------------------------------------------------------------- /megatools-reg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Helper script for faster creation of Mega accounts using `megatools` 4 | # 5 | # * Requirements: 6 | # - megatools: https://megatools.megous.com/ 7 | # - At the time of writing I'm using 1.11.0 (experimental) 8 | # - Bash, I guess? 9 | # - `sed` - Though I think this is available by default on Debian / Ubuntu, and probably on other systems. 10 | 11 | # If `$HOME/.megarc` is already defined with a password 12 | # We attempt to load it from there. 13 | MEGA_PASSWORD=`sed -n 's/^Password = \(.*\)/\1/p' < $HOME/.megarc`; 14 | MEGA_FULL_NAME=`sed -n 's/^FullName = \(.*\)/\1/p' < $HOME/.megarc`; 15 | 16 | EMAIL="${1}"; 17 | if [[ -z "${EMAIL}" ]]; then 18 | echo "Email address?"; 19 | read EMAIL; 20 | fi 21 | 22 | if [ -z "${MEGA_PASSWORD}" ]; then 23 | echo "Password?"; 24 | read PASSWORD; 25 | else 26 | echo "Found password in .megarc, using this one: ${MEGA_PASSWORD}"; 27 | PASSWORD="${MEGA_PASSWORD}"; 28 | fi 29 | 30 | FULL_NAME="${MEGA_FULL_NAME}"; 31 | if [[ -z "${FULL_NAME}" ]]; then 32 | echo "Name?" 33 | read FULL_NAME; 34 | fi 35 | 36 | # Replace `@LINK@` with an empty string, so the eval further down actually fucking works :rolling_eyes: 37 | # Not sure why the `--scripted` parameter is so fucking retarded and includes `@LINK@` like... why? 38 | VERIFY_CMD="$(megatools reg --scripted --register --email "${EMAIL}" --password "${PASSWORD}" --name "${FULL_NAME}")"; 39 | VERIFY_CMD="${VERIFY_CMD// @LINK@/}"; 40 | 41 | echo "Verification link?" 42 | read VERIFY_LINK; 43 | 44 | echo "$VERIFY_CMD $VERIFY_LINK"; 45 | eval "${VERIFY_CMD} ${VERIFY_LINK}" 46 | 47 | # If you want the script to override the `Username` value in `.megarc`, then you can set this variable 48 | # I recommend setting it as an environment variable, e.g. in .bashrc or similar, but you can optionally uncomment the line below. 49 | # MEGA_REGISTER_OVERRIDE_USERNAME="1" 50 | if [[ "${MEGA_REGISTER_OVERRIDE_USERNAME}" == 1 ]]; then 51 | echo "Replacing username in $HOME/.megarc"; 52 | sed -i "s/^Username = \(.*\)$/Username = ${EMAIL}/" "$HOME/.megarc"; 53 | fi -------------------------------------------------------------------------------- /ofans-party-dl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` 5 | # - `jq` 6 | # - `parallel` 7 | # 8 | # For Debian/Ubuntu: `apt install curl jq parallel` 9 | 10 | usage() 11 | { 12 | cat << EOF 13 | usage: $0 [space_separated_creators] 14 | 15 | Downloads posts from creators on ofans.party 16 | 17 | As of right now, the script downloads to the following directory: 18 | [output-directory]/[creator-name]/[post-id]_[date:YYYY-mm-dd]_[media-id].[filetype] 19 | 20 | * Dates are according to the API, which is either "OnlyFans create date" (I assume that's when it was posted to OnlyFans) 21 | and just "create date", which I believe is when the post was added to ofans.party. 22 | - Because sometimes the "OnlyFans create date" can be missing and the "create date" is pretty unreliable, 23 | I've decided to use the post ID at the beginning of the file name, for sorting purposes. 24 | - I also have no idea if my theories are correct in any regard lol 25 | 26 | 27 | If metadata is requested (\`-d\`), it will be written as a JSON dump under: 28 | [output-directory]/[creator-name]/metadata.json 29 | Keep in mind that metadata.json will be overwritten every time you run the script for said creator. 30 | 31 | ENVIRONMENT VARIABLES: 32 | Certain options need to be set via environment variables (at least for the time being): 33 | - \`OFANS_IPFS_URL\` - The IPFS gateway used for downloading the files (default: https://cloudflare-ipfs.com/ipfs). 34 | 35 | OPTIONS: 36 | -p How many \`NUM\` post downloads to run in parallel (default: 2). 37 | -o Output directory. Default is: `pwd`/ofans 38 | -h Show this message 39 | -d Dump metadata for creator as JSON 40 | EOF 41 | } 42 | 43 | # Print usage when no parameters specified. 44 | if [[ -z "$@" ]]; then 45 | usage 46 | exit 0 47 | fi 48 | 49 | which curl jq parallel > /dev/null; 50 | DEP_RESULT=$?; 51 | if [[ $DEP_RESULT -ne 0 ]]; then 52 | echo "curl, jq or parallel are missing. Please verify that they are all installed."; 53 | exit $DEP_RESULT; 54 | fi 55 | 56 | # Default settings 57 | PARALLEL_DL=2; 58 | DUMP_METADATA=0; 59 | OUTPUT_DIR="$(pwd)/ofans"; 60 | 61 | while getopts "hdp:o:" opt; do 62 | case $opt in 63 | h) 64 | usage 65 | exit 0 66 | ;; 67 | p) 68 | PARALLEL_DL=$OPTARG; 69 | echo "Parallel downloads: ${PARALLEL_DL}"; 70 | ;; 71 | d) 72 | DUMP_METADATA=1; 73 | ;; 74 | o) 75 | OUTPUT_DIR="${OPTARG}"; 76 | echo "Output directory specified: ${OUTPUT_DIR}"; 77 | ;; 78 | \?) 79 | echo "Invalid option: -$OPTARG" >&2 80 | exit 1 81 | ;; 82 | :) 83 | echo "Option -$OPTARG requires an argument." >&2 84 | exit 1 85 | ;; 86 | esac 87 | done 88 | 89 | # You can override the IPFS URL by setting it as an environment variable 90 | # before running the script. 91 | # 92 | # We fall back to the Cloudflare one, but I'm not sure if it's the most ideal... 93 | if [[ -z "${OFANS_IPFS_URL}" ]]; then 94 | OFANS_IPFS_URL="https://cloudflare-ipfs.com/ipfs"; 95 | fi 96 | 97 | if [[ -z "${OFANS_API_BASE_URL}" ]]; then 98 | OFANS_API_BASE_URL="https://api.ofans.party"; 99 | fi 100 | 101 | # Shorter variable name 102 | IPFS="${OFANS_IPFS_URL}"; 103 | 104 | # Get rid of command flags from input params 105 | shift $((OPTIND - 1)); 106 | 107 | CREATORS=$@; 108 | DL_DIR=$OUTPUT_DIR; 109 | for creator in $CREATORS; 110 | do 111 | echo "Fetching OnlyFans posts for creator: ${creator}"; 112 | CREATOR_URL="${OFANS_API_BASE_URL}/posts/${creator}"; 113 | FETCH_CREATOR="$(curl --fail --silent -L "${CREATOR_URL}")"; 114 | 115 | if [[ $? -ne 0 ]]; then 116 | echo "[Skipping] Error occurred fetching creator: ${creator}"; 117 | continue; 118 | fi 119 | 120 | CREATOR_DATA="$(jq -r . <<< "${FETCH_CREATOR}")"; 121 | POSTS="$(jq -r '.response.posts' <<< "${FETCH_CREATOR}")"; 122 | POST_COUNT="$(jq -r 'length' <<< "${POSTS}")"; 123 | 124 | if [[ $POST_COUNT -lt 1 ]]; then 125 | echo "[Skipping] No posts found for creator: ${creator}"; 126 | continue; 127 | fi 128 | 129 | echo "Found ${POST_COUNT} posts from ${creator} on ofans.party"; 130 | 131 | CREATOR_DIR="${DL_DIR}/${creator}"; 132 | if [[ -d "${CREATOR_DIR}" ]]; then 133 | echo "Creator directory exists: ${CREATOR_DIR}"; 134 | else 135 | echo "Attempting to create directory: ${CREATOR_DIR}"; 136 | mkdir -p "${CREATOR_DIR}"; 137 | 138 | if [[ $? -ne 0 ]]; then 139 | echo "[Skipping] Unable to create directory: ${CREATOR_DIR}"; 140 | continue; 141 | fi 142 | fi 143 | 144 | # Metadata 145 | if [[ $DUMP_METADATA -eq 1 ]]; then 146 | echo "${CREATOR_DATA}" > "${CREATOR_DIR}/metadata.json"; 147 | echo "[Metadata] Saved to: ${CREATOR_DIR}/metadata.json"; 148 | fi 149 | 150 | # Attempt to create temp file to 151 | # store the cURL commands in for the creator. 152 | TMP_PREFIX="ofans_${creator}.XXXXXXX"; 153 | TMP_FILE="$(mktemp -t "${TMP_PREFIX}")"; 154 | if [[ $? -ne 0 ]]; then 155 | echo "[Skipping] Unable to create TEMP file for creator: ${creator}"; 156 | continue; 157 | fi 158 | 159 | echo "Temp file created: ${TMP_FILE}"; 160 | 161 | # Thanks homie: https://unix.stackexchange.com/a/477218 162 | # Process each post 163 | for pIdx in $(jq -r 'keys | .[]' <<< "${POSTS}"); do 164 | POST="$(jq ".[${pIdx}]" <<< "${POSTS}")"; 165 | 166 | POST_ID="$(jq -r '.post_id' <<< "${POST}")"; 167 | POST_DATE="$(jq -r '.of_create_date' <<< "${POST}")"; 168 | 169 | # Fall back to `create_date` if `of_create_date` is not defined. 170 | if [[ "${POST_DATE}" == "null" ]]; then 171 | POST_DATE="$(jq -r '.create_date' <<< "${POST}")"; 172 | fi 173 | 174 | FILE_DATE="$(date --date="${POST_DATE}" +"%Y-%m-%d")"; 175 | 176 | # Process each media file 177 | MEDIAS="$(jq -r '.media' <<< "${POST}")"; 178 | for mIdx in $(jq -r 'keys | .[]' <<< "${MEDIAS}"); do 179 | # Extract values we want to use 180 | MEDIA="$(jq -r ".[${mIdx}]" <<< "${MEDIAS}")"; 181 | MEDIA_ID="$(jq -r '.of_media_id' <<< "${MEDIA}")"; 182 | TYPE="$(jq -r '.type' <<< "${MEDIA}")"; 183 | 184 | # File extensions are best guesses 185 | # Based on my own experience videos uploaded to OnlyFans ALWAYS get 186 | # converted to MP4s (H264) and photos are ALWAYS JPGs. 187 | EXTENSION=''; 188 | if [[ "${TYPE}" == "photo" ]]; then 189 | EXTENSION=".jpg"; 190 | elif [[ "${TYPE}" == "video" ]]; then 191 | EXTENSION=".mp4"; 192 | fi 193 | 194 | IPFS_HASH="$(jq -r '.ipfs_media_hash' <<< "${MEDIA}")"; 195 | 196 | # No IPFS hash available, skip 197 | if [[ "${IPFS_HASH}" == "null" ]]; then 198 | continue; 199 | fi 200 | 201 | FILE_NAME="${POST_ID}_${FILE_DATE}_${MEDIA_ID}${EXTENSION}"; 202 | 203 | # Alright, I know this is ghetto, but I figured it was the easiest 204 | # way after parsing the JSON by calling jq 9 billion times 205 | # Bash wasn't exactly the right tool for this, but whatever. 206 | CMD="curl -fLo '${CREATOR_DIR}/${FILE_NAME}' ${IPFS}/${IPFS_HASH}"; 207 | echo "${CMD}" >> "${TMP_FILE}"; 208 | done 209 | 210 | echo "[Processing] Processed post ID ${POST_ID} from creator: ${creator}"; 211 | done 212 | 213 | echo "[Processing] Finished processing creator: ${creator}"; 214 | 215 | echo "[Downloading] Medias from creator: ${creator}"; 216 | echo "[Downloading] Downloads in parallel: ${PARALLEL_DL}"; 217 | echo "[Info] Please keep in mind that since the files are hosted using IPFS (https://ipfs.io/), they may take a long time to download. Please be patient."; 218 | 219 | parallel -j $PARALLEL_DL < "${TMP_FILE}"; 220 | 221 | echo "[Downloading] Downloads complete for: ${creator}"; 222 | done 223 | -------------------------------------------------------------------------------- /onlyfans-backup-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Where the DIGITALCRIMINAL/OnlyFans directory exists on your PC. 4 | OF_PROJECT_DIR="$HOME/projects/OnlyFans"; 5 | 6 | # Where the settings directory is 7 | # Not necessary to change unless the project changes this in the future. 8 | OF_SETTINGS="$OF_PROJECT_DIR/.settings"; 9 | 10 | # Some of these files are extra files that normally aren't used with the project 11 | # See `onlyfans-switch-config.sh` 12 | FILES=( "config.json" "config.auto.json" "extra_auth.json" "old_auth.json" ); 13 | # Date format: YEAR-MONTH-DAY_HOUR-MINUTE-SECOND 14 | CURRENT_DT="$(date +"%Y-%m-%d_%H-%M-%S")"; 15 | 16 | # Current working directory 17 | # Mainly used to navigate back to it after script finishes 18 | CURRENT_PWD="$(pwd)"; 19 | 20 | # Navigate to settings directory in OF project. 21 | cd "${OF_SETTINGS}"; 22 | 23 | for file in "${FILES[@]}"; do 24 | if [ ! -f "${file}" ]; then 25 | echo "File does not exist: ${file} -- Skipping"; 26 | continue; 27 | fi 28 | 29 | # Take old name, remove `.json`, add datetime, add `.json` back at the end. 30 | backup_file="${file%%.json}.${CURRENT_DT}.json"; 31 | 32 | cp "${file}" "${backup_file}"; 33 | echo "Copied: ${file} => ${backup_file}"; 34 | done 35 | 36 | cd "${CURRENT_PWD}"; -------------------------------------------------------------------------------- /onlyfans-create-profile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script creates a profile directory that is the new "extra_auth" as of v6.1. 4 | # The profile directory and `auth.json` file will be in the format `.profiles/OnlyFans/YYYY-MM-DD_USER-INPUT-HERE/auth.json` 5 | # Like most of my scripts, it's biased after my own personal preferences. Feel free to modify as you like though. 6 | # 7 | # Keep in mind that the script isn't "idiot proof", so you should probably be a bit cautious when using it. 8 | # It doesn't remove anything (or at least shouldn't), but based on the input name you might create recursive folders 9 | # as the script uses `mkdir -p` and doesn't check the input before using it. 10 | 11 | # Where the DIGITALCRIMINAL/OnlyFans directory exists on your PC. 12 | OF_PROJECT_DIR="${HOME}/projects/OnlyFans"; 13 | 14 | # Where the `.profiles` directory is 15 | # Should not be necessary to change, but it might be different depending on `config.json`???? 16 | # Legit no idea. Try it out I guess? 17 | OF_PROFILES="${OF_PROJECT_DIR}/.profiles/OnlyFans"; 18 | 19 | TODAY="$(date +"%Y-%m-%d")"; 20 | 21 | echo "Name? - Used in the folder name after the date"; 22 | read FOLDER_NAME; 23 | 24 | PROFILE_DIR="${OF_PROFILES}/${TODAY}_${FOLDER_NAME}"; 25 | if [[ -d "${PROFILE_DIR}" ]]; then 26 | echo "Profile directory already exists -- Quitting: ${PROFILE_DIR}"; 27 | exit 1; 28 | fi 29 | 30 | mkdir -p "${PROFILE_DIR}"; 31 | touch "${PROFILE_DIR}/auth.json"; 32 | 33 | # Check & open Sublime Text if it's installed 34 | # fallback to `nano` instead. 35 | # `nano` is installed on all of my personal systems, can't speak for everyone else... 36 | HAS_SUBLIME="$(which subl)"; 37 | if [[ $? -eq 0 ]]; then 38 | subl "${PROFILE_DIR}/auth.json"; 39 | else 40 | nano "${PROFILE_DIR}/auth.json"; 41 | fi -------------------------------------------------------------------------------- /onlyfans-sort-creator-clipboard-list.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/node 2 | 3 | /** 4 | * Sort clipboard copy-paste from DIGITALCRIMINAL/OnlyFans into a unique, alphabetical list. 5 | * Useful when using multiple auths and a lot of shared creators (especially free ones...). 6 | */ 7 | 8 | const clipboardy = require('clipboardy'); 9 | const reg = / ?\| \d+ = /g; 10 | const input = clipboardy.readSync(); 11 | 12 | if (!input) { 13 | console.error('[OnlyFans Sort Clipboard] Nothing in clipboard'); 14 | process.exit(1); 15 | } 16 | 17 | const list = input 18 | // Replace the beginning part 19 | .replace(/^.+0 = All/g, '') 20 | // 'Split' the string into a newline-based list 21 | .replace(reg, '\n') 22 | // Trim the list (remove leading/trailing whitespace) 23 | .trim(); 24 | 25 | const unique = list 26 | // Split the list into an array 27 | .split('\n') 28 | // Sort it alphabetically, ascending 29 | .sort() 30 | // Filter unique values 31 | .filter((value, index, self) => { 32 | return self.indexOf(value) === index; 33 | }); 34 | 35 | // Join back the filtered list into a newline-based list 36 | const text = unique.join('\n'); 37 | // Write the filtered list to clipboard 38 | clipboardy.writeSync(text); 39 | console.log('[OnlyFans Sort Clipboard] Sorted/filtered list written to clipboard.'); -------------------------------------------------------------------------------- /onlyfans-switch-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Bash script for swapping DIGITALCRIMINAL/OnlyFans configs 4 | # so that I can easily change between "manual ripping" of profiles 5 | # or automatic ripping which just runs continuously every 15 minutes. 6 | # 7 | # For the script to work, you need to make a copy of the `config.json` file 8 | # and configure it to be either the MANUAL or AUTOMATIC one. 9 | # 10 | # If you make a copy that's named the same as $CONFIG_MANUAL, the script will assume that the 11 | # current config is the one used for AUTOMATIC ripping. 12 | # 13 | # The opposite occurs if you make a copy same as $CONFIG_AUTO, 14 | # current config is considered the one used for MANUAL ripping. 15 | # 16 | # TL;DR Make a copy that's either $CONFIG_MANUAL or $CONFIG_AUTO, not both. 17 | # 18 | # The idea is that $CONFIG_AUTO can rip everything automatically by itself constantly. 19 | # In my case that means: 20 | # - `loop_timeout` is set to a sensible value (e.g. 900 = runs 15 minutes after last completed rip) 21 | # - `auto_scrape_names` and `auto_scrape_apis` = true 22 | # - `choose_auth` = false* 23 | # 24 | # For $CONFIG_MANUAL it's basically the complete opposite: 25 | # - `loop_timeout` = "0" or "" 26 | # - `auto_scrape_names` and `auto_scrape_apis` = false 27 | # - `choose_auth` = true* 28 | # 29 | # * `choose_auth` assumes you have `extra_auth` = true 30 | # since it's meant for the times you have multiple accounts configured. 31 | 32 | # Where the DIGITALCRIMINAL/OnlyFans directory exists on your PC. 33 | OF_PROJECT_DIR="$HOME/projects/OnlyFans"; 34 | 35 | # Where the settings directory is 36 | # Not necessary to change unless the project changes this in the future. 37 | OF_SETTINGS="$OF_PROJECT_DIR/.settings"; 38 | 39 | # Current working directory 40 | # Mainly used to navigate back to it after script finishes 41 | CURRENT_PWD="$(pwd)"; 42 | 43 | # Config file names 44 | CONFIG_CURRENT="config.json"; 45 | CONFIG_AUTO="config.auto.json"; 46 | CONFIG_MANUAL="config.manual.json"; 47 | 48 | # Navigate to settings directory in OF project. 49 | cd "${OF_SETTINGS}"; 50 | 51 | # If both configs exist we quit the script 52 | # to not fuck up any of the configs. 53 | if [[ -f "${CONFIG_AUTO}" && -f "${CONFIG_MANUAL}" ]]; then 54 | echo "Both manual ripping config (${CONFIG_MANUAL}) and automatic ripping config (${CONFIG_AUTO}) exist."; 55 | echo "The script is not smart enough to figure out which one to use."; 56 | echo "Please fix before running script again."; 57 | echo "Thank you."; 58 | cd "${CURRENT_PWD}"; 59 | exit 1; 60 | fi 61 | 62 | # Check if the "automatic ripping" config exists 63 | # If it does, we assume that the current config is the "manual ripping" config. 64 | # Then we do a little switcharoo. 65 | if [[ -f "${CONFIG_AUTO}" ]]; then 66 | echo "Current config (old): Manual ripping"; 67 | echo "Moving current config to ${CONFIG_MANUAL}"; 68 | mv "${CONFIG_CURRENT}" "${CONFIG_MANUAL}"; 69 | mv "${CONFIG_AUTO}" "${CONFIG_CURRENT}"; 70 | echo "Current config (new): Automatic ripping"; 71 | elif [[ -f "${CONFIG_MANUAL}" ]]; then 72 | # Here we assume the opposite, that since the manual config file 73 | # exists, the current config is the automatic one. 74 | # But again it's just a switcharoo. 75 | echo "Current config (old): Automatic ripping"; 76 | echo "Moving current config to ${CONFIG_AUTO}"; 77 | mv "${CONFIG_CURRENT}" "${CONFIG_AUTO}"; 78 | mv "${CONFIG_MANUAL}" "${CONFIG_CURRENT}"; 79 | echo "Current config (new): Manual ripping"; 80 | else 81 | # Neither config file could be found, so we basically don't know wtf to do. 82 | echo "Could not find config for automatic ripping or manual ripping"; 83 | fi 84 | 85 | # Navigate back to old directory. 86 | cd "${CURRENT_PWD}"; -------------------------------------------------------------------------------- /output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.sample.json 3 | !.gitignore -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marcus-scripts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "marcus-scripts", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.6.0", 13 | "clipboardy": "^2.3.0", 14 | "meow": "^9.0.0", 15 | "signale": "^1.4.0" 16 | } 17 | }, 18 | "node_modules/@babel/code-frame": { 19 | "version": "7.24.7", 20 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", 21 | "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", 22 | "dependencies": { 23 | "@babel/highlight": "^7.24.7", 24 | "picocolors": "^1.0.0" 25 | }, 26 | "engines": { 27 | "node": ">=6.9.0" 28 | } 29 | }, 30 | "node_modules/@babel/helper-validator-identifier": { 31 | "version": "7.24.7", 32 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", 33 | "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", 34 | "engines": { 35 | "node": ">=6.9.0" 36 | } 37 | }, 38 | "node_modules/@babel/highlight": { 39 | "version": "7.24.7", 40 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", 41 | "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", 42 | "dependencies": { 43 | "@babel/helper-validator-identifier": "^7.24.7", 44 | "chalk": "^2.4.2", 45 | "js-tokens": "^4.0.0", 46 | "picocolors": "^1.0.0" 47 | }, 48 | "engines": { 49 | "node": ">=6.9.0" 50 | } 51 | }, 52 | "node_modules/@types/minimist": { 53 | "version": "1.2.5", 54 | "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", 55 | "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==" 56 | }, 57 | "node_modules/@types/normalize-package-data": { 58 | "version": "2.4.4", 59 | "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", 60 | "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==" 61 | }, 62 | "node_modules/ansi-styles": { 63 | "version": "3.2.1", 64 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 65 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 66 | "dependencies": { 67 | "color-convert": "^1.9.0" 68 | }, 69 | "engines": { 70 | "node": ">=4" 71 | } 72 | }, 73 | "node_modules/arch": { 74 | "version": "2.2.0", 75 | "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", 76 | "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", 77 | "funding": [ 78 | { 79 | "type": "github", 80 | "url": "https://github.com/sponsors/feross" 81 | }, 82 | { 83 | "type": "patreon", 84 | "url": "https://www.patreon.com/feross" 85 | }, 86 | { 87 | "type": "consulting", 88 | "url": "https://feross.org/support" 89 | } 90 | ] 91 | }, 92 | "node_modules/arrify": { 93 | "version": "1.0.1", 94 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 95 | "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", 96 | "engines": { 97 | "node": ">=0.10.0" 98 | } 99 | }, 100 | "node_modules/asynckit": { 101 | "version": "0.4.0", 102 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 103 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 104 | }, 105 | "node_modules/axios": { 106 | "version": "1.6.0", 107 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", 108 | "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", 109 | "dependencies": { 110 | "follow-redirects": "^1.15.0", 111 | "form-data": "^4.0.0", 112 | "proxy-from-env": "^1.1.0" 113 | } 114 | }, 115 | "node_modules/camelcase": { 116 | "version": "5.3.1", 117 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 118 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 119 | "engines": { 120 | "node": ">=6" 121 | } 122 | }, 123 | "node_modules/camelcase-keys": { 124 | "version": "6.2.2", 125 | "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", 126 | "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", 127 | "dependencies": { 128 | "camelcase": "^5.3.1", 129 | "map-obj": "^4.0.0", 130 | "quick-lru": "^4.0.1" 131 | }, 132 | "engines": { 133 | "node": ">=8" 134 | }, 135 | "funding": { 136 | "url": "https://github.com/sponsors/sindresorhus" 137 | } 138 | }, 139 | "node_modules/chalk": { 140 | "version": "2.4.2", 141 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 142 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 143 | "dependencies": { 144 | "ansi-styles": "^3.2.1", 145 | "escape-string-regexp": "^1.0.5", 146 | "supports-color": "^5.3.0" 147 | }, 148 | "engines": { 149 | "node": ">=4" 150 | } 151 | }, 152 | "node_modules/clipboardy": { 153 | "version": "2.3.0", 154 | "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", 155 | "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", 156 | "dependencies": { 157 | "arch": "^2.1.1", 158 | "execa": "^1.0.0", 159 | "is-wsl": "^2.1.1" 160 | }, 161 | "engines": { 162 | "node": ">=8" 163 | } 164 | }, 165 | "node_modules/color-convert": { 166 | "version": "1.9.3", 167 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 168 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 169 | "dependencies": { 170 | "color-name": "1.1.3" 171 | } 172 | }, 173 | "node_modules/color-name": { 174 | "version": "1.1.3", 175 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 176 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 177 | }, 178 | "node_modules/combined-stream": { 179 | "version": "1.0.8", 180 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 181 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 182 | "dependencies": { 183 | "delayed-stream": "~1.0.0" 184 | }, 185 | "engines": { 186 | "node": ">= 0.8" 187 | } 188 | }, 189 | "node_modules/cross-spawn": { 190 | "version": "6.0.5", 191 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 192 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 193 | "dependencies": { 194 | "nice-try": "^1.0.4", 195 | "path-key": "^2.0.1", 196 | "semver": "^5.5.0", 197 | "shebang-command": "^1.2.0", 198 | "which": "^1.2.9" 199 | }, 200 | "engines": { 201 | "node": ">=4.8" 202 | } 203 | }, 204 | "node_modules/decamelize": { 205 | "version": "1.2.0", 206 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 207 | "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", 208 | "engines": { 209 | "node": ">=0.10.0" 210 | } 211 | }, 212 | "node_modules/decamelize-keys": { 213 | "version": "1.1.1", 214 | "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", 215 | "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", 216 | "dependencies": { 217 | "decamelize": "^1.1.0", 218 | "map-obj": "^1.0.0" 219 | }, 220 | "engines": { 221 | "node": ">=0.10.0" 222 | }, 223 | "funding": { 224 | "url": "https://github.com/sponsors/sindresorhus" 225 | } 226 | }, 227 | "node_modules/decamelize-keys/node_modules/map-obj": { 228 | "version": "1.0.1", 229 | "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", 230 | "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", 231 | "engines": { 232 | "node": ">=0.10.0" 233 | } 234 | }, 235 | "node_modules/delayed-stream": { 236 | "version": "1.0.0", 237 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 238 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 239 | "engines": { 240 | "node": ">=0.4.0" 241 | } 242 | }, 243 | "node_modules/end-of-stream": { 244 | "version": "1.4.4", 245 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 246 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 247 | "dependencies": { 248 | "once": "^1.4.0" 249 | } 250 | }, 251 | "node_modules/error-ex": { 252 | "version": "1.3.2", 253 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 254 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 255 | "dependencies": { 256 | "is-arrayish": "^0.2.1" 257 | } 258 | }, 259 | "node_modules/escape-string-regexp": { 260 | "version": "1.0.5", 261 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 262 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 263 | "engines": { 264 | "node": ">=0.8.0" 265 | } 266 | }, 267 | "node_modules/execa": { 268 | "version": "1.0.0", 269 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 270 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 271 | "dependencies": { 272 | "cross-spawn": "^6.0.0", 273 | "get-stream": "^4.0.0", 274 | "is-stream": "^1.1.0", 275 | "npm-run-path": "^2.0.0", 276 | "p-finally": "^1.0.0", 277 | "signal-exit": "^3.0.0", 278 | "strip-eof": "^1.0.0" 279 | }, 280 | "engines": { 281 | "node": ">=6" 282 | } 283 | }, 284 | "node_modules/figures": { 285 | "version": "2.0.0", 286 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 287 | "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", 288 | "dependencies": { 289 | "escape-string-regexp": "^1.0.5" 290 | }, 291 | "engines": { 292 | "node": ">=4" 293 | } 294 | }, 295 | "node_modules/find-up": { 296 | "version": "4.1.0", 297 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 298 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 299 | "dependencies": { 300 | "locate-path": "^5.0.0", 301 | "path-exists": "^4.0.0" 302 | }, 303 | "engines": { 304 | "node": ">=8" 305 | } 306 | }, 307 | "node_modules/follow-redirects": { 308 | "version": "1.15.6", 309 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 310 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 311 | "funding": [ 312 | { 313 | "type": "individual", 314 | "url": "https://github.com/sponsors/RubenVerborgh" 315 | } 316 | ], 317 | "engines": { 318 | "node": ">=4.0" 319 | }, 320 | "peerDependenciesMeta": { 321 | "debug": { 322 | "optional": true 323 | } 324 | } 325 | }, 326 | "node_modules/form-data": { 327 | "version": "4.0.0", 328 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 329 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 330 | "dependencies": { 331 | "asynckit": "^0.4.0", 332 | "combined-stream": "^1.0.8", 333 | "mime-types": "^2.1.12" 334 | }, 335 | "engines": { 336 | "node": ">= 6" 337 | } 338 | }, 339 | "node_modules/function-bind": { 340 | "version": "1.1.2", 341 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 342 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 343 | "funding": { 344 | "url": "https://github.com/sponsors/ljharb" 345 | } 346 | }, 347 | "node_modules/get-stream": { 348 | "version": "4.1.0", 349 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 350 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 351 | "dependencies": { 352 | "pump": "^3.0.0" 353 | }, 354 | "engines": { 355 | "node": ">=6" 356 | } 357 | }, 358 | "node_modules/graceful-fs": { 359 | "version": "4.2.11", 360 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 361 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" 362 | }, 363 | "node_modules/hard-rejection": { 364 | "version": "2.1.0", 365 | "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", 366 | "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", 367 | "engines": { 368 | "node": ">=6" 369 | } 370 | }, 371 | "node_modules/has-flag": { 372 | "version": "3.0.0", 373 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 374 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 375 | "engines": { 376 | "node": ">=4" 377 | } 378 | }, 379 | "node_modules/hasown": { 380 | "version": "2.0.2", 381 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 382 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 383 | "dependencies": { 384 | "function-bind": "^1.1.2" 385 | }, 386 | "engines": { 387 | "node": ">= 0.4" 388 | } 389 | }, 390 | "node_modules/hosted-git-info": { 391 | "version": "4.1.0", 392 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", 393 | "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", 394 | "dependencies": { 395 | "lru-cache": "^6.0.0" 396 | }, 397 | "engines": { 398 | "node": ">=10" 399 | } 400 | }, 401 | "node_modules/indent-string": { 402 | "version": "4.0.0", 403 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 404 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 405 | "engines": { 406 | "node": ">=8" 407 | } 408 | }, 409 | "node_modules/is-arrayish": { 410 | "version": "0.2.1", 411 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 412 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" 413 | }, 414 | "node_modules/is-core-module": { 415 | "version": "2.15.1", 416 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", 417 | "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", 418 | "dependencies": { 419 | "hasown": "^2.0.2" 420 | }, 421 | "engines": { 422 | "node": ">= 0.4" 423 | }, 424 | "funding": { 425 | "url": "https://github.com/sponsors/ljharb" 426 | } 427 | }, 428 | "node_modules/is-docker": { 429 | "version": "2.2.1", 430 | "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", 431 | "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", 432 | "bin": { 433 | "is-docker": "cli.js" 434 | }, 435 | "engines": { 436 | "node": ">=8" 437 | }, 438 | "funding": { 439 | "url": "https://github.com/sponsors/sindresorhus" 440 | } 441 | }, 442 | "node_modules/is-plain-obj": { 443 | "version": "1.1.0", 444 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", 445 | "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", 446 | "engines": { 447 | "node": ">=0.10.0" 448 | } 449 | }, 450 | "node_modules/is-stream": { 451 | "version": "1.1.0", 452 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 453 | "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", 454 | "engines": { 455 | "node": ">=0.10.0" 456 | } 457 | }, 458 | "node_modules/is-wsl": { 459 | "version": "2.2.0", 460 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", 461 | "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", 462 | "dependencies": { 463 | "is-docker": "^2.0.0" 464 | }, 465 | "engines": { 466 | "node": ">=8" 467 | } 468 | }, 469 | "node_modules/isexe": { 470 | "version": "2.0.0", 471 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 472 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 473 | }, 474 | "node_modules/js-tokens": { 475 | "version": "4.0.0", 476 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 477 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 478 | }, 479 | "node_modules/json-parse-better-errors": { 480 | "version": "1.0.2", 481 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 482 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" 483 | }, 484 | "node_modules/json-parse-even-better-errors": { 485 | "version": "2.3.1", 486 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 487 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" 488 | }, 489 | "node_modules/kind-of": { 490 | "version": "6.0.3", 491 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 492 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 493 | "engines": { 494 | "node": ">=0.10.0" 495 | } 496 | }, 497 | "node_modules/lines-and-columns": { 498 | "version": "1.2.4", 499 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 500 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" 501 | }, 502 | "node_modules/load-json-file": { 503 | "version": "4.0.0", 504 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 505 | "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", 506 | "dependencies": { 507 | "graceful-fs": "^4.1.2", 508 | "parse-json": "^4.0.0", 509 | "pify": "^3.0.0", 510 | "strip-bom": "^3.0.0" 511 | }, 512 | "engines": { 513 | "node": ">=4" 514 | } 515 | }, 516 | "node_modules/load-json-file/node_modules/parse-json": { 517 | "version": "4.0.0", 518 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 519 | "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", 520 | "dependencies": { 521 | "error-ex": "^1.3.1", 522 | "json-parse-better-errors": "^1.0.1" 523 | }, 524 | "engines": { 525 | "node": ">=4" 526 | } 527 | }, 528 | "node_modules/locate-path": { 529 | "version": "5.0.0", 530 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 531 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 532 | "dependencies": { 533 | "p-locate": "^4.1.0" 534 | }, 535 | "engines": { 536 | "node": ">=8" 537 | } 538 | }, 539 | "node_modules/lru-cache": { 540 | "version": "6.0.0", 541 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 542 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 543 | "dependencies": { 544 | "yallist": "^4.0.0" 545 | }, 546 | "engines": { 547 | "node": ">=10" 548 | } 549 | }, 550 | "node_modules/map-obj": { 551 | "version": "4.3.0", 552 | "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", 553 | "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", 554 | "engines": { 555 | "node": ">=8" 556 | }, 557 | "funding": { 558 | "url": "https://github.com/sponsors/sindresorhus" 559 | } 560 | }, 561 | "node_modules/meow": { 562 | "version": "9.0.0", 563 | "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", 564 | "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", 565 | "dependencies": { 566 | "@types/minimist": "^1.2.0", 567 | "camelcase-keys": "^6.2.2", 568 | "decamelize": "^1.2.0", 569 | "decamelize-keys": "^1.1.0", 570 | "hard-rejection": "^2.1.0", 571 | "minimist-options": "4.1.0", 572 | "normalize-package-data": "^3.0.0", 573 | "read-pkg-up": "^7.0.1", 574 | "redent": "^3.0.0", 575 | "trim-newlines": "^3.0.0", 576 | "type-fest": "^0.18.0", 577 | "yargs-parser": "^20.2.3" 578 | }, 579 | "engines": { 580 | "node": ">=10" 581 | }, 582 | "funding": { 583 | "url": "https://github.com/sponsors/sindresorhus" 584 | } 585 | }, 586 | "node_modules/mime-db": { 587 | "version": "1.52.0", 588 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 589 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 590 | "engines": { 591 | "node": ">= 0.6" 592 | } 593 | }, 594 | "node_modules/mime-types": { 595 | "version": "2.1.35", 596 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 597 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 598 | "dependencies": { 599 | "mime-db": "1.52.0" 600 | }, 601 | "engines": { 602 | "node": ">= 0.6" 603 | } 604 | }, 605 | "node_modules/min-indent": { 606 | "version": "1.0.1", 607 | "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", 608 | "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", 609 | "engines": { 610 | "node": ">=4" 611 | } 612 | }, 613 | "node_modules/minimist-options": { 614 | "version": "4.1.0", 615 | "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", 616 | "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", 617 | "dependencies": { 618 | "arrify": "^1.0.1", 619 | "is-plain-obj": "^1.1.0", 620 | "kind-of": "^6.0.3" 621 | }, 622 | "engines": { 623 | "node": ">= 6" 624 | } 625 | }, 626 | "node_modules/nice-try": { 627 | "version": "1.0.5", 628 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 629 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 630 | }, 631 | "node_modules/normalize-package-data": { 632 | "version": "3.0.3", 633 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", 634 | "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", 635 | "dependencies": { 636 | "hosted-git-info": "^4.0.1", 637 | "is-core-module": "^2.5.0", 638 | "semver": "^7.3.4", 639 | "validate-npm-package-license": "^3.0.1" 640 | }, 641 | "engines": { 642 | "node": ">=10" 643 | } 644 | }, 645 | "node_modules/normalize-package-data/node_modules/semver": { 646 | "version": "7.6.3", 647 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 648 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 649 | "bin": { 650 | "semver": "bin/semver.js" 651 | }, 652 | "engines": { 653 | "node": ">=10" 654 | } 655 | }, 656 | "node_modules/npm-run-path": { 657 | "version": "2.0.2", 658 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 659 | "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", 660 | "dependencies": { 661 | "path-key": "^2.0.0" 662 | }, 663 | "engines": { 664 | "node": ">=4" 665 | } 666 | }, 667 | "node_modules/once": { 668 | "version": "1.4.0", 669 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 670 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 671 | "dependencies": { 672 | "wrappy": "1" 673 | } 674 | }, 675 | "node_modules/p-finally": { 676 | "version": "1.0.0", 677 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 678 | "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", 679 | "engines": { 680 | "node": ">=4" 681 | } 682 | }, 683 | "node_modules/p-limit": { 684 | "version": "2.3.0", 685 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 686 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 687 | "dependencies": { 688 | "p-try": "^2.0.0" 689 | }, 690 | "engines": { 691 | "node": ">=6" 692 | }, 693 | "funding": { 694 | "url": "https://github.com/sponsors/sindresorhus" 695 | } 696 | }, 697 | "node_modules/p-locate": { 698 | "version": "4.1.0", 699 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 700 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 701 | "dependencies": { 702 | "p-limit": "^2.2.0" 703 | }, 704 | "engines": { 705 | "node": ">=8" 706 | } 707 | }, 708 | "node_modules/p-try": { 709 | "version": "2.2.0", 710 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 711 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 712 | "engines": { 713 | "node": ">=6" 714 | } 715 | }, 716 | "node_modules/parse-json": { 717 | "version": "5.2.0", 718 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 719 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 720 | "dependencies": { 721 | "@babel/code-frame": "^7.0.0", 722 | "error-ex": "^1.3.1", 723 | "json-parse-even-better-errors": "^2.3.0", 724 | "lines-and-columns": "^1.1.6" 725 | }, 726 | "engines": { 727 | "node": ">=8" 728 | }, 729 | "funding": { 730 | "url": "https://github.com/sponsors/sindresorhus" 731 | } 732 | }, 733 | "node_modules/path-exists": { 734 | "version": "4.0.0", 735 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 736 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 737 | "engines": { 738 | "node": ">=8" 739 | } 740 | }, 741 | "node_modules/path-key": { 742 | "version": "2.0.1", 743 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 744 | "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", 745 | "engines": { 746 | "node": ">=4" 747 | } 748 | }, 749 | "node_modules/path-parse": { 750 | "version": "1.0.7", 751 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 752 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 753 | }, 754 | "node_modules/picocolors": { 755 | "version": "1.0.1", 756 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 757 | "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" 758 | }, 759 | "node_modules/pify": { 760 | "version": "3.0.0", 761 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 762 | "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", 763 | "engines": { 764 | "node": ">=4" 765 | } 766 | }, 767 | "node_modules/pkg-conf": { 768 | "version": "2.1.0", 769 | "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", 770 | "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", 771 | "dependencies": { 772 | "find-up": "^2.0.0", 773 | "load-json-file": "^4.0.0" 774 | }, 775 | "engines": { 776 | "node": ">=4" 777 | } 778 | }, 779 | "node_modules/pkg-conf/node_modules/find-up": { 780 | "version": "2.1.0", 781 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", 782 | "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", 783 | "dependencies": { 784 | "locate-path": "^2.0.0" 785 | }, 786 | "engines": { 787 | "node": ">=4" 788 | } 789 | }, 790 | "node_modules/pkg-conf/node_modules/locate-path": { 791 | "version": "2.0.0", 792 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", 793 | "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", 794 | "dependencies": { 795 | "p-locate": "^2.0.0", 796 | "path-exists": "^3.0.0" 797 | }, 798 | "engines": { 799 | "node": ">=4" 800 | } 801 | }, 802 | "node_modules/pkg-conf/node_modules/p-limit": { 803 | "version": "1.3.0", 804 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", 805 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", 806 | "dependencies": { 807 | "p-try": "^1.0.0" 808 | }, 809 | "engines": { 810 | "node": ">=4" 811 | } 812 | }, 813 | "node_modules/pkg-conf/node_modules/p-locate": { 814 | "version": "2.0.0", 815 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", 816 | "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", 817 | "dependencies": { 818 | "p-limit": "^1.1.0" 819 | }, 820 | "engines": { 821 | "node": ">=4" 822 | } 823 | }, 824 | "node_modules/pkg-conf/node_modules/p-try": { 825 | "version": "1.0.0", 826 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", 827 | "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", 828 | "engines": { 829 | "node": ">=4" 830 | } 831 | }, 832 | "node_modules/pkg-conf/node_modules/path-exists": { 833 | "version": "3.0.0", 834 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 835 | "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", 836 | "engines": { 837 | "node": ">=4" 838 | } 839 | }, 840 | "node_modules/proxy-from-env": { 841 | "version": "1.1.0", 842 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 843 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 844 | }, 845 | "node_modules/pump": { 846 | "version": "3.0.0", 847 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 848 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 849 | "dependencies": { 850 | "end-of-stream": "^1.1.0", 851 | "once": "^1.3.1" 852 | } 853 | }, 854 | "node_modules/quick-lru": { 855 | "version": "4.0.1", 856 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", 857 | "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", 858 | "engines": { 859 | "node": ">=8" 860 | } 861 | }, 862 | "node_modules/read-pkg": { 863 | "version": "5.2.0", 864 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", 865 | "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", 866 | "dependencies": { 867 | "@types/normalize-package-data": "^2.4.0", 868 | "normalize-package-data": "^2.5.0", 869 | "parse-json": "^5.0.0", 870 | "type-fest": "^0.6.0" 871 | }, 872 | "engines": { 873 | "node": ">=8" 874 | } 875 | }, 876 | "node_modules/read-pkg-up": { 877 | "version": "7.0.1", 878 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", 879 | "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", 880 | "dependencies": { 881 | "find-up": "^4.1.0", 882 | "read-pkg": "^5.2.0", 883 | "type-fest": "^0.8.1" 884 | }, 885 | "engines": { 886 | "node": ">=8" 887 | }, 888 | "funding": { 889 | "url": "https://github.com/sponsors/sindresorhus" 890 | } 891 | }, 892 | "node_modules/read-pkg-up/node_modules/type-fest": { 893 | "version": "0.8.1", 894 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", 895 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", 896 | "engines": { 897 | "node": ">=8" 898 | } 899 | }, 900 | "node_modules/read-pkg/node_modules/hosted-git-info": { 901 | "version": "2.8.9", 902 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 903 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" 904 | }, 905 | "node_modules/read-pkg/node_modules/normalize-package-data": { 906 | "version": "2.5.0", 907 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 908 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 909 | "dependencies": { 910 | "hosted-git-info": "^2.1.4", 911 | "resolve": "^1.10.0", 912 | "semver": "2 || 3 || 4 || 5", 913 | "validate-npm-package-license": "^3.0.1" 914 | } 915 | }, 916 | "node_modules/read-pkg/node_modules/type-fest": { 917 | "version": "0.6.0", 918 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", 919 | "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", 920 | "engines": { 921 | "node": ">=8" 922 | } 923 | }, 924 | "node_modules/redent": { 925 | "version": "3.0.0", 926 | "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", 927 | "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", 928 | "dependencies": { 929 | "indent-string": "^4.0.0", 930 | "strip-indent": "^3.0.0" 931 | }, 932 | "engines": { 933 | "node": ">=8" 934 | } 935 | }, 936 | "node_modules/resolve": { 937 | "version": "1.22.8", 938 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 939 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 940 | "dependencies": { 941 | "is-core-module": "^2.13.0", 942 | "path-parse": "^1.0.7", 943 | "supports-preserve-symlinks-flag": "^1.0.0" 944 | }, 945 | "bin": { 946 | "resolve": "bin/resolve" 947 | }, 948 | "funding": { 949 | "url": "https://github.com/sponsors/ljharb" 950 | } 951 | }, 952 | "node_modules/semver": { 953 | "version": "5.7.2", 954 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 955 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", 956 | "bin": { 957 | "semver": "bin/semver" 958 | } 959 | }, 960 | "node_modules/shebang-command": { 961 | "version": "1.2.0", 962 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 963 | "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", 964 | "dependencies": { 965 | "shebang-regex": "^1.0.0" 966 | }, 967 | "engines": { 968 | "node": ">=0.10.0" 969 | } 970 | }, 971 | "node_modules/shebang-regex": { 972 | "version": "1.0.0", 973 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 974 | "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", 975 | "engines": { 976 | "node": ">=0.10.0" 977 | } 978 | }, 979 | "node_modules/signal-exit": { 980 | "version": "3.0.7", 981 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 982 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 983 | }, 984 | "node_modules/signale": { 985 | "version": "1.4.0", 986 | "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", 987 | "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", 988 | "dependencies": { 989 | "chalk": "^2.3.2", 990 | "figures": "^2.0.0", 991 | "pkg-conf": "^2.1.0" 992 | }, 993 | "engines": { 994 | "node": ">=6" 995 | } 996 | }, 997 | "node_modules/spdx-correct": { 998 | "version": "3.2.0", 999 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", 1000 | "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", 1001 | "dependencies": { 1002 | "spdx-expression-parse": "^3.0.0", 1003 | "spdx-license-ids": "^3.0.0" 1004 | } 1005 | }, 1006 | "node_modules/spdx-exceptions": { 1007 | "version": "2.5.0", 1008 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", 1009 | "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" 1010 | }, 1011 | "node_modules/spdx-expression-parse": { 1012 | "version": "3.0.1", 1013 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 1014 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 1015 | "dependencies": { 1016 | "spdx-exceptions": "^2.1.0", 1017 | "spdx-license-ids": "^3.0.0" 1018 | } 1019 | }, 1020 | "node_modules/spdx-license-ids": { 1021 | "version": "3.0.20", 1022 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", 1023 | "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==" 1024 | }, 1025 | "node_modules/strip-bom": { 1026 | "version": "3.0.0", 1027 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1028 | "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", 1029 | "engines": { 1030 | "node": ">=4" 1031 | } 1032 | }, 1033 | "node_modules/strip-eof": { 1034 | "version": "1.0.0", 1035 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 1036 | "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", 1037 | "engines": { 1038 | "node": ">=0.10.0" 1039 | } 1040 | }, 1041 | "node_modules/strip-indent": { 1042 | "version": "3.0.0", 1043 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", 1044 | "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", 1045 | "dependencies": { 1046 | "min-indent": "^1.0.0" 1047 | }, 1048 | "engines": { 1049 | "node": ">=8" 1050 | } 1051 | }, 1052 | "node_modules/supports-color": { 1053 | "version": "5.5.0", 1054 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1055 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1056 | "dependencies": { 1057 | "has-flag": "^3.0.0" 1058 | }, 1059 | "engines": { 1060 | "node": ">=4" 1061 | } 1062 | }, 1063 | "node_modules/supports-preserve-symlinks-flag": { 1064 | "version": "1.0.0", 1065 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1066 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1067 | "engines": { 1068 | "node": ">= 0.4" 1069 | }, 1070 | "funding": { 1071 | "url": "https://github.com/sponsors/ljharb" 1072 | } 1073 | }, 1074 | "node_modules/trim-newlines": { 1075 | "version": "3.0.1", 1076 | "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", 1077 | "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", 1078 | "engines": { 1079 | "node": ">=8" 1080 | } 1081 | }, 1082 | "node_modules/type-fest": { 1083 | "version": "0.18.1", 1084 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", 1085 | "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", 1086 | "engines": { 1087 | "node": ">=10" 1088 | }, 1089 | "funding": { 1090 | "url": "https://github.com/sponsors/sindresorhus" 1091 | } 1092 | }, 1093 | "node_modules/validate-npm-package-license": { 1094 | "version": "3.0.4", 1095 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1096 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1097 | "dependencies": { 1098 | "spdx-correct": "^3.0.0", 1099 | "spdx-expression-parse": "^3.0.0" 1100 | } 1101 | }, 1102 | "node_modules/which": { 1103 | "version": "1.3.1", 1104 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1105 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1106 | "dependencies": { 1107 | "isexe": "^2.0.0" 1108 | }, 1109 | "bin": { 1110 | "which": "bin/which" 1111 | } 1112 | }, 1113 | "node_modules/wrappy": { 1114 | "version": "1.0.2", 1115 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1116 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1117 | }, 1118 | "node_modules/yallist": { 1119 | "version": "4.0.0", 1120 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1121 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1122 | }, 1123 | "node_modules/yargs-parser": { 1124 | "version": "20.2.9", 1125 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 1126 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 1127 | "engines": { 1128 | "node": ">=10" 1129 | } 1130 | } 1131 | } 1132 | } 1133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marcus-scripts", 3 | "version": "1.0.0", 4 | "description": "Random scripts", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/M-rcus/marcus-scripts.git" 12 | }, 13 | "author": "Marcus", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/M-rcus/marcus-scripts/issues" 17 | }, 18 | "homepage": "https://github.com/M-rcus/marcus-scripts#readme", 19 | "dependencies": { 20 | "axios": "^1.6.0", 21 | "clipboardy": "^2.3.0", 22 | "meow": "^9.0.0", 23 | "signale": "^1.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pixeldrain-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # - `curl` - Used for uploading 5 | # - `jq` - Used for extracting the file URL from the API 6 | # - `date` - Used for logging and data filenames in `output` 7 | # 8 | # For Debian/Ubuntu: `apt install curl jq` (as root/sudo) 9 | 10 | usage() 11 | { 12 | cat << EOF 13 | usage: $0 [FileName] 14 | 15 | Uploads files to Pixeldrain 16 | 17 | OPTIONS: 18 | -h Show this message 19 | -s Short format output: "\$FILENAME => \$URL". Short format will still display errors. 20 | -b Short format only: Wrap URL and filename into "list-format" BBCode, you know... for sharing. 21 | Example: \`[*][URL='https://pixeldrain/u/FILE-ID']filename-blah.mp4[/URL]\` 22 | -k Pixeldrain API key. Can also be defined via the \`PIXELDRAIN_API_KEY\` environment variable. 23 | If this option is specified, it will override the environment variable. 24 | -i Echoes only the file ID, nothing else. This flag is used by the \`pixeldrain-folder-upload.sh\` wrapper. 25 | This will still output errors. Errors should (hopefully) display with a non-zero exit code. 26 | This will also cause short format options (-s and -b) to be ignored. 27 | EOF 28 | } 29 | 30 | # Print usage when no parameters specified. 31 | if [[ -z "$@" ]]; then 32 | usage 33 | exit 0 34 | fi 35 | 36 | SHORT_FORMAT=0; 37 | BBCODE_FORMAT=0; 38 | ONLY_ID=0; 39 | 40 | while getopts "hsbk:i" opt; do 41 | case $opt in 42 | h) 43 | usage 44 | exit 0 45 | ;; 46 | s) 47 | SHORT_FORMAT=1; 48 | ;; 49 | b) 50 | BBCODE_FORMAT=1; 51 | ;; 52 | k) 53 | PIXELDRAIN_API_KEY="${OPTARG}"; 54 | ;; 55 | i) 56 | ONLY_ID=1; 57 | 58 | # Should bypass all the other if statements 59 | SHORT_FORMAT=2; 60 | BBCODE_FORMAT=2; 61 | ;; 62 | \?) 63 | echo "Invalid option: -$OPTARG" >&2 64 | exit 1 65 | ;; 66 | :) 67 | echo "Option -$OPTARG requires an argument." >&2 68 | exit 1 69 | ;; 70 | esac 71 | done 72 | 73 | SCRIPT_DIR="$(dirname "$0")"; 74 | 75 | shift $((OPTIND - 1)); 76 | 77 | DATE="$(date +"%Y%m%d_%H%I%S")"; 78 | RESPONSE_FILE="${SCRIPT_DIR}/output/${DATE}_pixeldrain.json"; 79 | 80 | FILE_NAME="$@"; 81 | 82 | if [[ $SHORT_FORMAT == 0 ]]; then 83 | echo "Uploading file: ${FILE_NAME}"; 84 | fi 85 | 86 | if [[ ! -z "${PIXELDRAIN_API_KEY}" ]]; then 87 | CURL_ARGS+=" -u :${PIXELDRAIN_API_KEY}"; 88 | fi 89 | 90 | # "URL encoding". Aka replacing the most common issues I encounter with my filenames lol 91 | # If anyone is some form of Bash guru, feel free to PR with a better solution 92 | URL_FILE_NAME="${FILE_NAME}"; 93 | URL_FILE_NAME="${URL_FILE_NAME/\#/%23}"; 94 | URL_FILE_NAME="${URL_FILE_NAME/ /%20}"; 95 | 96 | if [[ $SHORT_FORMAT == 1 || $ONLY_ID == 1 ]]; then 97 | curl -s -X PUT -T "${FILE_NAME}" $CURL_ARGS "https://pixeldrain.com/api/file/${URL_FILE_NAME}" -o "${RESPONSE_FILE}"; 98 | else 99 | curl --progress-bar -X PUT -T "${FILE_NAME}" $CURL_ARGS "https://pixeldrain.com/api/file/${URL_FILE_NAME}" -o "${RESPONSE_FILE}" | tee; 100 | fi 101 | 102 | UPLOAD_EXITCODE=$? 103 | 104 | if [[ $SHORT_FORMAT == 0 ]]; then 105 | echo "Saving Pixeldrain API response for ${FILE_NAME} to: ${RESPONSE_FILE}"; 106 | fi 107 | 108 | RESPONSE="$(cat "${RESPONSE_FILE}")"; 109 | STATUS="$(jq -r '.success' <<< "${RESPONSE}")"; 110 | 111 | # Ignore short format on errors 112 | if [[ $STATUS == "false" ]]; then 113 | echo "File upload failed"; 114 | jq -r '[.value, .message]' <<< "${RESPONSE}"; 115 | exit 1; 116 | fi 117 | 118 | # I'm not sure this will ever trigger, but maybe if the API times out and doesn't give us a response? 119 | if [[ $UPLOAD_EXITCODE != 0 ]]; then 120 | echo "File upload failed"; 121 | echo "Some unknown error occurred when uploading the file"; 122 | exit 1; 123 | fi 124 | 125 | RESPONSE="$(cat "${RESPONSE_FILE}")"; 126 | FILE_ID="$(jq -r '.id' <<< "${RESPONSE}")"; 127 | FILE_URL="https://pixeldrain.com/u/${FILE_ID}"; 128 | 129 | if [[ $SHORT_FORMAT == 1 ]]; then 130 | if [[ $BBCODE_FORMAT == 1 ]]; then 131 | echo "[*][URL='${FILE_URL}']${FILE_NAME}[/URL]"; 132 | else 133 | echo "${FILE_NAME} => ${FILE_URL}"; 134 | fi 135 | 136 | exit 0; 137 | fi 138 | 139 | if [[ $ONLY_ID == 1 ]]; then 140 | echo -n "${FILE_ID}"; 141 | exit 0; 142 | fi 143 | 144 | echo "File uploaded: ${FILE_URL}"; -------------------------------------------------------------------------------- /replace-bbcodes.js: -------------------------------------------------------------------------------- 1 | #!node 2 | const clipboardy = require('clipboardy'); 3 | 4 | /** 5 | * @type {Object} 6 | */ 7 | const regs = { 8 | bold: { 9 | regex: /\[B\]/g, 10 | replace: '# ', 11 | }, 12 | boldEnd: { 13 | regex: /\[\/B\]/g, 14 | replace: '', 15 | }, 16 | strikethrough: { 17 | regex: /\[\/?S\]/g, 18 | replace: '~~', 19 | }, 20 | listItem: { 21 | regex: /\[\*\]/g, 22 | replace: '- ', 23 | }, 24 | icode: { 25 | regex: /\[\/?\ICODE\]/gi, 26 | replace: '`', 27 | }, 28 | code: { 29 | regex: /\[\/?\CODE\]/gi, 30 | replace: '```', 31 | }, 32 | url: { 33 | regex: /\[URL='?(.*)'?](.*)\[\/URL\]/g, 34 | replace: '[$2]($1)', 35 | }, 36 | img: { 37 | regex: /\[IMG\](.*)\[\/IMG\]/g, 38 | replace: '![]($1)', 39 | }, 40 | remove: { 41 | regex: /\[\/?(B|LIST|COLOR|)(=rgb\([\d, ]+\))?\]/g, 42 | replace: '', 43 | }, 44 | }; 45 | 46 | const input = clipboardy.readSync(); 47 | 48 | if (!input) { 49 | console.error('Nothing in clipboard'); 50 | process.exit(1); 51 | } 52 | 53 | const removeMatches = input.match(regs.remove.regex); 54 | const urlsMatches = input.match(regs.url.regex); 55 | 56 | if ((!removeMatches || !urlsMatches) || removeMatches.length < 1 && urlsMatches.length < 1) { 57 | console.error('Initial checks could not find any valid BBCodes in clipboard. Script may not do anything.'); 58 | } 59 | 60 | /** 61 | * Order: 62 | * - Split string by newline into array 63 | * - Replace BBCode for the FIRST line. 64 | * - Join array by newlines 65 | * - Loop through `regs` to replace the rest: 66 | * - strikethrough 67 | * - urls 68 | * - remove 69 | */ 70 | const split = input.split('\n'); 71 | split[0] = split[0].replace(regs.bold.regex, regs.bold.replace); 72 | let text = split.join('\n'); 73 | 74 | delete regs.bold; 75 | 76 | for (const name in regs) 77 | { 78 | const params = regs[name]; 79 | text = text.replace(params.regex, params.replace); 80 | } 81 | 82 | /** 83 | * Replace template text no one cares about. 84 | */ 85 | text = text.replace(/^Download links:/m, ''); 86 | text = text.replace(/^I highly recommend an.+$/m, ''); 87 | 88 | /** 89 | * Replace excessive newlines with a maximum of two. 90 | */ 91 | text = text.replace(/\n{2,}/g, "\n\n"); 92 | 93 | /** 94 | * Special handling for 'QUOTE' bbcode 95 | */ 96 | let inQuote = false; 97 | const lines = text.split('\n'); 98 | for (const lineIdx in lines) 99 | { 100 | let line = lines[lineIdx]; 101 | const lower = line.toLowerCase(); 102 | if (!inQuote && !lower.includes('[quote')) { 103 | continue; 104 | } 105 | 106 | inQuote = true; 107 | 108 | /** 109 | * Add quote arrows where applicable. 110 | * 111 | * First we need to remove the BBCode 112 | * Then all following lines also need to have quote arrows 113 | */ 114 | line = line.replace(/^\[quote(=[\w\s]+\]?)/i, ''); 115 | line = line.replace(/^/, '> '); 116 | line = line.replace(/\[\/quote\]/i, ''); 117 | lines[lineIdx] = line; 118 | 119 | /** 120 | * This is the end of the quote, so we stop quoting. 121 | */ 122 | if (lower.includes('[/quote]')) { 123 | inQuote = false; 124 | } 125 | } 126 | 127 | text = lines.join('\n'); 128 | 129 | console.log(text); 130 | clipboardy.writeSync(text); 131 | console.log('Written to clipboard.'); 132 | -------------------------------------------------------------------------------- /ss-minio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Example usage: 4 | # - `xfce4-screenshooter -r -o "/home/marcus/projects/scripts/ss-minio.sh"` 5 | # - `./ss-minio.sh /home/marcus/Pictures/MyCuteCat.png` 6 | 7 | # Requirements: 8 | # - xclip 9 | # - mc (Minio CLI) 10 | # - notify-send (libnotify) - Optional: Only if desktop notifications are enabled. If notifications are not enabled, URL will be silently copied to the clipboard. 11 | 12 | # Generate random name 13 | SS_FILENAME="$(date '+%Y-%m-%d')_$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1).png"; 14 | # URL prefix (will be used for clipboard copy) 15 | URL_PREFIX="https://i.marcus.pw/ss"; 16 | # Connection alias (media), bucket (public), folder/path inside bucket (ss - optional) 17 | BUCKET_PATH="media/public/ss"; 18 | # Location for mc (Minio CLI) 19 | MC_PATH="/usr/bin/mc"; 20 | 21 | # Enable desktop notifications (requires libnotify) 22 | # Change to `1` to ENABLE desktop notifications. 23 | DESKTOP_NOTIFY_ENABLE=1; 24 | # Notifications: Icon shown 25 | NOTIFY_ICON_PATH="$HOME/Pictures/Apps/Camera_cropped.png"; 26 | # Notifications: Timeout in milliseconds 27 | NOTIFY_TIMEOUT=2500; 28 | 29 | # Copy input to bucket 30 | $MC_PATH cp "$1" "$BUCKET_PATH/$SS_FILENAME"; 31 | 32 | # Format URL and copy to clipboard 33 | SS_URL="$URL_PREFIX/$SS_FILENAME"; 34 | echo $SS_URL | xclip -selection clipboard; 35 | 36 | if [[ $DESKTOP_NOTIFY_ENABLE -eq 1 ]]; then 37 | notify-send -t $NOTIFY_TIMEOUT -u low -i $NOTIFY_ICON_PATH "Screenshot uploaded and URL copied to clipboard:" $SS_URL; 38 | fi -------------------------------------------------------------------------------- /twitch-founders.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | URL="https://founders.titty.stream/?channel="; 4 | 5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"; 6 | OUTPUT_DIR="${SCRIPT_DIR}/output"; 7 | 8 | if [[ -d "${OUTPUT_DIR}" ]]; then 9 | mkdir -p "${OUTPUT_DIR}"; 10 | fi 11 | 12 | OUTPUT="${OUTPUT_DIR}/twitch_founders.txt"; 13 | NOW="$(date +"%Y-%m-%d_%s")"; 14 | 15 | if [[ -f "${OUTPUT}" ]]; then 16 | mv "${OUTPUT}" "${OUTPUT_DIR}/twitch_founders.${NOW}.txt"; 17 | fi 18 | 19 | while read channel; 20 | do 21 | DATA="$(curl -fsSL "${URL}${channel}")"; 22 | FOUNDERS_COUNT="$(jq '.subscription.founders.count' <<< "${DATA}")"; 23 | 24 | # No founders available 25 | # Either too many subs or no sub button. 26 | if [[ $FOUNDERS_COUNT -lt 1 ]]; then 27 | continue; 28 | fi 29 | 30 | echo $channel >> "${OUTPUT}"; 31 | done < "$@"; -------------------------------------------------------------------------------- /upload-minio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RANDOM_NAME="$(date '+%Y-%m-%d')_$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 6 | head -n 1)"; 4 | # URL prefix (will be used for clipboard copy) 5 | URL_PREFIX="https://i.marcus.pw/ss"; 6 | # Connection alias (media), bucket (public), folder/path inside bucket (ss - optional) 7 | BUCKET_PATH="media/public/ss"; 8 | # Location for mc (Minio CLI) 9 | MC_PATH="/usr/bin/mc"; 10 | 11 | # Enable desktop notifications (requires libnotify) 12 | # Change to `1` to ENABLE desktop notifications. 13 | DESKTOP_NOTIFY_ENABLE=1; 14 | # Notifications: Icon shown 15 | NOTIFY_ICON_PATH="$HOME/Pictures/Apps/Camera_cropped.png"; 16 | # Notifications: Timeout in milliseconds 17 | NOTIFY_TIMEOUT=2500; 18 | 19 | INPUT_FILE="$@"; 20 | INPUT_BASE=$(basename -- "$INPUT_FILE"); 21 | INPUT_EXT="${INPUT_BASE##*.}" 22 | 23 | OUTPUT_FILE="${RANDOM_NAME}.${INPUT_EXT}"; 24 | 25 | # Copy input to bucket 26 | $MC_PATH cp "${INPUT_FILE}" "$BUCKET_PATH/$OUTPUT_FILE"; 27 | 28 | # Format URL and copy to clipboard 29 | FILE_URL="$URL_PREFIX/$OUTPUT_FILE"; 30 | 31 | echo $FILE_URL; 32 | echo -n $FILE_URL | xclip -selection clipboard; 33 | 34 | if [[ $DESKTOP_NOTIFY_ENABLE -eq 1 ]]; then 35 | notify-send -t $NOTIFY_TIMEOUT -u low -i $NOTIFY_ICON_PATH "File uploaded and URL copied to clipboard:" $FILE_URL; 36 | fi -------------------------------------------------------------------------------- /vcs-folder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: 4 | # 1. Navigate to folder 5 | # 2. Assuming `vcs-folder` is in your $PATH somewhere, run `vcs-folder` or the direct path to script `~/Downloads/vcs-folder.sh` 6 | # 7 | # This also assumes you've created a VCS configuration file that contains all the options you want: 8 | # https://p.outlyer.net/vcs/docs/conf_files 9 | 10 | for vid in *.{mp4,mkv,mov}; 11 | do 12 | vcs "$vid"; 13 | done --------------------------------------------------------------------------------