1# GitHub to Discord Changelog Notifier
2
3A val that automatically fetches GitHub commits (title and description) and posts formatted changelog updates to Discord. It categorizes commits based on [conventional commit messages](https://www.conventionalcommits.org/en/v1.0.0/) ie. `feat:`, `fix:` etc., and uses AI to generate user-friendly summaries.
4
5**Example:**
19
20### How to use
21- **`gh-to-discord.tsx`** is a cron that runs automatically based on your configured schedule, fetching commits since the last run
22
23- **`playground.tsx`**
159
160/**
161 * Fetch commits from GitHub for the given date range
162 */
163export async function fetchCommits(since: string, until: string): Promise<any[]> {
164 // Check if all environment variables are set
165 if (!GITHUB_REPO_OWNER || !GITHUB_REPO_NAME || !GITHUB_TOKEN) {
170 const octokit = new Octokit({ auth: GITHUB_TOKEN });
171
172 // Fetch commits from GitHub
173 console.log(`Fetching commits from ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME} between ${since} and ${until}`);
174
175 try {
192
193/**
194 * Fetch commits and post to Discord
195 */
196export async function fetchAndPostCommits(since: string, until: string): Promise<void> {
197 if (!DISCORD_WEBHOOK_URL) {
198 throw new Error("Missing DISCORD_WEBHOOK_URL environment variable");
199 }
200
201 // Fetch commits from GitHub
202 const commits = await fetchCommits(since, until);
203
204 // If no commits are found, return early
207 return;
208 }
209 console.log(`Fetched ${commits.length} new commits.`);
210
211 // Process commits with LLM summary
8 const [error, setError] = useState(null);
9
10 const fetchData = async () => {
11 try {
12 const userEndpoint = new URL(USER_ENDPOINT, window.location.origin);
13
14 const res = await fetch(userEndpoint);
15 const data = await res.json();
16 if (!res.ok) {
33
34 useEffect(() => {
35 fetchData();
36 }, []);
37
38 return { data, loading, error, refetch: fetchData };
39}
40
9 const [error, setError] = useState(null);
10
11 const fetchData = async () => {
12 try {
13 const projectEndpoint = new URL(PROJECT_ENDPOINT, window.location.origin);
17 if (branchId) filesEndpoint.searchParams.append("branchId", branchId);
18
19 const { project } = await fetch(projectEndpoint).then((res) =>
20 res.json()
21 );
22 const { files } = await fetch(filesEndpoint).then((res) => res.json());
23
24 setData({ project, files });
34 useEffect(() => {
35 if (!projectId) return;
36 fetchData();
37 }, [projectId, branchId]);
38
39 return { data, loading, error, refetch: fetchData };
40}
41
8 const [error, setError] = useState(null);
9
10 const fetchData = async () => {
11 try {
12 const res = await fetch(ENDPOINT);
13 const data = await res.json();
14 if (!res.ok) {
32
33 useEffect(() => {
34 fetchData();
35 }, []);
36
37 return { data, loading, error, refetch: fetchData };
38}
39
19 setError(null);
20 try {
21 const res = await fetch(ENDPOINT, {
22 method: "POST",
23 headers: {
12 setData(null);
13 setError(null);
14 const res = await fetch(ENDPOINT, {
15 method: "POST",
16 body: JSON.stringify({
7 const [loading, setLoading] = useState(true);
8
9 const fetchData = async () => {
10 const endpoint = new URL(ENDPOINT, window.location.origin);
11 endpoint.searchParams.append("projectId", projectId);
12
13 const res = await fetch(endpoint)
14 .then(res => res.json());
15 setData(res.branches);
19 useEffect(() => {
20 if (!projectId) return;
21 fetchData();
22 }, [projectId]);
23
24 return { data, loading, refetch: fetchData };
25}
47<!--
48
49- [x] refetch project data on create/etc
50- [x] Loading favicon
51- [x] Ensure main branch is default selected
166 let type_: "file" | "http" | "script";
167 if (path.includes("backend/index.ts")) type_ = "http";
168 if (file_text?.includes("export default app.fetch")) type_ = "http";
169 if ([".ts", ".tsx", ".js", ".jsx"].some(ext => path.endsWith(ext))) {
170 type_ = "script";