parent
305ca820d0
commit
e9cbc93bf8
src/routes
@ -2,6 +2,7 @@ import { getLastPosts } from '$lib/bluesky.js';
|
||||
import { getNowPlaying } from '$lib/lastfm';
|
||||
import { getLastGame } from '$lib/steam';
|
||||
import { noteFromBskyPost } from '../components/note.svelte';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
export const load = async () => {
|
||||
const lastTrack = getNowPlaying();
|
||||
@ -16,6 +17,20 @@ export const load = async () => {
|
||||
return { banners, lastTrack, lastGame, lastNote };
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
pushnotif: async ({ request }: RequestEvent) => {
|
||||
const form = await request.formData();
|
||||
const content = encodeURIComponent(form.get('content')?.toString().substring(0, 100));
|
||||
try {
|
||||
fetch(
|
||||
`https://api.day.app/${env.BARK_DEVICE_ID}/gaze.systems/${content}?icon=https://gaze.systems/icons/gaze_site.webp`
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(`failed to push notification: ${err}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getBannerNo = (others: number[]) => {
|
||||
const no = Math.floor(Math.random() * 20) + 1;
|
||||
if (others.includes(no)) {
|
||||
|
@ -15,14 +15,14 @@
|
||||
let { data }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4 md:gap-8 md:h-full h-card">
|
||||
<div class="flex flex-col gap-4 md:gap-8 ml-auto place-items-end">
|
||||
<div class="flex flex-col md:flex-row gap-2 md:gap-4 md:h-full h-card">
|
||||
<div class="flex flex-col gap-2 md:gap-6 ml-auto place-items-end">
|
||||
<Window title="stuff im doing.." iconUri="/icons/msg_information.webp">
|
||||
<div class="prose prose-ralsei prose-img:m-0 leading-6">
|
||||
<LatestStuff />
|
||||
</div>
|
||||
</Window>
|
||||
<Window style="md:mr-4" title="status" iconUri="/icons/msn.webp" removePadding>
|
||||
<Window style="md:mr-8" title="status" iconUri="/icons/msn.webp" removePadding>
|
||||
{#if data.lastNote}
|
||||
<div class="m-1.5 flex flex-col font-monospace text-sm">
|
||||
<p
|
||||
@ -118,13 +118,31 @@
|
||||
</div>
|
||||
{/if}
|
||||
</Window>
|
||||
<Window title="cool stuff,,">
|
||||
<Window title="notify me">
|
||||
<form class="flex flex-row gap-1" method="post">
|
||||
<input
|
||||
type="text"
|
||||
class="entry text-lg p-1 m-0 bg-transparent resize-none text-shadow-white placeholder-shown:[text-shadow:none] border-none"
|
||||
name="content"
|
||||
placeholder="push a notif into me~~"
|
||||
maxlength="100"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
value="send!!"
|
||||
formaction="?/pushnotif"
|
||||
class="entry text-ralsei-green-light leading-none hover:underline motion-safe:hover:animate-squiggle p-1 z-50"
|
||||
/>
|
||||
</form>
|
||||
</Window>
|
||||
<Window style="md:mr-2" title="cool stuff,,">
|
||||
<div class="max-w-[50ch] prose prose-ralsei prose-a:!animate-none prose-img:m-0 leading-snug">
|
||||
<CoolStuff />
|
||||
</div>
|
||||
</Window>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 md:gap-8 mr-auto w-full md:w-fit place-items-start">
|
||||
<div class="flex flex-col gap-2 md:gap-6 mr-auto w-full md:w-fit place-items-start">
|
||||
<Window title="links!" iconUri="/icons/contact.webp">
|
||||
<div
|
||||
class="[width:40ch] prose prose-ralsei prose-ul:px-[0.9rem] prose-ul:leading-[1.1rem] prose-headings:leading-none"
|
||||
@ -272,3 +290,13 @@
|
||||
</Window>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.entry {
|
||||
@apply bg-ralsei-green-dark/70 border-ralsei-green-light/30 border-x-[4px] border-y-[5px];
|
||||
border-style: ridge;
|
||||
}
|
||||
.entryflex {
|
||||
@apply flex flex-row gap-1;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { redirect, type Cookies, type RequestEvent } from '@sveltejs/kit'
|
||||
import { redirect, type Cookies, type RequestEvent } from '@sveltejs/kit';
|
||||
import { scopeCookies as _scopeCookies, fancyText } from '$lib';
|
||||
import { RetryAfterRateLimiter } from 'sveltekit-rate-limiter/server';
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
@ -10,92 +10,103 @@ import { get, writable } from 'svelte/store';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const callbackUrl = `${PUBLIC_BASE_URL}/guestbook/`
|
||||
const callbackUrl = `${PUBLIC_BASE_URL}/guestbook/`;
|
||||
|
||||
const createPostRatelimiter = new RetryAfterRateLimiter({
|
||||
IP: [10, 'd'],
|
||||
IPUA: [5, 'h'],
|
||||
})
|
||||
IP: [10, 'd'],
|
||||
IPUA: [5, 'h']
|
||||
});
|
||||
|
||||
const scopeCookies = (cookies: Cookies) => {
|
||||
return _scopeCookies(cookies, '/guestbook')
|
||||
}
|
||||
return _scopeCookies(cookies, '/guestbook');
|
||||
};
|
||||
|
||||
const postTokens = writable<Set<string>>(new Set());
|
||||
|
||||
export const actions = {
|
||||
post: async (event: RequestEvent) => {
|
||||
const { request, cookies } = event
|
||||
const scopedCookies = scopeCookies(cookies)
|
||||
const rateStatus = await createPostRatelimiter.check(event)
|
||||
if (rateStatus.limited) {
|
||||
scopedCookies.set("sendError", `you are being ratelimited sowwy :c, try again after ${rateStatus.retryAfter} seconds`)
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
const form = await request.formData()
|
||||
const content = form.get("content")?.toString().substring(0, 300)
|
||||
if (content === undefined) {
|
||||
scopedCookies.set("sendError", "content field is missing")
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
// save form content in a cookie
|
||||
scopedCookies.set("postData", content)
|
||||
// create a token we will use to validate
|
||||
const token = nanoid()
|
||||
postTokens.update((set) => set.add(token))
|
||||
scopedCookies.set("postAuth", token)
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
}
|
||||
post: async (event: RequestEvent) => {
|
||||
const { request, cookies } = event;
|
||||
const scopedCookies = scopeCookies(cookies);
|
||||
const rateStatus = await createPostRatelimiter.check(event);
|
||||
if (rateStatus.limited) {
|
||||
scopedCookies.set(
|
||||
'sendError',
|
||||
`you are being ratelimited sowwy :c, try again after ${rateStatus.retryAfter} seconds`
|
||||
);
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
const form = await request.formData();
|
||||
const content = form.get('content')?.toString().substring(0, 300);
|
||||
if (content === undefined) {
|
||||
scopedCookies.set('sendError', 'content field is missing');
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
// save form content in a cookie
|
||||
scopedCookies.set('postData', content);
|
||||
// create a token we will use to validate
|
||||
const token = nanoid();
|
||||
postTokens.update((set) => set.add(token));
|
||||
scopedCookies.set('postAuth', token);
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
};
|
||||
|
||||
export async function load({ url, cookies }) {
|
||||
const scopedCookies = scopeCookies(cookies)
|
||||
var data = {
|
||||
entries: [] as NoteData[],
|
||||
sendError: scopedCookies.get("sendError") || "",
|
||||
getError: "",
|
||||
sendRatelimited: scopedCookies.get('sendRatelimited') || "",
|
||||
getRatelimited: false,
|
||||
fillText: fancyText(getVisitorId(cookies) ?? nanoid()),
|
||||
}
|
||||
const rawPostData = scopedCookies.get("postData") || null
|
||||
const postAuth = scopedCookies.get("postAuth") || null
|
||||
if (rawPostData !== null && postAuth !== null) {
|
||||
// delete the postData cookie after we got it cause we dont need it anymore
|
||||
scopedCookies.delete("postData")
|
||||
scopedCookies.delete("postAuth")
|
||||
// get and validate token
|
||||
if (!get(postTokens).has(postAuth)) {
|
||||
scopedCookies.set("sendError", "invalid post token! this is either a bug or you should stop doing silly stuff")
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
postTokens.update((set) => { set.delete(postAuth); return set })
|
||||
// post entry
|
||||
try {
|
||||
// return error if content was not set or if empty
|
||||
const content = rawPostData.substring(0, 300).trim()
|
||||
if (content.length === 0) {
|
||||
scopedCookies.set("sendError", `content field was empty`)
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
// post to guestbook account
|
||||
await (await getBskyClient()).post({text: content, threadgate: { allowMentioned: false, allowFollowing: false }});
|
||||
} catch (err: any) {
|
||||
scopedCookies.set("sendError", err.toString())
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
redirect(303, callbackUrl)
|
||||
}
|
||||
// delete the cookies after we get em since we dont really need these more than once
|
||||
scopedCookies.delete("sendError")
|
||||
scopedCookies.delete("sendRatelimited")
|
||||
// actually get posts
|
||||
try {
|
||||
const { posts } = await getUserPosts("did:web:guestbook.gaze.systems", 16)
|
||||
data.entries = posts.map(noteFromBskyPost)
|
||||
} catch (err: any) {
|
||||
data.getError = err.toString()
|
||||
}
|
||||
const scopedCookies = scopeCookies(cookies);
|
||||
let data = {
|
||||
entries: [] as NoteData[],
|
||||
sendError: scopedCookies.get('sendError') || '',
|
||||
getError: '',
|
||||
sendRatelimited: scopedCookies.get('sendRatelimited') || '',
|
||||
getRatelimited: false,
|
||||
fillText: fancyText(getVisitorId(cookies) ?? nanoid())
|
||||
};
|
||||
const rawPostData = scopedCookies.get('postData') || null;
|
||||
const postAuth = scopedCookies.get('postAuth') || null;
|
||||
if (rawPostData !== null && postAuth !== null) {
|
||||
// delete the postData cookie after we got it cause we dont need it anymore
|
||||
scopedCookies.delete('postData');
|
||||
scopedCookies.delete('postAuth');
|
||||
// get and validate token
|
||||
if (!get(postTokens).has(postAuth)) {
|
||||
scopedCookies.set(
|
||||
'sendError',
|
||||
'invalid post token! this is either a bug or you should stop doing silly stuff'
|
||||
);
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
postTokens.update((set) => {
|
||||
set.delete(postAuth);
|
||||
return set;
|
||||
});
|
||||
// post entry
|
||||
try {
|
||||
// return error if content was not set or if empty
|
||||
const content = rawPostData.substring(0, 300).trim();
|
||||
if (content.length === 0) {
|
||||
scopedCookies.set('sendError', `content field was empty`);
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
// post to guestbook account
|
||||
await (
|
||||
await getBskyClient()
|
||||
).post({ text: content, threadgate: { allowMentioned: false, allowFollowing: false } });
|
||||
} catch (err: any) {
|
||||
scopedCookies.set('sendError', err.toString());
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
redirect(303, callbackUrl);
|
||||
}
|
||||
// delete the cookies after we get em since we dont really need these more than once
|
||||
scopedCookies.delete('sendError');
|
||||
scopedCookies.delete('sendRatelimited');
|
||||
// actually get posts
|
||||
try {
|
||||
const { posts } = await getUserPosts('did:web:guestbook.gaze.systems', 16);
|
||||
data.entries = posts.map(noteFromBskyPost);
|
||||
} catch (err: any) {
|
||||
data.getError = err.toString();
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
@ -8,14 +8,6 @@
|
||||
}
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
function resetEntriesAnimation() {
|
||||
var el = document.getElementById('guestbookentries');
|
||||
if (el === null) { return }
|
||||
el.style.animation = 'none';
|
||||
el.offsetHeight; /* trigger reflow */
|
||||
el.style.animation = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col-reverse md:flex-row gap-2 md:gap-4">
|
||||
@ -24,7 +16,11 @@
|
||||
<div class="prose prose-ralsei leading-6 entry p-2">
|
||||
<p>hia, here is the guestbook if you wanna post anything :)</p>
|
||||
<p>be a good human bean pretty please (and don't be shy!!!)</p>
|
||||
<p class="text-sm italic">(to see all the entries, look <a href="https://bsky.app/profile/guestbook.gaze.systems">here</a>)</p>
|
||||
<p class="text-sm italic">
|
||||
(to see all the entries, look <a href="https://bsky.app/profile/guestbook.gaze.systems"
|
||||
>here</a
|
||||
>)
|
||||
</p>
|
||||
</div>
|
||||
<form method="post">
|
||||
<div class="entry entryflex">
|
||||
@ -45,7 +41,8 @@
|
||||
/>
|
||||
<div class="marquee-wrapper entry text-ralsei-white/50">
|
||||
<div class="marquee font-monospace">
|
||||
<p class="text-shadow-none">{data.fillText}</p><p class="text-shadow-none">{data.fillText}</p>
|
||||
<p class="text-shadow-none">{data.fillText}</p>
|
||||
<p class="text-shadow-none">{data.fillText}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -61,7 +58,13 @@
|
||||
</form>
|
||||
</div>
|
||||
</Window>
|
||||
<Window id='guestbookentries' style="mr-auto" title="entries" iconUri="/icons/entries.webp" removePadding>
|
||||
<Window
|
||||
id="guestbookentries"
|
||||
style="mr-auto"
|
||||
title="entries"
|
||||
iconUri="/icons/entries.webp"
|
||||
removePadding
|
||||
>
|
||||
<div class="flex flex-col gap-2 md:gap-4 2xl:w-[60ch]">
|
||||
{#if data.getRatelimited}
|
||||
<p class="text-error">
|
||||
@ -80,21 +83,46 @@
|
||||
prose-pre:!bg-ralsei-black prose-code:!bg-ralsei-black
|
||||
"
|
||||
>
|
||||
<pre class="language-bash"><code class="language-bash"><nobr>
|
||||
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="source" funct/> <Token v="scripts/log.nu" />
|
||||
<br>
|
||||
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="let" funct/> <Token v="entries"/> <Token v="=" punct/> <Token v="(" punct/><Token v="ls" funct/> <Token v="guestbook" /> <Token v="|" punct/> <Token v="reverse" funct/> <Token v="|" punct/> <Token v="take" funct/> <Token v="16"/><Token v=")" punct/>
|
||||
<br>
|
||||
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="$entries" /> <Token v="|" punct/> <Token v="each" funct/> <Token v="{" punct/><Token v="|" punct/><Token v="file"/><Token v="|" punct/> <Token v="render" funct/> <Token v="(" punct/><Token v="open" funct/> <Token v="$file.name" /><Token v=")" punct/><Token v="}" punct/>
|
||||
<br>
|
||||
<br>
|
||||
<pre class="language-bash"><code class="language-bash"
|
||||
><nobr>
|
||||
<Token v="[" punct />gazesystems <Token v="/" keywd /><Token v="]$" punct /> <Token
|
||||
v="source"
|
||||
funct
|
||||
/> <Token v="scripts/log.nu" />
|
||||
<br />
|
||||
<Token v="[" punct />gazesystems <Token v="/" keywd /><Token v="]$" punct /> <Token
|
||||
v="let"
|
||||
funct
|
||||
/> <Token v="entries" /> <Token v="=" punct /> <Token v="(" punct /><Token
|
||||
v="ls"
|
||||
funct
|
||||
/> <Token v="guestbook" /> <Token v="|" punct /> <Token v="reverse" funct /> <Token
|
||||
v="|"
|
||||
punct
|
||||
/> <Token v="take" funct /> <Token v="16" /><Token v=")" punct />
|
||||
<br />
|
||||
<Token v="[" punct />gazesystems <Token v="/" keywd /><Token v="]$" punct /> <Token
|
||||
v="$entries"
|
||||
/> <Token v="|" punct /> <Token v="each" funct /> <Token v="{" punct /><Token
|
||||
v="|"
|
||||
punct
|
||||
/><Token v="file" /><Token v="|" punct /> <Token v="render" funct /> <Token
|
||||
v="("
|
||||
punct
|
||||
/><Token v="open" funct /> <Token v="$file.name" /><Token v=")" punct /><Token
|
||||
v="}"
|
||||
punct
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
{#each data.entries as note, index}
|
||||
<Note showOutgoing={false} {note}/>
|
||||
<Note showOutgoing={false} {note} />
|
||||
{#if index < data.entries.length - 1}
|
||||
<div class="mt-3"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</nobr></code></pre>
|
||||
<div class="mt-3"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</nobr></code
|
||||
></pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user