1
0
Fork 0
mirror of https://github.com/Findus23/cr-search.git synced 2024-09-19 15:23:44 +02:00

add suggestions

This commit is contained in:
Lukas Winkler 2021-07-13 22:21:26 +02:00
parent 4f832bd15e
commit 93a4a0d521
Signed by: lukas
GPG key ID: 54DE4D798D244853
10 changed files with 365 additions and 43 deletions

View file

@ -280,6 +280,11 @@ series_data = [
name="Vox Machina vs. Mighty Nein", name="Vox Machina vs. Mighty Nein",
slug="VoxMachinaVsMightyNein", slug="VoxMachinaVsMightyNein",
videos=["LpBIQhWAhuM"] videos=["LpBIQhWAhuM"]
),
SeriesData(
name="Critical Role and the Club of Misfits",
slug="ClubOfMisfits",
videos=["PRmVQKOy9Bo"]
) )
] ]

View file

@ -1,3 +1,4 @@
import random
from typing import List from typing import List
from flask import request, jsonify, Response from flask import request, jsonify, Response
@ -8,11 +9,10 @@ from psycopg2._psycopg import cursor
from app import app, db, cache from app import app, db, cache
from models import * from models import *
# logger = logging.getLogger('peewee') # logger = logging.getLogger('peewee')
# logger.addHandler(logging.StreamHandler()) # logger.addHandler(logging.StreamHandler())
# logger.setLevel(logging.DEBUG) # logger.setLevel(logging.DEBUG)
from suggestions import suggestions
def add_cors(response: Response) -> Response: def add_cors(response: Response) -> Response:
@ -195,6 +195,21 @@ def api_episodes():
return jsonify(data) return jsonify(data)
@app.route("/api/suggestion")
def api_suggestion():
until = request.args.get('until')
series = request.args.get('series')
if series not in suggestions:
return 404
all_suggestions = suggestions[series]
if until == "-":
possible_suggestions = [s.text for s in all_suggestions]
else:
possible_suggestions = [s.text for s in all_suggestions if s.episode <= int(until)]
chosen_suggestion = random.choice(possible_suggestions)
return Response(chosen_suggestion, mimetype='text/plain')
if __name__ == "__main__": if __name__ == "__main__":
app.debug = True app.debug = True
app.after_request(add_cors) app.after_request(add_cors)

236
suggestions.py Normal file
View file

@ -0,0 +1,236 @@
"""
ATTENTION: This file contains the suggestions displayed.
Only suggestions up to the selected episode are shown on the website,
but you might find spoilers below.
That said, I try to only use phrases that don't contain spoilers themselves.
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
"""
from dataclasses import dataclass
@dataclass
class Suggestion:
text: str
# only show this suggestion to people who have watched at least this episode
episode: int = None
suggestions = {
"campaign1": [
Suggestion(
text="I am Grog, the unintimidated.",
episode=18
),
Suggestion(
text="Bidet",
episode=60
),
Suggestion(
text="my esteemed panel of extremely mature",
episode=83
),
Suggestion(
text="I turn into a goldfish",
episode=97
),
Suggestion(
text="No, it's fine! We're gods!",
episode=97
),
Suggestion(
text="Life needs things to live",
episode=63
),
Suggestion(
text="I'd like to share the news of our Lord and Savior: My axe in your face!",
episode=29
),
Suggestion(
text="Your secret is safe with my indifference",
episode=45
)
],
"campaign2": [
Suggestion(
text="Tieflings can only see movement.",
episode=1
),
Suggestion(
text="Because I'm really fucking strong.",
episode=1
),
Suggestion(
text="I use thaumaturgy to open all the windows",
episode=2
),
Suggestion(
text="Regular gnoll",
episode=5
),
Suggestion(
text="Nudity. It usually works.",
episode=2
),
Suggestion(
text="Donuts",
episode=3
),
Suggestion(
text="The world does need an asshole",
episode=4
),
Suggestion(
text="We'll try to catch you in a jar",
episode=5
),
Suggestion(
text="Fjord Explorers",
episode=6
),
Suggestion(
text="Eldritch Blast",
episode=6
),
Suggestion(
text="You look like a nerd",
episode=8
),
Suggestion(
text="The best lay ever",
episode=8
),
Suggestion(
text="Only steal from grumpy people",
episode=9
),
Suggestion(
text="You can reply to this message",
episode=11
),
Suggestion(
text="Be the chaos you want to see in the world",
episode=12
),
Suggestion(
text="I'm always ready to make a damn fool of myself",
episode=12
),
Suggestion(
text="My name is Molly",
episode=14
),
Suggestion(
text="A simple tool",
episode=15
),
Suggestion(
text="I am your god, long may I reign",
episode=18
),
Suggestion(
text="I cast Regret",
episode=21
),
Suggestion(
text="The Traveler's bullshit!",
episode=26
),
Suggestion(
text="Uk'otoa",
episode=37
),
Suggestion(
text="Fluffernutter",
episode=39
),
Suggestion(
text="Sleep well with your bad decisions",
episode=40
),
Suggestion(
text="Not cool, man",
episode=44
),
Suggestion(
text="We're running, it's bad!",
episode=45
),
Suggestion(
text="Hello Bees",
episode=46
),
Suggestion(
text="Yes. It's a chair. It's a standard chair.",
episode=48
),
Suggestion(
text="Are you secretly in love with me?",
episode=50
),
Suggestion(
text="Its a regular fucking turtle",
episode=60
),
Suggestion(
text="The rule is that evil dies",
episode=74
),
Suggestion(
text="Oh shit, are we a cult?",
episode=77
),
Suggestion(
text="I want to role play fish and chips!",
episode=84
),
Suggestion(
text="I pick and choose my apologies",
episode=84
),
Suggestion(
text="Can I get a hug?",
episode=87
),
Suggestion(
text="I smell like a crayon",
episode=91
),
Suggestion(
text="Captain, we're being followed by a tiny island.",
episode=99
)
],
"VoxMachinaVsMightyNein": [
Suggestion(
text="Oh, you one of those rich boys?"
),
Suggestion(
text="Some days you get hit by a T-Rex."
)
],
"ClubOfMisfits": [
Suggestion("I'm going to try something weird.")
]
}

View file

@ -6,15 +6,3 @@
</div> </div>
</template> </template>
<style lang="scss">
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

View file

@ -22,8 +22,6 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
//border: 2px solid $border-color; //border: 2px solid $border-color;
z-index: 100; z-index: 100;
max-height: 95vh;
overflow: auto;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
width: 80%; width: 80%;
@ -56,7 +54,10 @@
.buttonrow { .buttonrow {
display: flex; display: flex;
padding: 0; padding: 0;
border-top: 2px solid $border-color;
@include media-breakpoint-down(xs) {
border-bottom: 2px solid $border-color;
}
button { button {
width: 100%; width: 100%;
} }
@ -88,6 +89,8 @@
.seriesSelector { .seriesSelector {
width: 95%; width: 95%;
max-width: 1500px; max-width: 1500px;
max-height: 95vh;
overflow: auto;
h1{ h1{
font-family: "Nodesto Caps Condensed", cursive; font-family: "Nodesto Caps Condensed", cursive;

View file

@ -60,7 +60,7 @@ export default Vue.extend({
const episode = (series.length === 1) ? "-" : 10; const episode = (series.length === 1) ? "-" : 10;
return { return {
name: "search", name: "search",
params: {series: series.slug, episode: episode} params: {series: series.slug, episode}
}; };
} }
} }

View file

@ -113,7 +113,10 @@ pre {
footer { footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap;
> * {
padding: .5rem;
}
button.btn-link { button.btn-link {
padding: 0; padding: 0;
} }

View file

@ -16,7 +16,7 @@ export default new Router({
{ {
path: "/episodes", path: "/episodes",
name: "episodes", name: "episodes",
component: Episodes, component: () => import(/* webpackChunkName: "episodes" */ "./views/Episodes.vue"),
}, },
{ {
path: "/:something/", path: "/:something/",

View file

@ -1 +1,8 @@
export const baseURL = (process.env.NODE_ENV === "production") ? "/api/" : "http://127.0.0.1:5000/api/"; export const baseURL = (process.env.NODE_ENV === "production") ? "/api/" : "http://127.0.0.1:5000/api/";
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
export const reducedMotion = (!mediaQuery || mediaQuery.matches);
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

View file

@ -34,6 +34,7 @@
<div class="inputlist"> <div class="inputlist">
<span>Search for</span> <span>Search for</span>
<autocomplete :defaultValue="this.$route.params.keyword" :search="suggest" @submit="handleSubmit" <autocomplete :defaultValue="this.$route.params.keyword" :search="suggest" @submit="handleSubmit"
:placeholder="placeholderText"
ref="searchInput"></autocomplete> ref="searchInput"></autocomplete>
<span v-if="!isOneShot">up to episode </span> <span v-if="!isOneShot">up to episode </span>
<input v-if="!isOneShot" title="search until episode number" <input v-if="!isOneShot" title="search until episode number"
@ -72,14 +73,15 @@
v-html="line.text"></span> v-html="line.text"></span>
</p> </p>
</div> </div>
<details> <!-- <details>-->
<summary>Raw Data</summary> <!-- <summary>Raw Data</summary>-->
<pre>{{ searchResult }}</pre> <!-- <pre>{{ searchResult }}</pre>-->
</details> <!-- </details>-->
</div> </div>
<footer> <footer>
<button @click="showIntro=true" class="btn btn-link">About this website</button> <button @click="showIntro=true" class="btn btn-link">About this website</button>
<router-link :to="{name:'episodes'}">Episode overview</router-link>
<a href="https://lw1.at">My other Projects</a> <a href="https://lw1.at">My other Projects</a>
<a href="https://lw1.at/i">Privacy Policy</a> <a href="https://lw1.at/i">Privacy Policy</a>
</footer> </footer>
@ -127,15 +129,30 @@ export default Vue.extend({
ytResult: undefined as Result | undefined, ytResult: undefined as Result | undefined,
ytWidth: 640, ytWidth: 640,
showIntro: true, showIntro: true,
showSeriesSelector: false showSeriesSelector: false,
placeholderText: "",
placeholderFullText: "",
placeholderTimeout: 0,
placeholderInterval: 0
}; };
}, },
created() {
fetch(baseURL + "series")
.then((response) => response.json())
.then((data: ServerData) => {
this.serverData = data;
});
this.placeholderTimeout = setTimeout(this.startTyping, 7 * 1000);
},
beforeDestroy() {
clearTimeout(this.placeholderTimeout);
},
mounted(): void { mounted(): void {
if (localStorage.showIntro) { if (localStorage.getItem("showIntro") !== null) {
this.showIntro = localStorage.showIntro; this.showIntro = localStorage.getItem("showIntro") === "true";
} }
if (localStorage.ytOptIn) { if (localStorage.getItem("ytOptIn") !== null) {
this.ytOptIn = localStorage.ytOptIn; this.ytOptIn = localStorage.getItem("ytOptIn") === "true";
} }
if (this.episode == null) { if (this.episode == null) {
this.episode = "10"; this.episode = "10";
@ -148,16 +165,13 @@ export default Vue.extend({
} }
const max = 640; const max = 640;
this.ytWidth = (window.innerWidth < max ? window.innerWidth : max) - 2 * 2; this.ytWidth = (window.innerWidth < max ? window.innerWidth : max) - 2 * 2;
fetch(baseURL + "series")
.then((response) => response.json())
.then((data: ServerData) => {
this.serverData = data;
});
}, },
methods: { methods: {
suggest(input: string) { suggest(input: string) {
const url = baseURL + "suggest?query=" + input + "&until=" + this.episode + "&series=" + this.$route.params.series; const url = baseURL
+ "suggest?query=" + input
+ "&until=" + this.episode
+ "&series=" + this.$route.params.series;
return new Promise((resolve) => { return new Promise((resolve) => {
if (input.length < 1) { if (input.length < 1) {
@ -173,6 +187,12 @@ export default Vue.extend({
handleSubmit(result: string) { handleSubmit(result: string) {
// @ts-ignore // @ts-ignore
const newKeyword = result || this.$refs.searchInput.value; const newKeyword = result || this.$refs.searchInput.value;
if (newKeyword === "" && this.placeholderFullText) {
// @ts-ignore
this.$refs.searchInput.value = this.placeholderFullText;
this.handleSubmit(this.placeholderFullText);
return;
}
this.$router.push({params: {...this.$route.params, keyword: newKeyword}}); this.$router.push({params: {...this.$route.params, keyword: newKeyword}});
}, },
@ -180,7 +200,10 @@ export default Vue.extend({
if (!this.$route.params.keyword) { if (!this.$route.params.keyword) {
return; return;
} }
const url = baseURL + "search?query=" + this.$route.params.keyword + "&until=" + this.episode + "&series=" + this.$route.params.series; const url = baseURL
+ "search?query=" + this.$route.params.keyword
+ "&until=" + this.episode
+ "&series=" + this.$route.params.series;
fetch(url) fetch(url)
.then((response) => response.json()) .then((response) => response.json())
@ -275,6 +298,46 @@ export default Vue.extend({
this.ytVideoID = undefined; this.ytVideoID = undefined;
this.ytResult = undefined; this.ytResult = undefined;
}, },
startTyping(): void {
// @ts-ignore
if (this.$refs.searchInput.value !== "") {
this.placeholderTimeout = setTimeout(this.startTyping, 5000);
return;
}
const url = baseURL
+ "suggestion"
+ "?until=" + this.episode
+ "&series=" + this.$route.params.series;
fetch(url)
.then((response) => response.text())
.then((data) => {
this.placeholderFullText = data;
clearTimeout(this.placeholderTimeout);
this.placeholderText = "";
this.typing(0);
// const waitTime = 150 * this.placeholderFullText.length + 5000;
// setTimeout(this.untype, waitTime);
});
},
typing(index: number): void {
if (index === this.placeholderFullText.length) {
this.placeholderTimeout = setTimeout(this.untype, 5000);
return;
}
this.placeholderText += this.placeholderFullText[index];
const offset = Math.random() * 80 - 40;
this.placeholderTimeout = setTimeout(this.typing, 70 + offset, index + 1);
},
untype(): void {
if (this.placeholderText.length === 0) {
this.placeholderTimeout = setTimeout(this.startTyping, 5000);
return;
}
this.placeholderText = this.placeholderText.slice(0, -1);
const offset = Math.random() * 40 - 20;
this.placeholderTimeout = setTimeout(this.untype, 35 + offset);
}
}, },
computed: { computed: {
ytLink(): string { ytLink(): string {
@ -312,7 +375,6 @@ export default Vue.extend({
return 300; return 300;
}, },
isOneShot(): boolean { isOneShot(): boolean {
console.log(this.episode);
return this.episode === "-"; return this.episode === "-";
} }
}, },
@ -331,7 +393,10 @@ export default Vue.extend({
this.search(); this.search();
}, },
ytOptIn(value: boolean): void { ytOptIn(value: boolean): void {
localStorage.ytOptIn = value; localStorage.setItem("ytOption", value.toString());
},
showIntro(value: boolean): void {
localStorage.setItem("showIntro", value.toString());
} }
}, },
}); });