1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono@3/jsx */
import { Hono } from "npm:hono";
import dayjs from "npm:dayjs@1.11.10";
const app = new Hono();
app.get("/", (c) => c.html(
<div>
<h1>Val Town Contribution Graph</h1>
<form action="/graph" method="get">
<label for="username">Enter a username: </label>
<input type="text" id="username" name="username" required />
<button type="submit">Generate Graph</button>
</form>
</div>
));
app.get("/graph", async (c) => {
const username = c.req.query("username");
if (!username) {
return c.html(<p style="color: red;">Error: Username is required</p>);
}
try {
let graph = await generateGraph(username);
return c.html(
<div>
<a href="/" style="display: block; margin-bottom: 1em;">Back to Home</a>
<div class="graph">{graph}</div>
</div>
);
} catch (error) {
console.error(`Error generating graph for ${username}:`, error);
return c.html(<p style="color: red;">Error: {error.message}</p>);
}
});
async function generateGraph(username) {
console.log(`Fetching contributions for ${username}`);
try {
const contributionData = await fetchContributions(username);
const totalContributions = Object.values(contributionData).reduce((sum, count) => sum + count, 0);
console.log(`Total contributions: ${totalContributions}`);
console.log("Contribution data:", contributionData);
return (
<>
<h2 style="font-size: 1.5em; font-weight: bold; margin-bottom: 1em;">Contribution Graph for {username}</h2>
{generateContributionGraph(contributionData)}
<p>Total Contributions: {totalContributions}</p>
</>
);
} catch (error) {
console.error(`Error processing data for ${username}:`, error);
throw new Error(`Failed to process data: ${error.message}`);
}
}
async function fetchContributions(username) {
const contributionMap = {};
const valsUrl = `https://api.val.town/v1/alias/${username}/vals`;
const valsResponse = await fetch(valsUrl);
if (!valsResponse.ok) {
throw new Error(`HTTP error! status: ${valsResponse.status}`);
}
const valsData = await valsResponse.json();
console.log("API Response:", JSON.stringify(valsData, null, 2));
let valsToProcess = [];
if (Array.isArray(valsData.data)) {
valsToProcess = valsData.data;
} else if (valsData.id) {
// If a single val object is returned
valsToProcess = [valsData];
} else {
console.log("Unexpected API response structure:", valsData);
return contributionMap;
}
for (const val of valsToProcess) {
if (val && val.createdAt) {
const creationDate = dayjs(val.createdAt).format('YYYY-MM-DD');
contributionMap[creationDate] = (contributionMap[creationDate] || 0) + 1;
await fetchValVersions(val.id, contributionMap, creationDate);
}
}
return contributionMap;
}
async function fetchValVersions(valId, contributionMap, creationDate) {
const versionsUrl = `https://api.val.town/v1/vals/${valId}/versions`;
try {
const versionsResponse = await fetch(versionsUrl);
if (!versionsResponse.ok) {
throw new Error(`HTTP ${versionsResponse.status}`);
}
const versionsData = await versionsResponse.json();