add reddit tracker page

This commit is contained in:
MuslemRahimi 2024-07-27 18:07:29 +02:00
parent 1a64b25b8d
commit c98d66d045
4 changed files with 521 additions and 6 deletions

View File

@ -6,17 +6,18 @@ export const load = async ({parent}) => {
const getCramerTracker = async () => {
let output;
const data = await parent();
const cachedData = getCache('', 'getCramerTracker');
if (cachedData) {
output = cachedData;
} else {
// make the POST request to the endpoint
const response = await fetch(data?.apiURL + '/cramer-tracker', {
const { apiKey, apiURL } = await parent();
const response = await fetch(apiURL + '/cramer-tracker', {
method: 'GET',
headers: {
"Content-Type": "application/json", "X-API-KEY": data?.apiKey
"Content-Type": "application/json", "X-API-KEY": apiKey
},
});

View File

@ -3,7 +3,6 @@
import { onMount } from 'svelte';
import * as Avatar from "$lib/components/shadcn/avatar/index.js";
import { Button } from "$lib/components/shadcn/button/index.ts";
import * as Card from "$lib/components/shadcn/card/index.ts";
import * as Table from "$lib/components/shadcn/table/index.ts";
import ArrowUpRight from "lucide-svelte/icons/arrow-up-right";
@ -19,6 +18,10 @@
const quickInfo = data?.getDashboard?.quickInfo;
function reformatDate(dateString) {
return dateString.substring(5, 7) + '/' + dateString.substring(8) + '/' + dateString.substring(2, 4);
}
function latestInfoDate(inputDate) {
// Convert the input date string to milliseconds since epoch
const inputDateMs = Date?.parse(inputDate);
@ -219,7 +222,7 @@ onMount( async() => {
{item?.put_call}
</Table.Cell>
<Table.Cell class="text-right">07/26/24</Table.Cell>
<Table.Cell class="text-right">{reformatDate(item?.date_expiration)}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>

View File

@ -0,0 +1,472 @@
<script lang='ts'>
import { numberOfUnreadNotification, screenWidth } from '$lib/store';
import { onMount } from 'svelte';
import * as Card from "$lib/components/shadcn/card/index.ts";
//import UpgradeToPro from '$lib/components/UpgradeToPro.svelte';
import { abbreviateNumber } from '$lib/utils';
import { Chart } from 'svelte-echarts'
import Link from "lucide-svelte/icons/external-link";
export let data;
let cloudFrontUrl = import.meta.env.VITE_IMAGE_URL;
let isLoaded = false;
let optionGraphPost;
let optionGraphComment;
let optionGraphCompanySpread;
let postList = [];
let commentList = [];
let numCompanyList = [];
function formatUtcTimestamp(timestamp) {
// Create a Date object from the UTC timestamp (in seconds)
let date = new Date(timestamp * 1000);
// Define arrays for month names
const monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
// Extract date components
let day = date.getUTCDate();
let month = monthNames[date.getUTCMonth()];
let year = date.getUTCFullYear();
let hours = date.getUTCHours();
let minutes = date.getUTCMinutes();
// Format minutes to always be two digits
minutes = minutes < 10 ? '0' + minutes : minutes;
// Determine AM or PM suffix and adjust hours for 12-hour format
let amPm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
// Construct formatted date string
let formattedDate = `${day} ${month} ${year}, ${hours}:${minutes} ${amPm}`;
return formattedDate;
}
function removeHttpsStrings(input) {
// Split the input string by spaces
let words = input.split(' ');
// Filter out words that contain "https"
let filteredWords = words.filter(word => !word.includes('https'));
// Join the filtered words back into a single string
let output = filteredWords.join(' ');
return output;
}
function getPlotOptions() {
let rawData = data?.getRedditTracker?.stats;
rawData = rawData?.sort((a, b) => new Date(a?.date) - new Date(b?.date));
let dates = [];
rawData?.forEach(item => {
dates?.push(item?.date);
postList?.push(item?.totalPosts);
commentList?.push(item?.totalComments)
numCompanyList?.push(item?.companySpread)
});
const optionPost = {
silent: true,
tooltip: {
trigger: 'axis',
hideDelay: 100, // Set the delay in milliseconds
},
grid: {
left: '3%',
right: '4%',
bottom: '0%',
top: '5%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
splitLine: {
show: false, // Disable x-axis grid lines
},
data: dates,
axisLabel: {
show: false // Hide x-axis labels
}
}
],
yAxis: [
{
type: 'value',
splitLine: {
show: false, // Disable x-axis grid lines
},
axisLabel: {
show: false // Hide y-axis labels
}
},
],
series: [
{
name: 'Total Posts',
type: 'line',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
opacity: 0.6,
color: '#2662D9'
},
emphasis: {
focus: 'series'
},
data: postList
},
]
};
const optionComment = {
silent: true,
tooltip: {
trigger: 'axis',
hideDelay: 100, // Set the delay in milliseconds
},
grid: {
left: '3%',
right: '4%',
bottom: '0%',
top: '5%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
splitLine: {
show: false, // Disable x-axis grid lines
},
data: dates,
axisLabel: {
show: false // Hide x-axis labels
}
}
],
yAxis: [
{
type: 'value',
splitLine: {
show: false, // Disable x-axis grid lines
},
axisLabel: {
show: false // Hide y-axis labels
}
},
],
series: [
{
name: 'Total Comments',
type: 'line',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
opacity: 0.6,
color: '#DE356E'
},
emphasis: {
focus: 'series'
},
data: commentList
},
]
};
const optionCompanySpread = {
silent: true,
tooltip: {
trigger: 'axis',
hideDelay: 100, // Set the delay in milliseconds
},
grid: {
left: '3%',
right: '4%',
bottom: '0%',
top: '5%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
splitLine: {
show: false, // Disable x-axis grid lines
},
data: dates,
axisLabel: {
show: false // Hide x-axis labels
}
}
],
yAxis: [
{
type: 'value',
splitLine: {
show: false, // Disable x-axis grid lines
},
axisLabel: {
show: false // Hide y-axis labels
}
},
],
series: [
{
name: 'Company Spreads',
type: 'bar',
smooth: true,
barWidth: '90%',
showSymbol: false,
data: numCompanyList,
itemStyle: {
color: '#4D9526'
}
},
]
};
return {optionPost, optionComment, optionCompanySpread};
}
onMount(() => {
const {optionPost, optionComment, optionCompanySpread } = getPlotOptions()
optionGraphPost = optionPost
optionGraphComment = optionComment
optionGraphCompanySpread = optionCompanySpread
isLoaded = true;
})
let charNumber = 40;
$: {
if ($screenWidth < 640)
{
charNumber = 15;
}
else {
charNumber = 40;
}
}
</script>
<svelte:head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ''} Wallstreetbets Tracker · stocknear
</title>
<meta name="description" content={`Track the stocks and discussion of Wallstreetbets in realtime.`} />
<!-- Other meta tags -->
<meta property="og:title" content={`Wallstreetbets Tracker · stocknear`}/>
<meta property="og:description" content={`Track the stocks and discussion of Wallstreetbets in realtime.`} />
<meta property="og:type" content="website"/>
<!-- Add more Open Graph meta tags as needed -->
<!-- Twitter specific meta tags -->
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content={`Wallstreetbets Tracker · stocknear`}/>
<meta name="twitter:description" content={`Track the stocks and discussion of Wallstreetbets in realtime.`} />
<!-- Add more Twitter meta tags as needed -->
</svelte:head>
<svelte:options immutable = {true} />
<section class="w-full max-w-screen overflow-hidden m-auto min-h-screen bg-[#09090B]">
<div class="flex flex-col w-full max-w-7xl m-auto justify-center items-center">
<div class="text-center mb-10 w-full px-4 sm:px-10 mt-10">
<div class="flex flex-col items-start mb-10">
<div class="flex flex-row items-center mb-10">
<div class="flex-shrink-0 rounded-full w-12 h-12 sm:w-20 sm:h-20 relative bg-[#27272A] flex items-center border border-gray-600 justify-center">
<img style="clip-path: circle(50%);" class="avatar w-9 h-9 sm:w-16 sm:h-16" src={cloudFrontUrl+'/reddit/wallstreetbets_logo.png'} alt="stock logo"/>
</div>
<h1 class="text-white text-2xl sm:text-4xl font-bold text-start w-full pl-4">
r/Wallstreetbets Tracker
</h1>
</div>
<div class="text-start w-full text-gray-300 text-[1rem]">
<span class="font-semibold text-white">Description:</span> Like 4chan found a Bloomberg Terminal.
</div>
</div>
<div class="sm:p-0 flex justify-center w-full m-auto overflow-hidden">
<div class="relative flex justify-center items-center overflow-hidden w-full">
<main class="w-full">
{#if isLoaded}
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 md:gap-8">
<Card.Root>
<Card.Header class="flex flex-col items-start space-y-0 pb-2">
<Card.Title class="text-start text-[1rem] sm:text-2xl font-semibold pb-2">Post Activity</Card.Title>
<Card.Description class="text-gray-300 text-sm pb-2">Number of Posts in the last 24 hours:</Card.Description>
<Card.Description class="text-white text-[1rem] pb-2"><span class="text-white font-bold text-2xl">
+{postList?.at(-1)}
</span> posts today</Card.Description>
</Card.Header>
<Card.Content>
<div class="app w-full h-[200px]">
<Chart options={optionGraphPost} class="chart" />
</div>
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header class="flex flex-col items-start space-y-0 pb-2">
<Card.Title class="text-start text-[1rem] sm:text-2xl font-semibold pb-2">Comment Activity</Card.Title>
<Card.Description class="text-gray-300 text-sm pb-2">Number of Comments in the last 24 hours:</Card.Description>
<Card.Description class="text-white text-[1rem] pb-2"><span class="text-white font-bold text-2xl">
+{abbreviateNumber(commentList?.at(-1))}
</span> comments today</Card.Description>
</Card.Header>
<Card.Content>
<div class="app w-full h-[200px]">
<Chart options={optionGraphComment} class="chart" />
</div>
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header class="flex flex-col items-start space-y-0 pb-2">
<Card.Title class="text-start text-[1rem] sm:text-2xl font-semibold pb-2">Company Spread</Card.Title>
<Card.Description class="text-gray-300 text-sm pb-2">Number of Tickers discussed in the last 24 hours:</Card.Description>
<Card.Description class="text-white text-[1rem] pb-2"><span class="text-white font-bold text-2xl">
+{numCompanyList?.at(-1)}
</span> discussed today</Card.Description>
</Card.Header>
<Card.Content>
<div class="app w-full h-[200px]">
<Chart options={optionGraphCompanySpread} class="chart" />
</div>
</Card.Content>
</Card.Root>
</div>
<div class="mt-10 grid gap-4 md:gap-8 grid-cols-1 md:grid-cols-2 text-start">
<Card.Root class="xl:col-span-2 overflow-x-scroll h-[500px]">
<Card.Header class="flex flex-row items-center">
<div class="flex flex-col items-start w-full">
<div class="flex flex-row w-full items-center">
<Card.Title class="text-xl sm:text-2xl tex-white font-semibold">Latest Posts</Card.Title>
</div>
</div>
</Card.Header>
<Card.Content>
{#each data?.getRedditTracker?.posts as item}
<div class="flex flex-col items-start mb-3 p-3 border border-gray-800 rounded-lg bg-[#141417]">
<div class="text-[1rem] font-semibold mb-3">
{item?.title}
</div>
{#if item?.selftext?.length !== 0}
<div class="text-sm mb-3">
{item?.selftext?.length < 400 ? removeHttpsStrings(item?.selftext) : removeHttpsStrings(item?.selftext)?.slice(0,240) +'...'}
</div>
{/if}
<div class="flex flex-row items-center justify-between w-full mt-3">
<a href={'https://www.reddit.com/user/'+item?.author} rel="noopener noreferrer" target="_blank" class="text-xs text-white sm:hover:text-blue-400">
Posted by {item?.author}
</a>
<a href={'https://www.reddit.com'+item?.permalink} rel="noopener noreferrer" target="_blank" class="text-xs text-white sm:hover:text-blue-400">
{formatUtcTimestamp(item?.created_utc)}
<Link class="h-3 w-3 inline-block shrink-0 -mt-1 ml-1" />
</a>
</div>
</div>
{/each}
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header>
<Card.Title class="text-start text-xl w-full flex flex-row items-center">
<span>
Currently Trending
</span>
</Card.Title>
</Card.Header>
<Card.Content class="grid gap-y-4">
</Card.Content>
</Card.Root>
</div>
{:else}
<div class="flex justify-center items-center h-80">
<div class="relative">
<label class="bg-[#09090B] rounded-xl h-14 w-14 flex justify-center items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<span class="loading loading-spinner loading-md"></span>
</label>
</div>
</div>
{/if}
</main>
</div>
</div>
</div>
</div>
</section>
<style>
.app {
height: 200px;
max-width: 100%; /* Ensure chart width doesn't exceed the container */
}
@media (max-width: 640px) {
.app {
height: 210px;
}
}
.chart {
width: 100%;
}
</style>

View File

@ -0,0 +1,39 @@
import {getCache, setCache } from '$lib/store';
export const load = async ({parent}) => {
const getRedditTracker = async () => {
let output;
const cachedData = getCache('', 'getRedditTracker');
if (cachedData) {
output = cachedData;
} else {
const { apiKey, apiURL } = await parent();
const response = await fetch(apiURL + '/reddit-tracker', {
method: 'GET',
headers: {
"Content-Type": "application/json", "X-API-KEY": apiKey
},
});
output = await response.json();
setCache('', output, 'getRedditTracker');
}
//output = data?.user?.tier !== 'Pro' ? output?.slice(0,6) : output;
return output;
};
// Make sure to return a promise
return {
getRedditTracker: await getRedditTracker()
};
};