Cleanup and go Live

master
j 2024-03-09 02:34:45 -05:00
parent 0b91c0dcf5
commit a17d520eea
7 changed files with 171 additions and 103 deletions

View File

@ -4,12 +4,9 @@ dotenv.config();
import WorkflowOrchestrator from './workflows/WorkflowOrchestrator';
import rDramaSession from './rdrama/session/SessionManager';
import redditSession from './reddit/session/SessionManager';
import { CommentParser } from './rdrama/services/CommentParser';
import { DatabaseInitializer } from './db/initializeDatabase';
import { DatabaseService } from './db/services/Database';
import { DatabaseMaintenanceService } from './db/services/DatabaseMaintenance';
import { CommentProcessor } from './rdrama/services/CommentProcessor';
import { CommentPoster } from './rdrama/services/CommentPoster';
// Import other necessary services or configurations
async function startApplication() {
@ -41,17 +38,8 @@ async function startApplication() {
console.log('Reddit Session Start')
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
const workflowOrchestrator = new WorkflowOrchestrator(
commentFetcher,
commentParser,
commentPoster
);
const workflowOrchestrator = new WorkflowOrchestrator();
await workflowOrchestrator.executeWorkflow();
}

View File

@ -1,17 +1,16 @@
import { Comment } from '../models/Comment';
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.
* @param comment A single Comment object to be processed.
* @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 matches = comment.body.match(this.regexPattern);
const matches = comment.body.match(regexPattern);
if (matches) {
matches.forEach(match => {
// Ensure the username is captured in a standardized format

View File

@ -3,11 +3,6 @@ import { Comment } from '../models/Comment';
import FormData from 'form-data';
export class CommentPoster {
private sessionManager: SessionManager;
constructor() {
this.sessionManager = SessionManager.getInstance();
}
/**
* 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.
* @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();
formData.append('parent_fullname', parentId);
formData.append('body', body);
try {
const response = await this.sessionManager.axiosInstance.post('/comment', formData, {
const response = await sessionManager.axiosInstance.post('/comment', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}

View File

@ -8,16 +8,6 @@ import { CommentFetcher } from './CommentFetcher';
* with DatabaseService for checking the existence and persisting new comments.
*/
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.
@ -27,11 +17,11 @@ export class CommentProcessor {
* @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.
*/
async processComments(): Promise<Comment[]> {
static async processComments(maxPages: number = 10): Promise<Comment[]> {
let comments: Comment[] = [];
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)
// Check each new comment against the database and existing comments in this batch

View File

@ -1,17 +1,26 @@
import axios, { AxiosError } from 'axios';
import SessionManager from '../session/SessionManager';
import { RedditUser } from '../model/User';
import RedditSessionManager from '../session/SessionManager';
/**
* Provides services for interacting with Reddit user data and sending messages.
*/
export class RedditService {
private sessionManager: SessionManager;
constructor(sessionManager: SessionManager) {
this.sessionManager = sessionManager;
}
async getUserInfo(username: string): Promise<RedditUser> {
/**
* Retrieves information about a Reddit user.
*
* @param {string} username - The username of the Reddit user to retrieve information for.
* @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 {
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;
} catch (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 {
console.log(`await this.sessionManager.axiosInstance.post('/api/compose', {\n\tapi_type: 'json',\n\tto: ${username},\n\tsubject: ${subject},\n\ttext: ${message},\n});`)
//await this.sessionManager.axiosInstance.post('/api/compose', {
// api_type: 'json',
// to: username,
// subject: subject,
// text: message,
//});
const redditSession = await RedditSessionManager.getInstance();
// Create a URLSearchParams object with your data
const params = new URLSearchParams();
params.append('api_type', 'json');
params.append('to', `u/${username}`);
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}`);
} catch (error) {
console.error('Error sending message:', error);

View File

@ -5,8 +5,46 @@ import { DatabaseService } from '../db/services/Database';
// Load environment variables from .env file
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;
const { is_mod, is_employee, accept_pms, total_karma } = userInfo.data;

View File

@ -4,71 +4,97 @@ import { CommentPoster } from "../rdrama/services/CommentPoster";
import { MessageService } from "../utils/MessageService";
import { DatabaseService } from "../db/services/Database";
import { RedditService } from "../reddit/services/Reddit";
import RedditSessionManager from "../reddit/session/SessionManager";
import { shouldNotifyUser } from "../utils/ShouldNotify";
import { Comment } from "../rdrama/models/Comment";
class WorkflowOrchestrator {
constructor(
private commentProcessor: CommentProcessor,
private commentParser: CommentParser,
private commentPoster: CommentPoster,
) { }
/**
* Executes the defined workflow for processing comments.
*/
async executeWorkflow() {
try {
// Fetch comments from the source
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`);
const comments = await this.fetchAndLogComments();
for (const comment of comments) {
const redditUsers = this.commentParser.extractUsernames(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;
}
await this.processComment(comment);
}
console.log('Workflow executed successfully.');
} catch (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;