├── tsconfig.json
├── base-tsconfig.json
├── site
├── images
│ └── waifus
│ │ └── rem.png
├── manifest.json
├── 404.html
├── _layouts
│ └── readme.html
├── index.html
└── style.css
├── site-generator-config.json
├── discord-bot
├── CMakeLists.txt
├── CMakeSettings.json
├── IO_file.h
└── bot.cpp
├── generate-site.sh
├── waifus
├── misty.json
├── ram.json
├── rom.json
├── uni.json
├── d.va.json
├── sagisawa chiho.json
├── vert.json
├── blanc.json
├── megumin.json
├── mercy.json
├── noire.json
├── yuri.json
├── sayori.json
├── kobayashi.json
├── natsuki.json
├── nepgear.json
├── neptune.json
├── lisa lisa.json
├── ahri.json
├── ryuko matoi.json
├── monika.json
├── riko.json
├── rizu ogata.json
├── ko yagami.json
├── lusamine.json
├── mai sakurajima.json
├── nene sakura.json
├── tsubasa hanekawa.json
├── yun iijima.json
├── ayano minegishi.json
├── fumino furuhashi.json
├── umiko ahagon.json
├── hajime shinoda.json
├── maho hiyajo.json
├── rem.json
├── rory mercury.json
├── hiyori tamura.json
├── konata izumi.json
├── megurine luka.json
├── minami iwasaki.json
├── kagami hiiragi.json
├── kurisu makise.json
├── mayuri shiina.json
├── miyuki takara.json
├── zero two.json
├── futaba sakura.json
├── patricia martin.json
├── tsukasa hiiragi.json
├── uruka takemoto.json
├── yunyun.json
├── lotte jansson.json
├── misao kusakabe.json
├── honoka.json
├── shaundi.json
├── shouko nishimiya.json
├── yutaka kobayakawa.json
├── diana cavendish.json
├── lillie.json
├── makoto niijima.json
├── marie rose.json
├── moeka kiryu.json
├── 2b.json
├── asumi kominami.json
├── haru okumura.json
├── kagamine rin.json
├── suzuha amane.json
├── akira kogami.json
├── ann takamaki.json
├── tae takemi.json
├── amanda o'neill.json
├── chika fujiwara.json
├── mafuyu kirisu.json
├── eris.json
├── nezuko.json
├── asuka tanaka.json
├── chloe price.json
├── ichiko ohya.json
├── kumiko oumae.json
├── sadayo kawakami.json
├── tohru honda.json
├── aoba suzukaze.json
├── reina kousaka.json
├── senko.json
├── atsuko kagari.json
├── chitoge kirisaki.json
├── croix meridies.json
├── emilia.json
├── ursula callistis.json
├── darkness.json
├── hifumi togo.json
├── holo.json
├── rin hoshizora.json
├── test dummy.json
├── artoria pendragon.json
├── faris nyannyan.json
├── constanze amalie von braunschbank-albrechtsberger.json
├── maxine caulfield.json
├── shizuku hazuki.json
├── hatsune miku.json
├── sae niijima.json
├── hifumi takimoto.json
├── momiji mochizuki.json
├── elizabeth comstock.json
├── kyoko kirigiri.json
├── junko enoshima.json
├── kinzie kensington.json
├── chihiro fujisaki.json
├── aqua.json
├── chiaki nanami.json
├── chihaya mifune.json
├── aoi asahina.json
├── haruhi suzumiya.json
├── ochaco uraraka.json
├── shinobu oshino.json
└── sucy manbavaran.json
├── site-tsconfig.json
├── site-generator-tsconfig.json
├── .github
└── FUNDING.yml
├── .vscode
├── launch.json
└── settings.json
├── package.json
├── LICENSE
├── .travis.yml
├── .gitignore
├── README.md
├── reference.md
├── site-generator.ts
└── script.ts
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./base-tsconfig",
3 | }
--------------------------------------------------------------------------------
/base-tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strictNullChecks": true
4 | }
5 | }
--------------------------------------------------------------------------------
/site/images/waifus/rem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yourWaifu/is-your-waifu-legal/master/site/images/waifus/rem.png
--------------------------------------------------------------------------------
/site-generator-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "copy-directories": [
3 | "waifus"
4 | ],
5 | "copy-files-in-directories": [
6 | "site",
7 | "tsc-output/site"
8 | ]
9 | }
--------------------------------------------------------------------------------
/discord-bot/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required (VERSION 3.6)
2 | project(example)
3 |
4 | add_executable(waifu-bot bot.cpp)
5 |
6 | add_subdirectory(sleepy-discord)
7 | target_link_libraries(waifu-bot sleepy-discord)
--------------------------------------------------------------------------------
/generate-site.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo Compiling site code
3 | tsc --p site-tsconfig.json
4 | echo Compiling site genertator code
5 | tsc --p site-generator-tsconfig.json
6 | echo running site genertator
7 | node tsc-output/site-generator.js
--------------------------------------------------------------------------------
/waifus/misty.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Misty",
4 | "image" : "https://cdn.bulbagarden.net/upload/f/fb/Misty_SM.png",
5 | "japanese-name" : "カスミ",
6 | "age-in-show": 10,
7 | "sources" : ["https://bulbapedia.bulbagarden.net/wiki/Misty_(anime)"]
8 | }
--------------------------------------------------------------------------------
/site-tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./base-tsconfig",
3 | "compilerOptions": {
4 | "outDir": "tsc-output/site",
5 | "target": "es6"
6 | },
7 | "files": [
8 | "script.ts"
9 | ],
10 | "exclude": [
11 | "**/*.spec.ts",
12 | "node_modules/"
13 | ]
14 | }
--------------------------------------------------------------------------------
/waifus/ram.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ram",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/8/81/Ram_V2.png",
5 | "japanese-name" : "ラム",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Ram"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/rom.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Rom",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/2/24/Rom_V2.png",
5 | "japanese-name" : "ロム",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Rom"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/uni.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Uni",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/5/59/Uni_V2.png",
5 | "japanese-name" : "ユニ",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Uni"]
8 | }
9 |
--------------------------------------------------------------------------------
/site/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Is Waifu legal?",
3 | "name": "Is Your Waifu Legal?",
4 | "start_url": "/is-your-waifu-legal",
5 | "background_color": "#212121",
6 | "display": "standalone",
7 | "scope": "/is-your-waifu-legal/",
8 | "theme_color": "#181818"
9 | }
--------------------------------------------------------------------------------
/waifus/d.va.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "D.Va",
4 | "aliases" : ["Hana Song", "송하나"],
5 | "image" : "https://gamepedia.cursecdn.com/overwatch_gamepedia/b/b0/DVa-portrait.png",
6 | "age-in-show": 19,
7 | "sources" : ["https://overwatch.gamepedia.com/D.Va"]
8 | }
--------------------------------------------------------------------------------
/waifus/sagisawa chiho.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Sagisawa Chiho",
4 | "image" : "https://static.zerochan.net/Sagisawa.Chiho.full.627144.jpg",
5 | "japanese-name" : "鷺澤 千帆",
6 | "sources" : ["https://princess-evangile.fandom.com/wiki/Sagisawa_Chiho"]
7 | }
8 |
--------------------------------------------------------------------------------
/waifus/vert.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Vert",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/3/34/Vert_V2.png",
5 | "japanese-name" : "ベール",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Vert"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/blanc.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Blanc",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/c/cd/Blanc_V2.png",
5 | "japanese-name" : "ブラン",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Blanc"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/megumin.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Megumin",
4 | "image" : "https://vignette.wikia.nocookie.net/konosuba/images/3/3f/Megumin-anime.png",
5 | "japanese-name" : "めぐみん",
6 | "age-in-show": 15,
7 | "sources" : ["https://konosuba.fandom.com/wiki/Megumin"]
8 | }
--------------------------------------------------------------------------------
/waifus/mercy.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Mercy",
4 | "aliases" : ["Angela Ziegler"],
5 | "image" : "https://gamepedia.cursecdn.com/overwatch_gamepedia/d/d2/Mercy-portrait.png",
6 | "age-in-show": 37,
7 | "sources" : ["https://overwatch.gamepedia.com/Mercy"]
8 | }
--------------------------------------------------------------------------------
/waifus/noire.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Noire",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/4/4e/Noire_V2.png",
5 | "japanese-name" : "ノワール",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Noire"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/yuri.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Yuri",
4 | "image" : "https://vignette.wikia.nocookie.net/doki-doki-literature-club/images/0/03/Yuri_Illustration.png",
5 | "age-in-show": 18,
6 | "sources" : ["https://doki-doki-literature-club.fandom.com/wiki/Yuri"]
7 | }
--------------------------------------------------------------------------------
/waifus/sayori.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Sayori",
4 | "image" : "https://vignette.wikia.nocookie.net/doki-doki-literature-club/images/c/ca/Sayori_Illustration.png",
5 | "age-in-show": 18,
6 | "sources" : ["https://doki-doki-literature-club.fandom.com/wiki/Sayori"]
7 | }
--------------------------------------------------------------------------------
/waifus/kobayashi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Kobayashi",
4 | "image" : "https://vignette.wikia.nocookie.net/maid-dragon/images/5/52/Kobayashi_5.png",
5 | "japanese-name" : "小林",
6 | "age-in-show": 25,
7 | "sources" : ["https://maid-dragon.fandom.com/wiki/Kobayashi"]
8 | }
--------------------------------------------------------------------------------
/waifus/natsuki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Natsuki",
4 | "image" : "https://vignette.wikia.nocookie.net/doki-doki-literature-club/images/2/2b/Natsuki_Illustration.png",
5 | "age-in-show": 18,
6 | "sources" : ["https://doki-doki-literature-club.fandom.com/wiki/Natsuki"]
7 | }
--------------------------------------------------------------------------------
/waifus/nepgear.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Nepgear",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/3/3f/Nepgear_V2.png",
5 | "japanese-name" : "ネプギア",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Nepgear"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/neptune.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Neptune",
4 | "image" : "https://vignette.wikia.nocookie.net/neptunia/images/b/bd/Neptune_V2.png",
5 | "japanese-name" : "ネプテューヌ",
6 | "age-in-show": "Ageless",
7 | "sources" : ["https://neptunia.fandom.com/wiki/Neptune"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/lisa lisa.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Lisa Lisa",
4 | "year" : 1888,
5 | "month" : 12,
6 | "age-in-show": 50,
7 | "definitely-legal" : true,
8 | "sources" : ["https://jojo.fandom.com/wiki/Lisa_Lisa", "https://mangarock.com/manga/mrs-serie-290799/chapter/mrs-chapter-290896"]
9 | }
--------------------------------------------------------------------------------
/waifus/ahri.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ahri",
4 | "aliases" : ["The Nine-Tailed Fox"],
5 | "image" : "https://vignette.wikia.nocookie.net/leagueoflegends/images/f/f1/Ahri_OriginalCentered.jpg",
6 | "age-group-by-appearance" : "adult",
7 | "sources" : ["https://leagueoflegends.fandom.com/wiki/Ahri"]
8 | }
--------------------------------------------------------------------------------
/waifus/ryuko matoi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ryūko Matoi",
4 | "image" : "https://vignette.wikia.nocookie.net/kill-la-kill/images/9/9b/Ryūko_Matoi_close-up.png",
5 | "japanese-name" : "纏 流子 / まとい りゅうこ",
6 | "age-in-show" : 17,
7 | "sources" : ["https://kill-la-kill.fandom.com/wiki/Ry%C5%ABko_Matoi"]
8 | }
--------------------------------------------------------------------------------
/waifus/monika.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Monika",
4 | "image" : "https://vignette.wikia.nocookie.net/doki-doki-literature-club/images/e/ef/Monika_Illustration.png",
5 | "month" : 9,
6 | "day-of-month" : 22,
7 | "age-in-show": 18,
8 | "sources" : ["https://doki-doki-literature-club.fandom.com/wiki/Monika"]
9 | }
--------------------------------------------------------------------------------
/waifus/riko.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Riko",
4 | "image" : "https://vignette.wikia.nocookie.net/madeinabyss/images/9/9b/Riko-Profile-Anime.png",
5 | "japanese-name" : "リコ",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "sources" : ["https://madeinabyss.fandom.com/wiki/Riko"]
10 | }
--------------------------------------------------------------------------------
/waifus/rizu ogata.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Rizu Ogata",
4 | "image" : "https://vignette.wikia.nocookie.net/bokutachi-study/images/e/e1/20190803_193514.png",
5 | "japanese-name" : "緒方理珠",
6 | "age-in-show": 17,
7 | "age-group-by-appearance" : "teenager",
8 | "sources" : ["https://weneverlearn.fandom.com/wiki/Rizu_Ogata"]
9 | }
--------------------------------------------------------------------------------
/waifus/ko yagami.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ko Yagami",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/c/ca/Th-c2.png",
5 | "japanese-name" : "八神 コウ",
6 | "year" : 1992,
7 | "month" : 8,
8 | "day-of-month" : 2,
9 | "age-in-show": 26,
10 | "sources" : ["https://new-game.fandom.com/wiki/Ko_Yagami"]
11 | }
--------------------------------------------------------------------------------
/waifus/lusamine.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Lusamine",
4 | "image" : "https://cdn.bulbagarden.net/upload/4/4a/Sun_Moon_Lusamine.png",
5 | "japanese-name" : "ルザミーネ",
6 | "definitely-legal": true,
7 | "age-in-show": 41,
8 | "notes": ["She's Lillie's mom"],
9 | "sources" : ["https://bulbapedia.bulbagarden.net/wiki/Lusamine"]
10 | }
--------------------------------------------------------------------------------
/waifus/mai sakurajima.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Mai Sakurajima",
4 | "image" : "https://i.pinimg.com/474x/34/5a/88/345a8893bd6dd27b3407be37ee50a885.jpg",
5 | "japanese-name" : "桜島 麻衣",
6 | "month" : 12,
7 | "day-of-month" : 2,
8 | "age-in-show": 17,
9 | "sources" : ["https://aobuta.fandom.com/wiki/Mai_Sakurajima"]
10 | }
11 |
--------------------------------------------------------------------------------
/waifus/nene sakura.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Nene Sakura",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/c/ce/Th-c7.png",
5 | "japanese-name" : "桜ねね",
6 | "year" : 1998,
7 | "month" : 5,
8 | "day-of-month" : 5,
9 | "age-in-show": 19,
10 | "sources" : ["https://new-game.fandom.com/wiki/Nene_Sakura"]
11 | }
--------------------------------------------------------------------------------
/waifus/tsubasa hanekawa.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Tsubasa Hanekawa",
4 | "image" : "https://vignette.wikia.nocookie.net/bakemonogatari1645/images/b/be/Bakehane.png/revision/latest?cb=20181125055426",
5 | "japanese-name" : "羽川 翼",
6 | "age-in-show": 18,
7 | "sources" : ["https://bakemonogatari.fandom.com/wiki/Tsubasa_Hanekawa"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/yun iijima.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Yun Iijima",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/0/0e/Th-c6.png",
5 | "japanese-name" : "飯島ゆん",
6 | "year" : 1996,
7 | "month" : 12,
8 | "day-of-month" : 6,
9 | "age-in-show": 21,
10 | "sources" : ["https://new-game.fandom.com/wiki/Yun_Iijima"]
11 | }
--------------------------------------------------------------------------------
/waifus/ayano minegishi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "峰岸あやの",
4 | "english-name" : "Ayano Minegishi",
5 | "year" : 1990,
6 | "month" : 11,
7 | "day-of-month" : 4,
8 | "age-in-show": 17,
9 | "image" : "https://img7-us.anidb.net/pics/anime/18652.jpg",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Ayano_Minegishi"]
11 | }
--------------------------------------------------------------------------------
/waifus/fumino furuhashi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Fumino Furuhashi",
4 | "image" : "https://vignette.wikia.nocookie.net/bokutachi-study/images/a/a8/20190803_193533.png",
5 | "japanese-name" : "古橋文乃",
6 | "age-in-show": 17,
7 | "age-group-by-appearance" : "teenager",
8 | "sources" : ["https://weneverlearn.fandom.com/wiki/Fumino_Furuhashi"]
9 | }
--------------------------------------------------------------------------------
/waifus/umiko ahagon.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Umiko Ahagon",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/0/0b/Th-c8.png",
5 | "japanese-name" : "阿波根うみこ",
6 | "year" : 1991,
7 | "month" : 7,
8 | "day-of-month" : 20,
9 | "age-in-show": 25,
10 | "sources" : ["https://new-game.fandom.com/wiki/Umiko_Ahagon"]
11 | }
--------------------------------------------------------------------------------
/waifus/hajime shinoda.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Hajime Shinoda",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/5/52/Th-c5.png",
5 | "japanese-name" : "篠田 はじめ",
6 | "year" : 1995,
7 | "month" : 1,
8 | "day-of-month" : 1,
9 | "age-in-show": 21,
10 | "sources" : ["https://new-game.fandom.com/wiki/Hajime_Shinoda"]
11 | }
--------------------------------------------------------------------------------
/waifus/maho hiyajo.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "比屋定真帆",
4 | "english-name" : "Maho Hiyajo",
5 | "year" : 1989,
6 | "month" : 11,
7 | "day-of-month" : 2,
8 | "age-in-show": 21,
9 | "image" : "https://vignette.wikia.nocookie.net/steins-gate/images/1/1c/Hiyajomaho.png",
10 | "sources" : ["https://steins-gate.fandom.com/wiki/Maho_Hiyajo"]
11 | }
--------------------------------------------------------------------------------
/waifus/rem.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Rem",
4 | "image" : "https://raw.githubusercontent.com/yourWaifu/is-your-waifu-legal/master/site/images/waifus/rem.png",
5 | "japanese-name" : "レム",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "age-in-show": 17,
10 | "sources" : ["https://rezero.fandom.com/wiki/Rem"]
11 | }
--------------------------------------------------------------------------------
/waifus/rory mercury.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Rory Mercury",
4 | "image" : "https://vignette.wikia.nocookie.net/gate-thus-the-jsdf-fought-there/images/f/fe/Rory.jpg/revision/latest?cb=20180112033407",
5 | "japanese-name" : "ロリー·マーキュリ",
6 | "age-in-show": 961,
7 | "sources" : ["https://gate-thus-the-jsdf-fought-there.fandom.com/wiki/Rory_Mercury"]
8 | }
9 |
--------------------------------------------------------------------------------
/waifus/hiyori tamura.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "田村ひより",
4 | "english-name" : "Hiyori Tamura",
5 | "year" : 1991,
6 | "month" : 5,
7 | "day-of-month" : 24,
8 | "age-in-show": 16,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/6/64/Hiyorin.jpg",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Hiyori_Tamura"]
11 | }
--------------------------------------------------------------------------------
/waifus/konata izumi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "泉こなた",
4 | "english-name" : "Konata Izumi",
5 | "year" : 1989,
6 | "month" : 5,
7 | "day-of-month" : 28,
8 | "age-in-show": 18,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/7/73/Konata-san.gif",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Konata_Izumi"]
11 | }
--------------------------------------------------------------------------------
/waifus/megurine luka.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Megurine Luka",
4 | "image": "https://vignette.wikia.nocookie.net/vocaloid/images/3/39/Luka_v4x_final.png",
5 | "japanese-name": "巡音ルカ",
6 | "year": 2009,
7 | "month": 1,
8 | "day-of-month": 30,
9 | "age-in-show": 20,
10 | "sources": [
11 | "https://vocaloid.fandom.com/wiki/Megurine_Luka"
12 | ]
13 | }
--------------------------------------------------------------------------------
/waifus/minami iwasaki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "岩崎みなみ",
4 | "english-name" : "Minami Iwasaki",
5 | "year" : 1991,
6 | "month" : 9,
7 | "day-of-month" : 12,
8 | "age-in-show": 16,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/b/ba/Minami.jpg",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Minami_Iwasaki"]
11 | }
--------------------------------------------------------------------------------
/waifus/kagami hiiragi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "柊かがみ",
4 | "english-name" : "Kagami Hiiragi",
5 | "year" : 1990,
6 | "month" : 7,
7 | "day-of-month" : 7,
8 | "age-in-show": 17,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/c/c9/Kagami-san.gif",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Kagami_Hiiragi"]
11 | }
--------------------------------------------------------------------------------
/waifus/kurisu makise.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "牧瀬 紅莉栖",
4 | "english-name" : "Kurisu Makise",
5 | "year" : 1992,
6 | "month" : 7,
7 | "day-of-month" : 25,
8 | "age-in-show": 18,
9 | "image" : "https://vignette.wikia.nocookie.net/steins-gate/images/4/4a/Kurisu_Full_profile.png",
10 | "sources" : ["https://steins-gate.fandom.com/wiki/Kurisu_Makise"]
11 | }
--------------------------------------------------------------------------------
/waifus/mayuri shiina.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "椎名 まゆり",
4 | "english-name" : "Mayuri Shiina",
5 | "year" : 1994,
6 | "month" : 2,
7 | "day-of-month" : 1,
8 | "age-in-show": 16,
9 | "image" : "https://vignette.wikia.nocookie.net/steins-gate/images/a/ac/Mayuri_full_profile.png",
10 | "sources" : ["https://steins-gate.fandom.com/wiki/Mayuri_Shiina"]
11 | }
--------------------------------------------------------------------------------
/waifus/miyuki takara.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "高良みゆき",
4 | "english-name" : "Miyuki Takara",
5 | "year" : 1991,
6 | "month" : 10,
7 | "day-of-month" : 25,
8 | "age-in-show": 16,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/b/b7/Miyuki-san.png",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Miyuki_Takara"]
11 | }
--------------------------------------------------------------------------------
/waifus/zero two.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Zero Two",
4 | "image" : "https://vignette.wikia.nocookie.net/darling-in-the-franxx/images/8/83/C_zerotwo_stand.png",
5 | "japanese-name" : "ゼロツー",
6 | "month" : 2,
7 | "day-of-month" : 27,
8 | "age-group-by-appearance" : "adult",
9 | "sources" : ["https://darling-in-the-franxx.fandom.com/wiki/Zero_Two"]
10 | }
--------------------------------------------------------------------------------
/waifus/futaba sakura.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Futaba Sakura",
4 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/9/95/P5_Futaba_Sakura.png",
5 | "japanese-name" : "佐倉 双葉, ナビ",
6 | "year" : 2001,
7 | "month" : 2,
8 | "day-of-month" : 19,
9 | "age-in-show": 16,
10 | "sources" : ["https://megamitensei.fandom.com/wiki/Futaba_Sakura"]
11 | }
--------------------------------------------------------------------------------
/waifus/patricia martin.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "パトリシア・マーティン",
4 | "english-name" : "Patricia Martin",
5 | "year" : 1991,
6 | "month" : 4,
7 | "day-of-month" : 16,
8 | "age-in-show": 16,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/b/ba/Minami.jpg",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Patricia_Martin"]
11 | }
--------------------------------------------------------------------------------
/waifus/tsukasa hiiragi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "柊つかさ",
4 | "english-name" : "Tsukasa Hiiragi",
5 | "year" : 1990,
6 | "month" : 7,
7 | "day-of-month" : 7,
8 | "age-in-show": 17,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/9/92/Tsukasa-san.gif",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Tsukasa_Hiiragi"]
11 | }
--------------------------------------------------------------------------------
/waifus/uruka takemoto.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Uruka Takemoto",
4 | "image" : "https://vignette.wikia.nocookie.net/bokutachi-study/images/f/f5/20190803_193621.png",
5 | "japanese-name" : "武元うるか",
6 | "age-group-by-appearance" : "teenager",
7 | "sources" : ["https://weneverlearn.fandom.com/wiki/Uruka_Takemoto"],
8 | "notes": ["She's a 3rd-year highschool student."]
9 | }
--------------------------------------------------------------------------------
/waifus/yunyun.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Yunyun",
4 | "image" : "https://static.wikia.nocookie.net/konosuba/images/c/c0/Yunyun-anime.png/revision/latest/scale-to-width-down/208?cb=20180328170203",
5 | "japanese-name" : "ゆんゆん",
6 | "month" : 2,
7 | "day-of-month" : 29,
8 | "age-in-show": 14,
9 | "sources" : ["https://konosuba.fandom.com/wiki/Yunyun"]
10 | }
11 |
--------------------------------------------------------------------------------
/waifus/lotte jansson.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Lotte Jansson",
4 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/7/72/Night_Fall_Lotte.png",
5 | "japanese-name" : "ロッテ・ヤンソン",
6 | "year" : null,
7 | "month" : 9,
8 | "day-of-month" : 6,
9 | "age-in-show": 16,
10 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Lotte_Jansson"]
11 | }
--------------------------------------------------------------------------------
/waifus/misao kusakabe.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "日下部みさお",
4 | "english-name" : "Misao Kusakabe",
5 | "year" : 1990,
6 | "month" : 7,
7 | "day-of-month" : 20,
8 | "age-in-show": 17,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/a/a8/Misao_Kusakabe.pic.png",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Misao_Kusakabe"]
11 | }
--------------------------------------------------------------------------------
/waifus/honoka.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Honoka",
4 | "image" : "https://vignette.wikia.nocookie.net/deadoralive/images/8/81/Img-honoka.png",
5 | "japanese-name" : "ほのか",
6 | "month" : 3,
7 | "day-of-month" : 24,
8 | "age-in-show": 18,
9 | "sources" : [
10 | "https://www.youtube.com/watch?v=CXdq8Un-NRA",
11 | "https://deadoralive.fandom.com/wiki/Honoka"
12 | ]
13 | }
--------------------------------------------------------------------------------
/waifus/shaundi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Shaundi",
4 | "image": "https://vignette.wikia.nocookie.net/saintsrow/images/c/c4/Shaundi_closeup_in_Saints_Row_2.png",
5 | "definitely-legal": false,
6 | "is-a-trap": false,
7 | "age-group-by-appearance": "adult",
8 | "age-range-by-appearance": [18, 35],
9 | "sources": ["https://saintsrow.fandom.com/wiki/Shaundi"]
10 | }
11 |
--------------------------------------------------------------------------------
/waifus/shouko nishimiya.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Shouko Nishimiya",
4 | "image" : "https://vignette.wikia.nocookie.net/koenokatachi/images/5/54/ShoukoTemplate.png",
5 | "japanese-name" : "西宮 硝子",
6 | "month" : 6,
7 | "day-of-month" : 7,
8 | "definitely-legal" : true,
9 | "age-in-show": 18,
10 | "sources" : ["https://koenokatachi.fandom.com/wiki/Shouko_Nishimiya"]
11 | }
--------------------------------------------------------------------------------
/waifus/yutaka kobayakawa.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "小早川ゆたか",
4 | "english-name" : "Yutaka Kobayakawa",
5 | "year" : 1991,
6 | "month" : 12,
7 | "day-of-month" : 20,
8 | "age-in-show": 16,
9 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/a/aa/Yutaka_Episode14.jpg",
10 | "sources" : ["https://luckystar.fandom.com/wiki/Yutaka_Kobayakawa"]
11 | }
--------------------------------------------------------------------------------
/waifus/diana cavendish.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Diana Cavendish",
4 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/c/cb/Diana_Cavendish.png",
5 | "japanese-name" : "ダイアナ・キャベンディッシュ",
6 | "year" : null,
7 | "month" : 11,
8 | "day-of-month" : 12,
9 | "age-in-show": 16,
10 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Diana_Cavendish"]
11 | }
--------------------------------------------------------------------------------
/waifus/lillie.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Lillie",
4 | "image" : "https://cdn.bulbagarden.net/upload/e/e5/LillieUltraPrism151.jpg",
5 | "japanese-name" : "リーリエ",
6 | "age-range-by-appearance" : [10, 13],
7 | "age-group-by-appearance" : "pre-teen",
8 | "notes": ["You need to be at least 10 to be a pokemon trainer"],
9 | "sources" : ["https://bulbapedia.bulbagarden.net/wiki/Lillie"]
10 | }
--------------------------------------------------------------------------------
/waifus/makoto niijima.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Makoto Niijima",
4 | "aliases" : null,
5 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/a/af/P5_Makoto_Nijima.png",
6 | "japanese-name" : "新島 真, クイーン",
7 | "year" : 1998,
8 | "month" : 4,
9 | "day-of-month" : 23,
10 | "age-in-show": 18,
11 | "sources" : ["https://megamitensei.fandom.com/wiki/Makoto_Niijima"]
12 | }
--------------------------------------------------------------------------------
/waifus/marie rose.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Marie Rose",
4 | "japanese-name" : "マリー・ローズ",
5 | "image" : "https://vignette.wikia.nocookie.net/deadoralive/images/3/3c/Img-marie.png",
6 | "month" : 6,
7 | "day-of-month" : 6,
8 | "age-in-show": 18,
9 | "sources" : [
10 | "https://deadoralive.fandom.com/wiki/Marie_Rose",
11 | "https://www.youtube.com/watch?v=CXdq8Un-NRA"
12 | ]
13 | }
--------------------------------------------------------------------------------
/waifus/moeka kiryu.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Moeka Kiryu",
4 | "aliases" : ["Shining Finger"],
5 | "image" : "https://vignette.wikia.nocookie.net/steins-gate/images/3/3d/Moeka_Full_Profile-0.png",
6 | "japanese-name" : "桐生萌郁",
7 | "year" : 1990,
8 | "month" : 6,
9 | "day-of-month" : 6,
10 | "age-in-show": 20,
11 | "sources" : ["https://steins-gate.fandom.com/wiki/Moeka_Kiryu"]
12 | }
--------------------------------------------------------------------------------
/waifus/2b.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "2B",
4 | "aliases" : ["YoRHa No.2 Type B"],
5 | "image" : "https://vignette.wikia.nocookie.net/nier/images/3/38/YoRHa_No.2_Type_B.png",
6 | "japanese-name" : "ヨルハ2号B型",
7 | "age-group-by-appearance" : "adult",
8 | "notes": ["She's an android, not sure if they are legal to marry"],
9 | "sources" : ["https://nier.fandom.com/wiki/YoRHa_No.2_Type_B"]
10 | }
--------------------------------------------------------------------------------
/waifus/asumi kominami.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Asumi Kominami",
4 | "image" : "https://vignette.wikia.nocookie.net/bokutachi-study/images/e/e8/20190529_025647.png",
5 | "japanese-name" : "小美浪あすみ",
6 | "age-group-by-appearance" : "teenager",
7 | "month" : 4,
8 | "day-of-month" : 9,
9 | "definitely-legal": true,
10 | "sources" : ["https://weneverlearn.fandom.com/wiki/Asumi_Kominami"]
11 | }
--------------------------------------------------------------------------------
/waifus/haru okumura.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Haru Okumura",
4 | "aliases" : ["Beauty Thief "],
5 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/7/77/P5_Haru_Okumura.png",
6 | "japanese-name" : "奥村 春 ノワール",
7 | "year" : 1998,
8 | "month" : 12,
9 | "day-of-month" : 5,
10 | "age-in-show": 22,
11 | "sources" : ["https://megamitensei.fandom.com/wiki/Haru_Okumura"]
12 | }
--------------------------------------------------------------------------------
/waifus/kagamine rin.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Kagamine Rin",
4 | "image": "https://vignette.wikia.nocookie.net/vocaloid/images/0/0a/Kagamine_Rin.jpg",
5 | "japanese-name": "鏡音リン",
6 | "year": 2007,
7 | "month": 12,
8 | "day-of-month": 27,
9 | "age-in-show": 14,
10 | "sources": [
11 | "https://vocaloid.fandom.com/wiki/Kagamine_Rin_%26_Len"
12 | ]
13 | }
--------------------------------------------------------------------------------
/waifus/suzuha amane.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Suzuha Amane",
4 | "image" : "https://vignette.wikia.nocookie.net/steins-gate/images/2/23/Suzuha_full_profile.png/revision/latest?cb=20141212195208",
5 | "japanese-name" : "阿万音鈴羽",
6 | "year" : 2017,
7 | "month" : 9,
8 | "day-of-month" : 27,
9 | "age-in-show": 18,
10 | "sources" : ["https://steins-gate.fandom.com/wiki/Suzuha_Amane"]
11 | }
12 |
--------------------------------------------------------------------------------
/waifus/akira kogami.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "小神あきら",
4 | "english-name" : "Akira Kogami",
5 | "year" : 1993,
6 | "month" : 2,
7 | "day-of-month" : 14,
8 | "age-group-by-appearance" : "teenager",
9 | "age-in-show": 17,
10 | "image" : "https://vignette.wikia.nocookie.net/luckystar/images/0/05/KogamiAkira002.jpg",
11 | "sources" : ["https://luckystar.fandom.com/wiki/Akira_Kogami"]
12 | }
--------------------------------------------------------------------------------
/waifus/ann takamaki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ann Takamaki",
4 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/b/be/An_takamaki.png",
5 | "japanese-name" : "高巻 杏 パンサー",
6 | "year" : 1999,
7 | "month" : 11,
8 | "day-of-month" : 12,
9 | "age-group-by-appearance" : "teenager",
10 | "age-in-show": 17,
11 | "sources" : ["https://megamitensei.fandom.com/wiki/Ann_Takamaki"]
12 | }
--------------------------------------------------------------------------------
/waifus/tae takemi.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Tae Takemi",
4 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/e/eb/P5_Tae_Takemi.png",
5 | "japanese-name" : "武見 妙",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "definitely-legal" : true,
10 | "notes": ["She's a doctor and owns a clinic"],
11 | "sources" : ["https://megamitensei.fandom.com/wiki/Tae_Takemi"]
12 | }
--------------------------------------------------------------------------------
/waifus/amanda o'neill.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Amanda O'Neill",
4 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/5/55/Amanda_O'_Neill.png",
5 | "japanese-name" : "アマンダ・オニール",
6 | "year" : null,
7 | "month" : 3,
8 | "day-of-month" : 17,
9 | "age-group-by-appearance": "teenager",
10 | "age-in-show": 16,
11 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Amanda_O%27Neill"]
12 | }
--------------------------------------------------------------------------------
/waifus/chika fujiwara.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Chika Fujiwara",
4 | "japanese-name" : "藤原 千花",
5 | "aliases" : "Love Detective Chika",
6 | "image" : "https://i.imgur.com/mL3zspe.jpg",
7 | "month" : 3,
8 | "day-of-month" : 3,
9 | "age-in-show": 17,
10 | "sources" : ["https://kaguyasama-wa-kokurasetai.fandom.com/wiki/Chika_Fujiwara", "https://www.reddit.com/r/ChikaFujiwara/comments/hnsvoe/yo_its_chika/"]
11 | }
--------------------------------------------------------------------------------
/waifus/mafuyu kirisu.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Mafuyu Kirisu",
4 | "image" : "https://vignette.wikia.nocookie.net/bokutachi-study/images/0/06/20190803_194047.png",
5 | "japanese-name" : "桐須真冬",
6 | "age-group-by-appearance" : "adult",
7 | "month" : 12,
8 | "day-of-month" : 28,
9 | "sources" : ["https://weneverlearn.fandom.com/wiki/Mafuyu_Kirisu"],
10 | "notes": ["She's a teacher, so most likely legal."]
11 | }
--------------------------------------------------------------------------------
/waifus/eris.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Eris",
4 | "aliases" : ["Chris"],
5 | "image" : "https://static.wikia.nocookie.net/konosuba/images/b/b6/Eris-anime.jpg/revision/latest?cb=20160323170348",
6 | "japanese-name" : "エリス",
7 | "definitely-legal" : false,
8 | "notes": ["Despite being a goddess, her physical form is a young teenage girl as Chris."],
9 | "sources" : ["https://konosuba.fandom.com/wiki/Eris"]
10 | }
11 |
--------------------------------------------------------------------------------
/waifus/nezuko.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Nezuko Kamado",
4 | "image" : "https://vignette.wikia.nocookie.net/kimetsu-no-yaiba/images/2/2f/Nezuko_anime_design.png/revision/latest?cb=20181128204224",
5 | "japanese-name" : "竈門 禰豆子",
6 | "month" : 12,
7 | "day-of-month" : 28,
8 | "age-in-show" : 14,
9 | "age-group-by-appearance" : "teenager",
10 | "sources" : ["https://kimetsu-no-yaiba.fandom.com/wiki/Nezuko_Kamado"]
11 | }
--------------------------------------------------------------------------------
/waifus/asuka tanaka.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "田中 あすか",
4 | "english-name" : "Asuka Tanaka",
5 | "year" : 1997,
6 | "month" : 12,
7 | "day-of-month" : 25,
8 | "image" : "https://vignette.wikia.nocookie.net/hibike-euphonium/images/3/3a/Asuka_tanaka.jpg",
9 | "sources" : ["https://hibike-euphonium.fandom.com/wiki/Asuka_Tanaka", "https://www.reddit.com/r/HibikeEuphonium/comments/3z29ol/hibikes_official_birthdays_list/"]
10 | }
--------------------------------------------------------------------------------
/waifus/chloe price.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Chloe Price",
4 | "image": "https://vignette.wikia.nocookie.net/life-is-strange/images/2/29/Chloe_.png",
5 | "month": 3,
6 | "day-of-month": 11,
7 | "year": 1994,
8 | "age-in-show": 19,
9 | "definitely-legal": true,
10 | "is-a-trap": false,
11 | "age-group-by-appearance": "teenager",
12 | "sources": ["https://life-is-strange.fandom.com/wiki/Chloe_Price"]
13 | }
14 |
--------------------------------------------------------------------------------
/waifus/ichiko ohya.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ichiko Ohya",
4 | "aliases" : null,
5 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/9/9c/Ichiko_Oya.png",
6 | "japanese-name" : "大宅 一子",
7 | "year" : null,
8 | "month" : null,
9 | "day-of-month" : null,
10 | "definitely-legal" : true,
11 | "age-range-by-appearance" : [20, 29],
12 | "sources" : ["https://megamitensei.fandom.com/wiki/Ichiko_Ohya"]
13 | }
--------------------------------------------------------------------------------
/waifus/kumiko oumae.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "黄前 久美子",
4 | "english-name" : "Kumiko Oumae",
5 | "year" : 1999,
6 | "month" : 8,
7 | "day-of-month" : 21,
8 | "image" : "https://vignette.wikia.nocookie.net/hibike-euphonium/images/6/68/Oumae_Kumiko.jpg",
9 | "sources" : ["https://hibike-euphonium.fandom.com/wiki/Kumiko_Oumae", "https://www.reddit.com/r/HibikeEuphonium/comments/3z29ol/hibikes_official_birthdays_list/"]
10 | }
--------------------------------------------------------------------------------
/waifus/sadayo kawakami.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Sadayo Kawakami",
4 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/4/43/Sadayo_Kawakami.png",
5 | "japanese-name" : "川上 貞代",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "definitely-legal" : true,
10 | "notes": ["She's a teacher at Shujin Academy"],
11 | "sources" : ["https://megamitensei.fandom.com/wiki/Sadayo_Kawakami"]
12 | }
--------------------------------------------------------------------------------
/waifus/tohru honda.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Tohru Honda",
4 | "japanese-name" : "本田 透",
5 | "image" : "https://vignette.wikia.nocookie.net/fruitsbasket/images/d/df/Tohru_Honda-2001.png",
6 | "month" : 5,
7 | "day-of-month" : 6,
8 | "age-in-show": 18,
9 | "notes" : ["Tohru is 16 at the start of the series, but turns 18 by the end of the series."],
10 | "sources" : ["https://fruitsbasket.fandom.com/wiki/Tohru_Honda"]
11 | }
--------------------------------------------------------------------------------
/waifus/aoba suzukaze.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Aoba Suzukaze",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/f/f2/Th-c1.png",
5 | "japanese-name" : "涼風 青葉",
6 | "year" : 1998,
7 | "month" : 2,
8 | "day-of-month" : 2,
9 | "age-group-by-appearance" : "teenager",
10 | "age-in-show": 19,
11 | "finally-legal-in-show" : "Fairies Story 3",
12 | "sources" : ["https://new-game.fandom.com/wiki/Aoba_Suzukaze"]
13 | }
--------------------------------------------------------------------------------
/waifus/reina kousaka.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "高坂 麗奈",
4 | "english-name" : "Reina Kousaka",
5 | "year" : 1999,
6 | "month" : 5,
7 | "day-of-month" : 15,
8 | "image" : "https://vignette.wikia.nocookie.net/hibike-euphonium/images/8/85/Kousaka_Reina.jpg",
9 | "sources" : ["https://hibike-euphonium.fandom.com/wiki/Reina_Kousaka", "https://www.reddit.com/r/HibikeEuphonium/comments/3z29ol/hibikes_official_birthdays_list/"]
10 | }
--------------------------------------------------------------------------------
/waifus/senko.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Senko",
4 | "image" : "http://senkosan.com/assets/character/c1.png",
5 | "japanese-name" : "仙狐",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "age-in-show": 800,
10 | "sources" : [ "http://senkosan.com/character/", "https://www.animenewsnetwork.com/news/2018-12-02/sewayaki-kitsune-no-senko-san-manga-about-fox-spirit-gets-tv-anime/.140270" ]
11 | }
12 |
--------------------------------------------------------------------------------
/waifus/atsuko kagari.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Atsuko Kagari",
4 | "aliases" : ["Akko"],
5 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/f/f1/Akko_Kagari.png",
6 | "japanese-name" : "アツコ・カガリ",
7 | "year" : null,
8 | "month" : 5,
9 | "day-of-month" : 25,
10 | "age-group-by-appearance" : "teenager",
11 | "age-in-show": 16,
12 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Atsuko_Kagari"]
13 | }
--------------------------------------------------------------------------------
/waifus/chitoge kirisaki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Chitoge Kirisaki",
4 | "image" : "https://vignette.wikia.nocookie.net/nisekoi/images/d/d5/Chitoge_appearence.png",
5 | "japanese-name" : "桐崎 千棘",
6 | "month" : 6,
7 | "day-of-month" : 7,
8 | "age-in-show": 18,
9 | "notes": ["Has different ages in the anime and manga. 16 in the anime, and 18 in the manga"],
10 | "sources" : ["https://nisekoi.fandom.com/wiki/Chitoge_Kirisaki"]
11 | }
12 |
--------------------------------------------------------------------------------
/waifus/croix meridies.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Croix Meridies",
4 | "aliases" : ["Professor Croix"],
5 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/6/6d/Croixfull.png",
6 | "japanese-name" : "クロワ・メリディエス",
7 | "year" : null,
8 | "month" : 10,
9 | "day-of-month" : 13,
10 | "definitely-legal" : true,
11 | "notes": ["She's a professor."],
12 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Croix_Meridies"]
13 | }
--------------------------------------------------------------------------------
/waifus/emilia.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Emilia",
4 | "image" : "https://vignette.wikia.nocookie.net/p__/images/6/60/Emilia_Infobox.png/revision/latest?cb=20200812110513&path-prefix=protagonist",
5 | "japanese-name" : "エミリア",
6 | "month" : 9,
7 | "day-of-month" : 23,
8 | "age-in-show": 19,
9 | "notes": ["Was frozen for about 100 years, but still takes the same physical appearence"],
10 | "sources" : ["https://rezero.fandom.com/wiki/Emilia"]
11 | }
12 |
--------------------------------------------------------------------------------
/waifus/ursula callistis.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ursula Callistis",
4 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/d/dd/Professor_Ursula_smile.png",
5 | "japanese-name" : "アーシュラ・カリスティス",
6 | "year" : null,
7 | "month" : 2,
8 | "day-of-month" : 20,
9 | "definitely-legal" : true,
10 | "notes": ["She's a teacher at Luna Nova Magical Academy"],
11 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Ursula_Callistis"]
12 | }
--------------------------------------------------------------------------------
/waifus/darkness.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Darkness",
4 | "image" : "https://static.wikia.nocookie.net/konosuba/images/d/d9/Darkness-anime.png/revision/latest/scale-to-width-down/238?cb=20180502130740",
5 | "japanese-name" : "ダクネス",
6 | "month" : 4,
7 | "day-of-month" : 6,
8 | "definitely-legal": true,
9 | "notes": ["Real name is Dustiness Ford Lalatina, is 18 years old in the series."],
10 | "sources" : ["https://konosuba.fandom.com/wiki/Darkness"]
11 | }
12 |
--------------------------------------------------------------------------------
/waifus/hifumi togo.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Hifumi Togo",
4 | "aliases" : null,
5 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/7/78/Hifumi_Togo.png",
6 | "japanese-name" : "東郷 一二三",
7 | "year" : null,
8 | "month" : null,
9 | "day-of-month" : null,
10 | "age-group-by-appearance" : "teenager",
11 | "notes": ["She's a high school student. Likely not of legal age."],
12 | "sources" : ["https://megamitensei.fandom.com/wiki/Hifumi_Togo"]
13 | }
--------------------------------------------------------------------------------
/waifus/holo.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Holo",
4 | "image" : "https://vignette.wikia.nocookie.net/spiceandwolf/images/5/51/Holo_Infobox.jpg",
5 | "japanese-name" : null,
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "definitely-legal" : true,
10 | "age-group-by-appearance": "teenager",
11 | "notes" : ["Holo has lived for many centuries and is not visibly aged past 15 years old"],
12 | "sources" : ["https://spiceandwolf.fandom.com/wiki/Holo"]
13 | }
--------------------------------------------------------------------------------
/waifus/rin hoshizora.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "星空凛",
4 | "english-name" : "Rin Hoshizora",
5 | "month" : 11,
6 | "day-of-month" : 1,
7 | "age-group-by-appearance" : "teenager",
8 | "definitely-legal": false,
9 | "age-in-show": 15,
10 | "is-a-trap": false,
11 | "image" : "https://www.clipartwiki.com/clipimg/full/126-1264823_hoshizora-rin-png-rin-hoshizora-render.png",
12 | "notes": ["Rinboi."],
13 | "sources" : ["https://love-live.fandom.com/wiki/Rin_Hoshizora"]
14 | }
--------------------------------------------------------------------------------
/waifus/test dummy.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "japanese-name" : "",
4 | "english-name" : "test dummy",
5 | "aliases" : ["dummy"],
6 | "year" : 2017,
7 | "month" : 2,
8 | "day-of-month" : 1,
9 | "age-group-by-appearance" : "teenager",
10 | "age-range-by-appearance" : [13, 18],
11 | "definitely-legal": false,
12 | "age-in-show": 17,
13 | "finally-legal-in-show" : "the movie",
14 | "is-a-trap": false,
15 | "image" : "",
16 | "notes": [""],
17 | "sources" : ["https://yourWaifu.dev"]
18 | }
--------------------------------------------------------------------------------
/waifus/artoria pendragon.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Artoria Pendragon",
4 | "aliases" : [
5 | "King of Knights of the Holy Sword",
6 | "King of Knights",
7 | "Altria Pendragon",
8 | "King Arthur",
9 | "Blue Saber"
10 | ],
11 | "image" : "https://vignette.wikia.nocookie.net/fategrandorder/images/4/43/Artoria1.png",
12 | "japanese-name" : "アルトリア・ペンドラゴン",
13 | "age-group-by-appearance" : "adult",
14 | "sources" : ["https://fategrandorder.fandom.com/wiki/Artoria_Pendragon"]
15 | }
--------------------------------------------------------------------------------
/site-generator-tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "noImplicitAny": true,
5 | "noImplicitReturns": true,
6 | "noUnusedLocals": true,
7 | "noUnusedParameters": true,
8 | "strictNullChecks": true,
9 | "strictFunctionTypes": true,
10 | "strictPropertyInitialization": true,
11 | "target": "es6",
12 | "lib": ["es5", "es6", "dom"],
13 | "module": "commonjs",
14 | "outDir": "tsc-output",
15 | "sourceMap": true
16 | },
17 | "files": [
18 | "site-generator.ts"
19 | ]
20 | }
--------------------------------------------------------------------------------
/waifus/faris nyannyan.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Faris Nyannyan",
4 | "notes": ["The show takes place in the year 2010. Faris first appearance on the show is on Episode 02: Time Travel Paranoia."],
5 | "image" : "https://vignette.wikia.nocookie.net/steins-gate/images/d/d3/Faris_full_profile.png",
6 | "japanese-name" : "フェイリス・ニャンニャン",
7 | "year" : 2002,
8 | "month" : 4,
9 | "day-of-month" : 3,
10 | "age-in-show": 17,
11 | "sources" : ["https://mywaifulist.moe/waifu/faris-nyannyan-steins-gate"]
12 | }
13 |
--------------------------------------------------------------------------------
/waifus/constanze amalie von braunschbank-albrechtsberger.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Constanze Amalie von Braunschbank-Albrechtsberger",
4 | "aliases" : ["Conse", "Ze-chan"],
5 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/9/93/Constanze_Amalie.png",
6 | "japanese-name" : "ロッテ・ヤンソン",
7 | "year" : null,
8 | "month" : 10,
9 | "day-of-month" : 12,
10 | "age-in-show": 16,
11 | "sources" : ["https://little-witch-academia.fandom.com/wiki/Constanze_Amalie_von_Braunschbank-Albrechtsberger"]
12 | }
--------------------------------------------------------------------------------
/waifus/maxine caulfield.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Maxine Caulfield",
4 | "aliases": ["Max", "Mad Max", "Max Factor", "Noir Angel", "Super Max"],
5 | "image": "https://vignette.wikia.nocookie.net/life-is-strange/images/d/df/Max_.png",
6 | "month": 9,
7 | "day-of-month": 21,
8 | "year": 1995,
9 | "age-in-show": 18,
10 | "definitely-legal": true,
11 | "is-a-trap": false,
12 | "age-group-by-appearance": "teenager",
13 | "sources": ["https://life-is-strange.fandom.com/wiki/Max_Caulfield"]
14 | }
15 |
--------------------------------------------------------------------------------
/waifus/shizuku hazuki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Shizuku Hazuki",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/4/44/Th-c9.png",
5 | "japanese-name" : "葉月しずく",
6 | "definitely-legal" : true,
7 | "month" : 5,
8 | "day-of-month" : 22,
9 | "notes": [
10 | "Age Explanation: Hazuki doesn't reveal her age and stops those giving hints about her age in the first season, (Since she is possibly the oldest one at Eagle Jump)."
11 | ],
12 | "sources" : ["https://new-game.fandom.com/wiki/Shizuku_Hazuki"]
13 | }
14 |
--------------------------------------------------------------------------------
/waifus/hatsune miku.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Hatsune Miku",
4 | "alias": [
5 | "Miku",
6 | "Hatsune",
7 | "Vocaloid"
8 | ],
9 | "image": "https://ec.crypton.co.jp/img/special/vocaloid/img_MIKU_us.png",
10 | "japanese-name": "初音ミク",
11 | "year": 2007,
12 | "month": 8,
13 | "day-of-month": 31,
14 | "age-in-show": 16,
15 | "sources": [
16 | "https://ec.crypton.co.jp/pages/prod/vocaloid/cv01_us",
17 | "https://en.wikipedia.org/wiki/Hatsune_Miku"
18 | ]
19 | }
--------------------------------------------------------------------------------
/waifus/sae niijima.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Sae Niijima",
4 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/d/dd/Sae_body.png",
5 | "japanese-name" : "新島 冴",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "definitely-legal" : true,
10 | "notes": ["She's Makoto Niijima's older sister, and Makoto Niijima is 18 years old."],
11 | "sources" : [
12 | "https://megamitensei.fandom.com/wiki/Sae_Niijima",
13 | "https://yourwaifu.dev/is-your-waifu-legal/?q=makoto%20niijima"
14 | ]
15 | }
--------------------------------------------------------------------------------
/waifus/hifumi takimoto.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Hifumi Takimoto",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/9/9d/Hifumi_Game_Render.png",
5 | "japanese-name" : "滝本ひふみ",
6 | "definitely-legal" : true,
7 | "month" : 1,
8 | "day-of-month" : 23,
9 | "age-group-by-appearance" : "teenager",
10 | "notes": [
11 | "Age Explanation: Hifumi is able to drink alcohol related drinks, which in Japan the minimum age to drink is 20."
12 | ],
13 | "sources" : ["https://new-game.fandom.com/wiki/Hifumi_Takimoto"]
14 | }
15 |
--------------------------------------------------------------------------------
/waifus/momiji mochizuki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Momiji Mochizuki",
4 | "image" : "https://vignette.wikia.nocookie.net/new-game/images/9/99/Th-c10.png",
5 | "japanese-name" : "望月 紅葉",
6 | "year" : 1998,
7 | "month" : 1,
8 | "day-of-month" : 17,
9 | "age-in-show": 19,
10 | "notes": [
11 | "Age Explanation: Hazuki doesn't reveal her age and stops those giving hints about her age in the first season, (Since she is possibly the oldest one at Eagle Jump)."
12 | ],
13 | "sources" : ["https://new-game.fandom.com/wiki/Momiji_Mochizuki"]
14 | }
--------------------------------------------------------------------------------
/waifus/elizabeth comstock.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Elizabeth Comstock",
4 | "aliases": ["Anna DeWitt"],
5 | "image": "https://vignette.wikia.nocookie.net/bioshock/images/0/07/Elizabeth-C.png",
6 | "age-in-show": 19,
7 | "year": 1893,
8 | "definitely-legal": true,
9 | "is-a-trap": false,
10 | "age-group-by-appearance": "adult",
11 | "notes": [
12 | "Her birth year is 1893, and near the beginning of BioShock Infinite it is 1912, making her around 19."
13 | ],
14 | "sources": ["https://bioshock.fandom.com/wiki/Elizabeth"]
15 | }
16 |
--------------------------------------------------------------------------------
/waifus/kyoko kirigiri.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Kyoko Kirigiri",
4 | "japanese-name": "霧切 響子",
5 | "aliases": ["Ultimate ???", "Super High School Level ???"],
6 | "image": "https://vignette.wikia.nocookie.net/danganronpa/images/1/12/Kyoko_Kirigiri_Illustration.png",
7 | "month": 10,
8 | "day-of-month": 6,
9 | "definitely-legal": false,
10 | "is-a-trap": false,
11 | "age-group-by-appearance": "teenager",
12 | "notes": ["Kyoko is a high school student."],
13 | "sources": ["https://danganronpa.fandom.com/wiki/Kyoko_Kirigiri"]
14 | }
15 |
--------------------------------------------------------------------------------
/waifus/junko enoshima.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Junko Enoshima",
4 | "japanese-name": "江ノ島 盾子",
5 | "aliases": ["Ultimate Fashionista", "Super High School Level Fashion Girl"],
6 | "image": "https://vignette.wikia.nocookie.net/danganronpa/images/5/5e/Junko_Enoshima_Illustration.png",
7 | "month": 12,
8 | "day-of-month": 24,
9 | "definitely-legal": false,
10 | "is-a-trap": false,
11 | "age-group-by-appearance": "teenager",
12 | "notes": ["Junko is a high school student."],
13 | "sources": ["https://danganronpa.fandom.com/wiki/Junko_Enoshima"]
14 | }
15 |
--------------------------------------------------------------------------------
/waifus/kinzie kensington.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Kinzie Kensington",
4 | "aliases": ["Mousey-one", "Agent Kensington"],
5 | "image": "https://vignette.wikia.nocookie.net/saintsrow/images/3/36/Kinzie_-_Saints_Row_The_Third_promo.png",
6 | "definitely-legal": true,
7 | "is-a-trap": false,
8 | "age-group-by-appearance": "adult",
9 | "age-range-by-appearance": [20, 35],
10 | "notes": [
11 | "Kinzie used to work for the FBI, so it's very unlikely that she's underage."
12 | ],
13 | "sources": ["https://saintsrow.fandom.com/wiki/Kinzie_Kensington"]
14 | }
15 |
--------------------------------------------------------------------------------
/waifus/chihiro fujisaki.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Chihiro Fujisaki",
4 | "japanese-name": "不二咲 千尋",
5 | "aliases": ["Ultimate Programmer", "Super High School Level Programmer"],
6 | "image": "https://vignette.wikia.nocookie.net/danganronpa/images/7/7e/Chihiro_Fujisaki_Illustration.png",
7 | "month": 3,
8 | "day-of-month": 14,
9 | "definitely-legal": false,
10 | "is-a-trap": true,
11 | "age-group-by-appearance": "teenager",
12 | "notes": ["Chihiro is a high school student."],
13 | "sources": ["https://danganronpa.fandom.com/wiki/Chihiro_Fujisaki"]
14 | }
15 |
--------------------------------------------------------------------------------
/waifus/aqua.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Aqua",
4 | "image" : "https://vignette.wikia.nocookie.net/konosuba/images/1/14/Aqua-anime.png",
5 | "japanese-name" : "アクア",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "definitely-legal": true,
10 | "age-group-by-appearance" : "teenager",
11 | "notes": ["Aqua has lived in the Fantasy World for hundreds of years. However, according to Aqua, time passes slower in the afterlife. So her age on earth would be much higher then her age in the fantasy world."],
12 | "sources" : ["https://konosuba.fandom.com/wiki/Aqua"]
13 | }
--------------------------------------------------------------------------------
/waifus/chiaki nanami.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Chiaki Nanami",
4 | "japanese-name": "七海 千秋",
5 | "aliases": ["Ultimate Gamer", "Super High School Level Gamer"],
6 | "image": "https://vignette.wikia.nocookie.net/danganronpa/images/a/a0/Chiaki_Nanami_Illustration.png",
7 | "month": 3,
8 | "day-of-month": 14,
9 | "definitely-legal": false,
10 | "is-a-trap": false,
11 | "age-group-by-appearance": "teenager",
12 | "notes": ["Chiaki is a high school student."],
13 | "sources": [
14 | "https://danganronpa.fandom.com/wiki/Chiaki_Nanami_(Danganronpa_2)"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/site/404.html:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /404.html
3 | ---
4 |
5 |
6 |
21 |
22 |
--------------------------------------------------------------------------------
/waifus/chihaya mifune.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Chihaya Mifune",
4 | "image" : "https://vignette.wikia.nocookie.net/megamitensei/images/2/2a/Chihaya_Mifune.png",
5 | "japanese-name" : "御船 千早",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "definitely-legal" : true,
10 | "notes": [
11 | "She's not a student. So she's likely already graduated from high school, and older then the protagonist, who's at least 16 years old.",
12 | "You can date her in game, and you can't date lolis."
13 | ],
14 | "sources" : ["https://megamitensei.fandom.com/wiki/Chihaya_Mifune"]
15 | }
--------------------------------------------------------------------------------
/waifus/aoi asahina.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Aoi Asahina",
4 | "japanese-name": "朝日奈 葵",
5 | "aliases": [
6 | "Hina",
7 | "Ultimate Swimming Pro",
8 | "Super High School Level Level Swimmer"
9 | ],
10 | "image": "https://vignette.wikia.nocookie.net/danganronpa/images/6/67/Aoi_Asahina_Illustration.png",
11 | "month": 4,
12 | "day-of-month": 24,
13 | "definitely-legal": false,
14 | "is-a-trap": false,
15 | "age-group-by-appearance": "teenager",
16 | "notes": ["Hina is a high school student."],
17 | "sources": ["https://danganronpa.fandom.com/wiki/Aoi_Asahina"]
18 | }
19 |
--------------------------------------------------------------------------------
/waifus/haruhi suzumiya.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 0,
3 | "english-name": "Haruhi Suzumiya",
4 | "image": "https://vignette.wikia.nocookie.net/haruhi/images/c/cb/Haruhi_Suzumiya.png",
5 | "japanese-name": "涼宮ハルヒ",
6 | "month": 10,
7 | "day-of-month": 8,
8 | "age-in-show": 16,
9 | "definitely-legal": true,
10 | "is-a-trap": false,
11 | "age-group-by-appearance": "teenager",
12 | "notes": [
13 | "In \"The Melancholy of Haruhi Suzumiya\", she is in high school.",
14 | "In the light novel \"The Surprise of Haruhi Suzumiya\", she is in college."
15 | ],
16 | "sources": ["https://haruhi.fandom.com/wiki/Haruhi_Suzumiya"]
17 | }
18 |
--------------------------------------------------------------------------------
/waifus/ochaco uraraka.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Ochaco Uraraka",
4 | "image" : "https://vignette.wikia.nocookie.net/bokunoheroacademia/images/6/69/Ochaco_School_Uniform_Full_Body.png",
5 | "japanese-name" : "うららかちゃこ 麗日お茶子",
6 | "year" : null,
7 | "month" : null,
8 | "day-of-month" : null,
9 | "age-in-show": 15,
10 | "notes" : ["The anime looks like it takes in the future, so she's not legal because she hasn't been born yet."],
11 | "sources" : [
12 | "https://bokunoheroacademia.fandom.com/wiki/Ochaco_Uraraka",
13 | "https://www.quora.com/What-year-does-Boku-no-Hero-Academia-take-place-What-are-your-theories"
14 | ]
15 | }
--------------------------------------------------------------------------------
/waifus/shinobu oshino.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Shinobu Oshino",
4 | "japanese-name" : "忍野 忍",
5 | "alias": ["Rola", "Acerola", "Kiss-Shot Acerola-Orion Heart-Under-Blade", "Kiss-Shot"],
6 | "image" : "https://vignette.wikia.nocookie.net/bakemonogatari1645/images/b/b9/Shinobu.png/",
7 | "notes" : ["Although her real age is 598 years old, her introduction in the show has her in the physical age of an 8 years old girl.", "Throughout the show, Shinobu has taken on many physical age appearance, ranging from child-like to late 20's. This appearance is not the same as age progression, due to non-linear display as the show progresses."],
8 | "age-in-show": 598,
9 | "sources" : ["https://bakemonogatari.fandom.com/wiki/Shinobu_Oshino"]
10 | }
11 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [yourWaifu] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | # patreon: # Replace with a single Patreon username
5 | # open_collective: # Replace with a single Open Collective username
6 | # ko_fi: # Replace with a single Ko-fi username
7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | # liberapay: # Replace with a single Liberapay username
10 | # issuehunt: # Replace with a single IssueHunt username
11 | # otechie: # Replace with a single Otechie username
12 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/waifus/sucy manbavaran.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : 0,
3 | "english-name" : "Sucy Manbavaran",
4 | "image" : "https://vignette.wikia.nocookie.net/littlewitch/images/4/46/Waving_Sucy.png",
5 | "japanese-name" : "スーシィ・マンババラン",
6 | "year" : 2001,
7 | "month" : 12,
8 | "day-of-month" : 31,
9 | "age-in-show": 16,
10 | "notes": [
11 | "Spoilers: Both Chariot and Croix went to class together. In episode 15, Croix graduated from Luna Nova in 2007. In episode 23, we see that Chariot preformed while she was still in school. By knowing that, the last performance has to be in or before 2007, when they graduated. 2007 + 10 years is 2017. Meaning the story takes place during 2017 or earlier. Her age during the first episode is 16. 2017 - 16 years is 2001."
12 | ],
13 | "sources" : [
14 | "https://little-witch-academia.fandom.com/wiki/Sucy_Manbavaran"
15 | ]
16 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}/site-generator.ts",
12 | "outFiles": [
13 | "${workspaceFolder}/tsc-output/**/*.js"
14 | ]
15 | },
16 | {
17 | "name": "Launch Discord Bot",
18 | "request":"launch",
19 | "type":"cppdbg",
20 | "program":"${workspaceRoot}/discord-bot/build/waifu-bot",
21 | "cwd":"${workspaceRoot}/discord-bot/build/"
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/discord-bot/CMakeSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "x64-Debug",
5 | "generator": "Ninja",
6 | "configurationType": "Debug",
7 | "inheritEnvironments": [ "msvc_x64_x64" ],
8 | "buildRoot": "${projectDir}\\out\\build\\${name}",
9 | "installRoot": "${projectDir}\\out\\install\\${name}",
10 | "cmakeCommandArgs": "",
11 | "buildCommandArgs": "-v",
12 | "ctestCommandArgs": "",
13 | "variables": []
14 | },
15 | {
16 | "name": "x64-Release",
17 | "generator": "Ninja",
18 | "configurationType": "RelWithDebInfo",
19 | "buildRoot": "${projectDir}\\out\\build\\${name}",
20 | "installRoot": "${projectDir}\\out\\install\\${name}",
21 | "cmakeCommandArgs": "",
22 | "buildCommandArgs": "-v",
23 | "ctestCommandArgs": "",
24 | "inheritEnvironments": [ "msvc_x64_x64" ],
25 | "variables": []
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "is-your-waifu-legal",
3 | "version": "1.0.0",
4 | "description": "It can be pretty hard to tell if a waifu is legal or a 100 year old dragon. Some people (Not us), have issues with lolis in NSFW stuff, which is why this tool exist. This tool's purpose is to make it easy to calculate the **current** age of a waifu.",
5 | "main": "site-generator.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/yourWaifu/is-your-waifu-legal.git"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/yourWaifu/is-your-waifu-legal/issues"
18 | },
19 | "homepage": "https://github.com/yourWaifu/is-your-waifu-legal#readme",
20 | "devDependencies": {
21 | "@types/node": "^11.10.4",
22 | "typescript": "^3.3.3333"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Sleepy Flower Girl
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 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | lang: node_js
2 | node_js:
3 | - '0.10'
4 | before_install:
5 | - npm install -g typescript
6 | - npm i @types/node
7 | script:
8 | - bash generate-site.sh
9 | deploy:
10 | - provider: pages
11 | local-dir: output-site
12 | target-branch: gh-pages
13 | skip-cleanup: true
14 | github-token: $GH_TOKEN # Set in the settings page of your repository, as a secure variable
15 | keep-history: true
16 | on:
17 | branch: master
18 | env:
19 | global:
20 | secure: JEN5ANgY83qhtJ0fI6T1Xn7ZFdCODasJJQ/KppuaCQw66jh4qFeg4ltJqRXPvQ3j2jJUCyINvf6lEx6Bo+YGeUMomprycpKXqr6buqFK4SyEct6WFpxeKuZ0W/QuyrNEwC3H2viHcKj666UqIXxRH6GO9t84caHd0NJJFkd1B3hoXl9vglIw4u3JgY722KODVYxJFO1GF2yV0v5uXiLqQonfriUjN0FlzmCDRUUW32JqLMv+7eRrjwpV6Pk9TGXdltMnk1xPlToAvJgT/YJSVhDBIA2vB8kYNdTHmoR1rUlLOuzLmvhFdfZSG66pS6x+1NkP96BH/aOnM2pyE9FaCSs+mcsZjWuBIwSjkrfAHBLJ2Vhlkwyfw+x1/J1QJX0ALVsYtnVBqENfxoYAvSz+WLRMo1lwgwSXT9Uqza/717Y0ABSiI07HcgxetRJY6IZ6Qn7ymLtJLDdGBZrEAtMXnDLi/oFzP2tbHd6rTY+Znj54tsH4I6886pe9WwPA/KhNCjBJOUianY5jY8kMnJOvl/nXt6gVitr60ZV17fud662gQwypar2m8vv0MsQlfL4Io38c7DgJX/c47bDddRh57BsTT2wyYXy+bo1T4fqVzPVaj5T1UJc0st6ruMpcv7sybwYc6rWvc1RiGblGq02nxjpYEks0SFDCcirFu5ba4Z8=
21 |
--------------------------------------------------------------------------------
/discord-bot/IO_file.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | //This is c++11 code, so there's no std filesystem
6 |
7 | class File {
8 | public:
9 | File(const char* path) : file(path, std::ios::binary | std::ios::ate) {
10 | /*std::ios::ate seeks to the end
11 | This tells us where the input position indicator is
12 | giving us the size */
13 | size = file.tellg();
14 | /*sets the input position indicator to the begining
15 | when the input position indicator reaches the end, a
16 | eof flag is set and so clear() fixes that. */
17 | file.clear();
18 | file.seekg(0, std::ios::beg);
19 | }
20 |
21 | File(std::string& path) : File(path.c_str()) {}
22 |
23 | ~File() = default;
24 |
25 | template
26 | const void get(std::vector& vector) {
27 | const std::size_t numOfElements = size / sizeof(Type);
28 | vector.resize(numOfElements);
29 | file.read(reinterpret_cast(vector.data()), size);
30 | }
31 |
32 | template
33 | const void get(Type* buffer) {
34 | file.read(reinterpret_cast(buffer), size);
35 | }
36 |
37 | const std::size_t getSize() {
38 | return size;
39 | }
40 |
41 | private:
42 | std::ifstream file;
43 | std::size_t size;
44 | };
--------------------------------------------------------------------------------
/site/_layouts/readme.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
12 | Age in anime is strange
13 |
14 |
18 | So, here's a database just for figuring out the age of your waifu
19 |
20 |
24 | Enter your waifu's name on the line above
25 |
26 |
27 |
40 |
53 |
54 |
55 |
58 |
59 | {{ content }}
60 |
61 |
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | output-site
2 | tsc-output
3 | discord-bot/out
4 | discord-bot/build
5 | discord-bot/DiscordToken.txt
6 | discord-bot/.vs
7 |
8 | ## node js files
9 |
10 | # Logs
11 | logs
12 | *.log
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # TypeScript v1 declaration files
49 | typings/
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional REPL history
58 | .node_repl_history
59 |
60 | # Output of 'npm pack'
61 | *.tgz
62 |
63 | # Yarn Integrity file
64 | .yarn-integrity
65 |
66 | # dotenv environment variables file
67 | .env
68 | .env.test
69 |
70 | # parcel-bundler cache (https://parceljs.org/)
71 | .cache
72 |
73 | # next.js build output
74 | .next
75 |
76 | # nuxt.js build output
77 | .nuxt
78 |
79 | # vuepress build output
80 | .vuepress/dist
81 |
82 | # Serverless directories
83 | .serverless/
84 |
85 | # FuseBox cache
86 | .fusebox/
87 |
88 | # DynamoDB Local files
89 | .dynamodb/
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.insertSpaces": false,
3 | "files.associations": {
4 | "cctype": "cpp",
5 | "clocale": "cpp",
6 | "cmath": "cpp",
7 | "csignal": "cpp",
8 | "cstdarg": "cpp",
9 | "cstddef": "cpp",
10 | "cstdio": "cpp",
11 | "cstdlib": "cpp",
12 | "cstring": "cpp",
13 | "ctime": "cpp",
14 | "cwchar": "cpp",
15 | "cwctype": "cpp",
16 | "*.ipp": "cpp",
17 | "array": "cpp",
18 | "atomic": "cpp",
19 | "hash_map": "cpp",
20 | "hash_set": "cpp",
21 | "strstream": "cpp",
22 | "*.tcc": "cpp",
23 | "bitset": "cpp",
24 | "chrono": "cpp",
25 | "cinttypes": "cpp",
26 | "codecvt": "cpp",
27 | "complex": "cpp",
28 | "condition_variable": "cpp",
29 | "cstdint": "cpp",
30 | "deque": "cpp",
31 | "forward_list": "cpp",
32 | "list": "cpp",
33 | "unordered_map": "cpp",
34 | "unordered_set": "cpp",
35 | "vector": "cpp",
36 | "exception": "cpp",
37 | "algorithm": "cpp",
38 | "functional": "cpp",
39 | "iterator": "cpp",
40 | "map": "cpp",
41 | "memory": "cpp",
42 | "memory_resource": "cpp",
43 | "numeric": "cpp",
44 | "optional": "cpp",
45 | "random": "cpp",
46 | "ratio": "cpp",
47 | "regex": "cpp",
48 | "set": "cpp",
49 | "string": "cpp",
50 | "string_view": "cpp",
51 | "system_error": "cpp",
52 | "tuple": "cpp",
53 | "type_traits": "cpp",
54 | "utility": "cpp",
55 | "fstream": "cpp",
56 | "future": "cpp",
57 | "initializer_list": "cpp",
58 | "iomanip": "cpp",
59 | "iosfwd": "cpp",
60 | "iostream": "cpp",
61 | "istream": "cpp",
62 | "limits": "cpp",
63 | "mutex": "cpp",
64 | "new": "cpp",
65 | "ostream": "cpp",
66 | "sstream": "cpp",
67 | "stdexcept": "cpp",
68 | "streambuf": "cpp",
69 | "thread": "cpp",
70 | "cfenv": "cpp",
71 | "typeindex": "cpp",
72 | "typeinfo": "cpp"
73 | }
74 | }
--------------------------------------------------------------------------------
/site/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Is Your Waifu Legal?
9 |
10 |
11 |
12 |
13 |
14 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Is This Waifu Legal](https://yourwaifu.dev/is-your-waifu-legal/)
2 |
3 | It can be pretty hard to tell if a waifu is legal or a 100 year old dragon. Some people (Not us), have issues with lolis in NSFW stuff, which is why this tool exist. This tool's purpose is to make it easy to calculate the **current** age of a waifu.
4 |
5 | ## Discord Bot
6 |
7 | Is Your Waifu Legal is also available as a Discord Bot. [Click here to invite](https://discord.com/oauth2/authorize?client_id=185977385416523776&scope=applications.commands&permissions=18432). Just start your message with ``whcg``, short for **W**hite **H**aired **C**at **G**irl, and the name of the waifu.
8 | * [View on Discord Bot List (top.gg)](https://top.gg/bot/186151807699910656)
9 | * [View on Discord Bots](https://discord.bots.gg/bots/186151807699910656)
10 |
11 | ## Donate
12 |
13 | [Sponsor on Github](https://github.com/sponsors/yourWaifu)
14 |
15 | # How to add a Waifu to the list?
16 |
17 | 1. Make sure you have a github account. If you don't, [create one](https://github.com/join).
18 | 2. [Click here to fork](https://github.com/yourWaifu/is-this-waifu-legal/fork)
19 | 3. In your fork, go to waifus folder.
20 | 4. Click the Create new file button.
21 | 5. Name it the english name.json
22 | 6. Fill in the details. [Use this as an example](https://github.com/yourWaifu/is-this-waifu-legal/tree/master/waifus/futaba%20sakura.json) and here's a [List of all values](https://github.com/yourWaifu/is-this-waifu-legal/tree/master/reference.md#reference).
23 | 7. Save and commit changes.
24 | 8. On your fork on GitHub, click Compare & pull request to create your request.
25 | 9. Wait for more instructions if needed. If not needed, it'll be accepted and the waifu will be added to the list.
26 |
27 | If you need more help, please [read the First Contributions Guide.](https://github.com/firstcontributions/first-contributions/blob/master/README.md)
28 |
29 | # FAQ
30 |
31 | ## How do waifus have 18th birthdays? do they age?
32 |
33 | Well it depends on the anime and the waifu in question.
34 |
35 | ## If a waifu turns 18 in the show, what would happen if you fapped to the wrong season?
36 |
37 | [This happens](https://www.youtube.com/watch?v=08vk9g-jcsM)
38 |
39 | ## Were we supposed to not fap before?
40 |
41 | It's the same answer to this question, Is it illegal to fap to underage fictional characters? I'll let you decide that for yourself.
42 |
43 | ## Is this a joke?
44 |
45 | Yea
46 |
47 | ## Why not just look it up on Google?
48 |
49 | Many wiki sites have info on this stuff, but you have to read through a bunch of other info to figure out the current age, the age in the show, and what season and ep they've become of legal age, and how mature they act. This site is one place with all the age data and information about anime girl ages.
50 |
--------------------------------------------------------------------------------
/reference.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | You'll need to install the dependencies, to do that [install node](https://nodejs.org/en/) and use this command. You only need to do this once.
4 |
5 | ```
6 | cd is-your-waifu-legal
7 | npm install
8 | ```
9 | then run the shell script to generate the site. You'll need to do this for every change you make.
10 | ```
11 | bash ./generate-site.sh
12 | ```
13 | change the current directory to the output and run jekyll. I also recommend opening a new console window/tab for doing this, so that jekyll can watch for changes from the generate-site script and automatically generate the site for you.
14 | ```
15 | cd output-site
16 | jekyll s
17 | ```
18 | Jekyll will output a server address, open your browser and use that address as a link to open the site on your browser.
19 | ```
20 | # Look out for this line from jekyll #
21 | Server address: http://127.0.0.1:4000
22 | ```
23 | To stop jekyll, press ctrl + c.
24 |
25 | # Reference
26 |
27 | ## General
28 |
29 | | Variable | Type | Flags | Description |
30 | | ----------------|------------------------| ---------------------|----------------------------------|
31 | | `type` | number | required | unused, for future use. Keep at 0 for now. |
32 | | `english-name` | string | optional | |
33 | | `japanese-name` | string | optional | |
34 | | `aliases` | array of strings | optional | other names the waifu is known as. |
35 | | `image` | string | optional | a link to a picture of the waifu |
36 | | `notes` | array of strings | optional | each element in the array is one bullet point or one point |
37 | | `sources` | array of strings | required | each element in the array is one link to where this info came from. It can be a video or an article or book or etc. |
38 | | `definitely-legal` | boolean | optional | true if notes or other info states she's legal |
39 | | `is-a-trap` | boolean | optional | unused |
40 |
41 | ## Birthday variables
42 |
43 | if not legal based on birthday, then there will be a countdown displayed for when they become legal.
44 |
45 | | Variable | Type | Flags | Description |
46 | | ----------------|------------------------| ---------------------|----------------------------------|
47 | | `month` | number | optional | |
48 | | `day-of-month` | number | optional | |
49 | | `year` | number | optional | |
50 |
51 | ## Appearence variables
52 |
53 | | Variable | Type | Flags | Description |
54 | | ----------------|------------------------| ---------------------|----------------------------------|
55 | | `age-group-by-appearance` | string | optional | can be child or teenager or adult |
56 | | `age-range-by-appearance` | array of 2 numbers | optional | the first number is the start of the range and the 2nd is the end of the range. For example, ages 1 to 10 is [1, 10] |
57 |
58 | ## Story variables
59 |
60 | | Variable | Type | Flags | Description |
61 | | ----------------|------------------------| ---------------------|----------------------------------|
62 | | `age-in-show` | number | optional | the age of the waifu during the story |
63 | | `finally-legal-in-show` | string | optional | the episode, movie, volume, etc. when they became of legal age |
64 |
--------------------------------------------------------------------------------
/site-generator.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "path";
3 |
4 | let outputDirName : string = "output-site";
5 | let inputFilename : string = "site-generator-config.json";
6 |
7 | // https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
8 |
9 | function copyFileSync(source: string, target: string) : void {
10 | let targetFile : string = target;
11 |
12 | if (fs.existsSync(target) && fs.lstatSync(target).isDirectory()) {
13 | targetFile = path.join(target, path.basename(source));
14 | }
15 |
16 | fs.writeFileSync(targetFile, fs.readFileSync(source));
17 | }
18 |
19 | function copyFolderRecursiveSync(source: string, target: string) : void {
20 | let files : Array = [];
21 |
22 | let targetFolder : string = path.join(target, path.basename(source));
23 | if (!fs.existsSync(targetFolder)) {
24 | fs.mkdirSync(targetFolder);
25 | }
26 |
27 | //copy
28 | if (fs.lstatSync(source).isDirectory()) {
29 | files = fs.readdirSync(source);
30 | files.forEach(function(file: string) {
31 | let curSource = path.join(source, file);
32 | if (fs.lstatSync(curSource).isDirectory()) {
33 | copyFolderRecursiveSync(curSource, targetFolder);
34 | } else {
35 | copyFileSync(curSource, targetFolder);
36 | }
37 | });
38 | }
39 | }
40 |
41 | function copyFilesInFolderSync(source: string, target: string) : void {
42 | let files: Array = [];
43 |
44 | //copy
45 | if (fs.lstatSync(source).isDirectory()) {
46 | files = fs.readdirSync(source);
47 | files.forEach(function(file: string) {
48 | let curSource = path.join(source, file);
49 | if (fs.lstatSync(curSource).isDirectory()) {
50 | copyFolderRecursiveSync(curSource, target);
51 | } else {
52 | copyFileSync(curSource, target);
53 | }
54 | });
55 | }
56 | }
57 |
58 | class InputType {
59 | copyDirectories: Array;
60 | copyFilesInDirectories: Array;
61 | constructor(input: any) {
62 | if (input.hasOwnProperty("copy-directories"))
63 | this.copyDirectories = >input["copy-directories"];
64 | else
65 | this.copyDirectories = [];
66 |
67 |
68 | if (input.hasOwnProperty("copy-files-in-directories"))
69 | this.copyFilesInDirectories = >input["copy-files-in-directories"];
70 | else
71 | this.copyFilesInDirectories = [];
72 | }
73 | }
74 |
75 | if (!fs.existsSync(outputDirName)) {
76 | fs.mkdirSync(outputDirName);
77 | }
78 |
79 | let input : InputType = new InputType(require("../" + inputFilename));
80 |
81 | input.copyDirectories.forEach(function(directory: string) {
82 | copyFolderRecursiveSync(directory, outputDirName);
83 | });
84 | input.copyFilesInDirectories.forEach(function(file:string){
85 | copyFilesInFolderSync(file, outputDirName);
86 | });
87 |
88 | //generate main page
89 | let indexPage : string = fs.readFileSync("README.md", "utf8");
90 | let indexHeader : string =
91 | "---\n" +
92 | "layout: readme\n" +
93 | "---\n";
94 | //replace first line with indexHeader
95 | indexPage = indexHeader + indexPage.substring(indexPage.indexOf('\n') + 1);
96 | fs.writeFile(path.join(outputDirName, "README.md"), indexPage, ()=>{});
97 |
98 | //generate trie tree
99 | namespace SearchTree {
100 | class Branch {
101 | children: any | undefined;
102 | //if value is not null, then we are at the end
103 | value: undefined | number;
104 | constructor() {
105 | this.children = undefined;
106 | }
107 | createChild(key: string) : Branch {
108 | if (this.children === undefined)
109 | this.children = {};
110 | this.children[key] = new Branch();
111 | let l = this.children[key];
112 | return l !== undefined ? l : new Branch();
113 | }
114 | setValue(value:number) : void {
115 | this.value = value;
116 | }
117 | };
118 |
119 | export class Tree {
120 | allKeys: Array;
121 | root:Branch;
122 | constructor() {
123 | this.allKeys = [];
124 | this.root = new Branch();
125 | }
126 | insert(sourceKey: string) : void {
127 | let key: string = sourceKey.substr(0, sourceKey.lastIndexOf('.'));
128 | let value:number = this.allKeys.push(key) - 1;
129 | let position : Branch = this.root;
130 | for (let i:number = 0; i < key.length; ++i) {
131 | let letter: string = key[i];
132 | //basically position = position.children[letter];
133 | if (position === undefined)
134 | throw "position is undefined";
135 | if (position.children === undefined) {
136 | position = position.createChild(letter);
137 | } else {
138 | let next: Branch | undefined = position.children[letter];
139 | if (next === undefined)
140 | position = position.createChild(letter);
141 | else
142 | position = next;
143 | }
144 | if (position.value === undefined)
145 | position.setValue(value);
146 | }
147 | }
148 | }
149 | }
150 |
151 | let searchTree : SearchTree.Tree = new SearchTree.Tree();
152 | let waifuFiles = fs.readdirSync("waifus");
153 | waifuFiles.sort().forEach(function(file:string) {
154 | searchTree.insert(file);
155 | });
156 |
157 | let searchTreeJson: string = JSON.stringify(searchTree);
158 | //replace repeated variables with shorten names
159 | searchTreeJson = searchTreeJson.replace(/"children":/g, "\"c\":");
160 | searchTreeJson = searchTreeJson.replace(/"value":/g, "\"v\":");
161 |
162 | fs.writeFile(path.join(outputDirName, "search-tree.json"), searchTreeJson, ()=>{});
--------------------------------------------------------------------------------
/site/style.css:
--------------------------------------------------------------------------------
1 | .header {
2 | position: relative; /*For some reason you need this for the shadow to show up*/
3 | background-color: #181818;
4 | box-shadow: 0 0 12px black;
5 | }
6 |
7 | .header-container {
8 | padding: .5em;
9 | display: flex;
10 | }
11 |
12 | .header-flex {
13 | margin: 0;
14 | list-style: none;
15 | width: auto;
16 | padding-left: 0em;
17 | }
18 |
19 | .left {
20 | flex: 0 0 auto; /*Don't change size*/
21 | }
22 |
23 | .right {
24 | text-align: right;
25 | width: auto;
26 | justify-content: flex-end;
27 | flex: 0 0 auto;
28 | }
29 |
30 | .left a, .right a {
31 | color: white;
32 | }
33 |
34 | .left a:hover, .right a:hover {
35 | color: darkgray;
36 | }
37 |
38 | .right button {
39 | background-color: transparent;
40 | color: white;
41 | border-style: none;
42 | border-width: 0;
43 | font-size: 1em;
44 | padding: 0 .5em 0 0;
45 | }
46 |
47 | .right button:hover {
48 | color:darkgray;
49 | cursor: pointer;
50 | }
51 |
52 | .middle {
53 | flex: 1 0 auto; /*Grow, but don't shrink*/
54 | margin-left: 0.5em;
55 | margin-right: 0.5em;
56 | position: relative; /*For some reason the search box needs this*/
57 | }
58 |
59 | .github {
60 | text-align: right;
61 | }
62 |
63 | .github i {
64 | padding-top: .1em;
65 | }
66 |
67 | /*
68 | Copied from
69 | https://github.com/yourWaifu/yourWaifu.github.io/blob/master/style.css
70 | */
71 | body {
72 | margin: 0 auto;
73 | background-color: #212121;
74 | font-family: 'Open Sans', sans-serif;
75 | color: white;
76 | text-rendering: optimizeLegibility;
77 | }
78 |
79 | a {
80 | color: #64b8fc;
81 | text-decoration: none;
82 | }
83 | a:hover {
84 | text-decoration: underline;
85 | }
86 |
87 | ul {
88 | overflow: auto;
89 | }
90 |
91 | h1 {
92 | /*There are plug ins that mess me with this, so this is a fix*/
93 | line-height: normal;
94 | }
95 |
96 | .title {
97 | font-size: 2em;
98 | }
99 |
100 | .search-box {
101 | width: 100%;
102 | position: absolute;
103 | bottom: .15em;
104 | background-color: transparent;
105 | border-top-color: transparent;
106 | border-left: transparent;
107 | border-right: transparent;
108 | color: white;
109 | font-size: 1em;
110 | }
111 |
112 | .waifu-name {
113 | overflow: auto;
114 | }
115 |
116 | .waifu-image {
117 | margin: 0 auto;
118 | display: block;
119 | max-width: 100%;
120 | flex: 0 1 100%;
121 | }
122 |
123 | .waifu-body {
124 | display: flex;
125 | flex-direction: row;
126 | }
127 |
128 | .dropdown-content-container {
129 | position: relative;
130 | height: 0;
131 | }
132 |
133 | .dropdown-content {
134 | display: none;
135 | position: absolute;
136 | top: 1.7em;
137 | left: -.5em;
138 | background: #181818;
139 | padding: 0 .5em .5em .5em;
140 | box-shadow: -2px 2px 10px black;
141 | }
142 |
143 | .output {
144 | display: flex;
145 | max-width: 100vw;
146 | flex-direction: column;
147 | }
148 |
149 | .read-me-container {
150 | position: absolute;
151 | top: 90%;
152 | display: flex;
153 | flex-direction: row;
154 | justify-content: center;
155 | width: 100%;
156 | }
157 |
158 | .read-me {
159 | max-width: 37em;
160 | margin: 0 auto;
161 | padding-left: 1em;
162 | padding-right: 1em;
163 | margin-top: 1em;
164 | }
165 |
166 | .waifu-stats {
167 | max-width: 37em;
168 | flex: 0 0 37em;
169 | }
170 |
171 | .front-screen, .front-screen-firefox {
172 | width: 100%;
173 | display: flex;
174 | flex-direction: column;
175 | justify-content: center;
176 | align-items: center;
177 | }
178 |
179 | .bottom-menu {
180 | flex: 1 0;
181 | display: flex;
182 | flex-direction: row;
183 | justify-content: center;
184 | align-content: center;
185 | }
186 |
187 | .bottom-menu button {
188 | border-style: none;
189 | border-width: 0;
190 | background-color: transparent;
191 | color: white;
192 | }
193 |
194 | .mobile-search-button {
195 | display: none;
196 | }
197 |
198 | .mobile-search-button-during-search {
199 | display: none;
200 | }
201 |
202 | .flex-margins-mobile-container {
203 | display: flex;
204 | flex-direction: row;
205 | width: 100%;
206 | }
207 |
208 | .flex-center-mobile {
209 | flex: 1 1;
210 | }
211 |
212 | @media screen and (max-width: 55.5em) {
213 | .header-container {
214 | flex-direction: column;
215 | }
216 |
217 | .left {
218 | margin-left: 0;
219 | text-align: center;
220 | }
221 |
222 | .middle {
223 | margin: 0;
224 | margin-top: 1em;
225 | }
226 |
227 | /*
228 | This doesn't look good on mobile,
229 | disable "right" for now as a temp fix
230 | */
231 | .right {
232 | display: none;
233 | }
234 |
235 | .dropdown-content {
236 | top: 0em;
237 | z-index: 1;
238 | }
239 |
240 | .waifu-body {
241 | flex-wrap: wrap;
242 | }
243 |
244 | .waifu-name {
245 | margin-left: auto;
246 | margin-right: auto;
247 | flex: 0 1 calc(37em/2);
248 | }
249 |
250 | .flex-margins-mobile {
251 | flex: 0 0 .5em;
252 | width: .5em;
253 | }
254 |
255 | .waifu-image-parent {
256 | overflow: auto;
257 | max-height: 50vh;
258 | width: 100%;
259 | }
260 |
261 | .waifu-stats {
262 | flex-shrink: 1;
263 | width: calc(100% - 1em);
264 | margin: 0 auto;
265 | }
266 |
267 | .front-screen {
268 | /*The difference between % and vh on chrome mobile
269 | % was the height of the screen - height of link bar
270 | vh is the height of the screen*/
271 | height: calc(100% - 7.25em);
272 | position: absolute;
273 | }
274 |
275 | .front-screen-firefox {
276 | /*There's an issue with firefox, where using %
277 | sets this to unset*/
278 | height: calc(100vh - 7.25em);
279 | position: absolute;
280 | }
281 |
282 | .mobile-search-button {
283 | display: unset;
284 | }
285 | }
286 |
287 | @media screen and (min-width: 55.5em) {
288 | .waifu-body {
289 | flex-wrap: nowrap;
290 | justify-content: space-between;
291 | }
292 |
293 | .flex-margins-mobile-container {
294 | display: contents;
295 | }
296 |
297 | .waifu-image-parent {
298 | flex: 1 1 auto;
299 | margin: calc(2em * 0.67) 0;
300 | }
301 |
302 | .flex-margins-mobile {
303 | display: none;
304 | }
305 |
306 | .flex-center-mobile {
307 | display: none;
308 | }
309 |
310 | .waifu-image {
311 | max-width: 100%;
312 | }
313 |
314 | .waifu-stats {
315 | margin-right: .5em;
316 | margin-left: calc(2em * 0.67);
317 | }
318 |
319 | .front-screen, .front-screen-firefox {
320 | height: calc(100vh - 5.5em);
321 | }
322 | }
--------------------------------------------------------------------------------
/script.ts:
--------------------------------------------------------------------------------
1 | //Warning make sure you are editing the ts file and not the js file
2 |
3 | var legalAge = 18;
4 | let siteName = "Is Your Waifu Legal?";
5 |
6 | //units
7 | let millisecond : number = 1;
8 | let second : number = 1000 * millisecond;
9 | let minute : number = 60 * second;
10 | let hour : number = 60 * minute;
11 | let day : number = 24 * hour;
12 |
13 | var countdown : number = -1;
14 |
15 | let months : Array = [
16 | "January",
17 | "February",
18 | "March",
19 | "April",
20 | "May",
21 | "June",
22 | "July",
23 | "August",
24 | "September",
25 | "October",
26 | "November",
27 | "December"
28 | ];
29 |
30 | function hasValue(data: JSON, key: string) : boolean {
31 | try {
32 | return data.hasOwnProperty(key) && data[key] !== null;
33 | } catch (error) {
34 | return false;
35 | }
36 | }
37 |
38 | function hasYear(waifu : JSON) : boolean {
39 | return hasValue(waifu, "year");
40 | }
41 |
42 | function hasMonth(waifu : JSON) : boolean {
43 | return hasValue(waifu, "month");
44 | }
45 |
46 | function hasDay(waifu : JSON) : boolean {
47 | return hasValue(waifu, "day-of-month");
48 | }
49 |
50 | function getCountdownHTML(countdownTime : number) : string {
51 | //sanity check
52 | if (countdownTime === undefined) {
53 | return "";
54 | }
55 |
56 | let currentDate : Date = new Date();
57 | let currentTime : number = currentDate.getTime();
58 | let difference : number = countdownTime - currentTime;
59 | let seconds : number = Math.floor((difference % minute) / second);
60 | let minutes : number = Math.floor((difference % hour) / minute);
61 | let hours : number = Math.floor((difference % day) / hour);
62 | let days : number = Math.floor(difference / day);
63 | //To do calculate years, take into account leap years.
64 |
65 | let html : string = "Countdown to 18th birthday: "
66 | html += days + " days ";
67 | html += hours + " hours ";
68 | html += minutes + " minutes ";
69 | html += seconds + " seconds
\n";
70 | return html;
71 | }
72 |
73 | function getAgeHTML(waifu : JSON) : string {
74 | let html : string = "";
75 | if (hasYear(waifu)) {
76 | let currentDate : Date = new Date();
77 | let age : number = currentDate.getFullYear() - waifu["year"];
78 | //take into count the month
79 | if (hasMonth(waifu)) {
80 | let month : number = waifu["month"];
81 | let currentMonth : number = currentDate.getMonth() + 1;
82 | if (
83 | (
84 | currentMonth < month
85 | ) || (
86 | //take into count the day
87 | currentMonth === month &&
88 | waifu.hasOwnProperty("day-of-month") &&
89 | waifu["day-of-month"] !== null &&
90 | currentDate.getDay() < waifu["day-of-month"]
91 | )
92 | ) {
93 | --age;
94 | }
95 | }
96 | html += "age: ";
97 | html += age.toString();
98 | html += " years old
\n"
99 | if (legalAge <= age) {
100 | html += "Looks legal to me.
\n"
101 | //stop timer
102 | if (countdown !== -1) {
103 | clearInterval(countdown);
104 | //congrats, your waifu is now of legal age
105 | }
106 | } else {
107 | html += "Not legal
\n" +
108 | "Wait ";
109 | html += legalAge - age;
110 | html += " more years.
\n"
111 |
112 | //We need to start the timer before we can display it
113 | if (countdown === -1) {
114 | countdown = window.setInterval(function() {
115 | let countdownElement : HTMLElement | null = document.getElementById("dynamic-data");
116 | if (countdownElement !== null) {
117 | countdownElement.innerHTML = getDynamicDataHTML(waifu);
118 | }
119 | }, 1 * second);
120 | }
121 |
122 | html += getCountdownHTML(getBirthDate(waifu, legalAge).getTime());
123 | }
124 | } else {
125 | html += "Year of birth is unknown. Sorry.
\n"
126 | }
127 | return html;
128 | }
129 |
130 | function getBirthDate(waifu : JSON, yearsOffset : number = 0) : Date {
131 | if (!hasYear(waifu)) {
132 | return new Date();
133 | }
134 | let year : number = waifu["year"] + yearsOffset;
135 | if (!hasMonth(waifu)) {
136 | return new Date(year);
137 | } else if (!hasDay(waifu)) {
138 | return new Date(year, waifu["month"] - 1);
139 | } else {
140 | return new Date(year, waifu["month"], waifu["day-of-month"]);
141 | }
142 | }
143 |
144 | function getDynamicDataHTML(waifu : JSON) : string {
145 | return getAgeHTML(waifu);
146 | }
147 |
148 | function sanitizeInput(input:string) : string {
149 | return input.replace(/&/g, '&').replace(/";
161 | HTML += englishName;
162 | HTML += "\n";
163 | return HTML;
164 | }
165 |
166 | function getMarginMobile() : string {
167 | return "";
168 | }
169 |
170 | function displayWaifuStats(name : string) : void {
171 | let input : string = sanitizeInput(name);
172 | let foundOutput : HTMLElement | null = document.getElementById("output");
173 | let output : HTMLElement;
174 | if (foundOutput === null) return;
175 | else output = foundOutput;
176 | output.innerHTML = "";
177 |
178 | if (input === "") {
179 |
180 | return;
181 | }
182 |
183 | //add search to history
184 | let parms : URLSearchParams | null = new URLSearchParams(window.location.search);
185 | let search : string | null = parms.get("q");
186 | let historyState : any = {"q":input};
187 | let query : string = "?q=" + input;
188 | if (search === null || search !== input) {
189 | history.pushState(historyState, "", query);
190 | } else {
191 | history.replaceState(historyState, "", query)
192 | }
193 |
194 | let request : XMLHttpRequest = new XMLHttpRequest();
195 | request.open("GET", getCurrentURL() + "waifus/" + input.toLowerCase() + ".json");
196 | request.responseType = "json";
197 | request.onerror = function(event) {
198 | console.log(event);
199 | output.innerHTML = "Something went wrong. Look at console for more info";
200 | };
201 | request.onload = function() {
202 | let newHTML : string = "";
203 | switch(this.status) {
204 | case 200: //OK
205 | break;
206 | case 404:
207 | newHTML +=
208 | "Could not find this person. Sorry.
\n" +
209 | "Maybe you spelled her name wrong.
\n" +
210 | "Maybe you forgot to enter her full name.
\n" +
211 | "If you know her age, " +
212 | "" +
213 | "please add her" +
214 | ".";
215 | default:
216 | let error : string = "Error " + this.status.toString()
217 | output.innerHTML = error + "
\n" + newHTML;
218 | document.title = error + " - " + siteName;
219 | return;
220 | }
221 |
222 | //clear values before starting
223 | if (countdown !== -1) {
224 | clearInterval(countdown);
225 | countdown = -1;
226 | }
227 |
228 | if (this.response === null) {
229 | output.innerHTML = "ERROR:
Response from server was null";
230 | return;
231 | }
232 |
233 | let data : JSON = this.response;
234 |
235 | newHTML += "\n"
236 |
237 | let englishName : string = data.hasOwnProperty("english-name") ? data["english-name"] : "";
238 | document.title = englishName + " - " + siteName;
239 |
240 | //display waifu image
241 | if (data.hasOwnProperty("image") && data["image"] !== null && data["image"] !== "") {
242 | newHTML += "
\n"
243 | newHTML += "

\n"
248 | newHTML += "
\n";
249 | }
250 |
251 | newHTML += "
\n"
252 | newHTML += getMarginMobile();
253 | newHTML += "
\n";
254 |
255 | newHTML += getWaifuNameHTML(englishName, "waifu-name");
256 |
257 | if (hasValue(data, "definitely-legal") && data["definitely-legal"] === true)
258 | newHTML += "Definitely Legal
\n"
259 |
260 | //display birthday
261 | newHTML += "Based on birthday:\n"
262 | let hasAnyBirthDayInfo : boolean = false;
263 | if (hasMonth(data)) {
264 | newHTML += months[Number(data["month"]) - 1] + " ";
265 | hasAnyBirthDayInfo = true;
266 | }
267 | if (hasDay(data)) {
268 | newHTML += data["day-of-month"].toString();
269 | hasAnyBirthDayInfo = true;
270 | }
271 | if (hasYear(data)) {
272 | if (hasAnyBirthDayInfo) {
273 | newHTML += ", ";
274 | }
275 | newHTML += data["year"].toString();
276 | hasAnyBirthDayInfo = true;
277 | }
278 | if (hasAnyBirthDayInfo)
279 | newHTML += "
\n"
280 |
281 | // Calculate data
282 | newHTML += "
\n"
283 | newHTML += getDynamicDataHTML(data);
284 | newHTML += "
\n"
285 |
286 | //based on appearance
287 | let appearanceDataHTML = "";
288 | let appearanceAnswer = "";
289 | if (hasValue(data, "age-group-by-appearance")) {
290 | appearanceDataHTML += "looks like a(n) ";
291 | appearanceDataHTML += data["age-group-by-appearance"];
292 | appearanceDataHTML += "\n";
293 | switch (data["age-group-by-appearance"]) {
294 | case "child":
295 | appearanceAnswer = "Doesn't look legal";
296 | break;
297 | case "teenager":
298 | appearanceAnswer = "Looks like they might be too young to be legal. Maybe?";
299 | break;
300 | default:
301 | appearanceAnswer = "looks legal";
302 | break;
303 | }
304 |
305 | }
306 | if (hasValue(data, "age-range-by-appearance") && data["age-range-by-appearance"][0] !== undefined) {
307 | if (appearanceDataHTML !== "")
308 | appearanceDataHTML += "
\n"
309 | let startAge : number = data["age-range-by-appearance"][0];
310 | appearanceDataHTML += "looks about ";
311 | appearanceDataHTML += startAge;
312 | let endAge : number | undefined = data["age-range-by-appearance"][1];
313 | if (endAge !== undefined && startAge !== endAge) {
314 | appearanceDataHTML += " to ";
315 | appearanceDataHTML += endAge;
316 | }
317 | appearanceDataHTML += " years old\n"
318 | if (appearanceAnswer !== "") {
319 | //to do, looks like there's repeated code here
320 | appearanceAnswer =
321 | startAge < legalAge ?
322 | "Doesn't look legal"
323 | : startAge <= legalAge + 1 ?
324 | "Looks barely legal"
325 | :
326 | "Looks legal";
327 | }
328 | }
329 | if(appearanceDataHTML !== "") {
330 | newHTML += "
\nBased on appearance:\n
\n";
331 | newHTML += appearanceDataHTML;
332 | newHTML += "
\n";
333 | newHTML += appearanceAnswer;
334 | newHTML += "
\n";
335 | }
336 |
337 | //in the story
338 | let storyAgeHTML : string = "";
339 | if (hasValue(data, "age-in-show")) {
340 | let storyAge : number = data["age-in-show"];
341 | storyAgeHTML += "Age in story: "
342 | storyAgeHTML += storyAge.toString();
343 | storyAgeHTML += "\n
\n";
344 | storyAgeHTML += storyAge < legalAge ? "Not legal" : "Legal";
345 | storyAgeHTML += "\n";
346 | }
347 | if (hasValue(data, "finally-legal-in-show")) {
348 | if (storyAgeHTML !== "")
349 | storyAgeHTML += "
\n";
350 | storyAgeHTML += "When they became legal: ";
351 | storyAgeHTML += data["finally-legal-in-show"];
352 | storyAgeHTML += "\n";
353 | }
354 | if (storyAgeHTML !== "") {
355 | newHTML += "
\nBased on story:\n
\n";
356 | newHTML += storyAgeHTML;
357 | newHTML += "
\n";
358 | }
359 |
360 | //list notes and sources
361 | function createListHtml(jsonKey:string, displayName:string) {
362 | let html : string = "";
363 | if (!data.hasOwnProperty(jsonKey) || data[jsonKey] === null || data[jsonKey].length === 0) {
364 | return html;
365 | }
366 | html += "
"
367 | html += displayName;
368 | html += ":
\n
\n";
369 | let values : Array = data[jsonKey];
370 | values.forEach(function(value : string) {
371 | html += "- "
372 | //https://stackoverflow.com/a/1500501
373 | let urlRegex : RegExp = /(https?:\/\/[^\s]+)/g;
374 | html += value.replace(urlRegex, function(url) {
375 | return '' + url + '';
376 | });
377 | html += "
\n";
378 | });
379 | html += "
\n";
380 | return html;
381 | }
382 |
383 | newHTML += createListHtml("notes", "Notes");
384 | newHTML += createListHtml("sources", "Sources");
385 |
386 | newHTML += "
\n"
387 | newHTML += getMarginMobile();
388 | newHTML += "
\n"
389 | newHTML += "
\n";
390 |
391 | output.innerHTML = newHTML;
392 | }
393 | request.send();
394 | }
395 |
396 | function onWaifuSearch() : void {
397 | let searchBar : HTMLElement | null = document.getElementById("waifu-search");
398 | if (searchBar !== null && (searchBar).value !== "")
399 | displayWaifuStats((searchBar).value);
400 | }
401 |
402 | //Search prediction
403 |
404 | let searchTree : JSON | undefined = undefined;
405 |
406 | function predictWaifu(input:string) : Array {
407 | if (
408 | searchTree === undefined ||
409 | searchTree["root"] === undefined ||
410 | searchTree["root"][/*children*/"c"] === undefined ||
411 | searchTree["allKeys"] === undefined
412 | )
413 | return [];
414 | let position : JSON = searchTree["root"];
415 | let filteredInput = input.toLowerCase();
416 | for (let i : number = 0; i < input.length; ++i) {
417 | let letter: string = filteredInput[i];
418 | let branches : JSON = position[/*children*/"c"];
419 | if (branches[letter] === undefined || branches[letter] === null)
420 | return [];
421 | position = branches[letter];
422 | }
423 | if (position[/*value*/"v"] === undefined)
424 | return [];
425 | let topPrediction : number = position[/*value*/"v"];
426 | let topPredictions : Array = [];
427 | for (let i : number = 0; i < 5; ++i) {
428 | let prediction: string | undefined = searchTree["allKeys"][topPrediction + i];
429 | if (prediction === undefined)
430 | break;
431 | topPredictions.push(prediction);
432 | }
433 | return topPredictions;
434 | }
435 |
436 | function displayWaifuPredictions() {
437 | let element : any = document.getElementById("waifu-predictions");
438 | if (element === null)
439 | return;
440 | let output : HTMLElement = element;
441 | let newHTML : string = "";
442 | let input : string = (document.getElementById("waifu-search")).value;
443 | input = sanitizeInput(input);
444 |
445 | if (input === "") {
446 | output.innerHTML =
447 | "Search predictions will show up here
\n";
448 | return;
449 | }
450 |
451 | let predictions : Array = predictWaifu(input);
452 | for (let i : number = 0; i < predictions.length; ++i) {
453 | let prediction : string = predictions[i];
454 | newHTML += ""
457 | newHTML += prediction;
458 | newHTML += "
\n"
459 | }
460 |
461 | if (newHTML === "") {
462 | newHTML +=
463 | "Sorry, no results.
\n" +
464 | "Maybe you misspelled her name.
\n" +
465 | "She might not be in the database. " +
466 | "" +
467 | "If so, please add her." +
468 | "
\n";
469 | }
470 | output.innerHTML = newHTML;
471 | }
472 |
473 | function onWaifuPrediction() {
474 | if (searchTree === undefined) {
475 | let request : XMLHttpRequest = new XMLHttpRequest();
476 | request.open("GET", "search-tree.json");
477 | request.responseType = "json";
478 | request.onerror = function () {
479 | }
480 | request.onload = function() {
481 | searchTree = this.response;
482 | displayWaifuPredictions();
483 | }
484 | request.send();
485 | } else {
486 | displayWaifuPredictions();
487 | }
488 | }
489 |
490 | let inputHistory : Array = [];
491 | let inputHistoryIndex : number = 0;
492 | let maxInputHistorySize : number = 128;
493 |
494 | function onWaifuPredictionAutoComplete() {
495 | if (searchTree === undefined)
496 | return;
497 | let inputElement = document.getElementById("waifu-search");
498 | let input : string = inputElement.value;
499 | input = sanitizeInput(input);
500 | let predictions : Array = predictWaifu(input);
501 | if (predictions.length === 0)
502 | return;
503 | if (inputElement.value === predictions[0] && 1 < predictions.length) {
504 | inputElement.value = predictions[1];
505 | } else {
506 | inputElement.value = predictions[0];
507 | }
508 | if (inputElement.value == input) //don't save input if it hasn't changed
509 | return;
510 | if (inputHistoryIndex < inputHistory.length) {
511 | let removedElements = inputHistory.splice(inputHistoryIndex, inputHistory.length - inputHistoryIndex);
512 | }
513 | if (maxInputHistorySize <= inputHistory.length)
514 | inputHistory.shift(); //keeps memory usage low
515 | inputHistory.push(input);
516 | inputHistoryIndex = inputHistory.length;
517 | }
518 |
519 | function getLastWaifuPredictionAutoComplete() {
520 | if (inputHistory.length <= 0 || inputHistoryIndex <= 0)
521 | return;
522 | let inputElement = document.getElementById("waifu-search");
523 | --inputHistoryIndex;
524 | inputElement.value = inputHistory[inputHistoryIndex];
525 | }
526 |
527 | //read me
528 | function displayReadMe() : void {
529 | let request : XMLHttpRequest = new XMLHttpRequest();
530 | request.open("GET", getCurrentURL() + "/README.html");
531 | request.responseType = "document";
532 | request.onload = function() {
533 | switch(this.status) {
534 | case 200: break; //OK
535 | default: return;
536 | }
537 | let data : Document | null = this.responseXML;
538 | if (data === null) return;
539 | let output : HTMLElement | null = document.getElementById("output");
540 | if (output === null) return;
541 | output.innerHTML = "";
542 | output.appendChild(data.documentElement);
543 |
544 | //Detect firefox mobile
545 | let frontScreen : HTMLElement | null = document.getElementById("front-screen");
546 | if (frontScreen === null) return;
547 | let frontScreenHeight = frontScreen.clientHeight;
548 | let frontScreenText : HTMLElement | null = document.getElementById("front-screen-text");
549 | if (frontScreenText === null) return;
550 | let frontScreenTextHeight : number = frontScreenText.clientHeight;
551 | if (frontScreenHeight <= frontScreenTextHeight)
552 | frontScreen.className = "front-screen-firefox";
553 | };
554 | request.send();
555 |
556 | //auto set focus on search bar
557 | let element : HTMLElement | null = document.getElementById("waifu-search");
558 | if (element === null)
559 | return;
560 | let searchBar : HTMLElement = element;
561 | searchBar.focus();
562 | }
563 |
564 | function displaySiteContent(q: string | null | undefined) : void {
565 | if (q === undefined || q === null)
566 | displayReadMe();
567 | else
568 | displayWaifuStats(q);
569 | }
570 |
571 | //read query string values
572 | window.onload = function () : void {
573 | let parms : URLSearchParams = new URLSearchParams(window.location.search);
574 | let search : string | null = parms.get("q");
575 | displaySiteContent(search);
576 | }
577 |
578 | window.onpopstate = function(event) : void {
579 | let search : string | null =
580 | hasValue(event.state, "q") ? event.state["q"] : null;
581 | displaySiteContent(search);
582 | };
583 |
584 | // Some UI stuff
585 |
586 | function onClickWaifuPrediction(elementID:string) {
587 | //using on foucs and on blur causes clicking on
588 | //predictions to close instead of going to the predicted
589 | //link
590 | //Doing this fixes this issue
591 | let showElements : Set = new Set();
592 | showElements.add("waifu-search");
593 | showElements.add("waifu-predictions");
594 | showElements.add("mobile-waifu-search-button");
595 |
596 | let show : boolean =
597 | showElements.has(elementID) ||
598 | (document.activeElement !== null && document.activeElement.id === "waifu-search");
599 |
600 | let menu : any = document.getElementById("waifu-predictions");
601 | menu.style.display = show ? "unset" : "none";
602 |
603 | //hide search button on mobile
604 | let mobileSearchButton : HTMLElement | null =
605 | document.getElementById("mobile-waifu-search-button");
606 | if (mobileSearchButton === null)
607 | return;
608 | mobileSearchButton.className = show ?
609 | "mobile-search-button-during-search" : "mobile-search-button"
610 | }
611 |
612 | let uiOnClickCallbacks : Array = new Array();
613 | uiOnClickCallbacks.push(onClickWaifuPrediction);
614 |
615 | window.onclick = function(mouse: MouseEvent) {
616 | let element : Element | null = document.elementFromPoint(mouse.clientX, mouse.clientY);
617 | if (element === null)
618 | return;
619 | let clickedElement : Element = element;
620 | uiOnClickCallbacks.forEach(function (f:Function){
621 | f(clickedElement.id);
622 | });
623 | }
--------------------------------------------------------------------------------
/discord-bot/bot.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "sleepy_discord/sleepy_discord.h"
5 | #include "IO_file.h"
6 |
7 | //bolderplate code
8 | bool startsWith(const std::string& target, const std::string& test) {
9 | return target.compare(0, test.size(), test) == 0;
10 | }
11 |
12 | std::queue split(const std::string& source) {
13 | std::stringstream ss(source);
14 | std::string item;
15 | std::queue target;
16 | while (std::getline(ss, item, ' '))
17 | if (!item.empty())
18 | target.push(item);
19 | return target;
20 | }
21 |
22 | void makeLowerCaseOnly(std::string& string) {
23 | std::transform(string.begin(), string.end(), string.begin(),
24 | [](unsigned char c) { return std::tolower(c); });
25 | }
26 |
27 | //Discord repo watch
28 | class DiscordAPIDocsRepoWatcher {
29 | public:
30 | void pollTomarrow(SleepyDiscord::DiscordClient& client) {
31 | client.schedule([=, &client]() {
32 | client.sendMessage("466386704438132736", "poll repo", SleepyDiscord::Async);
33 | pollImplementation(client);
34 | pollTomarrow(client);
35 | }, oneDayInMilliseconds);
36 | }
37 |
38 | const bool started() {
39 | return !lastCommitSha.empty();
40 | }
41 |
42 | void start(SleepyDiscord::DiscordClient& client) {
43 | if (started())
44 | return;
45 |
46 | asio::post([=, &client]() {
47 | //get lastCommitSha
48 | //to do this code is used twice, make it a function
49 | auto response = cpr::Get(cpr::Url{ repoCommitsLink });
50 | if (response.status_code != 200)
51 | return;
52 |
53 | rapidjson::Document document;
54 | document.Parse(response.text.c_str(), response.text.length());
55 | if (document.HasParseError())
56 | return;
57 |
58 | auto commits = document.GetArray();
59 | auto& lastCommit = commits[0];
60 | auto sha = lastCommit.FindMember("sha");
61 | if (sha == lastCommit.MemberEnd() || !sha->value.IsString())
62 | return;
63 |
64 | lastCommitSha = std::string{sha->value.GetString(),
65 | sha->value.GetStringLength()};
66 |
67 | pollTomarrow(client);
68 | });
69 | }
70 | private:
71 | void pollImplementation(SleepyDiscord::DiscordClient& client) {
72 | asio::post([=, &client]() {
73 | const SleepyDiscord::Snowflake
74 | channel = "721828297087909950";
75 |
76 | auto response = cpr::Get(cpr::Url{ repoCommitsLink });
77 | if (response.status_code != 200)
78 | return;
79 |
80 | rapidjson::Document document;
81 | document.Parse(response.text.c_str(), response.text.length());
82 | if (document.HasParseError())
83 | return;
84 |
85 | auto commits = document.GetArray();
86 | auto lastCommitIterator = commits.end();
87 | int index = 0;
88 | for (auto& commit : commits) {
89 | auto sha = commit.FindMember("sha");
90 | if (sha == commit.MemberEnd() || !sha->value.IsString()) {
91 | index += 1;
92 | continue;
93 | }
94 | //to do use string_view
95 | std::string shaStr{ sha->value.GetString(), sha->value.GetStringLength() };
96 | if (lastCommitSha == shaStr) {
97 | lastCommitIterator = commits.begin() + index;
98 | break;
99 | }
100 | index += 1;
101 | }
102 |
103 | if (lastCommitIterator == commits.begin()) {
104 | //no new commits
105 | return;
106 | }
107 |
108 | SleepyDiscord::Embed embed;
109 |
110 | //since the commits are sorted newest first, we need to go backwards to make it
111 | //fit Discord's message order being oldest first/top.
112 | for (
113 | auto commit = lastCommitIterator - 1;
114 | commit != commits.begin() - 1; //sub 1 as begin() is valid
115 | commit -= 1
116 | ) {
117 | auto sha = commit->FindMember("sha");
118 | if (sha == commit->MemberEnd() || !sha->value.IsString()) {
119 | continue;
120 | }
121 |
122 | if (commit == commits.begin()) {
123 | lastCommitSha = std::string {
124 | sha->value.GetString(),
125 | sha->value.GetStringLength()
126 | };
127 | }
128 |
129 | auto data = commit->FindMember("commit");
130 | if (data == commit->MemberEnd() || !data->value.IsObject()) {
131 | continue;
132 | }
133 | auto messageMember = data->value.FindMember("message");
134 | if (messageMember == data->value.MemberEnd() || !messageMember->value.IsString()) {
135 | continue;
136 | }
137 | auto htmlLinkMember = commit->FindMember("html_url");
138 | if (htmlLinkMember == commit->MemberEnd() || !htmlLinkMember->value.IsString()) {
139 | continue;
140 | }
141 |
142 | std::string hashDisplay {
143 | sha->value.GetString(),
144 | sha->value.GetStringLength() <= 9 ?
145 | sha->value.GetStringLength(): 9
146 | };
147 |
148 | std::string message {
149 | messageMember->value.GetString(),
150 | messageMember->value.GetStringLength()
151 | };
152 |
153 | std::string commitTitle;
154 | size_t newLinePOS = message.find_first_of('\n');
155 | if (newLinePOS == std::string::npos) {
156 | commitTitle = message;
157 | } else {
158 | commitTitle = message.substr(0, newLinePOS);
159 | }
160 |
161 | std::string commitLink;
162 | size_t linkSize =
163 | 1 + hashDisplay.length() + 2 +
164 | htmlLinkMember->value.GetStringLength() + 1;
165 | commitLink.reserve(linkSize);
166 | commitLink += '[';
167 | commitLink += hashDisplay;
168 | commitLink += "](";
169 | commitLink += htmlLinkMember->value.GetString();
170 | commitLink += ')';
171 |
172 | embed.fields.push_back(SleepyDiscord::EmbedField{
173 | commitTitle,
174 | commitLink
175 | });
176 |
177 | //check if we are over the embed limits
178 | //to do list the embed limits in the library
179 | if (25 <= embed.fields.size()) {
180 | SleepyDiscord::SendMessageParams messageToSend;
181 | messageToSend.channelID = channel;
182 | messageToSend.embed = embed;
183 |
184 | client.sendMessage(messageToSend, SleepyDiscord::Async);
185 | embed = SleepyDiscord::Embed{};
186 | }
187 | }
188 |
189 | SleepyDiscord::SendMessageParams messageToSend;
190 | messageToSend.channelID = channel;
191 | messageToSend.embed = embed;
192 |
193 | client.sendMessage(messageToSend, SleepyDiscord::Async);
194 | });
195 | }
196 |
197 | const time_t oneDayInMilliseconds = 86400000;
198 | std::string lastCommitSha;
199 | const std::string repoCommitsLink =
200 | "https://api.github.com/repositories/54995014/commits";
201 | };
202 |
203 | //Discord client code
204 | class WaifuClient;
205 |
206 | namespace Command {
207 | using Verb = std::function<
208 | void(
209 | WaifuClient&,
210 | SleepyDiscord::Interaction&
211 | )
212 | >;
213 | using CreateAppCommandAction = std::function<
214 | void(WaifuClient&, SleepyDiscord::AppCommand&)>;
215 | using MessageVerb = std::function<
216 | void(
217 | WaifuClient&,
218 | SleepyDiscord::Message&,
219 | std::queue&
220 | )
221 | >;
222 | struct Command {
223 | std::string name;
224 | std::string description;
225 | std::vector params;
226 | CreateAppCommandAction createAppCommand;
227 | Verb verb;
228 | MessageVerb messageVerb;
229 | };
230 | using MappedCommands = std::unordered_map;
231 | using MappedCommand = MappedCommands::value_type;
232 | static MappedCommands all;
233 | static void addCommand(Command command) {
234 | all.emplace(command.name, command);
235 | }
236 | static Command* defaultCommand = nullptr;
237 | }
238 |
239 | class WaifuClient : public SleepyDiscord::DiscordClient {
240 | public:
241 | WaifuClient(const std::string token) :
242 | SleepyDiscord::DiscordClient(token, SleepyDiscord::USER_CONTROLED_THREADS),
243 | botStatusReporter(*this)
244 | {
245 | updateSearchTree();
246 | }
247 |
248 | void addServerID(SleepyDiscord::Snowflake& serverID) {
249 | if (serverIDs.count(serverID) <= 0)
250 | serverIDs.insert(serverID);
251 | }
252 |
253 | void onReady(SleepyDiscord::Ready ready) override {
254 | static bool isFirstTime = true;
255 | discordAPIDocsRepoWatcher.start(*this);
256 |
257 | //set up serverIDs
258 | for (SleepyDiscord::UnavailableServer& server : ready.servers) {
259 | addServerID(server.ID);
260 | }
261 | if (!botsToken.empty() && !topToken.empty() && isFirstTime)
262 | botStatusReporter.start();
263 |
264 | //handle slash commands
265 | if (isFirstTime) {
266 | std::vector commands{ Command::all.size() };
267 | for (const auto& command : Command::all) {
268 | SleepyDiscord::AppCommand appCommand;
269 | appCommand.name = command.first;
270 | appCommand.description = command.second.description;
271 | appCommand.applicationID = getID().string();
272 | command.second.createAppCommand(*this, appCommand);
273 | createGlobalAppCommand(getID().string(), appCommand.name, appCommand.description, std::move(appCommand.options), SleepyDiscord::Async);
274 | }
275 | }
276 |
277 | isFirstTime = false;
278 | }
279 |
280 | void onServer(SleepyDiscord::Server server) override {
281 | addServerID(server.ID);
282 | }
283 |
284 | void onDeleteServer(SleepyDiscord::UnavailableServer server) override {
285 | if (
286 | 0 < serverIDs.count(server.ID) &&
287 | server.unavailable ==
288 | SleepyDiscord::UnavailableServer::AvailableFlag::NotSet
289 | ) {
290 | serverIDs.erase(server.ID);
291 | }
292 | }
293 |
294 | void onInteraction(SleepyDiscord::Interaction interaction) override {
295 | if (interaction.type == SleepyDiscord::Interaction::Type::ApplicationCommandAutocomplete) {
296 | if (interaction.data.name != "legal")
297 | return;
298 | for (auto& option : interaction.data.options) {
299 | if (option.name != "waifu-name")
300 | return;
301 | std::string query;
302 | if (!option.get(query))
303 | return;
304 | std::vector prediction = getSearchResults(query);
305 |
306 | SleepyDiscord::Interaction::AutocompleteResponse response;
307 | for (std::string& result : prediction) {
308 | SleepyDiscord::AppCommand::Option::Choice choice;
309 | choice.name = result;
310 | choice.set(result);
311 | response.data.choices.push_back(std::move(choice));
312 | }
313 |
314 | createInteractionResponse(interaction.ID, interaction.token, std::move(response));
315 | }
316 | }
317 | else if (interaction.type == SleepyDiscord::Interaction::Type::ApplicationCommand) {
318 | Command::MappedCommands::iterator foundCommand =
319 | Command::all.find(interaction.data.name);
320 | if (foundCommand == Command::all.end()) {
321 | SleepyDiscord::Interaction::Response<> response;
322 | response.type = SleepyDiscord::InteractionCallbackType::ChannelMessageWithSource;
323 | response.data.flags = SleepyDiscord::InteractionCallback::Message::Flags::Ephemeral;
324 | response.data.content = "Command not found";
325 | createInteractionResponse(interaction.ID, interaction.token, response, SleepyDiscord::Async);
326 | return;
327 | }
328 |
329 | foundCommand->second.verb(*this, interaction);
330 | }
331 | else if (interaction.type == SleepyDiscord::Interaction::Type::MessageComponent) {
332 | rapidjson::Document document;
333 | document.Parse(interaction.data.customID.c_str(), interaction.data.customID.length());
334 | if (document.HasParseError())
335 | return;
336 | auto interactionIDMember = document.FindMember("id");
337 | if (interactionIDMember == document.MemberEnd() || !interactionIDMember->value.IsString())
338 | return;
339 |
340 | auto originalInteractionID = SleepyDiscord::Snowflake(interactionIDMember->value);
341 | std::unordered_map, InteractionSession>::iterator iterator;
342 | if (!findInteractionToken(originalInteractionID, iterator))
343 | return;
344 |
345 | auto& interactionSession = iterator->second;
346 |
347 | SleepyDiscord::Interaction::EditMessageResponse message;
348 | message.data.content = "OK, look above this message.";
349 | message.data.components = std::vector>{};
350 | createInteractionResponse(interaction.ID, interaction.token, message, SleepyDiscord::Async);
351 |
352 | auto newInteraction = interaction.token;
353 | interaction.token = interactionSession.token;
354 | interaction.ID = originalInteractionID;
355 |
356 | //check that user is the same
357 | SleepyDiscord::Snowflake< SleepyDiscord::User> originalUserID(interactionSession.userID);
358 | SleepyDiscord::Snowflake userID;
359 | if (!interaction.member.ID.empty())
360 | userID = interaction.member.ID;
361 | else if (!interaction.user.ID.empty())
362 | userID = interaction.user.ID;
363 | if (!userID.empty() && originalUserID != userID) {
364 | SleepyDiscord::Interaction::Response<> response;
365 | response.type = SleepyDiscord::InteractionCallbackType::ChannelMessageWithSource;
366 | response.data.content = "You aren't the original user";
367 | response.data.flags = SleepyDiscord::InteractionCallback::Message::Flags::Ephemeral;
368 | createInteractionResponse(interaction.ID, interaction.token, response, SleepyDiscord::Async);
369 | return;
370 | }
371 |
372 | auto indexMember = document.FindMember("in");
373 | if (indexMember == document.MemberEnd() || !indexMember->value.IsInt())
374 | return;
375 | auto index = indexMember->value.GetInt();
376 |
377 | WaifuButton& button = interactionSession.buttons[index];
378 |
379 | createLegalInteractionResponse(interaction, button.waifuName, true);
380 | }
381 | }
382 |
383 | void onMessage(SleepyDiscord::Message message) override {
384 | if (message.isMentioned(getID()) || message.startsWith("whcg "))
385 | {
386 | std::queue parameters = split(message.content);
387 | const std::string mention = "<@" + getID().string() + ">";
388 | const std::string mentionNick = "<@!" + getID().string() + ">";
389 | if (
390 | !(
391 | //only allow if has more then 1 parameter
392 | 1 < parameters.size() && (
393 | //only allow if starts with a mention
394 | parameters.front() == mention || parameters.front() == mentionNick ||
395 | //or starts with whcg
396 | parameters.front() == "whcg"
397 | )
398 | )
399 | )
400 | return;
401 |
402 | //remove the parameters as we go
403 | parameters.pop();
404 | if (parameters.empty())
405 | return;
406 |
407 | //get command
408 | Command::MappedCommands::iterator foundCommand =
409 | Command::all.find(parameters.front());
410 | if (foundCommand == Command::all.end()) {
411 | if (Command::defaultCommand != nullptr) {
412 | Command::defaultCommand->messageVerb(*this, message, parameters);
413 | }
414 | else {
415 | sendMessage(message.channelID, "Error: Command not found", SleepyDiscord::Async);
416 | }
417 | return;
418 | }
419 | parameters.pop();
420 | if (
421 | parameters.size() <
422 | foundCommand->second.params.size()
423 | ) {
424 | sendMessage(message.channelID, "Error: Too few parameters", SleepyDiscord::Async);
425 | return;
426 | }
427 |
428 | //call command
429 | foundCommand->second.messageVerb(*this, message, parameters);
430 | }
431 | // else if channel is the github updates channel, update search tree
432 | else if (message.channelID == "700570024523595786") {
433 | //we have the github webhook set up to only send page build updates
434 | asio::post([this]() {
435 | updateSearchTree();
436 | });
437 | }
438 | }
439 |
440 | void updateSearchTree() {
441 | //note this is blocking, so the whole bot stops while getting this data
442 | rapidjson::Document newSearchTree;
443 | auto response = cpr::Get(
444 | cpr::Url{ "https://yourwaifu.dev/is-your-waifu-legal/search-tree.json" });
445 |
446 | if (response.status_code != 200)
447 | return;
448 |
449 | newSearchTree.Parse(response.text.c_str(), response.text.length());
450 | if (newSearchTree.HasParseError())
451 | return;
452 |
453 | searchTree = std::move(newSearchTree);
454 | }
455 |
456 | const rapidjson::Document& getSearchTree() const {
457 | return searchTree;
458 | }
459 |
460 | std::vector getSearchResults(const std::string& query) const {
461 | //use the search tree to get predictions on what the user wanted
462 | const auto& searchTree = getSearchTree();
463 | if (searchTree.HasParseError())
464 | return {};
465 |
466 | const auto searchRootIterator = searchTree.FindMember("root");
467 | if (searchRootIterator == searchTree.MemberEnd() || !searchRootIterator->value.IsObject())
468 | return {};
469 |
470 | const auto& searchRootValue = searchRootIterator->value;
471 | auto childrenIterator = searchRootValue.FindMember("c"/*children*/);
472 | if (childrenIterator == searchRootValue.MemberEnd() || !childrenIterator->value.IsObject())
473 | return {};
474 |
475 | const auto allKeysIterator = searchTree.FindMember("allKeys");
476 | if (allKeysIterator == searchTree.MemberEnd() || !allKeysIterator->value.IsArray())
477 | return {};
478 |
479 | int topPrediction = 0;
480 | size_t letterIndex = 0;
481 | if (!query.empty()) {
482 | auto position = searchRootIterator;
483 | for (const std::string::value_type& letter : query) {
484 | const auto branches = position->value.FindMember("c"/*children*/);
485 | if (branches == position->value.MemberEnd())
486 | break;
487 | rapidjson::Value letterValue;
488 | letterValue.SetString(&letter, 1);
489 | auto nextPosition = branches->value.FindMember(letterValue);
490 | if (nextPosition == branches->value.MemberEnd() || nextPosition->value.IsNull())
491 | break;
492 |
493 | position = nextPosition;
494 | letterIndex += 1;
495 | }
496 |
497 | auto topPredictionIterator = position->value.FindMember("v"/*value*/);
498 | if (topPredictionIterator == position->value.MemberEnd() ||
499 | !topPredictionIterator->value.IsInt() || letterIndex == 0)
500 | return {};
501 |
502 | topPrediction = topPredictionIterator->value.GetInt();
503 | }
504 |
505 | const nonstd::string_view lettersInCommonAtStart{
506 | query.c_str(), letterIndex
507 | };
508 | const int maxNumOfPredictions = 5;
509 | std::vector topPredictions;
510 | topPredictions.reserve(maxNumOfPredictions);
511 |
512 | const auto allKeys = allKeysIterator->value.GetArray();
513 | for (int i = 0; i < maxNumOfPredictions; i += 1) {
514 | std::size_t index = topPrediction + i;
515 | if (allKeys.Size() <= index)
516 | break; //out of range
517 | const auto& prediction = allKeys.operator[](index);
518 | //check if it starts with letters in common
519 | if (prediction.GetStringLength() < letterIndex)
520 | //too small
521 | break;
522 | if (nonstd::string_view{ prediction.GetString(), letterIndex } ==
523 | lettersInCommonAtStart)
524 | {
525 | topPredictions.emplace_back(prediction.GetString(), prediction.GetStringLength());
526 | }
527 | }
528 |
529 | return topPredictions;
530 | }
531 |
532 | inline const int getServerCount() {
533 | return serverIDs.size();
534 | }
535 |
536 | const SleepyDiscord::Embed getStatus() {
537 | SleepyDiscord::Embed status;
538 | //to do use some template tuple magic
539 | status.fields.emplace_back("Server Count",
540 | std::to_string(getServerCount()), true);
541 | return status;
542 | }
543 |
544 | struct StatusData {
545 | int serverCount = 0;
546 | std::string& botsToken;
547 | std::string& topToken;
548 | };
549 |
550 | const StatusData getStatusData() {
551 | //add token data here
552 | return StatusData {
553 | getServerCount(),
554 | botsToken, topToken
555 | };
556 | }
557 |
558 | void setTokens(rapidjson::Document& tokenDoc) {
559 | auto tokenIterator = tokenDoc.FindMember("bots-ggToken");
560 | if (
561 | tokenIterator != tokenDoc.MemberEnd() &&
562 | tokenIterator->value.IsString()
563 | ) {
564 | botsToken.assign(
565 | tokenIterator->value.GetString(),
566 | tokenIterator->value.GetStringLength());
567 | } else {
568 | std::cout << "bots-gg token not found\n";
569 | }
570 |
571 | //to do remove dup code
572 | tokenIterator = tokenDoc.FindMember("top-ggToken");
573 | if (
574 | tokenIterator != tokenDoc.MemberEnd() &&
575 | tokenIterator->value.IsString()
576 | ) {
577 | topToken.assign(
578 | tokenIterator->value.GetString(),
579 | tokenIterator->value.GetStringLength());
580 | } else {
581 | std::cout << "top-gg token not found\n";
582 | }
583 | }
584 |
585 | void createLegalInteractionResponse(SleepyDiscord::Interaction& interaction, std::string waifuName, bool updateMessage = false) {
586 | if (waifuName.empty())
587 | return;
588 |
589 | waifuName = SleepyDiscord::escapeURL(waifuName);
590 | makeLowerCaseOnly(waifuName);
591 |
592 | asio::post([&, interaction = std::move(interaction), waifuName = std::move(waifuName), updateMessage = std::move(updateMessage)]() {
593 | auto response = cpr::Get(
594 | cpr::Url{ "https://yourwaifu.dev/is-your-waifu-legal/waifus/" +
595 | waifuName + ".json" });
596 |
597 | std::string topMessage;
598 |
599 | if (response.status_code != 200) {
600 | const std::string messageStart =
601 | "Couldn't find the waifu you requested.\n";
602 | const std::string messageEnd =
603 | "You can add them by following this link: "
604 | "";
605 |
606 | std::vector topPredictions = getSearchResults(waifuName);
607 |
608 | SleepyDiscord::Interaction::Response<> response;
609 |
610 | if (topPredictions.empty()) {
611 | response.type = SleepyDiscord::InteractionCallbackType::ChannelMessageWithSource;
612 | response.data.content = messageStart + messageEnd;
613 | response.data.flags = SleepyDiscord::InteractionCallback::Message::Flags::Ephemeral;
614 | createInteractionResponse(interaction.ID, interaction.token, response, SleepyDiscord::Async);
615 | return;
616 | }
617 |
618 | std::string didYouMeanMessage = "\nDid you mean: \n";
619 |
620 | InteractionSession session;
621 | session.token = interaction.token;
622 | SleepyDiscord::Snowflake userID;
623 | if (!interaction.member.ID.empty())
624 | userID = interaction.member.ID;
625 | else if (!interaction.user.ID.empty())
626 | userID = interaction.user.ID;
627 | if (!userID.empty()) {
628 | session.userID = userID;
629 | }
630 |
631 | //use buttons
632 | auto actionRow = std::make_shared();
633 | std::size_t index = 0;
634 | for (std::string& prediction : topPredictions) {
635 | auto button = std::make_shared();
636 | button->style = SleepyDiscord::ButtonStyle::Primary;
637 | button->label = prediction;
638 | //data to json for button
639 | rapidjson::Document json;
640 | json.SetObject();
641 | std::string interactionID = interaction.ID.string();
642 | json.AddMember("id",
643 | rapidjson::Document::StringRefType{ interactionID.c_str(), interactionID.size() },
644 | json.GetAllocator());
645 | json.AddMember("in", index, json.GetAllocator());
646 | button->customID = SleepyDiscord::json::stringify(json);
647 |
648 | actionRow->components.push_back(button);
649 | session.buttons.push_back(WaifuButton{ prediction });
650 | index += 1;
651 | }
652 |
653 | size_t messageLength = messageStart.length();
654 | messageLength += messageEnd.length();
655 | messageLength += didYouMeanMessage.length();
656 | topMessage.clear();
657 | topMessage.reserve(topMessage.length() + messageLength);
658 | topMessage += messageStart;
659 | topMessage += messageEnd;
660 | topMessage += didYouMeanMessage;
661 |
662 | response.type = SleepyDiscord::InteractionCallbackType::ChannelMessageWithSource;
663 | response.data.content = "Waiting on user";
664 | createInteractionResponse(interaction.ID, interaction.token, response,
665 | { SleepyDiscord::Async, [&, topMessage = std::move(topMessage), token = interaction.token, actionRow](SleepyDiscord::BooleanResponse) {
666 | SleepyDiscord::FollowupMessage message;
667 | message.flags = SleepyDiscord::InteractionCallback::Message::Flags::Ephemeral;
668 | message.content = topMessage;
669 | message.components.push_back(actionRow);
670 | createFollowupMessage(getID(), token, message, SleepyDiscord::Async);
671 | } }
672 | );
673 | createInteractionSession(interaction.ID, std::move(session));
674 | return;
675 | }
676 |
677 | rapidjson::Document document;
678 | document.Parse(response.text.c_str(), response.text.length());
679 | if (document.HasParseError())
680 | return;
681 |
682 | SleepyDiscord::Embed embed{};
683 |
684 | auto definitelyLegal = document.FindMember("definitely-legal");
685 | if (definitelyLegal != document.MemberEnd() && definitelyLegal->value.IsBool())
686 | embed.description += definitelyLegal->value.GetBool() ? "Definitely of legal age\n" :
687 | "Definitely not of legal age\n";
688 |
689 | auto year = document.FindMember("year");
690 | if (year != document.MemberEnd() && year->value.IsInt())
691 | embed.fields.push_back(SleepyDiscord::EmbedField{ "Birth Year",
692 | std::to_string(year->value.GetInt()), true });
693 |
694 | auto appearence = document.FindMember("age-group-by-appearance");
695 | if (appearence != document.MemberEnd() && appearence->value.IsString())
696 | embed.fields.push_back(SleepyDiscord::EmbedField{ "Looks like a(n)",
697 | std::string{ appearence->value.GetString(), appearence->value.GetStringLength() }, true });
698 |
699 | auto ageInShow = document.FindMember("age-in-show");
700 | if (ageInShow != document.MemberEnd()) {
701 | const auto addAgeInStory = [&embed](std::string value) {
702 | embed.fields.push_back(SleepyDiscord::EmbedField{ "Age in Story",
703 | value, true });
704 | };
705 |
706 | if (ageInShow->value.IsInt()) {
707 | addAgeInStory(std::to_string(ageInShow->value.GetInt()));
708 | }
709 | else if (ageInShow->value.IsString()) {
710 | addAgeInStory(std::string{ ageInShow->value.GetString(), ageInShow->value.GetStringLength() });
711 | }
712 | }
713 |
714 | auto image = document.FindMember("image");
715 | if (image != document.MemberEnd() && image->value.IsString())
716 | embed.image.url = std::string{ image->value.GetString(), image->value.GetStringLength() };
717 |
718 | embed.description += "[Source](https://yourwaifu.dev/is-your-waifu-legal/?q=" + waifuName + ')';
719 |
720 | if (updateMessage) {
721 | SleepyDiscord::EditWebhookParams message;
722 | message.content = std::string{};
723 | message.embeds = std::vector{};
724 | message.embeds->push_back(embed);
725 | editOriginalInteractionResponse(getID(), interaction.token, message, SleepyDiscord::Async);
726 | }
727 | else {
728 | SleepyDiscord::Interaction::Response<> message;
729 | message.type = SleepyDiscord::InteractionCallbackType::ChannelMessageWithSource;
730 | message.data.flags = SleepyDiscord::InteractionCallback::Message::Flags::NONE;
731 | message.data.embeds.push_back(embed);
732 | createInteractionResponse(interaction.ID, interaction.token, message, SleepyDiscord::Async);
733 | }
734 | });
735 | }
736 |
737 | private:
738 | rapidjson::Document searchTree;
739 | DiscordAPIDocsRepoWatcher discordAPIDocsRepoWatcher;
740 |
741 | //Discord Bot status poster
742 | std::string botsToken;
743 | std::string topToken;
744 |
745 | struct WaifuButton {
746 | std::string waifuName = "";
747 | };
748 |
749 | struct InteractionSession {
750 | std::string token = "";
751 | SleepyDiscord::Snowflake userID = {};
752 | std::vector buttons = {};
753 | };
754 |
755 | std::unordered_map, InteractionSession>
756 | interactionSessions;
757 |
758 | std::mutex interactionSessionsMutex;
759 |
760 | void createInteractionSession(const SleepyDiscord::Snowflake& interactionID, InteractionSession interactionSession) {
761 | //the interactionSessions can be access between threads so we should lock it to prevent issues
762 | const std::lock_guard lock(interactionSessionsMutex);
763 | auto result = interactionSessions.insert({ interactionID, std::move(interactionSession) }); //copy/move to pair, and then moves pair to map
764 | if (result.second == true) { //if secceeded
765 | schedule(
766 | [&, interator = result.first]() {
767 | const std::lock_guard lock(interactionSessionsMutex);
768 | interactionSessions.erase(interator);
769 | //to do remove buttons too
770 | }, 15 /*mins*/ * 60 /*seconds per min*/ * 1000 /*milliseconds*/);
771 | }
772 | }
773 |
774 | bool findInteractionToken(
775 | const SleepyDiscord::Snowflake& interactionID,
776 | std::unordered_map, InteractionSession>::iterator& target)
777 | {
778 | const std::lock_guard lock(interactionSessionsMutex);
779 | target = interactionSessions.find(interactionID);
780 | return target != interactionSessions.end();
781 | }
782 |
783 | class BotStatusReporter {
784 | public:
785 | BotStatusReporter(WaifuClient& _client) :
786 | client(_client)
787 | {
788 |
789 | }
790 |
791 | void start() {
792 | postStatus();
793 | }
794 |
795 | void postStatus() {
796 | asio::post([this]() {
797 | const StatusData data = client.getStatusData();
798 |
799 | {
800 | rapidjson::Document json;
801 | json.SetObject();
802 | rapidjson::Value guildCount;
803 | guildCount.SetInt(data.serverCount);
804 | json.AddMember("guildCount", guildCount, json.GetAllocator());
805 |
806 | rapidjson::StringBuffer buffer;
807 | rapidjson::Writer writer(buffer);
808 | json.Accept(writer);
809 |
810 | std::string postStatsLink = "https://discord.bots.gg/api/v1/bots/186151807699910656/stats";
811 | cpr::Post(
812 | cpr::Url{ postStatsLink },
813 | cpr::Header{
814 | { "Content-Type", "application/json" },
815 | { "Authorization", data.botsToken }
816 | },
817 | cpr::Body{ buffer.GetString(), buffer.GetSize() }
818 | );
819 | }
820 |
821 | //to do remove dup code
822 | {
823 | rapidjson::Document json;
824 | json.SetObject();
825 | rapidjson::Value guildCount;
826 | guildCount.SetInt(data.serverCount);
827 | json.AddMember("server_count", guildCount, json.GetAllocator());
828 |
829 | rapidjson::StringBuffer buffer;
830 | rapidjson::Writer writer(buffer);
831 | json.Accept(writer);
832 |
833 | std::string postStatsLink = "https://top.gg/api/bots/186151807699910656/stats";
834 | cpr::Post(
835 | cpr::Url{ postStatsLink },
836 | cpr::Header{
837 | { "Content-Type", "application/json" },
838 | { "Authorization", data.topToken }
839 | },
840 | cpr::Body{ buffer.GetString(), buffer.GetSize() }
841 | );
842 | }
843 |
844 | const time_t postInterval = 300000; //5 mins
845 | client.schedule([this]() {
846 | postStatus();
847 | }, postInterval);
848 | });
849 | }
850 |
851 | WaifuClient& client;
852 | };
853 | BotStatusReporter botStatusReporter;
854 |
855 | //to do add language options for each server
856 | std::unordered_set<
857 | SleepyDiscord::Snowflake::RawType
858 | > serverIDs;
859 | };
860 |
861 | int main() {
862 | rapidjson::Document tokenDoc;
863 | std::string token;
864 | {
865 | std::string tokenJSON;
866 | File tokenFile("tokens.json");
867 | const std::size_t tokenSize = tokenFile.getSize();
868 | if (tokenSize == static_cast(-1)) {
869 | std::cout << "Error: Can't find tokens.json\n";
870 | return 1;
871 | }
872 | tokenJSON.resize(tokenSize);
873 | tokenFile.get(&tokenJSON[0]);
874 | tokenDoc.Parse(tokenJSON.c_str(), tokenJSON.length());
875 | if (tokenDoc.HasParseError()) {
876 | std::cout << "Error: Couldn't parse tokens.json\n";
877 | return 1;
878 | }
879 |
880 | auto tokenIterator = tokenDoc.FindMember("discordToken");
881 | if (
882 | tokenIterator == tokenDoc.MemberEnd() &&
883 | tokenIterator->value.IsString()
884 | ) {
885 | std::cout << "Error: Can't find discordToken in tokens.json\n";
886 | return 1;
887 | }
888 | token.assign(
889 | tokenIterator->value.GetString(),
890 | tokenIterator->value.GetStringLength());
891 | }
892 |
893 | //to do add a on any message array of actions to do
894 |
895 | Command::addCommand({
896 | "hello", "Says Hello as a test", {},
897 | [](WaifuClient& client, SleepyDiscord::AppCommand& appCommand) {
898 |
899 | }, [](
900 | WaifuClient& client,
901 | SleepyDiscord::Interaction& interaction
902 | ) {
903 | std::string& username = !interaction.user.ID.empty() ?
904 | interaction.user.username
905 | : interaction.member.user.username;
906 |
907 | SleepyDiscord::Interaction::Response<> response;
908 | response.type = SleepyDiscord::InteractionCallbackType::ChannelMessageWithSource;
909 | response.data.content = "Hello " + username;
910 | client.createInteractionResponse(interaction.ID, interaction.token, response, SleepyDiscord::Async);
911 | },
912 | [](
913 | WaifuClient& client,
914 | SleepyDiscord::Message& message,
915 | std::queue& params
916 | ) {
917 | client.sendMessage(message.channelID, "Hello, " + message.author.username, SleepyDiscord::Async);
918 | }
919 | });
920 |
921 | Command::addCommand({
922 | "legal", "Shows the age of a Waifu", {"waifu's name"},
923 | [](WaifuClient& client, SleepyDiscord::AppCommand& appCommand) {
924 | //create options for command
925 | SleepyDiscord::AppCommand::Option option;
926 | option.name = "waifu-name";
927 | option.description = "looks for the name in the search tree";
928 | option.type = SleepyDiscord::AppCommand::Option::TypeHelper::getType();
929 | option.autocomplete = true;
930 | option.isRequired = true;
931 | appCommand.options.push_back(std::move(option));
932 | }, [](
933 | WaifuClient& client,
934 | SleepyDiscord::Interaction& interaction
935 | ) {
936 | if (interaction.data.options.empty())
937 | return;
938 |
939 | std::string waifuName{};
940 | for (auto& option : interaction.data.options) {
941 | if (option.name == "waifu-name") {
942 | if (!option.get(waifuName)) {
943 | return;
944 | }
945 | }
946 | }
947 |
948 | client.createLegalInteractionResponse(interaction, std::move(waifuName), false);
949 | },
950 | //a copy of the old version because I don't feel like making a better solution right now
951 | //this will be removed after the scope migration
952 | [](
953 | WaifuClient& client,
954 | SleepyDiscord::Message& message,
955 | std::queue& params
956 | ) {
957 | if (params.empty())
958 | return;
959 |
960 | std::string waifuName{};
961 | waifuName.reserve(message.content.length());
962 |
963 | while (true) {
964 | waifuName += params.front();
965 | params.pop();
966 | if (!params.empty()) {
967 | waifuName += "%20";
968 | } else {
969 | break;
970 | }
971 | }
972 | makeLowerCaseOnly(waifuName);
973 |
974 | asio::post([waifuName, &client, message]() {
975 | auto response = cpr::Get(
976 | cpr::Url{ "https://yourwaifu.dev/is-your-waifu-legal/waifus/" +
977 | waifuName + ".json" });
978 |
979 | std::string topMessage;
980 |
981 | if (response.status_code != 200) {
982 | const std::string messageStart =
983 | "Couldn't find the waifu you requested.\n";
984 | const std::string messageEnd =
985 | "You can add them by following this link: "
986 | "";
987 | const auto failure = [=, &client]() {
988 | client.sendMessage(message.channelID,
989 | messageStart + messageEnd,
990 | SleepyDiscord::Async);
991 | };
992 |
993 | //use the search tree to get predictions on what the user wanted
994 | const auto& searchTree = client.getSearchTree();
995 | if (searchTree.HasParseError())
996 | return failure();
997 |
998 | const auto searchRootIterator = searchTree.FindMember("root");
999 | if (searchRootIterator == searchTree.MemberEnd() || !searchRootIterator->value.IsObject())
1000 | return failure();
1001 |
1002 | const auto& searchRootValue = searchRootIterator->value;
1003 | auto childrenIterator = searchRootValue.FindMember("c"/*children*/);
1004 | if (childrenIterator == searchRootValue.MemberEnd() || !childrenIterator->value.IsObject())
1005 | return failure();
1006 |
1007 | const auto allKeysIterator = searchTree.FindMember("allKeys");
1008 | if (allKeysIterator == searchTree.MemberEnd() || !allKeysIterator->value.IsArray())
1009 | return failure();
1010 |
1011 | size_t letterIndex = 0;
1012 | auto position = searchRootIterator;
1013 | for (const std::string::value_type& letter : waifuName) {
1014 | const auto branches = position->value.FindMember("c"/*children*/);
1015 | if (branches == position->value.MemberEnd())
1016 | break;
1017 | rapidjson::Value letterValue;
1018 | letterValue.SetString(&letter, 1);
1019 | auto nextPosition = branches->value.FindMember(letterValue);
1020 | if (nextPosition == branches->value.MemberEnd() || nextPosition->value.IsNull())
1021 | break;
1022 |
1023 | position = nextPosition;
1024 | letterIndex += 1;
1025 | }
1026 |
1027 | auto topPredictionIterator = position->value.FindMember("v"/*value*/);
1028 | if (topPredictionIterator == position->value.MemberEnd() ||
1029 | !topPredictionIterator->value.IsInt() || letterIndex == 0)
1030 | return failure();
1031 |
1032 | const nonstd::string_view lettersInCommonAtStart{
1033 | waifuName.c_str(), letterIndex
1034 | };
1035 | const int maxNumOfPredictions = 5;
1036 | int topPrediction = topPredictionIterator->value.GetInt();
1037 | std::vector topPredictions;
1038 | topPredictions.reserve(maxNumOfPredictions);
1039 |
1040 | const auto allKeys = allKeysIterator->value.GetArray();
1041 | for (int i = 0; i < maxNumOfPredictions; i += 1) {
1042 | int index = topPrediction + i;
1043 | if (allKeys.Size() <= index)
1044 | break; //out of range
1045 | const auto& prediction = allKeys.operator[](index);
1046 | //check if it starts with letters in common
1047 | if (prediction.GetStringLength() < letterIndex)
1048 | //too small
1049 | break;
1050 | if (nonstd::string_view{ prediction.GetString(), letterIndex } ==
1051 | lettersInCommonAtStart)
1052 | {
1053 | topPredictions.emplace_back(prediction.GetString(), prediction.GetStringLength());
1054 | }
1055 | }
1056 |
1057 | std::string didYouMeanMessage = "Did you mean: \n";
1058 | size_t messageLength = messageStart.length();
1059 | messageLength += didYouMeanMessage.length();
1060 | for (std::string& prediction : topPredictions) {
1061 | messageLength += prediction.length() + 1/*\n*/;
1062 | }
1063 | messageLength += messageEnd.length();
1064 | topMessage.reserve(topMessage.length() + messageLength);
1065 | topMessage += messageStart;
1066 | topMessage += didYouMeanMessage;
1067 | for (std::string& prediction : topPredictions) {
1068 | topMessage += prediction;
1069 | topMessage += '\n';
1070 | }
1071 | topMessage += messageEnd;
1072 |
1073 | client.sendMessage(message.channelID, topMessage, SleepyDiscord::Async);
1074 | return;
1075 | }
1076 |
1077 | rapidjson::Document document;
1078 | document.Parse(response.text.c_str(), response.text.length());
1079 | if (document.HasParseError())
1080 | return;
1081 |
1082 | SleepyDiscord::Embed embed{};
1083 |
1084 | auto definitelyLegal = document.FindMember("definitely-legal");
1085 | if (definitelyLegal != document.MemberEnd() && definitelyLegal->value.IsBool())
1086 | embed.description += definitelyLegal->value.GetBool() ? "Definitely of legal age\n" :
1087 | "Definitely not of legal age\n";
1088 |
1089 | auto year = document.FindMember("year");
1090 | if (year != document.MemberEnd() && year->value.IsInt())
1091 | embed.fields.push_back(SleepyDiscord::EmbedField{ "Birth Year",
1092 | std::to_string(year->value.GetInt()), true });
1093 |
1094 | auto appearence = document.FindMember("age-group-by-appearance");
1095 | if (appearence != document.MemberEnd() && appearence->value.IsString())
1096 | embed.fields.push_back(SleepyDiscord::EmbedField{ "Looks like a(n)",
1097 | std::string{ appearence->value.GetString(), appearence->value.GetStringLength() }, true });
1098 |
1099 | auto ageInShow = document.FindMember("age-in-show");
1100 | if (ageInShow != document.MemberEnd()) {
1101 | const auto addAgeInStory = [&embed](std::string value) {
1102 | embed.fields.push_back(SleepyDiscord::EmbedField{ "Age in Story",
1103 | value, true });
1104 | };
1105 |
1106 | if (ageInShow->value.IsInt()) {
1107 | addAgeInStory(std::to_string(ageInShow->value.GetInt()));
1108 | } else if (ageInShow->value.IsString()) {
1109 | addAgeInStory(std::string{ ageInShow->value.GetString(), ageInShow->value.GetStringLength() });
1110 | }
1111 | }
1112 |
1113 | auto image = document.FindMember("image");
1114 | if (image != document.MemberEnd() && image->value.IsString())
1115 | embed.image.url = std::string{ image->value.GetString(), image->value.GetStringLength() };
1116 |
1117 | embed.description += "[Source](https://yourwaifu.dev/is-your-waifu-legal/?q=" + waifuName + ')';
1118 |
1119 | SleepyDiscord::SendMessageParams messageToSend;
1120 | messageToSend.channelID = message.channelID;
1121 | messageToSend.content = topMessage;
1122 | messageToSend.embed = embed;
1123 |
1124 | client.sendMessage(messageToSend, SleepyDiscord::Async);
1125 | });
1126 | }
1127 | });
1128 |
1129 | Command::defaultCommand = &(Command::all.at("legal"));
1130 |
1131 | WaifuClient client(token);
1132 | client.setTokens(tokenDoc);
1133 | client.setIntents(SleepyDiscord::Intent::SERVER_MESSAGES, SleepyDiscord::Intent::SERVERS);
1134 | client.run();
1135 | }
--------------------------------------------------------------------------------