Compare commits
10 Commits
34a3de1fce
...
b4ef318182
Author | SHA1 | Date |
---|---|---|
j | b4ef318182 | |
j | ecd8df3ca9 | |
J | 8ca573e3ad | |
j | be40118b1a | |
j | 689df79ab0 | |
j | de4da43242 | |
j | 5f6d0671cf | |
j | 96e3661fe2 | |
j | 87ff7c7208 | |
j | 6a8bacd265 |
76
README.MD
76
README.MD
|
@ -1,9 +1,75 @@
|
||||||
# CrossTalk PM
|
# CrossTalk PM
|
||||||
|
|
||||||
CrossTalk PM is an innovative application designed to bridge communication gaps between users across different platforms. By monitoring public mentions of usernames from one site and notifying these users on Reddit, CrossTalk PM enhances user engagement and ensures important messages are conveyed seamlessly between communities.
|
CrossTalk PM serves as a digital intermediary, facilitating connections and communications between users from distinct online platforms. It primarily focuses on identifying mentions of Reddit usernames in external platforms like rDrama and subsequently, notifying the mentioned Reddit users. This endeavor aims to foster a seamless information exchange and enhance inter-community interactions.
|
||||||
|
|
||||||
## Features
|
## Key Features
|
||||||
|
|
||||||
|
- **User Mention Monitoring:** CrossTalk PM diligently scans specified platforms for Reddit usernames being mentioned, ensuring no significant mention goes unnoticed.
|
||||||
|
- **Automated User Notifications:** Upon detecting a mention, it autonomously initiates a Reddit chatroom or dispatches a private message to the mentioned user, thereby alerting them.
|
||||||
|
- **Adherence to Privacy:** The application is designed with a strong commitment to privacy, processing only publicly disclosed information and adhering to privacy norms.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Below you'll find everything you need to know to get CrossTalk PM up and running.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Ensure you have the following installed:
|
||||||
|
- Node.js (version 14 or later is recommended for optimal compatibility)
|
||||||
|
- Redis server (for caching and message queuing functionalities)
|
||||||
|
- Drama Harvester https://fsdfsd.net/J/DramaHarvester
|
||||||
|
|
||||||
|
### Installation Steps
|
||||||
|
|
||||||
|
1. **Clone the Project:**
|
||||||
|
```bash
|
||||||
|
git clone https://fsdfsd.net/J/CrossTalkPM.git
|
||||||
|
```
|
||||||
|
2. **Navigate to the Project Directory:**
|
||||||
|
```bash
|
||||||
|
cd CrossTalkPM
|
||||||
|
```
|
||||||
|
3. **Install Dependencies:**
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
4. **Set Up Environment Variables:** Create a `.env` file in the root of your project directory and populate it with the necessary configuration as shown below:
|
||||||
|
```env
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORT=6379
|
||||||
|
# REDIS_PASSWORD=optional (uncomment if applicable)
|
||||||
|
RDRAMA_API_KEY=your_rdrama_api_key_here
|
||||||
|
redditClientId=your_reddit_client_id
|
||||||
|
redditSecret=your_reddit_secret
|
||||||
|
redditUsername=your_reddit_username
|
||||||
|
redditPassword=your_reddit_password
|
||||||
|
EXCLUDE_EMPLOYEES=True
|
||||||
|
EXCLUDE_MODS=True
|
||||||
|
KARMA_THRESHOLD=100000
|
||||||
|
NOTIFICATION_COOLDOWN_HOURS=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running CrossTalk PM
|
||||||
|
|
||||||
|
To activate CrossTalk PM, execute the following command within the project's root directory:
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
Upon launch, CrossTalk PM will commence its operation, scanning for user mentions and facilitating notifications through Redis.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We highly value and appreciate contributions from our community. Whether it's through submitting bug reports, requesting new features, or contributing directly to the codebase, your involvement is encouraged.
|
||||||
|
|
||||||
|
### How to Contribute:
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Create your feature branch (`git checkout -b feature/YourFeature`).
|
||||||
|
3. Commit your changes (`git commit -am 'Add YourFeature'`).
|
||||||
|
4. Push to the branch (`git push origin feature/YourFeature`).
|
||||||
|
5. Create a new Pull Request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
CrossTalk PM is made available under the MIT License.
|
||||||
|
|
||||||
- **User Mention Detection:** Automatically scans for and identifies mentions of Reddit usernames on specified platforms.
|
|
||||||
- **Automated Notifications:** Creates a Reddit chatroom or sends a private message to notify users of their mentions.
|
|
||||||
- **Privacy-Focused:** Operates with user privacy in mind, ensuring that only publicly available information is processed.
|
|
|
@ -26,43 +26,6 @@ export class DatabaseService {
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a new comment into the database.
|
|
||||||
* This static method constructs an SQL statement to insert all fields of the Comment object
|
|
||||||
* into the corresponding columns in the 'comments' table.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await DatabaseService.insertComment({
|
|
||||||
* id: 1,
|
|
||||||
* author_id: 123,
|
|
||||||
* author_name: 'exampleUser',
|
|
||||||
* body: 'This is a comment.',
|
|
||||||
* // More fields as per the Comment type
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {Comment} comment - The comment object to insert.
|
|
||||||
* @throws {Error} Will throw an error if the insert operation fails.
|
|
||||||
*/
|
|
||||||
public static async insertComment(comment: Comment): Promise<void> {
|
|
||||||
const db = await DatabaseService.getDatabase()
|
|
||||||
const sql = `
|
|
||||||
INSERT INTO comments (
|
|
||||||
id, author_id, author_name, body, body_html, created_utc, deleted_utc,
|
|
||||||
distinguished, downvotes, edited_utc, is_banned, is_bot, is_nsfw, level,
|
|
||||||
permalink, pinned, post_id, replies, reports, score, upvotes
|
|
||||||
) VALUES (
|
|
||||||
?, ?, ?, ?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?, ?, ?, ?
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
await db.run(sql, [
|
|
||||||
comment.id, comment.author_id, comment.author_name, comment.body, comment.body_html, comment.created_utc, comment.deleted_utc,
|
|
||||||
comment.distinguished ? 1 : 0, comment.downvotes, comment.edited_utc, comment.is_banned ? 1 : 0, comment.is_bot ? 1 : 0, comment.is_nsfw ? 1 : 0, comment.level,
|
|
||||||
comment.permalink, comment.pinned, comment.post_id, JSON.stringify(comment.replies), JSON.stringify(comment.reports), comment.score, comment.upvotes
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a new user mention into the database.
|
* Inserts a new user mention into the database.
|
||||||
* This static method adds a record of a user being mentioned in a comment.
|
* This static method adds a record of a user being mentioned in a comment.
|
||||||
|
@ -86,24 +49,6 @@ export class DatabaseService {
|
||||||
await db.run(sql, [mention.rdrama_comment_id, mention.username, mention.message]);
|
await db.run(sql, [mention.rdrama_comment_id, mention.username, mention.message]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Queries the database for an existing comment by its ID.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const exists = await DatabaseService.commentExists('123');
|
|
||||||
* console.log(exists ? 'Comment exists.' : 'Comment does not exist.');
|
|
||||||
*
|
|
||||||
* @param {string} commentId - The ID of the comment to search for.
|
|
||||||
* @returns {Promise<boolean>} A boolean indicating whether the comment exists.
|
|
||||||
* @throws {Error} Will throw an error if the query operation fails.
|
|
||||||
*/
|
|
||||||
public static async commentExists(commentId: string): Promise<boolean> {
|
|
||||||
const db = await DatabaseService.getDatabase()
|
|
||||||
const sql = `SELECT 1 FROM comments WHERE id = ?`;
|
|
||||||
const result = await db.get(sql, [commentId]);
|
|
||||||
return !!result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries the database to check if a username has been mentioned.
|
* Queries the database to check if a username has been mentioned.
|
||||||
*
|
*
|
||||||
|
@ -122,60 +67,6 @@ export class DatabaseService {
|
||||||
return !!result;
|
return !!result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the last run timestamp for a maintenance task, using an "upsert" approach.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await DatabaseService.updateLastRunTimestamp('purgeOldComments');
|
|
||||||
*
|
|
||||||
* @param {string} taskName - The name of the maintenance task.
|
|
||||||
* @throws {Error} Will throw an error if the update operation fails.
|
|
||||||
*/
|
|
||||||
public static async getLastRunTimestamp(taskName: string): Promise<Date | null> {
|
|
||||||
const db = await DatabaseService.getDatabase()
|
|
||||||
const result = await db.get(`SELECT last_run FROM maintenance_log WHERE task_name = ?`, [taskName]);
|
|
||||||
return result ? new Date(result.last_run) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the last run timestamp for a maintenance task, using an "upsert" approach.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await DatabaseService.updateLastRunTimestamp('purgeOldComments');
|
|
||||||
*
|
|
||||||
* @param {string} taskName - The name of the maintenance task.
|
|
||||||
* @throws {Error} Will throw an error if the update operation fails.
|
|
||||||
*/
|
|
||||||
public static async updateLastRunTimestamp(taskName: string): Promise<void> {
|
|
||||||
// Assumes an "upsert" approach for the maintenance_log table
|
|
||||||
const db = await DatabaseService.getDatabase()
|
|
||||||
await db.run(
|
|
||||||
`INSERT INTO maintenance_log (task_name, last_run)
|
|
||||||
VALUES (?, ?)
|
|
||||||
ON CONFLICT(task_name)
|
|
||||||
DO UPDATE SET last_run = ?`,
|
|
||||||
[taskName, new Date(), new Date()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes comments from the database older than a specified number of days.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await DatabaseService.purgeOldComments(30); // Purge comments older than 30 days
|
|
||||||
*
|
|
||||||
* @param {number} days - The age of comments to be purged, in days.
|
|
||||||
* @throws {Error} Will throw an error if the purge operation fails.
|
|
||||||
*/
|
|
||||||
public static async purgeOldComments(days: number = 1): Promise<void> {
|
|
||||||
const db = await DatabaseService.getDatabase()
|
|
||||||
console.log(`Purging comments older than ${days} days...`);
|
|
||||||
await db.run(`
|
|
||||||
DELETE FROM comments
|
|
||||||
WHERE datetime(created_utc, 'unixepoch') < datetime('now', '-${days} days')
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts or updates the OAuth token in the database for a specific service.
|
* Inserts or updates the OAuth token in the database for a specific service.
|
||||||
*
|
*
|
||||||
|
@ -261,9 +152,9 @@ export class DatabaseService {
|
||||||
const lastNotificationTime = new Date(result.last_notification_time).getTime();
|
const lastNotificationTime = new Date(result.last_notification_time).getTime();
|
||||||
const currentTime = new Date(new Date().toISOString().slice(0, 19).replace('T', ' ')).getTime();
|
const currentTime = new Date(new Date().toISOString().slice(0, 19).replace('T', ' ')).getTime();
|
||||||
const timeElapsed = currentTime - lastNotificationTime;
|
const timeElapsed = currentTime - lastNotificationTime;
|
||||||
console.log('timeElapsed', timeElapsed)
|
//console.log('timeElapsed', timeElapsed)
|
||||||
const cooldownPeriod = +cooldownHours * 60 * 60 * 1000; // Convert hours to milliseconds
|
const cooldownPeriod = +cooldownHours * 60 * 60 * 1000; // Convert hours to milliseconds
|
||||||
console.log('cooldownPeriod', cooldownPeriod)
|
//console.log('cooldownPeriod', cooldownPeriod)
|
||||||
|
|
||||||
return timeElapsed >= cooldownPeriod;
|
return timeElapsed >= cooldownPeriod;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
We noticed your recent mention of a Reddit user in your post/comment. While fostering a community built on respect and privacy, we wanted to inform you that the mentioned user has already been notified of a similar mention before. We didn't send another notification to respect their inbox. Please continue to consider the implications of mentioning users in the future.
|
||||||
|
|
||||||
|
Thank you for helping us maintain a respectful community.
|
||||||
|
|
||||||
|
Warm regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your recent action involving the mention of a Reddit user caught our attention. Our community values privacy and respectful discourse above all. Given that the mentioned individual has previously been notified of a mention, we have chosen not to send another notification this time. We encourage you to reflect on the privacy and respect of others in all your interactions.
|
||||||
|
|
||||||
|
Best wishes,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings {author_name},
|
||||||
|
|
||||||
|
We're writing to you about your mention of a Reddit user in your communication. As part of our commitment to privacy and respectful interactions, we wanted to let you know that the individual you mentioned has already been informed about a previous mention. To avoid redundant notifications, we did not send an additional message. We kindly ask you to keep the potential impact of your mentions in mind for future posts and comments.
|
||||||
|
|
||||||
|
Thank you for your understanding,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
We observed your mention of a Reddit user in your recent post/comment. Our community cherishes the principles of privacy and respect. The user you mentioned has been previously notified about a mention. To prevent overwhelming them, we've opted not to send a second notification. Your mindfulness in future mentions is greatly appreciated.
|
||||||
|
|
||||||
|
Kind regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
Just a quick heads-up about your mention of a Reddit user. We're all about keeping things respectful and private here. The mentioned user has been notified before, so we skipped sending another alert to keep their notifications tidy. Let's continue to be thoughtful about our mentions, shall we?
|
||||||
|
|
||||||
|
Cheers,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your recent mention of a Reddit user has been noted. Our aim is to foster a space of respect and privacy. Since the mentioned user has previously received a notification, we refrained from sending another to avoid repetition. Please be considerate of the impacts of your mentions going forward.
|
||||||
|
|
||||||
|
With appreciation,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi there, {author_name},
|
||||||
|
|
||||||
|
We caught your mention of a Reddit user in your post/comment. As a friendly reminder, our community values privacy and kindness. The user mentioned has already been alerted once, so we decided not to send a repeat notification. Thanks for understanding and for your cooperation in maintaining a positive space.
|
||||||
|
|
||||||
|
Warm wishes,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Regarding your mention of a Reddit user: we're dedicated to respectful and private interactions. The user has been notified previously, hence we did not send an additional message this time. It's all part of ensuring everyone's experience remains pleasant. Thank you for playing your part!
|
||||||
|
|
||||||
|
Best,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
We noticed your mention of a Reddit user. In line with our respect for privacy, we want to inform you that the mentioned user has been previously notified, so we've refrained from sending another message. Your awareness and discretion in future mentions are much appreciated.
|
||||||
|
|
||||||
|
Kindly,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
Saw your mention of a Reddit user! Just so you know, we've already notified them about a past mention, so we didn't send another one to keep things chill. Let's keep being awesome and respectful in our mentions, okay?
|
||||||
|
|
||||||
|
Thanks a bunch,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings {author_name},
|
||||||
|
|
||||||
|
In your recent post/comment, you mentioned a Reddit user. We value our community's privacy and have previously informed the user about a mention. To avoid notification overload, we opted out of sending another. Your understanding and respect for privacy are appreciated.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your mention of a Reddit user was noted. Since they've been notified previously, we chose not to send a duplicate notification, aligning with our community's values of respect and discretion. We trust you understand and will continue to consider these values in your future engagements.
|
||||||
|
|
||||||
|
Sincerely,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
You mentioned a Reddit user recently. To maintain a respectful atmosphere and prevent redundancy, we didn't send another notification since they were already informed once before. Your help in keeping our community considerate and respectful is invaluable.
|
||||||
|
|
||||||
|
Thank you,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
We've observed your recent activity involving a mention of a Reddit user. To maintain our community's commitment to discretion and respect, we've chosen not to resend a notification to the mentioned user, as they have already been contacted. We appreciate your understanding and your contributions to a respectful community atmosphere.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your recent mention of a Reddit user caught our attention. In line with our dedication to privacy and respect, we've decided against sending another notification to avoid duplicity. Your continued mindfulness and consideration in interactions are what make our community great. Thank you for your cooperation.
|
||||||
|
|
||||||
|
Cheers,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
Thanks for your engagement in our community. Regarding your mention of a Reddit user: they've already been made aware of a previous mention. To prevent notification fatigue, we've not sent another alert. Your understanding and adherence to our community values are greatly appreciated.
|
||||||
|
|
||||||
|
Kindly,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings {author_name},
|
||||||
|
|
||||||
|
We noticed your mention of a Reddit user and wanted to remind you of the importance of respect and privacy within our community. As the mentioned user has previously been notified, we have refrained from sending an additional message. Your thoughtful participation helps us maintain a positive community environment.
|
||||||
|
|
||||||
|
Warm regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
Your action of mentioning a Reddit user has been noted. Our policy is to keep our community respectful and private, so we've chosen not to resend a notification to the mentioned user, who has already been informed once. We're thankful for your understanding and for helping us preserve a respectful space.
|
||||||
|
|
||||||
|
Sincerely,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
In your recent contribution, you mentioned a Reddit user. Our community principles guide us to avoid over-notifying users who've already been mentioned, to respect their privacy. Therefore, we did not send an additional notification. Your cooperation in these matters is highly valued.
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
Just touching base about your recent mention of a Reddit user. To keep our community vibe positive and respectful, we've held off on sending another notification to the user you mentioned since they've been notified before. Thanks for helping us keep the peace and privacy!
|
||||||
|
|
||||||
|
All the best,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
We've seen your mention of a Reddit user in your recent post/comment. Our community ethos of privacy and respect means we've opted not to send another notification, as the user has been previously informed. Your understanding and respect in this matter help us maintain a healthy community dynamic.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your mention of a Reddit user has been registered. In keeping with our commitment to privacy, we've chosen not to issue another notification to them, considering they've been alerted before. We trust you appreciate our discretion and thank you for your thoughtful participation in our community.
|
||||||
|
|
||||||
|
Warm wishes,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
We noticed your recent mention of a Reddit user. Our aim is to respect everyone's privacy and avoid notification overload, so we didn't send another alert to the mentioned user, who has been previously notified. Thanks for understanding and for being a valued member of our respectful community.
|
||||||
|
|
||||||
|
Take care,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
|
@ -0,0 +1,231 @@
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
Thanks for engaging within our community. We noticed your mention of a Reddit user who's quite the connoisseur of Reddit's ways. Their rich history and karma speak to their deep engagement, so we've opted not to send a notification. Your consideration in these matters is invaluable.
|
||||||
|
|
||||||
|
Kindly,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
We caught your mention of a user who's no stranger to the Reddit scene. Their impressive karma and platform wisdom mean we'll skip the notification this time. Thanks for being a part of our respectful and privacy-conscious community.
|
||||||
|
|
||||||
|
All the best,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your recent reference to a Reddit stalwart caught our attention. Given their storied journey and significant karma, we've decided it's best to hold back on sending them another ping. Your engagement is what makes our community vibrant.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings {author_name},
|
||||||
|
|
||||||
|
We've noticed you mentioned a user deeply woven into the fabric of Reddit. Their experience and karma levels suggest they're already adept at navigating the platform's nuances. To avoid overburdening them, we won't be sending a notification. Thanks for helping maintain a considerate environment.
|
||||||
|
|
||||||
|
Best,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
Your mention of a seasoned Reddit navigator has been duly noted. Their extensive karma and active participation highlight their expertise. As such, we believe an additional notification might not be necessary. We appreciate your efforts in cultivating a thoughtful community.
|
||||||
|
|
||||||
|
Warmly,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
You've mentioned a Reddit veteran, someone well-versed in the ebb and flow of this vast platform. Considering their significant karma and contributions, we're skipping the notification step this round. Your mindfulness is what makes this community special.
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
It seems you've mentioned a Reddit user who's quite familiar with the platform's ins and outs, judging by their high karma and active engagement. We'll forgo the notification to respect their seasoned user status. Your thoughtful interaction is what keeps our community thriving.
|
||||||
|
|
||||||
|
Cheers,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your reference to a Reddit user known for their extensive engagement and high karma has been recorded. In recognition of their profound experience, we'll hold off on sending a notification. Your sensitivity to community dynamics is truly valued.
|
||||||
|
|
||||||
|
Thank you,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings {author_name},
|
||||||
|
|
||||||
|
We've seen your mention of a user who's no newbie to Reddit's corridors. Their impressive karma tally and history suggest they're well-equipped to handle mentions, so we'll skip the extra notification. Thank you for contributing to a respectful community.
|
||||||
|
|
||||||
|
Sincerely,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
Your recent activity brought to light a mention of a Reddit user whose karma and historical engagement are nothing short of remarkable. With respect to their seasoned presence, we see no need for an additional notification. Your participation in fostering a considerate community is greatly appreciated.
|
||||||
|
|
||||||
|
Best wishes,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
You've highlighted a user who's a Reddit aficionado, someone with a karma score and engagement level that speaks volumes. We'll hold off on notifying them this time, trusting in their seasoned approach to community interactions. Thanks for being considerate and respectful.
|
||||||
|
|
||||||
|
Take care,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
Your engagement has led to the mention of a Reddit user whose journey on the platform is both extensive and impressive. Given their high karma and active participation, we'll refrain from sending a notification. Your thoughtful presence is a boon to our community.
|
||||||
|
|
||||||
|
With gratitude,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
Your shoutout to a seasoned Reddit user caught our eye. With their wealth of experience and karma, they're well-equipped to navigate mentions. To honor their established presence, we're holding back on sending a notification this time. Your understanding enhances our community's respectfulness.
|
||||||
|
|
||||||
|
Sincerely,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
You've highlighted a Reddit veteran in your recent post. Their journey through Reddit's corridors, marked by significant karma, suggests they're adept at managing interactions. We're skipping the notification to acknowledge their seasoned expertise. Thanks for your thoughtful engagement.
|
||||||
|
|
||||||
|
Best,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings {author_name},
|
||||||
|
|
||||||
|
We noticed you mentioned a Reddit luminary. Given their extensive karma and notable contributions, we believe they're more than capable of handling mentions independently. In respect of their veteran status, no notification will be sent. Your sensitivity to these nuances is much appreciated.
|
||||||
|
|
||||||
|
Warmly,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi there, {author_name},
|
||||||
|
|
||||||
|
Your recent nod to a Reddit heavyweight was observed. Their towering karma and active participation speak volumes of their adeptness on the platform. Recognizing their capability, we've decided against a notification. We value your contribution to our thoughtful community.
|
||||||
|
|
||||||
|
Cheers,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
You've referenced a Reddit stalwart in your dialogue. Their profound karma and enduring presence underscore a comprehensive Reddit experience. To honor their adept handling of such mentions, no additional notification will be dispatched. We're grateful for your discerning interaction.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
Your mention of a Reddit sage has been noted. Their extensive karma accumulation and insightful contributions signify a seasoned journey on Reddit. Acknowledging their familiarity with community dynamics, we're omitting the notification. Thank you for promoting respectful discourse.
|
||||||
|
|
||||||
|
Best wishes,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
|
||||||
|
Your callout to a Reddit old-timer didn't go unnoticed. Their enviable karma score and history of engagement demonstrate their thorough Reddit literacy. In deference to their seasoned insight, we'll forego the notification. Your mindfulness is what makes our community special.
|
||||||
|
|
||||||
|
Take care,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
|
||||||
|
In your recent post, you've acknowledged a Reddit authority. Their rich karma and history of insightful interactions highlight a profound engagement with the Reddit community. Recognizing their adeptness, we've opted out of sending a notification. Your respect for community pillars is commendable.
|
||||||
|
|
||||||
|
Kind regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
|
||||||
|
We've taken note of your homage to a Reddit icon. With a karma score that's through the roof and countless contributions, they epitomize the essence of Reddit. This time, we'll hold back on the notification to honor their mastery. We thank you for your understanding and respectful engagement.
|
||||||
|
|
||||||
|
Sincerely,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
|
||||||
|
Your pointer towards a Reddit legend has been appreciated. Their stellar karma and active presence are a testament to their Reddit savviness. To respect their well-earned stature, no notification will be sent. Your actions contribute greatly to our community's positive ethos.
|
||||||
|
|
||||||
|
Warmest regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
Greetings, {author_name},
|
||||||
|
You've mentioned a Reddit veteran whose contributions have already painted them as a notable figure. Recognizing their seasoned presence, we're holding back on the notification. It's the shared understanding and respect that keep our community thriving.
|
||||||
|
Cheers,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello {author_name},
|
||||||
|
Your shoutout to a user well-versed in the Reddit realm caught our eye. Given their established rapport and karma wealth, we'll forgo the alert. It's contributors like you that make our community a richer place.
|
||||||
|
With gratitude,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Dear {author_name},
|
||||||
|
We observed your nod to an esteemed Reddit user. Their journey and karma milestones suggest they're well acquainted with the ebb and flow here, so we've decided not to dispatch a notification. Thanks for helping maintain a courteous environment.
|
||||||
|
Sincerely,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi there, {author_name},
|
||||||
|
A tip of the hat to a distinguished Reddit user, we see! Their legacy and karma scores are testament enough, thus negating the need for further notice. Your actions reinforce the mutual respect that underpins our community.
|
||||||
|
Warmly,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hey {author_name},
|
||||||
|
A shoutout to a seasoned Redditor, eh? Their karma and tenure are indicative of a well-trodden path here, sparing us the need to send out a notification. It's this thoughtful engagement that cultivates our community's spirit.
|
||||||
|
Appreciatively,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Good day, {author_name},
|
||||||
|
Your recent mention of a Reddit stalwart didn't go unnoticed. With their karma echoing years of engagement, we'll refrain from sending a notice. It's the understanding and respect you show that keeps our community's foundation strong.
|
||||||
|
Best,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Salutations, {author_name},
|
||||||
|
Recognizing a Reddit luminary, we noted your mention. Their vast karma and contributions make further notification redundant. Your mindfulness in community interactions is truly commendable.
|
||||||
|
Regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hello again, {author_name},
|
||||||
|
You've acknowledged a Reddit user who's practically a legend here, given their karma and activity. In light of their veteran status, we see no need for an additional notification. Your insight into community dynamics is appreciated.
|
||||||
|
Take care,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Hi {author_name},
|
||||||
|
You've tipped your hat to a user whose Reddit karma and history speak volumes. As such, we're bypassing the notification step. Your keen sense of community etiquette doesn't go unnoticed.
|
||||||
|
Thank you,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
||||||
|
|
||||||
|
Greetings, {author_name},
|
||||||
|
Your mention of a Reddit user known for their extensive karma and platform engagement caught our attention. Given their familiarity with the Reddit ecosystem, we'll omit the notification. Your thoughtful participation helps shape a respectful community.
|
||||||
|
Warm regards,
|
||||||
|
CrossTalk PM - Automated Message (Unmonitored Account)
|
||||||
|
---END---
|
|
@ -20,7 +20,7 @@ class SessionManager {
|
||||||
datastore: "ioredis",
|
datastore: "ioredis",
|
||||||
clearDatastore: false,
|
clearDatastore: false,
|
||||||
clientOptions: {
|
clientOptions: {
|
||||||
host: process.env.REDIS_HOST,
|
host: process.env.REDIS_HOST!,
|
||||||
port: Number(process.env.REDIS_PORT),
|
port: Number(process.env.REDIS_PORT),
|
||||||
password: process.env.REDIS_PASSWORD || undefined, // Use undefined if no password is set
|
password: process.env.REDIS_PASSWORD || undefined, // Use undefined if no password is set
|
||||||
enableOfflineQueue: true
|
enableOfflineQueue: true
|
||||||
|
@ -40,7 +40,7 @@ class SessionManager {
|
||||||
this.wrapAxiosInstance(this.axiosInstance);
|
this.wrapAxiosInstance(this.axiosInstance);
|
||||||
|
|
||||||
axiosRetry(this.axiosInstance, {
|
axiosRetry(this.axiosInstance, {
|
||||||
retries: 3,
|
retries: 10,
|
||||||
retryDelay: this.retryDelayStrategy,
|
retryDelay: this.retryDelayStrategy,
|
||||||
retryCondition: this.retryCondition,
|
retryCondition: this.retryCondition,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,33 @@
|
||||||
import axios, { AxiosInstance, AxiosError } from 'axios';
|
import axios, { AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig } from 'axios';
|
||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import axiosRetry from 'axios-retry';
|
import axiosRetry from 'axios-retry';
|
||||||
import axiosThrottle from 'axios-request-throttle';
|
import Bottleneck from 'bottleneck';
|
||||||
import { DatabaseService } from '../../db/services/Database';
|
import { DatabaseService } from '../../db/services/Database';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
class RedditSessionManager {
|
class RedditSessionManager {
|
||||||
private static instance: RedditSessionManager;
|
private static instance: RedditSessionManager;
|
||||||
public axiosInstance: AxiosInstance;
|
public readonly axiosInstance: AxiosInstance;
|
||||||
|
private limiter: Bottleneck;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
axiosThrottle.use(axios, { requestsPerSecond: 1 }); // Throttle setup
|
|
||||||
|
// Initialize the Bottleneck limiter
|
||||||
|
this.limiter = new Bottleneck({
|
||||||
|
id: "reddit-limiter",
|
||||||
|
datastore: "ioredis",
|
||||||
|
clearDatastore: false,
|
||||||
|
clientOptions: {
|
||||||
|
host: process.env.REDIS_HOST,
|
||||||
|
port: Number(process.env.REDIS_PORT),
|
||||||
|
password: process.env.REDIS_PASSWORD || undefined, // Use undefined if no password is set
|
||||||
|
enableOfflineQueue: true
|
||||||
|
},
|
||||||
|
maxConcurrent: 1, // Maximum number of concurrent requests
|
||||||
|
minTime: 1000 // Minimum time between dispatches of requests in milliseconds
|
||||||
|
});
|
||||||
|
|
||||||
this.axiosInstance = axios.create({
|
this.axiosInstance = axios.create({
|
||||||
baseURL: 'https://oauth.reddit.com/', // Base URL for OAuth2 Reddit API
|
baseURL: 'https://oauth.reddit.com/', // Base URL for OAuth2 Reddit API
|
||||||
|
@ -20,6 +35,10 @@ class RedditSessionManager {
|
||||||
'User-Agent': 'CrossTalk PM/0.1 by Whitneywisconson'
|
'User-Agent': 'CrossTalk PM/0.1 by Whitneywisconson'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wrap axios requests with the limiter
|
||||||
|
this.wrapAxiosInstance(this.axiosInstance);
|
||||||
|
|
||||||
axiosRetry(this.axiosInstance, {
|
axiosRetry(this.axiosInstance, {
|
||||||
retries: 3,
|
retries: 3,
|
||||||
retryDelay: this.retryDelayStrategy,
|
retryDelay: this.retryDelayStrategy,
|
||||||
|
@ -35,6 +54,10 @@ class RedditSessionManager {
|
||||||
return RedditSessionManager.instance;
|
return RedditSessionManager.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async shutdown(): Promise<void> {
|
||||||
|
await this.limiter.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
private async initializeAuthentication() {
|
private async initializeAuthentication() {
|
||||||
// Check the database for an existing token
|
// Check the database for an existing token
|
||||||
const currentToken = await DatabaseService.getCurrentOAuthToken(this.axiosInstance.defaults.baseURL as string);
|
const currentToken = await DatabaseService.getCurrentOAuthToken(this.axiosInstance.defaults.baseURL as string);
|
||||||
|
@ -103,6 +126,32 @@ class RedditSessionManager {
|
||||||
const status = error.response?.status ?? 0;
|
const status = error.response?.status ?? 0;
|
||||||
return status === 429 || status >= 400;
|
return status === 429 || status >= 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private wrapAxiosInstance(instance: AxiosInstance): void {
|
||||||
|
// Wrap the get method
|
||||||
|
const originalGet = instance.get;
|
||||||
|
instance.get = <T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R> => {
|
||||||
|
return this.limiter.schedule(() => originalGet.apply(instance, [url, config])) as Promise<R>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap the post method
|
||||||
|
const originalPost = instance.post;
|
||||||
|
instance.post = <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R> => {
|
||||||
|
return this.limiter.schedule(() => originalPost.apply(instance, [url, data, config])) as Promise<R>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap the put method
|
||||||
|
const originalPut = instance.put;
|
||||||
|
instance.put = <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R> => {
|
||||||
|
return this.limiter.schedule(() => originalPut.apply(instance, [url, data, config])) as Promise<R>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap the delete method
|
||||||
|
const originalDelete = instance.delete;
|
||||||
|
instance.delete = <T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R> => {
|
||||||
|
return this.limiter.schedule(() => originalDelete.apply(instance, [url, config])) as Promise<R>;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RedditSessionManager;
|
export default RedditSessionManager;
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
import Redis, { RedisOptions } from 'ioredis';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
interface RedisConfig extends RedisOptions {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RedisSessionManager {
|
||||||
|
private static instance: RedisSessionManager;
|
||||||
|
public readonly client: Redis;
|
||||||
|
private subscriber: Redis | null = null;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
const redisConfig: RedisConfig = {
|
||||||
|
host: process.env.REDIS_HOST!,
|
||||||
|
port: Number(process.env.REDIS_PORT),
|
||||||
|
password: process.env.REDIS_PASSWORD || undefined,
|
||||||
|
showFriendlyErrorStack: true,
|
||||||
|
};
|
||||||
|
this.client = new Redis(redisConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): RedisSessionManager {
|
||||||
|
if (!RedisSessionManager.instance) {
|
||||||
|
RedisSessionManager.instance = new RedisSessionManager();
|
||||||
|
}
|
||||||
|
return RedisSessionManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async subscribe(channel: string, messageHandler: (channel: string, message: string) => void): Promise<void> {
|
||||||
|
if (!this.subscriber) {
|
||||||
|
this.subscriber = new Redis({
|
||||||
|
host: process.env.REDIS_HOST!,
|
||||||
|
port: Number(process.env.REDIS_PORT),
|
||||||
|
password: process.env.REDIS_PASSWORD || undefined,
|
||||||
|
showFriendlyErrorStack: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.subscriber.subscribe(channel, (err, count) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`Failed to subscribe: ${err.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Subscribed to ${count} channel(s). Currently subscribed to ${channel}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.subscriber.on('message', messageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
async unsubscribe(channel: string): Promise<void> {
|
||||||
|
if (this.subscriber) {
|
||||||
|
await this.subscriber.unsubscribe(channel);
|
||||||
|
console.log(`Unsubscribed from ${channel}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async storeObject(keyPrefix: string, objectId: string, data: Record<string, any>, expirationSec: number = 3600): Promise<void> {
|
||||||
|
const key = `${keyPrefix}:${objectId}`;
|
||||||
|
const dataToStore = JSON.stringify(data);
|
||||||
|
await this.client.set(key, dataToStore, 'EX', expirationSec);
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieveObject(keyPrefix: string, objectId: string): Promise<Record<string, any> | null> {
|
||||||
|
const key = `${keyPrefix}:${objectId}`;
|
||||||
|
const data = await this.client.get(key);
|
||||||
|
return data ? JSON.parse(data) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async publishObject(channel: string, data: Record<string, any>): Promise<number> {
|
||||||
|
const dataToPublish = JSON.stringify(data);
|
||||||
|
return this.client.publish(channel, dataToPublish);
|
||||||
|
}
|
||||||
|
|
||||||
|
async shutdown(): Promise<void> {
|
||||||
|
if (this.subscriber) {
|
||||||
|
await this.subscriber.quit();
|
||||||
|
}
|
||||||
|
await this.client.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RedisSessionManager;
|
|
@ -1,88 +1,48 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
/**
|
// Define an enum for message file names
|
||||||
* Manages the retrieval and formatting of messages stored in text files.
|
export enum MessageFileName {
|
||||||
* This class provides functionality to load messages for rDrama and Reddit,
|
RdramaPreviousMessage = 'rdrama_PreviousMessage.txt',
|
||||||
* select a random message, and replace placeholders within that message
|
RdramaShouldntNotify = 'rdrama_ShouldntNotify.txt',
|
||||||
* with specified values.
|
RedditMessages = 'reddit_messages.txt',
|
||||||
*/
|
RdramaMessages = 'rdrama_messages.txt',
|
||||||
|
}
|
||||||
|
|
||||||
export class MessageService {
|
export class MessageService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads rDrama messages from a text file, splitting by a specific delimiter.
|
* Loads messages from a specified text file based on the enum, splitting by a specific delimiter.
|
||||||
* Each message is separated by '---END---' in the text file.
|
* Each message is separated by '---END---' in the text file.
|
||||||
*
|
*
|
||||||
* @example
|
* @param {MessageFileName} fileName - The enum value representing the file containing the messages.
|
||||||
* const rdramaMessages = MessageService.loadRdramaMessages();
|
* @returns {string[] | undefined} An array of messages, or undefined if there was an error loading the messages.
|
||||||
*
|
|
||||||
* @returns {string[] | undefined} An array of rDrama messages, or undefined if there was an error loading the messages.
|
|
||||||
*/
|
*/
|
||||||
private static loadRdramaMessages(): string[] | undefined {
|
private static loadMessages(fileName: MessageFileName): string[] | undefined {
|
||||||
try {
|
try {
|
||||||
const rdramaMessagesPath = path.join(__dirname, '..', 'messages', 'rdrama_messages.txt');
|
const messagesPath = path.join(__dirname, '..', 'messages', fileName);
|
||||||
return fs.readFileSync(rdramaMessagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
return fs.readFileSync(messagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load rDrama messages:', error);
|
console.error(`Failed to load messages from ${fileName}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads Reddit messages from a text file, splitting by a specific delimiter.
|
* Selects a random message from the loaded messages and replaces placeholders within it.
|
||||||
* Each message is separated by '---END---' in the text file.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const redditMessages = MessageService.loadRedditMessages();
|
|
||||||
*
|
|
||||||
* @returns {string[] | undefined} An array of Reddit messages, or undefined if there was an error loading the messages.
|
|
||||||
*/
|
|
||||||
private static loadRedditMessages(): string[] | undefined {
|
|
||||||
try {
|
|
||||||
const redditMessagesPath = path.join(__dirname, '..', 'messages', 'reddit_messages.txt');
|
|
||||||
return fs.readFileSync(redditMessagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load Reddit messages:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects a random Reddit message from the loaded messages and replaces placeholders within it.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const message = MessageService.getRandomRedditMessage({ username: 'exampleUser' });
|
|
||||||
*
|
*
|
||||||
|
* @param {MessageFileName} fileName - The enum value representing the file containing the messages to load.
|
||||||
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
||||||
* @returns {string | undefined} A formatted Reddit message with placeholders replaced, or undefined if messages couldn't be loaded.
|
* @returns {string | undefined} A formatted message with placeholders replaced, or undefined if messages couldn't be loaded.
|
||||||
*/
|
*/
|
||||||
public static getRandomRedditMessage(placeholders: { [key: string]: string }): string | undefined {
|
public static getRandomMessage(fileName: MessageFileName, placeholders: { [key: string]: string }): string | undefined {
|
||||||
const redditMessages = this.loadRedditMessages()
|
const messages = this.loadMessages(fileName);
|
||||||
if (!redditMessages) return
|
if (!messages) return undefined;
|
||||||
const message = redditMessages[Math.floor(Math.random() * redditMessages.length)];
|
const message = messages[Math.floor(Math.random() * messages.length)];
|
||||||
return this.replacePlaceholders(message, placeholders);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects a random rDrama message from the loaded messages and replaces placeholders within it.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const message = MessageService.getRandomRdramaMessage({ username: 'exampleUser' });
|
|
||||||
*
|
|
||||||
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
|
||||||
* @returns {string | undefined} A formatted rDrama message with placeholders replaced, or undefined if messages couldn't be loaded.
|
|
||||||
*/
|
|
||||||
public static getRandomRdramaMessage(placeholders: { [key: string]: string }): string | undefined {
|
|
||||||
const rdramaMessages = this.loadRdramaMessages()
|
|
||||||
if (!rdramaMessages) return
|
|
||||||
const message = rdramaMessages[Math.floor(Math.random() * rdramaMessages.length)];
|
|
||||||
return this.replacePlaceholders(message, placeholders);
|
return this.replacePlaceholders(message, placeholders);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces placeholders in a message with values from a provided mapping.
|
* Replaces placeholders in a message with values from a provided mapping.
|
||||||
*
|
*
|
||||||
* @example
|
|
||||||
* const formattedMessage = MessageService.replacePlaceholders('Hello, {username}!', { username: 'exampleUser' });
|
|
||||||
*
|
|
||||||
* @param {string} message - The message containing placeholders.
|
* @param {string} message - The message containing placeholders.
|
||||||
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
||||||
* @returns {string} The message with placeholders replaced by actual values.
|
* @returns {string} The message with placeholders replaced by actual values.
|
||||||
|
@ -93,4 +53,4 @@ export class MessageService {
|
||||||
return acc.replace(regex, placeholders[key]);
|
return acc.replace(regex, placeholders[key]);
|
||||||
}, message);
|
}, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { CommentProcessor } from "../rdrama/services/CommentProcessor";
|
|
||||||
import { CommentParser } from "../rdrama/services/CommentParser";
|
import { CommentParser } from "../rdrama/services/CommentParser";
|
||||||
import { CommentPoster } from "../rdrama/services/CommentPoster";
|
import { CommentPoster } from "../rdrama/services/CommentPoster";
|
||||||
import { MessageService } from "../utils/MessageService";
|
import { MessageFileName, 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 { shouldNotifyUser } from "../utils/ShouldNotify";
|
import { shouldNotifyUser } from "../utils/ShouldNotify";
|
||||||
|
@ -12,30 +11,20 @@ class WorkflowOrchestrator {
|
||||||
/**
|
/**
|
||||||
* Executes the defined workflow for processing comments.
|
* Executes the defined workflow for processing comments.
|
||||||
*/
|
*/
|
||||||
async executeWorkflow() {
|
async executeWorkflow(comment: Comment) {
|
||||||
try {
|
try {
|
||||||
const comments = await this.fetchAndLogComments();
|
const canSend = await DatabaseService.canSendNotification();
|
||||||
|
const coolDownHours = process.env.NOTIFICATION_COOLDOWN_HOURS
|
||||||
for (const comment of comments) {
|
if (!canSend) {
|
||||||
await this.processComment(comment);
|
console.log(`Last Message Sent less than ${coolDownHours ? coolDownHours : 4} hours ago. Set NOTIFICATION_COOLDOWN_HOURS to change this`)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await this.processComment(comment);
|
||||||
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.
|
* Processes a single comment, including posting responses and sending notifications.
|
||||||
* @param {Object} comment The comment to process.
|
* @param {Object} comment The comment to process.
|
||||||
|
@ -45,47 +34,45 @@ class WorkflowOrchestrator {
|
||||||
if (redditUsers.length === 0) return;
|
if (redditUsers.length === 0) return;
|
||||||
console.log('found:', redditUsers);
|
console.log('found:', redditUsers);
|
||||||
|
|
||||||
for (const redditUser of redditUsers) {
|
await this.postCommentAndNotify(comment, redditUsers[0]);
|
||||||
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.
|
* Posts a comment response and sends a notification if the user should be notified.
|
||||||
* @param {Object} comment The original comment.
|
* @param {Object} comment The original comment.
|
||||||
* @param {string} redditUser The Reddit user to notify.
|
* @param {string} redditUser The Reddit user to notify.
|
||||||
* @param {string} commentResponseRdrama The response to post.
|
|
||||||
*/
|
*/
|
||||||
async postCommentAndNotify(comment: Comment, redditUser: string, commentResponseRdrama: string) {
|
async postCommentAndNotify(comment: Comment, redditUser: string) {
|
||||||
// Placeholder for posting a comment. Uncomment and implement as needed.
|
const placeholdersRdrama = { author_name: comment.author_name };
|
||||||
|
|
||||||
|
const userMentionExists = await DatabaseService.userMentionExists(redditUser);
|
||||||
|
if (userMentionExists) {
|
||||||
|
const commentPreviouslyMessaged = MessageService.getRandomMessage(MessageFileName.RdramaPreviousMessage, placeholdersRdrama);
|
||||||
|
if (!commentPreviouslyMessaged) throw new Error('No comments for previous Message found');
|
||||||
|
const postedComment = await CommentPoster.postComment(`c_${comment.id}`, `${commentPreviouslyMessaged}`);
|
||||||
|
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resultshouldNotifyUser = await shouldNotifyUser(redditUser);
|
||||||
|
if (!resultshouldNotifyUser) {
|
||||||
|
const commentShouldntNotify = MessageService.getRandomMessage(MessageFileName.RdramaShouldntNotify, placeholdersRdrama);
|
||||||
|
if (!commentShouldntNotify) throw new Error('No comments for Shouldnt Notify found');
|
||||||
|
const postedComment = await CommentPoster.postComment(`c_${comment.id}`, `${commentShouldntNotify}`);
|
||||||
|
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentResponseRdrama = MessageService.getRandomMessage(MessageFileName.RdramaMessages, placeholdersRdrama);
|
||||||
|
if (!commentResponseRdrama) throw new Error('No comments for Rdrama found');
|
||||||
const postedComment = await CommentPoster.postComment(`c_${comment.id}`, `${commentResponseRdrama}`);
|
const postedComment = await CommentPoster.postComment(`c_${comment.id}`, `${commentResponseRdrama}`);
|
||||||
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4));
|
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4));
|
||||||
|
|
||||||
const resultshouldNotifyUser = await shouldNotifyUser(redditUser);
|
|
||||||
if (!resultshouldNotifyUser) return;
|
|
||||||
|
|
||||||
const placeholdersReddit = {
|
const placeholdersReddit = {
|
||||||
author_name: comment.author_name,
|
author_name: comment.author_name,
|
||||||
username: redditUser,
|
username: redditUser,
|
||||||
permalink: comment.permalink
|
permalink: comment.permalink
|
||||||
};
|
};
|
||||||
const redditMessage = MessageService.getRandomRedditMessage(placeholdersReddit);
|
const redditMessage = MessageService.getRandomMessage(MessageFileName.RedditMessages, placeholdersReddit);
|
||||||
if (!redditMessage) throw new Error('No comments for Reddit found');
|
if (!redditMessage) throw new Error('No comments for Reddit found');
|
||||||
|
|
||||||
await DatabaseService.insertUserMention({
|
await DatabaseService.insertUserMention({
|
||||||
|
|
Loading…
Reference in New Issue