Cleanup and go Live
parent
0b91c0dcf5
commit
a17d520eea
14
src/index.ts
14
src/index.ts
|
@ -4,12 +4,9 @@ dotenv.config();
|
||||||
import WorkflowOrchestrator from './workflows/WorkflowOrchestrator';
|
import WorkflowOrchestrator from './workflows/WorkflowOrchestrator';
|
||||||
import rDramaSession from './rdrama/session/SessionManager';
|
import rDramaSession from './rdrama/session/SessionManager';
|
||||||
import redditSession from './reddit/session/SessionManager';
|
import redditSession from './reddit/session/SessionManager';
|
||||||
import { CommentParser } from './rdrama/services/CommentParser';
|
|
||||||
import { DatabaseInitializer } from './db/initializeDatabase';
|
import { DatabaseInitializer } from './db/initializeDatabase';
|
||||||
import { DatabaseService } from './db/services/Database';
|
import { DatabaseService } from './db/services/Database';
|
||||||
import { DatabaseMaintenanceService } from './db/services/DatabaseMaintenance';
|
import { DatabaseMaintenanceService } from './db/services/DatabaseMaintenance';
|
||||||
import { CommentProcessor } from './rdrama/services/CommentProcessor';
|
|
||||||
import { CommentPoster } from './rdrama/services/CommentPoster';
|
|
||||||
// Import other necessary services or configurations
|
// Import other necessary services or configurations
|
||||||
|
|
||||||
async function startApplication() {
|
async function startApplication() {
|
||||||
|
@ -41,17 +38,8 @@ async function startApplication() {
|
||||||
console.log('Reddit Session Start')
|
console.log('Reddit Session Start')
|
||||||
await redditSession.getInstance()
|
await redditSession.getInstance()
|
||||||
|
|
||||||
// Initialize services with any required dependencies
|
|
||||||
const commentFetcher = new CommentProcessor();
|
|
||||||
const commentParser = new CommentParser();
|
|
||||||
const commentPoster = new CommentPoster()
|
|
||||||
|
|
||||||
// Initialize and start your workflow
|
// Initialize and start your workflow
|
||||||
const workflowOrchestrator = new WorkflowOrchestrator(
|
const workflowOrchestrator = new WorkflowOrchestrator();
|
||||||
commentFetcher,
|
|
||||||
commentParser,
|
|
||||||
commentPoster
|
|
||||||
);
|
|
||||||
await workflowOrchestrator.executeWorkflow();
|
await workflowOrchestrator.executeWorkflow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { Comment } from '../models/Comment';
|
import { Comment } from '../models/Comment';
|
||||||
|
|
||||||
export class CommentParser {
|
export class CommentParser {
|
||||||
private regexPattern: RegExp = /(^|\s|\\r\\n|\\t|[".,;(){}\[\]!?@#])(\/?u\/[a-zA-Z0-9_]+)/g;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts Reddit usernames from the body of a single comment.
|
* Extracts Reddit usernames from the body of a single comment.
|
||||||
* @param comment A single Comment object to be processed.
|
* @param comment A single Comment object to be processed.
|
||||||
* @returns An array of unique Reddit usernames found in the comment.
|
* @returns An array of unique Reddit usernames found in the comment.
|
||||||
*/
|
*/
|
||||||
public extractUsernames(comment: Comment): string[] {
|
public static extractUsernames(comment: Comment): string[] {
|
||||||
|
const regexPattern: RegExp = /(^|\s|\\r\\n|\\t|[".,;(){}\[\]!?@#])(\/?u\/[a-zA-Z0-9_]+)/g;
|
||||||
const foundUsernames: Set<string> = new Set();
|
const foundUsernames: Set<string> = new Set();
|
||||||
|
|
||||||
const matches = comment.body.match(this.regexPattern);
|
const matches = comment.body.match(regexPattern);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
matches.forEach(match => {
|
matches.forEach(match => {
|
||||||
// Ensure the username is captured in a standardized format
|
// Ensure the username is captured in a standardized format
|
||||||
|
|
|
@ -3,11 +3,6 @@ import { Comment } from '../models/Comment';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
|
|
||||||
export class CommentPoster {
|
export class CommentPoster {
|
||||||
private sessionManager: SessionManager;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.sessionManager = SessionManager.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts a comment as a reply to a given rdrama comment.
|
* Posts a comment as a reply to a given rdrama comment.
|
||||||
|
@ -16,13 +11,14 @@ export class CommentPoster {
|
||||||
* @param body The body of the comment to post.
|
* @param body The body of the comment to post.
|
||||||
* @returns A promise resolving to the Axios response.
|
* @returns A promise resolving to the Axios response.
|
||||||
*/
|
*/
|
||||||
public async postComment(parentId: string, body: string): Promise<Comment> {
|
public static async postComment(parentId: string, body: string): Promise<Comment> {
|
||||||
|
const sessionManager = SessionManager.getInstance();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('parent_fullname', parentId);
|
formData.append('parent_fullname', parentId);
|
||||||
formData.append('body', body);
|
formData.append('body', body);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.sessionManager.axiosInstance.post('/comment', formData, {
|
const response = await sessionManager.axiosInstance.post('/comment', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,6 @@ import { CommentFetcher } from './CommentFetcher';
|
||||||
* with DatabaseService for checking the existence and persisting new comments.
|
* with DatabaseService for checking the existence and persisting new comments.
|
||||||
*/
|
*/
|
||||||
export class CommentProcessor {
|
export class CommentProcessor {
|
||||||
private maxPages: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of CommentProcessor.
|
|
||||||
* @param {DatabaseService} databaseService - The service for database operations.
|
|
||||||
* @param {number} maxPages - The maximum number of pages to fetch from the r/Drama API. Defaults to 10.
|
|
||||||
*/
|
|
||||||
constructor(maxPages: number = 10) {
|
|
||||||
this.maxPages = maxPages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches comments from the r/Drama API across multiple pages, up to the specified maximum.
|
* Fetches comments from the r/Drama API across multiple pages, up to the specified maximum.
|
||||||
|
@ -27,11 +17,11 @@ export class CommentProcessor {
|
||||||
* @returns {Promise<Comment[]>} A promise that resolves to an array of comments fetched from the API.
|
* @returns {Promise<Comment[]>} A promise that resolves to an array of comments fetched from the API.
|
||||||
* @throws {Error} Throws an error if there is a failure in fetching comments from the API.
|
* @throws {Error} Throws an error if there is a failure in fetching comments from the API.
|
||||||
*/
|
*/
|
||||||
async processComments(): Promise<Comment[]> {
|
static async processComments(maxPages: number = 10): Promise<Comment[]> {
|
||||||
let comments: Comment[] = [];
|
let comments: Comment[] = [];
|
||||||
let stopFetching = false;
|
let stopFetching = false;
|
||||||
|
|
||||||
for (let page = 1; page <= this.maxPages && !stopFetching; page++) {
|
for (let page = 1; page <= maxPages && !stopFetching; page++) {
|
||||||
const newComments = await CommentFetcher.fetchComments(page)
|
const newComments = await CommentFetcher.fetchComments(page)
|
||||||
|
|
||||||
// Check each new comment against the database and existing comments in this batch
|
// Check each new comment against the database and existing comments in this batch
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
import axios, { AxiosError } from 'axios';
|
|
||||||
import SessionManager from '../session/SessionManager';
|
|
||||||
import { RedditUser } from '../model/User';
|
import { RedditUser } from '../model/User';
|
||||||
|
import RedditSessionManager from '../session/SessionManager';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides services for interacting with Reddit user data and sending messages.
|
||||||
|
*/
|
||||||
export class RedditService {
|
export class RedditService {
|
||||||
private sessionManager: SessionManager;
|
|
||||||
|
|
||||||
constructor(sessionManager: SessionManager) {
|
/**
|
||||||
this.sessionManager = sessionManager;
|
* Retrieves information about a Reddit user.
|
||||||
}
|
*
|
||||||
|
* @param {string} username - The username of the Reddit user to retrieve information for.
|
||||||
async getUserInfo(username: string): Promise<RedditUser> {
|
* @returns {Promise<RedditUser>} A promise that resolves with the RedditUser object containing user information.
|
||||||
|
* @throws {Error} Throws an error if fetching user information fails.
|
||||||
|
* @example
|
||||||
|
* RedditService.getUserInfo('exampleUser')
|
||||||
|
* .then(userInfo => console.log(userInfo))
|
||||||
|
* .catch(error => console.error(error));
|
||||||
|
*/
|
||||||
|
static async getUserInfo(username: string): Promise<RedditUser> {
|
||||||
try {
|
try {
|
||||||
const response = await this.sessionManager.axiosInstance.get(`/user/${username}/about`);
|
const redditSession = await RedditSessionManager.getInstance()
|
||||||
|
const response = await redditSession.axiosInstance.get(`/user/${username}/about`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching user info:', error);
|
console.error('Error fetching user info:', error);
|
||||||
|
@ -19,15 +28,37 @@ export class RedditService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage(username: string, subject: string, message: string): Promise<void> {
|
/**
|
||||||
|
* Sends a private message to a Reddit user.
|
||||||
|
*
|
||||||
|
* @param {string} username - The username of the recipient Reddit user.
|
||||||
|
* @param {string} subject - The subject of the message.
|
||||||
|
* @param {string} message - The body text of the message.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the message is successfully sent.
|
||||||
|
* @throws {Error} Throws an error if sending the message fails.
|
||||||
|
* @example
|
||||||
|
* RedditService.sendMessage('exampleUser', 'Hello', 'This is a test message.')
|
||||||
|
* .then(() => console.log('Message sent successfully.'))
|
||||||
|
* .catch(error => console.error('Error sending message:', error));
|
||||||
|
*/
|
||||||
|
static async sendMessage(username: string, subject: string, message: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`await this.sessionManager.axiosInstance.post('/api/compose', {\n\tapi_type: 'json',\n\tto: ${username},\n\tsubject: ${subject},\n\ttext: ${message},\n});`)
|
const redditSession = await RedditSessionManager.getInstance();
|
||||||
//await this.sessionManager.axiosInstance.post('/api/compose', {
|
// Create a URLSearchParams object with your data
|
||||||
// api_type: 'json',
|
const params = new URLSearchParams();
|
||||||
// to: username,
|
params.append('api_type', 'json');
|
||||||
// subject: subject,
|
params.append('to', `u/${username}`);
|
||||||
// text: message,
|
params.append('subject', subject);
|
||||||
//});
|
params.append('text', message);
|
||||||
|
|
||||||
|
// Use the params object directly in the data field
|
||||||
|
await redditSession.axiosInstance.post('/api/compose', params, {
|
||||||
|
headers: {
|
||||||
|
// Ensure the content type is set to application/x-www-form-urlencoded
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify(params, null, 4))
|
||||||
console.log(`Message sent to ${username}`);
|
console.log(`Message sent to ${username}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending message:', error);
|
console.error('Error sending message:', error);
|
||||||
|
|
|
@ -5,8 +5,46 @@ import { DatabaseService } from '../db/services/Database';
|
||||||
// Load environment variables from .env file
|
// Load environment variables from .env file
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export async function shouldNotifyUser(username: string, redditService: RedditService): Promise<boolean> {
|
/**
|
||||||
const userInfo = await redditService.getUserInfo(username);
|
* Determines whether a user should be notified based on various criteria.
|
||||||
|
*
|
||||||
|
* This function checks a Reddit user's information against a set of conditions
|
||||||
|
* defined by environment variables and user attributes. These conditions include
|
||||||
|
* whether the user is a moderator, an employee, accepts private messages, has
|
||||||
|
* karma below a certain threshold, and has not been notified before.
|
||||||
|
*
|
||||||
|
* @param {string} username - The Reddit username of the user to check.
|
||||||
|
* @returns {Promise<boolean>} - A promise that resolves to `true` if the user meets
|
||||||
|
* the criteria for notification, otherwise `false`.
|
||||||
|
*
|
||||||
|
* @throws {Error} Throws an error if there's a problem fetching user information
|
||||||
|
* from Reddit or checking the user's notification status in the database.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Example of checking if a user should be notified
|
||||||
|
* shouldNotifyUser('exampleUser').then(shouldNotify => {
|
||||||
|
* if (shouldNotify) {
|
||||||
|
* console.log('User should be notified.');
|
||||||
|
* } else {
|
||||||
|
* console.log('User should not be notified.');
|
||||||
|
* }
|
||||||
|
* }).catch(error => {
|
||||||
|
* console.error('Error checking if user should be notified:', error);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Environment variables used in this function
|
||||||
|
* // .env file
|
||||||
|
* EXCLUDE_MODS=true
|
||||||
|
* EXCLUDE_EMPLOYEES=true
|
||||||
|
* KARMA_THRESHOLD=100000
|
||||||
|
*
|
||||||
|
* These environment variables control the behavior of the function, such as whether
|
||||||
|
* to exclude moderators or employees from notifications, and the karma threshold for
|
||||||
|
* notifications.
|
||||||
|
*/
|
||||||
|
export async function shouldNotifyUser(username: string): Promise<boolean> {
|
||||||
|
const userInfo = await RedditService.getUserInfo(username);
|
||||||
if (!userInfo) return false;
|
if (!userInfo) return false;
|
||||||
|
|
||||||
const { is_mod, is_employee, accept_pms, total_karma } = userInfo.data;
|
const { is_mod, is_employee, accept_pms, total_karma } = userInfo.data;
|
||||||
|
|
|
@ -4,71 +4,97 @@ import { CommentPoster } from "../rdrama/services/CommentPoster";
|
||||||
import { MessageService } from "../utils/MessageService";
|
import { MessageService } from "../utils/MessageService";
|
||||||
import { DatabaseService } from "../db/services/Database";
|
import { DatabaseService } from "../db/services/Database";
|
||||||
import { RedditService } from "../reddit/services/Reddit";
|
import { RedditService } from "../reddit/services/Reddit";
|
||||||
import RedditSessionManager from "../reddit/session/SessionManager";
|
|
||||||
import { shouldNotifyUser } from "../utils/ShouldNotify";
|
import { shouldNotifyUser } from "../utils/ShouldNotify";
|
||||||
|
import { Comment } from "../rdrama/models/Comment";
|
||||||
|
|
||||||
class WorkflowOrchestrator {
|
class WorkflowOrchestrator {
|
||||||
constructor(
|
|
||||||
private commentProcessor: CommentProcessor,
|
|
||||||
private commentParser: CommentParser,
|
|
||||||
private commentPoster: CommentPoster,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the defined workflow for processing comments.
|
* Executes the defined workflow for processing comments.
|
||||||
*/
|
*/
|
||||||
async executeWorkflow() {
|
async executeWorkflow() {
|
||||||
try {
|
try {
|
||||||
// Fetch comments from the source
|
const comments = await this.fetchAndLogComments();
|
||||||
const comments = await this.commentProcessor.processComments();
|
|
||||||
console.log(`Fetched ${comments.length} comments`);
|
|
||||||
|
|
||||||
// Extract and deduplicate usernames from comments
|
|
||||||
const allUsernames = comments.flatMap(comment => this.commentParser.extractUsernames(comment));
|
|
||||||
const uniqueUsernames = [...new Set(allUsernames)];
|
|
||||||
console.log(`Extracted ${uniqueUsernames.length} unique usernames`);
|
|
||||||
|
|
||||||
for (const comment of comments) {
|
for (const comment of comments) {
|
||||||
const redditUsers = this.commentParser.extractUsernames(comment)
|
await this.processComment(comment);
|
||||||
if (redditUsers.length === 0) continue
|
|
||||||
console.log('found:', redditUsers)
|
|
||||||
const placeholdersRdrama = {
|
|
||||||
author_name: comment.author_name,
|
|
||||||
};
|
|
||||||
for (const redditUser of redditUsers) {
|
|
||||||
const userMentionExists = await DatabaseService.userMentionExists(redditUser)
|
|
||||||
if (userMentionExists) continue
|
|
||||||
const commentResponseRdrama = MessageService.getRandomRdramaMessage(placeholdersRdrama)
|
|
||||||
if (!commentResponseRdrama) throw new Error('No comments for Rdrama found')
|
|
||||||
const postedComment = await this.commentPoster.postComment(`c_${comment.id}`, `##### TEST MESSAGE NO REDDITOR PINGED (YET...)\n${commentResponseRdrama}`)
|
|
||||||
//const postedComment = await this.commentPoster.postComment(`c_${comment.id}`, ${commentResponse}`) //TODO uncomment after golive
|
|
||||||
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4))
|
|
||||||
const redditSession = await RedditSessionManager.getInstance()
|
|
||||||
const redditService = new RedditService(redditSession)
|
|
||||||
const resultshouldNotifyUser = await shouldNotifyUser(redditUser, redditService)
|
|
||||||
if (!resultshouldNotifyUser) continue
|
|
||||||
const placeholdersReddit = {
|
|
||||||
author_name: comment.author_name,
|
|
||||||
username: redditUser,
|
|
||||||
permalink: comment.permalink
|
|
||||||
};
|
|
||||||
const redditMessage = MessageService.getRandomRedditMessage(placeholdersReddit)
|
|
||||||
if (!redditMessage) throw new Error('No comments for Reddit found')
|
|
||||||
await DatabaseService.insertUserMention({
|
|
||||||
rdrama_comment_id: comment.id,
|
|
||||||
username: redditUser,
|
|
||||||
message: redditMessage,
|
|
||||||
})
|
|
||||||
await redditService.sendMessage(redditUser, 'Crosstalk PM Notification', redditMessage)
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Workflow executed successfully.');
|
console.log('Workflow executed successfully.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('An error occurred during workflow execution:', error);
|
console.error('An error occurred during workflow execution:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches comments and logs the count.
|
||||||
|
* @returns {Promise<Array>} The fetched comments.
|
||||||
|
*/
|
||||||
|
async fetchAndLogComments(): Promise<Comment[]> {
|
||||||
|
const comments = await CommentProcessor.processComments();
|
||||||
|
console.log(`Fetched ${comments.length} comments`);
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a single comment, including posting responses and sending notifications.
|
||||||
|
* @param {Object} comment The comment to process.
|
||||||
|
*/
|
||||||
|
async processComment(comment: Comment) {
|
||||||
|
const redditUsers = CommentParser.extractUsernames(comment);
|
||||||
|
if (redditUsers.length === 0) return;
|
||||||
|
console.log('found:', redditUsers);
|
||||||
|
|
||||||
|
for (const redditUser of redditUsers) {
|
||||||
|
await this.handleUserMention(comment, redditUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a mention of a user in a comment, including checking for previous mentions, posting a response, and sending a notification.
|
||||||
|
* @param {Object} comment The comment mentioning the user.
|
||||||
|
* @param {string} redditUser The mentioned Reddit user's username.
|
||||||
|
*/
|
||||||
|
async handleUserMention(comment: Comment, redditUser: string) {
|
||||||
|
const userMentionExists = await DatabaseService.userMentionExists(redditUser);
|
||||||
|
if (userMentionExists) return;
|
||||||
|
|
||||||
|
const placeholdersRdrama = { author_name: comment.author_name };
|
||||||
|
const commentResponseRdrama = MessageService.getRandomRdramaMessage(placeholdersRdrama);
|
||||||
|
if (!commentResponseRdrama) throw new Error('No comments for Rdrama found');
|
||||||
|
|
||||||
|
await this.postCommentAndNotify(comment, redditUser, commentResponseRdrama);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a comment response and sends a notification if the user should be notified.
|
||||||
|
* @param {Object} comment The original comment.
|
||||||
|
* @param {string} redditUser The Reddit user to notify.
|
||||||
|
* @param {string} commentResponseRdrama The response to post.
|
||||||
|
*/
|
||||||
|
async postCommentAndNotify(comment: Comment, redditUser: string, commentResponseRdrama: string) {
|
||||||
|
// Placeholder for posting a comment. Uncomment and implement as needed.
|
||||||
|
const postedComment = await CommentPoster.postComment(`c_${comment.id}`, `${commentResponseRdrama}`);
|
||||||
|
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4));
|
||||||
|
|
||||||
|
const resultshouldNotifyUser = await shouldNotifyUser(redditUser);
|
||||||
|
if (!resultshouldNotifyUser) return;
|
||||||
|
|
||||||
|
const placeholdersReddit = {
|
||||||
|
author_name: comment.author_name,
|
||||||
|
username: redditUser,
|
||||||
|
permalink: comment.permalink
|
||||||
|
};
|
||||||
|
const redditMessage = MessageService.getRandomRedditMessage(placeholdersReddit);
|
||||||
|
if (!redditMessage) throw new Error('No comments for Reddit found');
|
||||||
|
|
||||||
|
await DatabaseService.insertUserMention({
|
||||||
|
rdrama_comment_id: comment.id,
|
||||||
|
username: redditUser,
|
||||||
|
message: redditMessage,
|
||||||
|
});
|
||||||
|
await RedditService.sendMessage(redditUser, 'Crosstalk PM Notification', redditMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WorkflowOrchestrator;
|
export default WorkflowOrchestrator;
|
Loading…
Reference in New Issue