Compare commits
No commits in common. "ce0871b0527a846460fe75b337c803506aba0aa0" and "efdce954acc63b43cb29461897a1419026275155" have entirely different histories.
ce0871b052
...
efdce954ac
@ -1,31 +0,0 @@
|
||||
import { env } from '$env/dynamic/private'
|
||||
import { Agent, CredentialSession, RichText } from '@atproto/api'
|
||||
import { get, writable } from 'svelte/store'
|
||||
|
||||
const bskyClient = writable<null | Agent>(null)
|
||||
|
||||
export const getBskyClient = async () => {
|
||||
let client = get(bskyClient)
|
||||
if (client === null) {
|
||||
client = await loginToBsky()
|
||||
bskyClient.set(client)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
export const postToBsky = async (text: string) => {
|
||||
let client = await getBskyClient()
|
||||
const rt = new RichText({ text })
|
||||
await rt.detectFacets(client)
|
||||
const {uri} = await client.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
})
|
||||
return uri
|
||||
}
|
||||
|
||||
const loginToBsky = async () => {
|
||||
const creds = new CredentialSession(new URL("https://bsky.social"))
|
||||
await creds.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
|
||||
return new Agent(creds)
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
import type { Cookies } from '@sveltejs/kit'
|
||||
import { env } from '$env/dynamic/private'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { Agent, CredentialSession } from '@atproto/api'
|
||||
import SGDB from 'steamgriddb'
|
||||
|
||||
export const scopeCookies = (cookies: Cookies, path: string) => {
|
||||
return {
|
||||
@ -12,4 +17,79 @@ export const scopeCookies = (cookies: Cookies, path: string) => {
|
||||
cookies.delete(key, { ...props, path })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`
|
||||
export const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0'));
|
||||
|
||||
export const loginToBsky = async () => {
|
||||
const creds = new CredentialSession(new URL("https://bsky.social"))
|
||||
await creds.login({ identifier: 'gaze.systems', password: env.BSKY_PASSWORD ?? "" })
|
||||
return new Agent(creds)
|
||||
}
|
||||
export const bskyClient = writable<null | Agent>(null)
|
||||
|
||||
const cachedLastTrack = writable<{track: LastTrack | null, since: number}>({track: null, since: 0})
|
||||
export type LastTrack = {name: string, artist: string, image: string | null, link: string}
|
||||
export const lastFmGetNowPlaying: () => Promise<LastTrack | null> = async () => {
|
||||
var cached = get(cachedLastTrack)
|
||||
if (Date.now() - cached.since < 10 * 1000) {
|
||||
return cached.track
|
||||
}
|
||||
try {
|
||||
const API_URL = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1"
|
||||
var resp = await (await fetch(API_URL)).json()
|
||||
var track = resp.recenttracks.track[0] ?? null
|
||||
if (!(track['@attr'].nowplaying ?? null)) {
|
||||
throw "no nowplaying track found"
|
||||
}
|
||||
var data = {
|
||||
name: track.name,
|
||||
artist: track.artist['#text'],
|
||||
image: track.image[2]['#text'] ?? null,
|
||||
link: track.url,
|
||||
}
|
||||
cachedLastTrack.set({track: data, since: Date.now()})
|
||||
return data
|
||||
} catch(why) {
|
||||
console.log("could not fetch last fm: ", why)
|
||||
cachedLastTrack.set({track: null, since: Date.now()})
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const steamgriddbClient = writable<SGDB | null>(null);
|
||||
const cachedLastGame = writable<{game: LastGame | null, since: number}>({game: null, since: 0})
|
||||
export type LastGame = {name: string, link: string, icon: string, pfp: string}
|
||||
export const steamGetNowPlaying: () => Promise<LastGame | null> = async () => {
|
||||
var griddbClient = get(steamgriddbClient)
|
||||
if (griddbClient === null) {
|
||||
griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY)
|
||||
steamgriddbClient.set(griddbClient)
|
||||
}
|
||||
var cached = get(cachedLastGame)
|
||||
if (Date.now() - cached.since < 10 * 1000) {
|
||||
return cached.game
|
||||
}
|
||||
try {
|
||||
const API_URL = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=76561198106829949&format=json`
|
||||
var profile = (await (await fetch(API_URL)).json()).response.players[0]
|
||||
if (!profile.gameid) {
|
||||
throw "no game is being played"
|
||||
}
|
||||
var icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official'])
|
||||
console.log(icons)
|
||||
var game = {
|
||||
name: profile.gameextrainfo,
|
||||
link: `https://store.steampowered.com/app/${profile.gameid}`,
|
||||
icon: icons[0].thumb.toString(),
|
||||
pfp: profile.avatarmedium,
|
||||
}
|
||||
cachedLastGame.set({game, since: Date.now()})
|
||||
return game
|
||||
} catch(why) {
|
||||
console.log("could not fetch steam: ", why)
|
||||
cachedLastGame.set({game: null, since: Date.now()})
|
||||
return null
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import { get, writable } from "svelte/store"
|
||||
|
||||
const GET_RECENT_TRACKS_ENDPOINT = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1"
|
||||
const CACHE_EXPIRY_SECONDS = 10
|
||||
|
||||
type LastTrack = {name: string, artist: string, image: string | null, link: string}
|
||||
type CachedLastTrack = {track: LastTrack | null, since: number}
|
||||
const cachedLastTrack = writable<CachedLastTrack>({track: null, since: 0})
|
||||
|
||||
export const lastFmGetNowPlaying: () => Promise<LastTrack | null> = async () => {
|
||||
var cached = get(cachedLastTrack)
|
||||
if (Date.now() - cached.since < CACHE_EXPIRY_SECONDS * 1000) {
|
||||
return cached.track
|
||||
}
|
||||
try {
|
||||
var resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json()
|
||||
var track = resp.recenttracks.track[0] ?? null
|
||||
if (!(track['@attr'].nowplaying ?? null)) {
|
||||
throw "no nowplaying track found"
|
||||
}
|
||||
var data = {
|
||||
name: track.name,
|
||||
artist: track.artist['#text'],
|
||||
image: track.image[2]['#text'] ?? null,
|
||||
link: track.url,
|
||||
}
|
||||
cachedLastTrack.set({track: data, since: Date.now()})
|
||||
return data
|
||||
} catch(why) {
|
||||
console.log("could not fetch last fm: ", why)
|
||||
cachedLastTrack.set({track: null, since: Date.now()})
|
||||
return null
|
||||
}
|
||||
}
|
@ -2,15 +2,9 @@ import { existsSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { env } from '$env/dynamic/private'
|
||||
|
||||
export interface OutgoingLinkData {
|
||||
name: string,
|
||||
link: string,
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
content: string,
|
||||
published: number,
|
||||
outgoingLinks?: OutgoingLinkData[],
|
||||
}
|
||||
type NoteId = string
|
||||
|
||||
@ -38,7 +32,8 @@ export const writeNote = (id: NoteId, note: Note) => {
|
||||
writeNotesList([id].concat(noteList))
|
||||
}
|
||||
}
|
||||
export const createNote = (id: NoteId, note: Note) => {
|
||||
export const createNote = (note: Note) => {
|
||||
const id = genNoteId()
|
||||
writeNote(id, note)
|
||||
return id
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
import { env } from "$env/dynamic/private";
|
||||
import SGDB from "steamgriddb";
|
||||
import { get, writable } from "svelte/store";
|
||||
|
||||
const STEAM_ID = "76561198106829949"
|
||||
const GET_PLAYER_SUMMARY_ENDPOINT = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=${STEAM_ID}&format=json`
|
||||
const CACHE_EXPIRY_SECONDS = 10
|
||||
|
||||
type LastGame = {name: string, link: string, icon: string, pfp: string}
|
||||
type CachedLastGame = {game: LastGame | null, since: number}
|
||||
|
||||
const steamgriddbClient = writable<SGDB | null>(null);
|
||||
const cachedLastGame = writable<CachedLastGame>({game: null, since: 0})
|
||||
|
||||
export const steamGetNowPlaying: () => Promise<LastGame | null> = async () => {
|
||||
var griddbClient = get(steamgriddbClient)
|
||||
if (griddbClient === null) {
|
||||
griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY)
|
||||
steamgriddbClient.set(griddbClient)
|
||||
}
|
||||
var cached = get(cachedLastGame)
|
||||
if (Date.now() - cached.since < CACHE_EXPIRY_SECONDS * 1000) {
|
||||
return cached.game
|
||||
}
|
||||
try {
|
||||
var profile = (await (await fetch(GET_PLAYER_SUMMARY_ENDPOINT)).json()).response.players[0]
|
||||
if (!profile.gameid) {
|
||||
throw "no game is being played"
|
||||
}
|
||||
var icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official'])
|
||||
console.log(icons)
|
||||
var game = {
|
||||
name: profile.gameextrainfo,
|
||||
link: `https://store.steampowered.com/app/${profile.gameid}`,
|
||||
icon: icons[0].thumb.toString(),
|
||||
pfp: profile.avatarmedium,
|
||||
}
|
||||
cachedLastGame.set({game, since: Date.now()})
|
||||
return game
|
||||
} catch(why) {
|
||||
console.log("could not fetch steam: ", why)
|
||||
cachedLastGame.set({game: null, since: Date.now()})
|
||||
return null
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
import { env } from "$env/dynamic/private";
|
||||
import { scopeCookies } from "$lib";
|
||||
import type { Cookies } from "@sveltejs/kit";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { get, writable } from "svelte/store";
|
||||
|
||||
const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`
|
||||
const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0'));
|
||||
|
||||
export const incrementVisitCount = (request: Request, cookies: Cookies) => {
|
||||
let currentVisitCount = get(visitCount)
|
||||
// check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
|
||||
const ua = request.headers.get('user-agent')
|
||||
const isBot = ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk)/) !== null : true
|
||||
if (!isBot) {
|
||||
const scopedCookies = scopeCookies(cookies, '/')
|
||||
// parse the last visit timestamp from cookies if it exists
|
||||
const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || "0")
|
||||
// get unix timestamp
|
||||
const currentTime = new Date().getTime()
|
||||
const timeSinceVisit = currentTime - visitedTimestamp
|
||||
// check if this is the first time a client is visiting or if an hour has passed since they last visited
|
||||
if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) {
|
||||
// increment current and write to the store
|
||||
currentVisitCount += 1; visitCount.set(currentVisitCount)
|
||||
// update the cookie with the current timestamp
|
||||
scopedCookies.set('visitedTimestamp', currentTime.toString())
|
||||
// write the visit count to a file so we can load it later again
|
||||
writeFileSync(visitCountFile, currentVisitCount.toString())
|
||||
}
|
||||
}
|
||||
return currentVisitCount
|
||||
}
|
||||
|
||||
export const notifyDarkVisitors = (url: URL, request: Request) => {
|
||||
fetch('https://api.darkvisitors.com/visits', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: `Bearer ${env.DARK_VISITORS_TOKEN}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
request_path: url.pathname,
|
||||
request_method: request.method,
|
||||
request_headers: request.headers,
|
||||
})
|
||||
}).catch((why) => {
|
||||
console.log("failed sending dark visitors analytics:", why)
|
||||
return null
|
||||
}).then(async (resp) => {
|
||||
if (resp !== null) {
|
||||
const msg = await resp.json()
|
||||
const host = `(${request.headers.get('host')} ${request.headers.get('x-real-ip')})`
|
||||
console.log(`sent visitor analytic to dark visitors: ${resp.statusText}; ${msg.message ?? ''}${host}`)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,17 +1,62 @@
|
||||
import { incrementVisitCount, notifyDarkVisitors } from '$lib/visits.js';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { scopeCookies, visitCount, visitCountFile } from '$lib';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export const csr = true;
|
||||
export const ssr = true;
|
||||
export const prerender = false;
|
||||
export const trailingSlash = 'always';
|
||||
export const trailingSlash = 'always';
|
||||
|
||||
export async function load({ request, cookies, url, setHeaders }) {
|
||||
notifyDarkVisitors(url, request) // no await so it doesnt block load
|
||||
export async function load({ request, cookies, url, setHeaders, fetch }) {
|
||||
fetch('https://api.darkvisitors.com/visits', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: `Bearer ${env.DARK_VISITORS_TOKEN}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
request_path: url.pathname,
|
||||
request_method: request.method,
|
||||
request_headers: request.headers,
|
||||
})
|
||||
}).catch((why) => {
|
||||
console.log("failed sending dark visitors analytics:", why)
|
||||
return null
|
||||
}).then(async (resp) => {
|
||||
if (resp !== null) {
|
||||
const msg = await resp.json()
|
||||
const host = `(${request.headers.get('host')} ${request.headers.get('x-real-ip')})`
|
||||
console.log(`sent visitor analytic to dark visitors: ${resp.statusText}; ${msg.message ?? ''}${host}`)
|
||||
}
|
||||
})
|
||||
|
||||
let currentVisitCount = get(visitCount)
|
||||
// check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
|
||||
const ua = request.headers.get('user-agent')
|
||||
const isBot = ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk)/) !== null : true
|
||||
if (!isBot) {
|
||||
const scopedCookies = scopeCookies(cookies, '/')
|
||||
// parse the last visit timestamp from cookies if it exists
|
||||
const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || "0")
|
||||
// get unix timestamp
|
||||
const currentTime = new Date().getTime()
|
||||
const timeSinceVisit = currentTime - visitedTimestamp
|
||||
// check if this is the first time a client is visiting or if an hour has passed since they last visited
|
||||
if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) {
|
||||
// increment current and write to the store
|
||||
currentVisitCount += 1; visitCount.set(currentVisitCount)
|
||||
// update the cookie with the current timestamp
|
||||
scopedCookies.set('visitedTimestamp', currentTime.toString())
|
||||
// write the visit count to a file so we can load it later again
|
||||
writeFileSync(visitCountFile, currentVisitCount.toString())
|
||||
}
|
||||
}
|
||||
|
||||
setHeaders({ 'Cache-Control': 'no-cache' })
|
||||
|
||||
return {
|
||||
route: url.pathname,
|
||||
visitCount: incrementVisitCount(request, cookies),
|
||||
visitCount: currentVisitCount,
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { lastFmGetNowPlaying } from "$lib/lastfm"
|
||||
import { steamGetNowPlaying } from "$lib/steam"
|
||||
import { lastFmGetNowPlaying, steamGetNowPlaying } from "$lib"
|
||||
|
||||
export const load = async ({}) => {
|
||||
const lastTrack = await lastFmGetNowPlaying()
|
||||
|
@ -15,14 +15,6 @@
|
||||
}
|
||||
|
||||
const highlightedNote = data.notes.get(data.highlightedNote ?? '') ?? null
|
||||
|
||||
// this is ASS this should be a tailwind class
|
||||
const getTextShadowStyle = (color: string) => {
|
||||
return `text-shadow: 0 0 1px theme(colors.ralsei.black), 0 0 5px ${color};`
|
||||
}
|
||||
const outgoingLinkColors: Record<string, string> = {
|
||||
bsky: "rgb(0, 133, 255)",
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@ -41,19 +33,15 @@
|
||||
"
|
||||
>
|
||||
<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" />
|
||||
<Token v="[" punct/>gazesystems <Token v="/log/" keywd/><Token v="]$" punct/> <Token v="source" funct/> log.nu
|
||||
<br>
|
||||
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="ls" funct/> <Token v="log" /> <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/>
|
||||
<Token v="[" punct/>gazesystems <Token v="/log/" keywd/><Token v="]$" punct/> <Token v="ls" funct/> log <Token v="|" punct/> <Token v="each" funct/> <Token v="{" punct/><Token v="|" punct/>file<Token v="|" punct/> <Token v="render" funct/> <Token v="(" punct/><Token v="open" funct/> $file.name<Token v=")" punct/><Token v="}" punct/>
|
||||
<br>
|
||||
<br>
|
||||
{#each data.notes as [noteId, note], index}
|
||||
{@const isHighlighted = noteId === data.highlightedNote}
|
||||
<div class="text-wrap break-words max-w-[70ch] leading-none">
|
||||
<Token v={renderDate(note.published)} small={!isHighlighted}/> <Token v={noteId} keywd small={!isHighlighted}/><Token v="#" punct/> <Token v={note.content} str/>
|
||||
{#each note.outgoingLinks ?? [] as {name, link}}
|
||||
{@const color = outgoingLinkColors[name]}
|
||||
<span class="text-sm"><Token v="(" punct/><a style="color: {color};{getTextShadowStyle(color)}" href={link}>{name}</a><Token v=")" punct/></span>
|
||||
{/each}
|
||||
</div>
|
||||
{#if index < data.notes.size - 1}
|
||||
<div class="mt-3"/>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
import { postToBsky } from '$lib/bluesky';
|
||||
import { createNote, genNoteId, type Note } from '$lib/notes';
|
||||
import { bskyClient, loginToBsky } from '$lib';
|
||||
import { createNote } from '$lib/notes.js';
|
||||
import { RichText } from '@atproto/api';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
interface NoteData {
|
||||
content: string,
|
||||
@ -13,33 +15,30 @@ export const POST = async ({ request }) => {
|
||||
if (token !== env.GAZEBOT_TOKEN) {
|
||||
return new Response("rizz failed", { status: 403 })
|
||||
}
|
||||
// get id
|
||||
const noteId = genNoteId()
|
||||
// get note data
|
||||
const noteData: NoteData = await request.json()
|
||||
console.log(`want to create note #${noteId} with data: `, noteData)
|
||||
// get a date before we start publishing to other platforms
|
||||
let note: Note = {
|
||||
content: noteData.content,
|
||||
published: Date.now(),
|
||||
outgoingLinks: [],
|
||||
}
|
||||
let errors: string[] = []
|
||||
console.log("want to create note with data: ", noteData)
|
||||
// create note
|
||||
const published = Date.now()
|
||||
const noteId = createNote({ content: noteData.content, published })
|
||||
// bridge to bsky if want to bridge
|
||||
if (noteData.bskyPosse) {
|
||||
const postContent = `${noteData.content} (${PUBLIC_BASE_URL}/log?id=${noteId})`
|
||||
try {
|
||||
const bskyUrl = await postToBsky(postContent)
|
||||
note.outgoingLinks?.push({name: "bsky", link: bskyUrl})
|
||||
} catch(why) {
|
||||
console.log(`failed to post note #${noteId} to bsky: `, why)
|
||||
errors.push(`error while posting to bsky: ${why}`)
|
||||
let client = get(bskyClient)
|
||||
if (client === null) {
|
||||
client = await loginToBsky()
|
||||
bskyClient.set(client)
|
||||
}
|
||||
const rt = new RichText({
|
||||
text: `${noteData.content} (${PUBLIC_BASE_URL}/log?id=${noteId})`,
|
||||
})
|
||||
await rt.detectFacets(client)
|
||||
await client.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
})
|
||||
}
|
||||
// create note (this should never fail otherwise it would defeat the whole purpose lol)
|
||||
createNote(noteId, note)
|
||||
// send back created note id and any errors that occurred
|
||||
return new Response(JSON.stringify({ noteId, errors }), {
|
||||
// send back created note id
|
||||
return new Response(JSON.stringify({ noteId }), {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'cache-control': 'no-store',
|
||||
|
Loading…
Reference in New Issue
Block a user