1# Discord Reaction to Linear Ticket Automation
2
3**How it works:** React to any Discord message with a configured emoji (like
4`:huss:` or `๐งฟ` - used as an example in this val) and it automatically becomes
5a Linear ticket with context from the Discord thread.
512. **Set permissions:** OAuth2 โ URL Generator โ selecting `bot` unfurls a Bot
52 Permissions dropdown below โ Select `View Channels`, `Read Message History`,
53 `Add Reactions`
543. **Enable intents:** Bot โ Privileged Gateway Intents โ โ
Message Content
55 Intent
59
60- **`main.tsx`** - Main cron job that runs every minute checking for new
61 reactions, you can also manually press `Run` to run it immediately
62- **Emoji Configuration** - Edit `backend/config.ts` to customize team member
63 emojis:
5
6/**
7 * Cron job to check for new Discord reactions and create Linear tickets
8 * Runs every minute to check for messages with the target emoji reaction
9 */
10export default async function() {
11 console.log(`๐ Discord reaction cron job started at ${new Date().toISOString()}`);
12
13 // Get configuration directly
29 // Process each monitored channel
30 for (const channelId of monitoredChannels) {
31 console.log(`๐ Checking channel ${channelId} for new reactions...`);
32
33 try {
34 await processChannelReactions(discord, linear, serverId, channelId, teamId);
35 } catch (error) {
36 console.error(`โ Error processing channel ${channelId}:`, error);
38 }
39
40 console.log(`โ
Discord reaction cron job completed at ${new Date().toISOString()}`);
41}
42
43/**
44 * Process reactions for a specific channel
45 */
46async function processChannelReactions(
47 discord: DiscordAPI,
48 linear: LinearSDK,
55
56 for (const message of messages) {
57 const reactionResult = discord.findTargetReaction(message);
58 if (!reactionResult) continue;
59
60 const { reaction, createdBy } = reactionResult;
61 const emojiName = reaction.emoji.name;
62
63 // Check if already processed
10 LINEAR_PRIORITY: 3, // 1=Urgent, 2=High, 3=Medium, 4=Low; default everything created by this bot to 3 - Medium Pri
11 MAX_TITLE_LENGTH: 100,
12 MAX_MESSAGES_TO_CHECK: 50, // Check last 50 messages, you can adjust this based on server activity and how far back you want for reacts to be checked
13} as const;
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import React from "https://esm.sh/react@18.2.0?deps=react@18.2.0";
3import { createRoot } from "https://esm.sh/react-dom@18.2.0/client?deps=react@18.2.0,react-dom@18.2.0";
4import App from "./components/App.tsx";
5
1# Subcurrent RSS Reader
2
3A React application that displays random posts from the Subcurrent Astro RSS feed.
4
5## Structure
7- `backend/` - Hono API server
8 - `index.ts` - Main entry point with RSS fetching and serving
9- `frontend/` - React UI
10 - `index.html` - Main HTML template
11 - `index.tsx` - React app entry point
12 - `components/App.tsx` - Main app component
13
16- Fetches RSS feed from https://astoria-tech.github.io/subcurrent-astro/
17- Displays a random post from the feed
18- Simple, clean React UI with TailwindCSS styling
10 LINEAR_PRIORITY: 3, // 1=Urgent, 2=High, 3=Medium, 4=Low; default everything created by this bot to 3 - Medium Pri
11 MAX_TITLE_LENGTH: 100,
12 MAX_MESSAGES_TO_CHECK: 50, // Check last 50 messages, you can adjust this based on server activity and how far back you want for reacts to be checked
13} as const;
24 }
25
26 // Simplified findTargetReaction without debugging
27 findTargetReaction(message: any): { reaction: any; createdBy: string } | null {
28 if (!message.reactions) return null;
29
30 for (const reaction of message.reactions) {
31 const emojiName = reaction.emoji.name;
32
33 if (emojiName in CONFIG.TARGET_EMOJIS) {
34 const createdBy = CONFIG.TARGET_EMOJIS[emojiName as keyof typeof CONFIG.TARGET_EMOJIS];
35 return {
36 reaction,
37 createdBy,
38 };
51 formatMessageContent(message: any, discordUrl: string, createdBy: string): string {
52 const messageDate = new Date(message.timestamp);
53 const reactionCount = message.reactions?.reduce((sum: number, r: any) => sum + r.count, 0) || 0;
54
55 let content = `**๐ซ Ticket Created By:** ${createdBy}\n\n`;
56 content += `**๐จ Original Discord Message:** ${discordUrl}\n\n`;
57 content += `**๐
Posted:** ${messageDate.toLocaleString()}\n`;
58 content += `**๐ Total Reactions:** ${reactionCount}\n\n`;
59
60 if (message.content) {
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { buildDiscordMessage, categorizeCommits, fetchAndPostCommits, fetchCommits } from "./process-commits.tsx";
3
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { buildDiscordMessage, categorizeCommits, fetchAndPostCommits, fetchCommits } from "./process-commits.tsx";
3
1/** @jsxImportSource https://esm.sh/react@18.2.0?dev */
2
3export function ValTownLogo () {