Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions docs/discordstatscounter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# DiscordStatscounter Component

## Description

The `DiscordStatscounter` component displays three animated statistical counters with labels using Svelte’s `tweened` store and Sveltestrap's responsive layout system.
It is ideal for displaying key Discord server metrics such as total users, total messages, and online users.
Each counter features a smooth animation and unique easing behavior.

---

## Usage

Use this component to render **exactly three** animated counters.
Each counter counts from `0` to a given `max` value and displays a label underneath.

---

## Props

| Prop Name | Type | Default | Description |
|-------------------|-----------------|---------|-------------|
| `discordCounters` | `Array<Object>` | `[]` | An array of **exactly three** objects. Each object must contain:<br>• `max` (`number`): The final value to animate up to.<br>• `label` (`string`): A label describing the statistic. |

> **Note**: The component supports exactly **three** counters. Passing more or fewer may cause errors or visual inconsistency.

---

## Example

```svelte
<script>
import DiscordStatscounter from '$lib/components/statscounters/DiscordStatscounter.svelte';

const data = {
statistics: [
{
totalUsers: 5230,
totalMessages: 158239,
onlineUsers: 420
}
]
};

const stats = data.statistics[0];

const discordCounters = [
{ max: stats.totalUsers, label: 'Discord Users' },
{ max: stats.totalMessages, label: 'Discord Messages' },
{ max: stats.onlineUsers, label: 'Discord Online' }
];
</script>

<main>
<DiscordStatscounter {discordCounters} />
</main>

```
This example is available for build and test at [Examples](../examples/discordstatscounter.md)

The above code outputs:

![DiscordStatscounters image.](./docsImages/discordstatscounterImage.png "This is a Discordstatscounters image.")
Binary file added docs/docsImages/discordstatscounterImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions examples/discordstatscounter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
```svelte
<script>
import DiscordStatscounter from '$lib/components/statscounters/DiscordStatscounter.svelte';

const data = {
statistics: [
{
totalUsers: 5230,
totalMessages: 158239,
onlineUsers: 420
}
]
};

const stats = data.statistics[0];

const discordCounters = [
{ max: stats.totalUsers, label: 'Discord Users' },
{ max: stats.totalMessages, label: 'Discord Messages' },
{ max: stats.onlineUsers, label: 'Discord Online' }
];
</script>

<main>
<DiscordStatscounter {discordCounters} />
</main>

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script module>
</script>

<script lang="ts">
import { Container, Row, Col } from '@sveltestrap/sveltestrap';
import { Styles } from '@sveltestrap/sveltestrap';
import { tweened } from 'svelte/motion';
import { cubicOut, quadOut, linear } from 'svelte/easing';
let {discordCounters} = $props();



const c1 = tweened(0, { duration: 3000, easing: cubicOut});
const c2 = tweened(0, { duration: 5000, easing: quadOut});
const c3 = tweened(0, { duration: 3900, easing: linear});

c1.set(discordCounters[0].max);
c2.set(discordCounters[1].max);
c3.set(discordCounters[2].max);
</script>

<Styles />
<div class="stats-container">
<Container>
<Row>
<Col md="4" class="stats-col">
<div class="stats-item">
<span class="counter countup">{Math.round($c1)}</span>
<div class="label">
{discordCounters[0].label}
</div>
</div>
</Col>
<Col md="4" class="stats-col">
<div class="stats-item">
<span class="counter countup">
{Math.round($c2)}
</span>
<div class="label">
{discordCounters[1].label}
</div>
</div>
</Col>
<Col md="4" class="stats-col">
<div class="stats-item">
<span class="counter countup">
{Math.round($c3)}
</span>
<div class="label">
{discordCounters[2].label}
</div>
</div>
</Col>
</Row>
</Container>
</div>

<style>
.stats-container {
padding: 3rem 0;
background-color: #f8f9fa;
margin: 2rem 0;
border-radius: 8px;
}

.stats-col {
position: relative;
}

.stats-col:not(:first-child)::before {
content: '';
position: absolute;
left: 0;
top: 10%;
height: 80%;
width: 1px;
background-color: #ddd;
}

.stats-item {
text-align: center;
padding: 1rem;
}

.counter {
display: block;
margin-bottom: 0.5rem;
}

.countup {
font-size: clamp(2rem, 6vw, 3rem);
color: #f5455c;
font-weight: 700;
line-height: 1.2;
}

.label {
font-size: clamp(1rem, 2vw, 1.25rem);
font-weight: 500;
color: #333;
line-height: 1.4;
}

@media (max-width: 767px) {
.stats-col:not(:first-child)::before {
display: none;
}

.stats-item {
margin-bottom: 2rem;
}

.stats-col:last-child .stats-item {
margin-bottom: 0;
}
}
</style>
70 changes: 70 additions & 0 deletions generatedsvelte/src/lib/util/countDiscordStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as fs from "fs";
import * as path from "path";
const DISCORD_AUTH_TOKEN = import.meta.env.DISCORD_USER_TOKEN;
const DISCORD_GUILD_ID = import.meta.env.DISCORD_GUILD_ID;
const rootDir = path.resolve(process.cwd(), '../');
const outputFileDir = path.join(rootDir, '/build/discordstats.js');
const maxRetries = 3;
let validResponse = false;

export const fetchDiscordStats = async () => {
const headers = {
'Authorization':DISCORD_AUTH_TOKEN,
'accept': 'application/json'
};

const res = await fetch(`https://discord.com/api/guilds/${DISCORD_GUILD_ID}?with_counts=true`,{
method:"GET",
headers:headers
});
for(let retryCount = 1; retryCount <= maxRetries; retryCount++){
if(res.ok){
validResponse = true; break;
}
}

if(validResponse){
const data = await res.json();
let totalMessages=0;
const totalUsers = data.approximate_member_count;
const activeUsers = data.approximate_presence_count;
const channelRes = await fetch(
`https://discord.com/api/v10/guilds/${DISCORD_GUILD_ID}/channels`,
{ method: "GET", headers }
);
const channels = await channelRes.json();


for (const channel of channels) { // For each text channel, count messages
if (channel.type === 0) {
let lastMessageId: string | undefined = undefined;
let channelCount = 0;

while (true) {

const url:string = lastMessageId
? `https://discord.com/api/v10/channels/${channel.id}/messages?limit=100&before=${lastMessageId}`
: `https://discord.com/api/v10/channels/${channel.id}/messages?limit=100`;

const msgRes = await fetch(url, { method: "GET", headers });
if (!msgRes.ok) break;

const messages = await msgRes.json();
if (messages.length === 0) break;

channelCount += messages.length;
lastMessageId = messages[messages.length - 1].id;

// Avoid hitting rate limits
await new Promise(r => setTimeout(r, 300));
}

totalMessages += channelCount;
}
}
fs.writeFileSync(outputFileDir, `export const data = {"statistics":[{"onlineUsers":${activeUsers},"totalUsers":${totalUsers},"totalMessages":${totalMessages}}]}`);
}else{
fs.writeFileSync(outputFileDir, `export const data = {"statistics":[{"totalUsers":100000,
"totalMessages":8008500, "onlineUsers":10000 }]}`);
}
}
2 changes: 2 additions & 0 deletions generatedsvelte/src/lib/util/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { fetchStats } from './countStats';
import { fetchContributors, fetchLastUpdated } from './fetchContributors';
import {fetchRcStats} from "./countRcStats"
import { fetchEventData } from './fetchEventData';
import { fetchDiscordStats } from './countDiscordStats';

fetchRcStats();
fetchDiscordStats();
// fetchStats();
// fetchContributors();
// fetchLastUpdated();
Expand Down
3 changes: 3 additions & 0 deletions generatedsvelte/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { personas } from "../../../src/personas";
import { images } from "../../../src/images";
import { rcCounters } from "../../../src/rcCounters";
import { discordCounters } from "../../../src/discordCounters";
import Menubar from '$lib/components/menubar/Menubar.svelte';
import Herounit from '$lib/components/herounit/Herounit.svelte';
import Docslink from '$lib/components/docslink/Docslink.svelte';
Expand All @@ -22,12 +23,14 @@ import Simonlinktiles from '$lib/components/simonlinktiles/Simonlinktiles.svelte
import Carousel from '$lib/components/carousel/Carousel.svelte';
import Personatiles from '$lib/components/personatiles/Personatiles.svelte';
import Rcstatscounter from '$lib/components/rcstatscounter/Rcstatscounter.svelte';
import Discordstatscounter from '$lib/components/discordstatscounter/Discordstatscounter.svelte';
</script>

<Menubar {brand} {menutree} />
<Herounit title={herotitle} subtitle={herosub}/>
<Docslink {leftlink} {rightlink}/>
<Searchbar {searchactions} />
<Discordstatscounter {discordCounters} />
<Rcstatscounter {rcCounters} />
<Statscounters {counters}/>
<Simonlinktiles {tilelinks}/>
Expand Down
5 changes: 5 additions & 0 deletions src/discordCounters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {data} from "../build/discordstats.js";

const stats = data.statistics;
export const discordCounters = [ { max: stats[0].totalUsers, label: "Discord Users"},
{max: stats[0].totalMessages, label: "Discord Messages"}, {max: stats[0].onlineUsers, label: "Discord Online"}];
3 changes: 3 additions & 0 deletions src/main.agml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use Simonlinktiles from $lib/components/simonlinktiles/Simonlinktiles.svelte
use Carousel from $lib/components/carousel/Carousel.svelte
use Personatiles from $lib/components/personatiles/Personatiles.svelte
use Rcstatscounter from $lib/components/rcstatscounter/Rcstatscounter.svelte
use Discordstatscounter from $lib/components/discordstatscounter/Discordstatscounter.svelte

get brand
get menutree
Expand All @@ -22,12 +23,14 @@ get caption
get personas
get images
get rcCounters
get discordCounters

<main>
<Menubar {brand} {menutree} />
<Herounit title={herotitle} subtitle={herosub}/>
<Docslink {leftlink} {rightlink}/>
<Searchbar {searchactions} />
<Discordstatscounter {discordCounters} />
<Rcstatscounter {rcCounters} />
<Statscounters {counters}/>
<Simonlinktiles {tilelinks}/>
Expand Down