Compare commits
13 Commits
34dbc8d232
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1baf37ae41 | |||
| efa258fdde | |||
| cab2eaa22c | |||
| 49a74cf4df | |||
| e0a99cc455 | |||
| 1214b7e98a | |||
| 369b3bf133 | |||
| d15eab2421 | |||
| b71fb89832 | |||
| 788f1d24f7 | |||
| 48b52edd38 | |||
| 714b2d8bae | |||
| f51580e8ea |
5
.github/workflows/cron.yml
vendored
5
.github/workflows/cron.yml
vendored
@@ -1,9 +1,8 @@
|
||||
name: Daily Cron (Make New DailyStats Document)
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
- cron: "0 5 * * *"
|
||||
|
||||
jobs:
|
||||
cron:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
45
package.json
45
package.json
@@ -12,47 +12,48 @@
|
||||
"prisma-update": "yarn prisma db push"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/charts": "^3.30.0",
|
||||
"@chakra-ui/react": "^3.30.0",
|
||||
"@chakra-ui/charts": "^3.31.0",
|
||||
"@chakra-ui/react": "^3.31.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@escape.tech/graphql-armor": "^3.1.7",
|
||||
"@prisma/client": "^6.19.0",
|
||||
"@prisma/extension-accelerate": "^2.0.2",
|
||||
"@escape.tech/graphql-armor": "^3.2.0",
|
||||
"@prisma/client": "^6.19.2",
|
||||
"@prisma/extension-accelerate": "^3.0.1",
|
||||
"@urql/next": "^2.0.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"graphql": "^16.12.0",
|
||||
"graphql-scalars": "^1.25.0",
|
||||
"graphql-yoga": "^5.17.1",
|
||||
"next": "16.0.7",
|
||||
"graphql-yoga": "^5.18.0",
|
||||
"next": "16.1.5",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-icons": "^5.5.0",
|
||||
"recharts": "^3.5.1",
|
||||
"recharts": "^3.7.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"urql": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@iconify/react": "^6.0.2",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-next": "16.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"baseline-browser-mapping": "^2.9.18",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "16.1.5",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"prettier": "3.7.4",
|
||||
"prisma": "^6.19.0",
|
||||
"prettier": "3.8.1",
|
||||
"prisma": "^6.19.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.1"
|
||||
"typescript-eslint": "^8.54.0"
|
||||
},
|
||||
"packageManager": "yarn@4.11.0"
|
||||
"packageManager": "yarn@4.12.0"
|
||||
}
|
||||
|
||||
57
src/app/CommandsListTable.tsx
Normal file
57
src/app/CommandsListTable.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { JSX } from "react";
|
||||
import { Checkmark, Heading, Table, VStack } from "@chakra-ui/react";
|
||||
import botCommands from "@/data/botCommands";
|
||||
|
||||
const CommandsListTable = (): JSX.Element => {
|
||||
return (
|
||||
<VStack gap={6} w={{ base: "90%", md: "100%" }}>
|
||||
<Heading as="h3" fontSize="3xl">
|
||||
{"Bot Commands"}
|
||||
</Heading>
|
||||
<Table.Root maxW="fit-content" interactive showColumnBorder>
|
||||
<Table.Caption>
|
||||
{"The bot will only respond to admins and moderators within groups."}
|
||||
</Table.Caption>
|
||||
<Table.Header>
|
||||
<Table.Row bg="cyan.emphasized">
|
||||
<Table.ColumnHeader> {"Command"}</Table.ColumnHeader>
|
||||
<Table.ColumnHeader> {"Description"}</Table.ColumnHeader>
|
||||
<Table.ColumnHeader> {"Private"}</Table.ColumnHeader>
|
||||
<Table.ColumnHeader> {"Group"}</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{botCommands.map((commandObj, index) => {
|
||||
const { command, description, groups } = commandObj;
|
||||
return (
|
||||
<Table.Row
|
||||
key={`${index}-${command}`}
|
||||
bg="cyan.muted"
|
||||
_hover={{ bg: "purple.emphasized" }}
|
||||
>
|
||||
<Table.Cell>{command}</Table.Cell>
|
||||
<Table.Cell>{description.replaceAll("\\", "")}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{groups ? (
|
||||
<Checkmark colorPalette="purple" checked />
|
||||
) : (
|
||||
<Checkmark colorPalette="purple" indeterminate />
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{commandObj.private ? (
|
||||
<Checkmark colorPalette="purple" checked />
|
||||
) : (
|
||||
<Checkmark colorPalette="purple" indeterminate />
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandsListTable;
|
||||
@@ -41,7 +41,7 @@ const StatsList = ({
|
||||
groups >= 0 ? (
|
||||
<SingleStatComponent
|
||||
loading={loading}
|
||||
title="Groups Bot Helped"
|
||||
title="Groups Helped"
|
||||
error={error}
|
||||
stat={groups}
|
||||
/>
|
||||
@@ -55,13 +55,13 @@ const StatsList = ({
|
||||
/>
|
||||
<SingleStatComponent
|
||||
loading={loading}
|
||||
title="Commands Responded To"
|
||||
title="Commands Responses"
|
||||
error={error}
|
||||
stat={commands}
|
||||
/>
|
||||
<SingleStatComponent
|
||||
loading={loading}
|
||||
title="Times Triggered"
|
||||
title="Triggers"
|
||||
error={error}
|
||||
stat={triggers}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ const TimedTriggeredChart = ({
|
||||
return (
|
||||
<VStack gap={6} w="100%">
|
||||
<Heading as="h4" fontSize="2xl">{`Times Bot Was Triggered`}</Heading>
|
||||
<LineChartComponent data={lineChartData} label="Times Triggered" />
|
||||
<LineChartComponent data={lineChartData} label="Triggers" />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
37
src/app/api/bot/route.ts
Normal file
37
src/app/api/bot/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { headers } from "next/headers";
|
||||
import botCommands from "@/data/botCommands";
|
||||
|
||||
const environment = process.env.NODE_ENV || "development";
|
||||
|
||||
const isValidApiKey = (apiKey: string): boolean => {
|
||||
const envApiKey =
|
||||
process.env.API_TOKEN || process.env.NEXT_PUBLIC_API_TOKEN || "";
|
||||
|
||||
return apiKey === envApiKey;
|
||||
};
|
||||
|
||||
export async function GET(/*request: Request*/) {
|
||||
const headersList = await headers();
|
||||
const apiKey = headersList.get("x-api-key");
|
||||
|
||||
if (environment === "production") {
|
||||
if (!apiKey || apiKey == null) {
|
||||
return new Response("No API Key provided", {
|
||||
status: 401,
|
||||
headers: headersList
|
||||
});
|
||||
}
|
||||
|
||||
if (apiKey !== null && !(await isValidApiKey(apiKey))) {
|
||||
return new Response("Invalid API Key", {
|
||||
status: 403,
|
||||
headers: headersList
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Response.json(botCommands, {
|
||||
status: 200,
|
||||
headers: headersList
|
||||
});
|
||||
}
|
||||
@@ -34,9 +34,9 @@ export default function RootLayout({
|
||||
return [client, ssr];
|
||||
}, []);
|
||||
|
||||
const title = "Nazi Site Patrol Stats";
|
||||
const title = "Anti Nazi-Sites Bot Stats";
|
||||
const description =
|
||||
"A website to display various stats for the Nazi Site Patrol bot for Telegram. Developed and maintained by Werewolf Kid Creations.";
|
||||
"A website to display various stats for the Anti Nazi-Sites bot for Telegram. Developed and maintained by Werewolf Kid Creations.";
|
||||
const url = "https://bot-stats.werewolfkid.monster";
|
||||
const img = "/images/SPD-arrows.svg";
|
||||
const domain = "werewolfkid.monster";
|
||||
|
||||
@@ -23,6 +23,7 @@ import GetTotalGroupsQuery from "@/graphql/queries/getTotalGroups";
|
||||
import GetTotalStatsQuery from "@/graphql/queries/getTotalStats";
|
||||
import GetTodaysStatsQuery from "@/graphql/queries/getTodaysStats";
|
||||
import GetStatsRange from "@/graphql/queries/getStatsRange";
|
||||
import CommandsListTable from "./CommandsListTable";
|
||||
|
||||
export default function Home() {
|
||||
// * Total Groups * //
|
||||
@@ -93,7 +94,14 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<VStack bg="cyan.950" minH="100vh" h="100%" py="5vh" minW="fit-content">
|
||||
<VStack
|
||||
bg="cyan.950"
|
||||
minH="100dvh"
|
||||
h="100%"
|
||||
py="5dvh"
|
||||
minW="fit-content"
|
||||
textAlign="center"
|
||||
>
|
||||
<VStack w={{ base: "100%", md: "90%" }} gap={10} minW="fit-content">
|
||||
<VStack w="80%" gap={10}>
|
||||
<Icon h="7.5rem" color="whiteAlpha">
|
||||
@@ -114,8 +122,12 @@ export default function Home() {
|
||||
</svg>
|
||||
</Icon>
|
||||
<VStack gap={4} maxW="34rem">
|
||||
<Heading as="h1" fontSize="5xl">
|
||||
{"Anti Nazi Sites Bot Stats"}
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize="5xl"
|
||||
lineHeight={{ base: "3rem", md: "" }}
|
||||
>
|
||||
{"Anti Nazi-Sites Bot Stats"}
|
||||
</Heading>
|
||||
<Heading
|
||||
as="h2"
|
||||
@@ -124,7 +136,7 @@ export default function Home() {
|
||||
textAlign="center"
|
||||
>
|
||||
{
|
||||
"A telegram bot that removes links and embeds to sites that align with the Fascist Right political agenda."
|
||||
"A Telegram bot that deletes links and embeds to sites that align with the Fascist Right political agenda or are otherwise a safety/privacy risk."
|
||||
}
|
||||
</Heading>
|
||||
</VStack>
|
||||
@@ -141,6 +153,7 @@ export default function Home() {
|
||||
</Text>
|
||||
</VStack>
|
||||
</VStack>
|
||||
<CommandsListTable />
|
||||
<VStack gap={10} w="100%">
|
||||
{totalGroups ? (
|
||||
<StatsList
|
||||
@@ -183,7 +196,7 @@ export default function Home() {
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
<VStack w="95%" gap="5vh">
|
||||
<VStack w="95%" gap="5dvh">
|
||||
<VStack gap={1}>
|
||||
<Heading as="h1" fontSize="3xl">{`30 Day Stats`}</Heading>
|
||||
<Text textAlign="center" fontSize="sm" color="whiteAlpha.800">
|
||||
@@ -229,13 +242,21 @@ export default function Home() {
|
||||
rgb(147, 40, 142)
|
||||
)`}
|
||||
w="100%"
|
||||
py="5vh"
|
||||
py="5dvh"
|
||||
textAlign="center"
|
||||
>
|
||||
<VStack
|
||||
color="white"
|
||||
textShadow="0px 0px 5px black;"
|
||||
WebkitTextStroke="0.3px black;"
|
||||
textShadow={`
|
||||
2px 2px 0 #000,
|
||||
2px -2px 0 #000,
|
||||
-2px 2px 0 #000,
|
||||
-2px -2px 0 #000,
|
||||
2px 0px 0 #000,
|
||||
0px 2px 0 #000,
|
||||
-2px 0px 0 #000,
|
||||
0px -2px 0 #000
|
||||
`}
|
||||
gap={6}
|
||||
w="100%"
|
||||
>
|
||||
@@ -244,17 +265,19 @@ export default function Home() {
|
||||
minW="fit-content"
|
||||
w={{ base: "100%", sm: "auto" }}
|
||||
bg="blackAlpha.600"
|
||||
maxW={{ base: "", sm: "62vw" }}
|
||||
maxW={{ base: "", sm: "62dvw" }}
|
||||
px={2}
|
||||
>
|
||||
{"Down with fascism! Fuck MAGA! Fuck Trump! Fuck Nazis!"}
|
||||
{
|
||||
"Down with fascism! Fuck MAGA! Fuck Trump! Fuck Nazis! Abolish ICE!"
|
||||
}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="3xl"
|
||||
minW="fit-content"
|
||||
w={{ base: "100%", sm: "auto" }}
|
||||
bg="blackAlpha.600"
|
||||
maxW={{ base: "", sm: "62vw" }}
|
||||
maxW={{ base: "", sm: "62dvw" }}
|
||||
px={2}
|
||||
>
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { LineChartArr } from "@/types/LineChartStats";
|
||||
|
||||
interface LineChartComponentProps {
|
||||
label: "Times Triggered" | "Links Deleted" | "Command Responses";
|
||||
label: "Triggers" | "Links Deleted" | "Command Responses";
|
||||
data: LineChartArr;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const LineChartComponent = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<Chart.Root maxH="xs" chart={chart} maxW="100vw">
|
||||
<Chart.Root maxH="xs" chart={chart} maxW="100dvw">
|
||||
<LineChart data={chart.data}>
|
||||
<CartesianGrid stroke={chart.color("border")} vertical={false} />
|
||||
<XAxis
|
||||
|
||||
56
src/data/botCommands.ts
Normal file
56
src/data/botCommands.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// * Commands *
|
||||
|
||||
interface Commands {
|
||||
command: String;
|
||||
description: String;
|
||||
groups: boolean;
|
||||
private: boolean;
|
||||
}
|
||||
|
||||
const botInfo: Commands = {
|
||||
command: "/botInfo",
|
||||
description:
|
||||
"Lists information about the bot such as: creator, reason for my creation, purpose and goal, etc",
|
||||
groups: true,
|
||||
private: true
|
||||
};
|
||||
|
||||
const getGroupStats: Commands = {
|
||||
command: "/groupStats",
|
||||
description:
|
||||
"Displays the number of times the bot has deleted links in your group for the lifetime of the bot",
|
||||
groups: true,
|
||||
private: false
|
||||
};
|
||||
|
||||
const help: Commands = {
|
||||
command: "/help",
|
||||
description:
|
||||
"Lists information about how to setup the bot and the list of available commands",
|
||||
groups: true,
|
||||
private: true
|
||||
};
|
||||
|
||||
const registerGroup: Commands = {
|
||||
command: "/registerGroup",
|
||||
description: `Will add your group to the database, if your group doesn't exist already\\. This is used to calculate the "total groups" count on the bot stats website`,
|
||||
groups: true,
|
||||
private: true
|
||||
};
|
||||
|
||||
const botStatsSite: Commands = {
|
||||
command: "/botStats",
|
||||
description: "Provides the bot stats website link as an embed",
|
||||
groups: true,
|
||||
private: true
|
||||
};
|
||||
|
||||
const commands: Commands[] = [
|
||||
botInfo,
|
||||
getGroupStats,
|
||||
help,
|
||||
registerGroup,
|
||||
botStatsSite
|
||||
];
|
||||
|
||||
export default commands;
|
||||
@@ -243,7 +243,7 @@ export const resolvers = {
|
||||
increment: async (
|
||||
_parent: unknown,
|
||||
data: {
|
||||
link: boolean;
|
||||
link: number;
|
||||
command: boolean;
|
||||
trigger: boolean;
|
||||
mutationKey?: string;
|
||||
@@ -282,7 +282,7 @@ export const resolvers = {
|
||||
return await prisma.dailyStats.update({
|
||||
where: { createdAt: latestDailyStats.createdAt },
|
||||
data: {
|
||||
linksDeleted: { increment: link ? 1 : 0 },
|
||||
linksDeleted: { increment: link ? link : 0 },
|
||||
commandResponses: { increment: command ? 1 : 0 },
|
||||
timesTriggered: { increment: trigger ? 1 : 0 }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const typeDefs = /* GraphQL */ `
|
||||
}
|
||||
type Mutation {
|
||||
init(mutationKey: String): String!
|
||||
cronJob(mutationKey: String): TotalStats!
|
||||
cronJob(mutationKey: String): TotalStats
|
||||
addGroup(
|
||||
groupID: String!
|
||||
groupName: String!
|
||||
@@ -26,7 +26,7 @@ const typeDefs = /* GraphQL */ `
|
||||
mutationKey: String
|
||||
): Groups!
|
||||
increment(
|
||||
link: Boolean
|
||||
link: Int
|
||||
command: Boolean
|
||||
trigger: Boolean
|
||||
mutationKey: String
|
||||
|
||||
@@ -15,7 +15,7 @@ const lineChartArr = (dailyStatsArr: DailyStats): LineChartArr => {
|
||||
day,
|
||||
"Links Deleted": linksDeleted,
|
||||
"Command Responses": commandResponses,
|
||||
"Times Triggered": timesTriggered
|
||||
Triggers: timesTriggered
|
||||
};
|
||||
|
||||
lineChartArr.push(lineChartItem);
|
||||
|
||||
2
src/types/LineChartStats.d.ts
vendored
2
src/types/LineChartStats.d.ts
vendored
@@ -2,7 +2,7 @@ export interface LineChartItem {
|
||||
day: number;
|
||||
"Links Deleted": number;
|
||||
"Command Responses": number;
|
||||
"Times Triggered": number;
|
||||
Triggers: number;
|
||||
}
|
||||
|
||||
export type LineChartArr = LineChartItem[];
|
||||
|
||||
Reference in New Issue
Block a user