Skip to content

Commit 6f647e9

Browse files
Added comments & more reddit functionality.
1 parent 400675d commit 6f647e9

File tree

1 file changed

+151
-13
lines changed

1 file changed

+151
-13
lines changed

src/lib/reddit.ts

+151-13
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,162 @@
11
// lib/reddit.ts
22

33
import axios from 'axios'
4+
import Snoowrap from 'snoowrap'
5+
import { PrismaClient } from '@prisma/client'
6+
import { CommentStream } from 'snoostorm'
47

8+
const prisma = new PrismaClient()
9+
10+
// Initialize Reddit API client with environment variables
11+
export const redditClient = new Snoowrap({
12+
userAgent: 'your-app-name by /u/your-username',
13+
clientId: process.env.REDDIT_CLIENT_ID,
14+
clientSecret: process.env.REDDIT_CLIENT_SECRET,
15+
username: process.env.REDDIT_USERNAME,
16+
password: process.env.REDDIT_PASSWORD,
17+
})
18+
19+
/**
20+
* Stores Reddit messages/comments in database with deduplication
21+
* @param items Array of Reddit messages or comments
22+
* @returns Array of newly created database records
23+
*/
24+
export async function storeMessages(items: Array<Snoowrap.PrivateMessage | Snoowrap.Comment>) {
25+
const newMessages = []
26+
console.debug(`Processing ${items.length} message(s)`)
27+
28+
for (const item of items) {
29+
try {
30+
const isMessage = item instanceof Snoowrap.PrivateMessage
31+
const type = isMessage ? 'private_message' : 'comment'
32+
33+
// Check for existing record
34+
const existing = await prisma.redditMessage.findUnique({
35+
where: { redditId: item.name },
36+
})
37+
38+
if (existing) {
39+
console.debug(`Skipping duplicate ${type} [${item.name}]`)
40+
continue
41+
}
42+
43+
// Common fields for both messages and comments
44+
const baseData = {
45+
redditId: item.name,
46+
type,
47+
author: item.author.name,
48+
content: item.body,
49+
bodyHtml: item.body_html,
50+
subreddit: item.subreddit?.display_name,
51+
createdAt: new Date(item.created_utc * 1000),
52+
parentId: item.parent_id,
53+
rawData: item.toJSON(),
54+
}
55+
56+
// Type-specific fields
57+
const additionalData = isMessage
58+
? {
59+
isRead: item.new === false,
60+
contextUrl: item.context,
61+
}
62+
: {
63+
isRead: false, // Comments don't have a 'new' property in API response
64+
contextUrl: item.permalink,
65+
}
66+
67+
const created = await prisma.redditMessage.create({
68+
data: { ...baseData, ...additionalData },
69+
})
70+
71+
console.debug(`Stored new ${type} [${created.redditId}] from /u/${created.author}`)
72+
newMessages.push(created)
73+
} catch (error) {
74+
console.error(`Error processing message ${item.name}:`, error.message)
75+
}
76+
}
77+
78+
return newMessages
79+
}
80+
81+
/**
82+
* Fetches and processes Reddit inbox items
83+
* @returns Array of newly stored messages
84+
*/
85+
export async function checkRedditMessages() {
86+
try {
87+
console.debug('Fetching Reddit inbox...')
88+
89+
// Parallel fetch of comments and messages
90+
const [commentReplies, messages] = await Promise.all([
91+
redditClient.getInbox({ filter: 'comments' }),
92+
redditClient.getInbox({ filter: 'messages' }),
93+
])
94+
95+
console.debug(`Found ${commentReplies.length} comment(s), ${messages.length} message(s)`)
96+
return await storeMessages([...commentReplies, ...messages])
97+
} catch (error) {
98+
console.error('Reddit API Error:', error.message, error.stack)
99+
throw new Error(`Failed to fetch messages: ${error.message}`)
100+
}
101+
}
102+
103+
/**
104+
* Retrieves unread messages from Reddit inbox
105+
* @returns Array of unread messages
106+
*/
107+
export const getUnreadMessages = async () => {
108+
console.debug('Fetching unread messages...')
109+
const messages = await redditClient.getUnreadMessages({ limit: 25 })
110+
console.debug(`Found ${messages.length} unread message(s)`)
111+
return messages
112+
}
113+
114+
/**
115+
* Marks a specific message as read
116+
* @param messageId Reddit message ID (fullname format, e.g. "t4_12345")
117+
*/
118+
export const markMessageRead = (messageId: string) => {
119+
try {
120+
console.debug(`Marking message ${messageId} as read...`)
121+
return redditClient.getMessage(messageId).markAsRead()
122+
} catch (error) {
123+
console.error(`Error marking message ${messageId} as read:`, error.message)
124+
}
125+
}
126+
127+
/**
128+
* Fetches recent posts from specified subreddits
129+
* @param subreddits Array of subreddit names (without r/)
130+
* @returns Array of formatted post objects
131+
*/
5132
export const fetchRedditPosts = async (subreddits: string[]) => {
6133
const posts = []
134+
console.debug(`Fetching posts from ${subreddits.length} subreddit(s)`)
7135

8136
for (const subreddit of subreddits) {
9-
const response = await axios.get(`https://www.reddit.com/r/${subreddit}/new.json?limit=10`)
10-
const subredditPosts = response.data.data.children.map((child: any) => ({
11-
title: child.data.title,
12-
author: child.data.author,
13-
subreddit: child.data.subreddit,
14-
url: child.data.url,
15-
createdAt: new Date(child.data.created_utc * 1000),
16-
body: child.data.selftext,
17-
body_html: child.data.selftext_html,
18-
upvotes: child.data.ups,
19-
downvotes: child.data.downs,
20-
}))
21-
posts.push(...subredditPosts)
137+
try {
138+
console.debug(`Fetching /r/${subreddit}...`)
139+
const response = await axios.get(`https://www.reddit.com/r/${subreddit}/new.json?limit=10`, {
140+
timeout: 5000,
141+
})
142+
143+
const subredditPosts = response.data.data.children.map((child: any) => ({
144+
title: child.data.title,
145+
author: child.data.author,
146+
subreddit: child.data.subreddit,
147+
url: child.data.url,
148+
createdAt: new Date(child.data.created_utc * 1000),
149+
body: child.data.selftext,
150+
bodyHtml: child.data.selftext_html,
151+
upvotes: child.data.ups,
152+
downvotes: child.data.downs,
153+
}))
154+
155+
console.debug(`Found ${subredditPosts.length} post(s) in /r/${subreddit}`)
156+
posts.push(...subredditPosts)
157+
} catch (error) {
158+
console.error(`Error fetching /r/${subreddit}:`, error.message)
159+
}
22160
}
23161

24162
return posts

0 commit comments

Comments
 (0)