├── .npmignore ├── .gitignore ├── src ├── defaults.ts ├── consts.ts ├── types.ts └── request.ts ├── tsconfig.json ├── package.json ├── .github └── workflows │ └── Publish.yml ├── README.md └── index.ts /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **.*.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/ 2 | node_modules/ 3 | test.* 4 | build/ -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | import pkg from '../package.json' 2 | 3 | export default { 4 | killswitch: "https://yiff.click", 5 | useragent: `yiff/v${pkg.version} (wrwlf.de/yiff);` 6 | } -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // ! Supported APIs 3 | direct: { 4 | e621: "https://e621.net/posts.json", 5 | e926: "https://e926.net/posts.json", 6 | yiffrest: "https://v2.yiff.rest/", 7 | floofy: "https://api.floofy.dev", 8 | shibe: "https://shibe.online/api/", 9 | thaldrin: "https://thaldr.in/api/v1", 10 | fox: "https://randomfox.ca/floof/", 11 | sheri: "https://sheri.bot/api", 12 | gelbooru: "https://gelbooru.com/" 13 | 14 | }, 15 | killswitch: { 16 | e621: "/e6", 17 | e926: "/e9", 18 | yiffrest: "/yiffrest", 19 | floofy: "/floofydev", 20 | shibe: "/shibe", 21 | fox: "/fox", 22 | sheri: "/sheri", 23 | gelbooru: "/gel", 24 | thaldrin: "/thaldrin", 25 | 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "allowJs": true, 6 | "target": "esnext", 7 | "baseUrl": "src", 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "module": "commonjs", 13 | "paths": {}, 14 | "typeRoots": ["./node_modules/@types", "./src/"], 15 | "inlineSourceMap": true, 16 | "strict": true, 17 | "declaration": true, 18 | "strictBindCallApply": true, 19 | "strictPropertyInitialization": false 20 | }, 21 | "include": ["**/*"], 22 | "exclude": [ 23 | "node_modules", 24 | "build", 25 | "out", 26 | "tmp", 27 | "logs", 28 | "test", 29 | "old", 30 | "test.js" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiff", 3 | "version": "3.1.3", 4 | "description": "Wrapper for various Furry APIs", 5 | "main": "build/index.js", 6 | "types": "index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/hokkqi/yiff.git" 10 | }, 11 | "scripts": { 12 | "build": "rm -rf build/ && tsc", 13 | "dev": "nodemon --ignore \"build/\" -e js,ts --exec \"npm run build && ts-node test.ts\"" 14 | }, 15 | "keywords": [ 16 | "yiff", 17 | "furry", 18 | "booru", 19 | "api", 20 | "image", 21 | "discord", 22 | "bot", 23 | "e621", 24 | "e926", 25 | "wrapper" 26 | ], 27 | "author": "hokkqi", 28 | "license": "MIT", 29 | "dependencies": { 30 | "axios": "^0.21.4", 31 | "chalk": "^4.0.0" 32 | }, 33 | "devDependencies": { 34 | "typescript": "^4.2.3" 35 | }, 36 | "engines": { 37 | "node": ">=14" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type config = { 2 | /** 3 | * Custom Useragent which gets sent 4 | * @default yiff/v{currentVersion} (wrwlf.de) 5 | */ 6 | useragent?: string; 7 | /** 8 | * [Killswitch](https://wrwlf.de/killswitch) Settings 9 | */ 10 | killswitch?: { 11 | /** 12 | * Decides if requests are routed through a [Killswitch](https://wrwlf.de/killswitch) Instance or sent to the API directly 13 | * @default true 14 | */ 15 | enabled?: boolean 16 | /** 17 | * Custom [Killswitch](https://wrwlf.de/killswitch) Instance to use 18 | * @default "https://yiff.click" 19 | */ 20 | instance?: string 21 | } 22 | /** 23 | * Your API Keys 24 | */ 25 | apikey?: { 26 | 27 | /** 28 | * API Key for sheri.bot 29 | */ 30 | sheri?: string; 31 | /** 32 | * API Key for sheri.bot 33 | */ 34 | thaldrin?: string; 35 | /** 36 | * API Key for yiff.rest 37 | */ 38 | yiffrest?: string; 39 | /** 40 | * API Key for e621.net 41 | */ 42 | e621?: string; 43 | /** 44 | * API Key for e926.net 45 | */ 46 | e926?: string; 47 | } 48 | | undefined; 49 | }; -------------------------------------------------------------------------------- /.github/workflows/Publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | 7 | jobs: 8 | compile: 9 | name: Compile latest Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: install node v14 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 14 17 | - name: yarn install 18 | run: yarn install 19 | - name: tsc 20 | uses: icrawl/action-tsc@v1 21 | 22 | publish: 23 | name: Publish to NPM 24 | runs-on: ubuntu-latest 25 | # if: contains(github.ref, 'master') # Publish it only if the push comes from the master branch 26 | needs: compile # We need to wait for the build to be committed before publishing 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v2 31 | with: 32 | ref: master 33 | 34 | - name: Set up Node.js for NPM 35 | uses: actions/setup-node@v1 36 | with: 37 | registry-url: "https://registry.npmjs.org" 38 | 39 | - name: Install dependencies 40 | run: npm install 41 | 42 | - name: TSC / Compile Again 43 | uses: icrawl/action-tsc@v1 44 | 45 | - name: Release 46 | if: ${{ !env.ACT}} 47 | uses: ph-fritsche/action-release@v1 48 | env: 49 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yiff by [Lio](https://wrwlf.co "my homepage") 2 | 3 | [![NPM](https://nodei.co/npm/yiff.png)](https://nodei.co/npm/yiff/) 4 | 5 | ## **VERSION 3 IS A BREAKING CHANGE** 6 | ## NodeJS V14 or higher is required! 7 | 8 | ### **\*Do not** update from v2 if your current code is still based on v2\* 9 | 10 | If you need Support or want more Information, join [my discord server](https://discord.gg/He2822y "a link to my discord server") 11 | 12 | 13 | ## ⭐️ Supported APIs 14 | 15 | - e621.net 16 | - e926.net 17 | - [yiff.rest](https://yiff.rest) 18 | - [sheri.bot](https://sheri.bot) 19 | - [shibe.online](https://shibe.online) 20 | - randomfox.ca 21 | - [api.floofy.dev](https://floofy.dev) 22 | - [gelbooru.com](https://gelbooru.com) 23 | - [thaldr.in](https://thaldr.in) 24 | 25 | **Yiff, by default, uses the main [Killswitch Instance](https://yiff.click) as a Proxy for any Requests.** 26 | 27 | # Usage 28 | 29 | ```ts 30 | import Yiff from "yiff"; 31 | 32 | // every object in the config is optional 33 | let config = { 34 | useragent: `Project/version (website)`; 35 | killswitch: { 36 | enabled: true 37 | instance: "https://yiff.click" 38 | } 39 | apikey: { 40 | sheri: ""; 41 | yiffrest: ""; 42 | e621: ""; 43 | e926: ""; 44 | } 45 | } 46 | const yiff = new Yiff(config); 47 | 48 | let tags = "werewolf male/male" 49 | let limit = 1 50 | // Basically: 51 | 52 | yiff.e621(tags, limit).then((r) => {console.log("e621", r)}) 53 | // => Unfiltered E621 Response 54 | 55 | yiff.e926(tags, limit).then((r) => {console.log("e926", r)}) 56 | // => Unfiltered E926 Response 57 | 58 | yiff.yiffy('animals', 'birb').then((r) => {console.log("yiffy", r)}) 59 | // => Unfiltered yiff.rest Response 60 | 61 | yiff.floofy().then((r) => {console.log("floofy", r)}) 62 | // => Unfiltered api.floofy.dev Response 63 | 64 | yiff.sheri('paws').then((r) => {console.log("sheri", r)}) 65 | // => Unfiltered sheri.bot Response 66 | 67 | yiff.shibe("shibes", 2).then((r) => {console.log("shibes", r)}) 68 | // => Unfiltered shibe.online Response 69 | 70 | yiff.fox().then((r) => {console.log("fox", r)}) 71 | // => Unfiltered randomfox.ca Response 72 | 73 | yiff.thaldrin("categories/foxes").then((r) => {console.log("thaldrin", r)}) 74 | // => Unfiltered thaldr.in Response 75 | ``` 76 | 77 | ## ❓ FAQ 78 | 79 | > **Q:** Will you add other APIs as well?\ 80 | > **A:** Depends on if they're either free or I get an all-access token, as well as if there's proper documentation for the API 81 | 82 | ## Contributing & Issues 83 | 84 | Feel free to make a Pull request and fix some things, otherwise just open a Issue or join [my discord server](https://discord.gg/He2822y) and let me know what you want added! 85 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import pgk from "./package.json" 3 | import defaults from "./src/defaults"; 4 | import { config } from "./src/types"; 5 | import consts from "./src/consts"; 6 | import request from "./src/request"; 7 | 8 | class Yiff { 9 | private config: config 10 | private useragent: string 11 | private killswitch: { enabled: boolean, instance: string } 12 | 13 | constructor(config?: config) { 14 | this.config = config 15 | this.useragent = this.config?.useragent ? `${defaults.useragent} ${this.config.useragent}` : defaults.useragent 16 | // console.log(config) 17 | this.killswitch = { 18 | enabled: config?.killswitch?.enabled ?? true, 19 | instance: config?.killswitch?.instance || defaults.killswitch 20 | 21 | } 22 | } 23 | 24 | /** 25 | * Request random posts from e621 26 | * @param tags The Tags your want to use for your Search 27 | * @param limit the amount of posts you want back 28 | * @param page the page you want to request 29 | * @returns A Unfiltered e621 Response 30 | */ 31 | async e621(tags: string | string[], limit?: number, page?: number) { 32 | try { 33 | let res = await request({ 34 | site: "e621", 35 | limit, tags, page, 36 | useragent: this.useragent, 37 | killswitch: this.killswitch, 38 | apikey: this.config?.apikey?.e621 39 | }) 40 | return res 41 | } catch (error) { 42 | console.error(error) 43 | } 44 | } 45 | /** 46 | * Request random posts from gelbooru 47 | * @param tags The Tags your want to use for your Search 48 | * @param limit the amount of posts you want back 49 | * @returns A Unfiltered e621 Response 50 | */ 51 | async gelbooru(tags: string | string[], limit?: number) { 52 | try { 53 | let res = await request({ 54 | site: "gelbooru", 55 | limit, tags, 56 | useragent: this.useragent, 57 | killswitch: this.killswitch, 58 | 59 | }) 60 | return res 61 | } catch (error) { 62 | console.error(error) 63 | } 64 | } 65 | /** 66 | * Request random posts from e926 67 | * @param tags The Tags your want to use for your Search 68 | * @param limit the amount of posts you want back 69 | * @param page the page you want to request 70 | * @returns A Unfiltered e926 Response 71 | */ 72 | 73 | async e926(tags: string | string[], limit?: number, page?: number) { 74 | try { 75 | let res = await request({ 76 | site: "e926", 77 | limit, tags, page, 78 | useragent: this.useragent, 79 | killswitch: this.killswitch, 80 | apikey: this.config?.apikey?.e926 81 | }) 82 | return res 83 | } catch (error) { 84 | console.error(error) 85 | } 86 | } 87 | /** 88 | * Request an Image from yiff.rest 89 | * @param category the category you want an image from 90 | * @param endpoint a valid endpoint of the category you want an image from 91 | * @returns Unfiltered yiff.rest Response 92 | */ 93 | async yiffy(category: string, endpoint: string) { 94 | try { 95 | let res = await request({ 96 | site: "yiffrest", 97 | category, endpoint, 98 | useragent: this.useragent, 99 | killswitch: this.killswitch, 100 | apikey: this.config?.apikey?.yiffrest 101 | }) 102 | return res 103 | } catch (error) { 104 | console.error(error) 105 | } 106 | } 107 | /** 108 | * Request an Image from the Sheri.Bot API *(most endpoints require a API Key)* 109 | * @param endpoint a valid endpoint you want an image from 110 | * @returns Unfiltered sheri.bot Response 111 | */ 112 | async sheri(endpoint: string) { 113 | try { 114 | let res = await request({ 115 | site: "sheri", 116 | endpoint, 117 | useragent: this.useragent, 118 | killswitch: this.killswitch, 119 | apikey: this.config?.apikey?.sheri 120 | }) 121 | return res 122 | } catch (error) { 123 | console.error(error) 124 | } 125 | } 126 | /** 127 | * Request a random Image from api.floofy.dev (ALL NSFW) 128 | * @returns Unfiltered api.floofy.dev Response 129 | */ 130 | async floofy() { 131 | try { 132 | let res = await request({ 133 | site: "floofy", 134 | useragent: this.useragent, 135 | killswitch: this.killswitch, 136 | }) 137 | return res 138 | } catch (error) { 139 | console.error(error) 140 | } 141 | } 142 | /** 143 | * Request a random Image from shibe.online 144 | * @param animal the animal you want an image of 145 | * @param limit the amount of images you want back 146 | * @returns Unfiltered shibe.online Response 147 | */ 148 | async shibe(animal: string, limit: number) { 149 | try { 150 | let res = await request({ 151 | site: "shibe", 152 | animal, limit, 153 | useragent: this.useragent, 154 | killswitch: this.killswitch, 155 | 156 | }) 157 | return res 158 | } catch (error) { 159 | console.error(error) 160 | } 161 | } 162 | /** 163 | * Get a random fox from randomfox.ca 164 | * @returns Unfiltered randomfox.ca Response 165 | */ 166 | async fox() { 167 | try { 168 | let res = await request({ 169 | site: "fox", 170 | useragent: this.useragent, 171 | killswitch: this.killswitch, 172 | 173 | }) 174 | return res 175 | } catch (error) { 176 | console.error(error) 177 | } 178 | } 179 | /** 180 | * Request an Image from Thaldr.in's API *(Endpoints require an API Key)* 181 | * @param endpoint a valid endpoint you want an image from 182 | * @returns Unfiltered thaldr.in/api Response 183 | */ 184 | async thaldrin(endpoint: string) { 185 | try { 186 | let res = await request({ 187 | site: "thaldrin", 188 | endpoint, 189 | useragent: this.useragent, 190 | killswitch: this.killswitch, 191 | apikey: this.config?.apikey?.thaldrin 192 | }) 193 | return res 194 | } catch (error) { 195 | console.error(error) 196 | } 197 | } 198 | 199 | 200 | } 201 | 202 | Yiff.default = Yiff; 203 | export = Yiff; -------------------------------------------------------------------------------- /src/request.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import c from "./consts"; 3 | export default async function request(options: 4 | { 5 | site: "e621" | "e926" | "gelbooru" | "furrybot" | "yiffrest" | "floofy" | "sheri" | "fox" | "shibe" | "thaldrin", 6 | killswitch?: { 7 | enabled?: boolean, 8 | instance?: string 9 | }, 10 | tags?: string | string[], 11 | apikey?: { 12 | sheri?: string; 13 | yiffrest?: string; 14 | e621?: string; 15 | e926?: string; 16 | } 17 | limit?: number, 18 | page?: number, 19 | useragent?: string, 20 | animal?: string 21 | category?: string 22 | endpoint?: string 23 | } 24 | ) { 25 | 26 | switch (options.site) { 27 | case 'e621': 28 | if (!options.tags) throw Error("No Tags provided") 29 | if (options.page && options.page > 750) throw Error("You cannot go beyond page 750") 30 | let e6request = await axios({ 31 | method: 'get', 32 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.e621}?limit=${options.limit || 1}&page=${options.page || 1}&tags=${options.tags}&useragent=${options.useragent}` : 33 | `${c.direct.e621}?page=${options.page || 1}&tags=limit:${options.limit || 1} order:random -young ${options.tags.toString()}`, 34 | headers: { 35 | "User-Agent": options.useragent, 36 | // @ts-ignore 37 | ...(options?.apikey?.e621 ? { 38 | // @ts-ignore 39 | "Authorization": options?.apikey?.e621 40 | } : {}) 41 | } 42 | }) 43 | 44 | return e6request.data 45 | case 'e926': 46 | if (!options.tags) throw Error("No Tags provided") 47 | if (options.page && options.page > 750) throw Error("You cannot go beyond page 750") 48 | let e9request = await axios({ 49 | 50 | method: 'get', 51 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.e926}?limit=${options.limit || 1}&page=${options.page || 1}&tags=${options.tags}&useragent=${options.useragent}` : 52 | `${c.direct.e926}?page=${options.page || 1}&tags=limit:${options.limit || 1} order:random -young ${options.tags.toString()}`, 53 | headers: { 54 | "User-Agent": options.useragent, 55 | // @ts-ignore 56 | ...(options?.apikey?.e926 ? { 57 | // @ts-ignore 58 | "Authorization": options?.apikey?.e926 59 | } : {}) 60 | } 61 | }) 62 | return e9request.data 63 | case 'gelbooru': 64 | if (!options.tags) throw Error("No Tags provided") 65 | let gelboorureq = await axios({ 66 | method: 'get', 67 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.gelbooru}?limit=${options.limit || 1}&tags=${options.tags}&useragent=${options.useragent}` : 68 | `${c.direct.gelbooru}/index.php?page=dapi&s=post&json=1&q=index&limit=${Number(options.limit) || 1}&tags=sort:random+${options.tags.toString().split(' ').join("+")}`, 69 | headers: { 70 | "User-Agent": options.useragent, 71 | // @ts-ignore 72 | // ...(options?.apikey?.e926 ? { 73 | // // @ts-ignore 74 | // "Authorization": options?.apikey?.e926 75 | // } : {}) 76 | } 77 | }) 78 | return gelboorureq.data 79 | 80 | 81 | case 'furrybot': 82 | case 'yiffrest': 83 | let customMethod: any; 84 | if (options.killswitch?.enabled) { 85 | customMethod = "POST" 86 | } else { 87 | customMethod = "GET" 88 | } 89 | 90 | let yiffreq = await axios({ 91 | method: customMethod, 92 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.yiffrest}` : `${c.direct.yiffrest}/${options.category}/${options.endpoint}`, 93 | data: { 94 | category: options.category, 95 | endpoint: options.endpoint, 96 | apikey: options.apikey, 97 | useragent: options.useragent 98 | }, 99 | headers: { 100 | "User-Agent": options.useragent, 101 | // @ts-ignore 102 | ...(options.apikey?.yiffrest ? { 103 | // @ts-ignore 104 | "Authorization": options?.apikey?.yiffrest 105 | } : {}) 106 | } 107 | }) 108 | return yiffreq.data 109 | case 'sheri': 110 | if (options.apikey) { 111 | // @ts-ignore 112 | if (!options.apikey.startsWith('Token')) { 113 | // @ts-ignore 114 | options.apikey = `Token ${options.apikey}` 115 | } 116 | } 117 | let sherireq = await axios({ 118 | method: 'get', 119 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.sheri}?endpoint=${options.endpoint}${options.apikey ? `&apikey=${options.apikey}` : ""}` : `${c.direct.sheri}/${options.endpoint}?format=json`, 120 | headers: { 121 | "User-Agent": options.useragent, 122 | // @ts-ignore 123 | ...(options.apikey ? { 124 | // @ts-ignore 125 | "Authorization": options.apikey 126 | } : {}) 127 | } 128 | }) 129 | return sherireq.data 130 | case 'floofy': 131 | let floofyreq = await axios({ 132 | method: 'get', 133 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.floofy}` : `${c.direct.floofy}/yiff`, 134 | headers: { 135 | "User-Agent": options.useragent, 136 | } 137 | }) 138 | return floofyreq.data 139 | case 'shibe': 140 | 141 | let shibereq = await axios({ 142 | method: 'get', 143 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.shibe}?animal=${options.animal}&limit=${options.limit}` : 144 | `${c.direct.shibe}/${options.animal}?count=${options.limit}&urls=true&httpsUrls=true`, 145 | headers: { 146 | "User-Agent": options.useragent, 147 | } 148 | }) 149 | return shibereq.data 150 | case 'fox': 151 | let randomfoxreq = await axios({ 152 | method: 'get', 153 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.fox}` : c.direct.fox, 154 | headers: { 155 | "User-Agent": options.useragent, 156 | } 157 | }) 158 | return randomfoxreq.data 159 | case 'thaldrin': 160 | let thaldrinreq = await axios({ 161 | method: 'get', 162 | url: options.killswitch?.enabled ? `${options.killswitch.instance}${c.killswitch.thaldrin}?endpoint=${options.endpoint}${options.apikey ? `&apikey=${options.apikey}` : ""}` : `${c.direct.thaldrin}/categories/${options.endpoint}`, 163 | headers: { 164 | "User-Agent": options.useragent, 165 | // @ts-ignore 166 | ...(options.apikey ? { 167 | // @ts-ignore 168 | "Authorization": options.apikey 169 | } : {}) 170 | } 171 | }) 172 | return thaldrinreq.data 173 | 174 | 175 | default: 176 | return { 177 | success: false, 178 | message: "No URL provided" 179 | } 180 | } 181 | 182 | 183 | } --------------------------------------------------------------------------------