Skip to content

Commit

Permalink
feat(muti-rooms): diff rooms now has diff memo
Browse files Browse the repository at this point in the history
  • Loading branch information
Sma1lboy committed Aug 24, 2024
1 parent 3340efd commit 2532c07
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 81 deletions.
101 changes: 67 additions & 34 deletions src/command-handler.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { Message, Room, Contact } from 'wechaty';
import { InternJobProvider } from './providers/internship-job-provider';
import { NGJobProvider } from './providers/new-graduate-job-provider';
import { InternshipJobProvider } from './providers/internship-job-provider';
import { NewGraduateJobProvider } from './providers/new-graduate-job-provider';
import { FileSystemService } from './file-system-service';

interface Command {
name: string;
aliases: string[];
description: string;
execute: (message: Message, args: string[]) => Promise<void>;
execute: (message: Message, args: string[], silent?: boolean) => Promise<void>;
}

export class CommandHandler {
private commands: Command[];
private internJob: InternJobProvider;
private newGradJob: NGJobProvider;
private internJob: InternshipJobProvider;
private newGradJob: NewGraduateJobProvider;
private targetRooms: Room[];

constructor(internJob: InternJobProvider, newGradJob: NGJobProvider, targetRooms: Room[]) {
constructor(
internJob: InternshipJobProvider,
newGradJob: NewGraduateJobProvider,
targetRooms: Room[],
) {
this.internJob = internJob;
this.newGradJob = newGradJob;
this.targetRooms = targetRooms;
Expand All @@ -33,9 +37,13 @@ export class CommandHandler {
if (room && sender && (await this.isRoomOwnerOrAdmin(room, sender))) {
const roomTopic = await room.topic();
await this.addRoomToRegistry(roomTopic);
await message.say(`Room "${roomTopic}" has been added to the bot's target list.`);
await this.sendMessage(
message,
`Room "${roomTopic}" has been added to the bot's target list.`,
);
} else {
await message.say(
await this.sendMessage(
message,
"Sorry, only room owners or admins can add rooms to the bot's target list.",
);
}
Expand All @@ -45,19 +53,33 @@ export class CommandHandler {
name: 'intern',
aliases: ['internjobs'],
description: 'Get new intern job postings',
execute: async (message: Message) => {
if (!(await this.sendJobUpdates(this.internJob))) {
await message.say('No new jobs found for Intern roles');
execute: async (message: Message, args: string[], silent = false) => {
const room = message.room();
if (room) {
if (!(await this.sendJobUpdates(this.internJob, room))) {
if (!silent) {
await this.sendMessage(message, 'No new jobs found for Intern roles');
}
}
} else {
await this.sendMessage(message, 'This command can only be used in a room.');
}
},
},
{
name: 'ng',
aliases: ['ngjobs'],
description: 'Get new graduate job postings',
execute: async (message: Message) => {
if (!(await this.sendJobUpdates(this.newGradJob))) {
await message.say('No new jobs found for New Graduate roles');
execute: async (message: Message, args: string[], silent = false) => {
const room = message.room();
if (room) {
if (!(await this.sendJobUpdates(this.newGradJob, room))) {
if (!silent) {
await this.sendMessage(message, 'No new jobs found for New Graduate roles');
}
}
} else {
await this.sendMessage(message, 'This command can only be used in a room.');
}
},
},
Expand All @@ -73,13 +95,13 @@ export class CommandHandler {
helpMessage += ` Aliases: ${command.aliases.join(', ')}\n`;
}
});
await message.say(helpMessage);
await this.sendMessage(message, helpMessage);
},
},
];
}

async handleCommand(message: Message) {
async handleCommand(message: Message, silent = false) {
const text = message.text().toLowerCase();
const commandMatch = text.match(/@\S+\s+(.+)/);
const commandName = commandMatch ? commandMatch[1].split(' ')[0] : '';
Expand All @@ -90,9 +112,21 @@ export class CommandHandler {

if (command) {
const args = commandMatch ? commandMatch[1].split(' ').slice(1) : [];
await command.execute(message, args);
await command.execute(message, args, silent);
} else if (!silent) {
await this.sendMessage(
message,
'Unrecognized command. Use "@BOT help" for available commands.',
);
}
}

private async sendMessage(message: Message, content: string) {
const room = message.room();
if (room) {
await room.say(content);
} else {
await message.say('Unrecognized command. Use "@BOT help" for available commands.');
await message.say(content);
}
}

Expand All @@ -105,37 +139,36 @@ export class CommandHandler {
const registeredTopicsPath = 'registered-topics.json';
let topicsLocal: { topics: string[] } = { topics: [] };

if (FileSystemService.fileExists(registeredTopicsPath)) {
topicsLocal = FileSystemService.readJSON<{ topics: string[] }>(registeredTopicsPath);
if (FileSystemService.globalFileExists(registeredTopicsPath)) {
topicsLocal = FileSystemService.readGlobalJSON<{ topics: string[] }>(registeredTopicsPath);
}

if (!topicsLocal.topics.includes(roomTopic)) {
topicsLocal.topics.push(roomTopic);
FileSystemService.writeJSON(registeredTopicsPath, topicsLocal);
FileSystemService.writeGlobalJSON(registeredTopicsPath, topicsLocal);
console.log(`Added room "${roomTopic}" to the registry.`);
} else {
console.log(`Room "${roomTopic}" is already in the registry.`);
}
}

private async sendJobUpdates(provider: InternJobProvider | NGJobProvider) {
console.log(`Checking for new ${provider.jobType} jobs... now !`);
if (this.targetRooms.length === 0) {
console.log('No target rooms set');
return false;
}
const newJobs = await provider.getNewJobs();
private async sendJobUpdates(
provider: InternshipJobProvider | NewGraduateJobProvider,
room: Room,
) {
const roomTopic = await room.topic();
console.log(`Checking for new ${provider.jobType} jobs for room ${roomTopic}... now !`);

const newJobs = await provider.getNewJobs(roomTopic);
if (newJobs.length > 0) {
const messages = provider.formatJobMessages(newJobs);
for (const room of this.targetRooms) {
for (const message of messages) {
await room.say(message);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
for (const message of messages) {
await room.say(message);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return true;
} else {
console.log('No new jobs found');
console.log(`No new ${provider.jobType} jobs found for room ${roomTopic}`);
return false;
}
}
Expand Down
69 changes: 56 additions & 13 deletions src/file-system-service.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,81 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';

export class FileSystemService {
private static configDir: string = path.join(os.homedir(), '.job-wx-bot');
private static baseDir: string = path.join(os.homedir(), '.job-wx-bot');
private static roomsDir: string = path.join(FileSystemService.baseDir, 'rooms');

static initialize(): void {
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, { recursive: true });
if (!fs.existsSync(this.baseDir)) {
fs.mkdirSync(this.baseDir, { recursive: true });
}
if (!fs.existsSync(this.roomsDir)) {
fs.mkdirSync(this.roomsDir, { recursive: true });
}
}

private static getRoomHash(roomTopic: string): string {
return crypto.createHash('md5').update(roomTopic).digest('hex');
}

private static getRoomDir(roomTopic: string): string {
const roomHash = this.getRoomHash(roomTopic);
const roomDir = path.join(this.roomsDir, roomHash);
if (!fs.existsSync(roomDir)) {
fs.mkdirSync(roomDir, { recursive: true });
}
return roomDir;
}

static readFile(fileName: string): string {
const filePath = path.join(this.configDir, fileName);
static readFile(roomTopic: string, fileName: string): string {
const filePath = path.join(this.getRoomDir(roomTopic), fileName);
return fs.readFileSync(filePath, 'utf8');
}

static writeFile(fileName: string, data: string): void {
const filePath = path.join(this.configDir, fileName);
static writeFile(roomTopic: string, fileName: string, data: string): void {
const filePath = path.join(this.getRoomDir(roomTopic), fileName);
fs.writeFileSync(filePath, data);
}

static fileExists(fileName: string): boolean {
const filePath = path.join(this.configDir, fileName);
static fileExists(roomTopic: string, fileName: string): boolean {
const filePath = path.join(this.getRoomDir(roomTopic), fileName);
return fs.existsSync(filePath);
}

static readJSON<T>(fileName: string): T {
const data = this.readFile(fileName);
static readJSON<T>(roomTopic: string, fileName: string): T {
const data = this.readFile(roomTopic, fileName);
return JSON.parse(data) as T;
}

static writeJSON(fileName: string, data: any): void {
static writeJSON(roomTopic: string, fileName: string, data: any): void {
const jsonData = JSON.stringify(data, null, 2);
this.writeFile(fileName, jsonData);
this.writeFile(roomTopic, fileName, jsonData);
}

static writeGlobalFile(fileName: string, data: string): void {
const filePath = path.join(this.baseDir, fileName);
fs.writeFileSync(filePath, data);
}

static readGlobalFile(fileName: string): string {
const filePath = path.join(this.baseDir, fileName);
return fs.readFileSync(filePath, 'utf8');
}

static globalFileExists(fileName: string): boolean {
const filePath = path.join(this.baseDir, fileName);
return fs.existsSync(filePath);
}

static writeGlobalJSON(fileName: string, data: any): void {
const jsonData = JSON.stringify(data, null, 2);
this.writeGlobalFile(fileName, jsonData);
}

static readGlobalJSON<T>(fileName: string): T {
const data = this.readGlobalFile(fileName);
return JSON.parse(data) as T;
}
}
49 changes: 27 additions & 22 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,19 @@ import { WechatyBuilder, Contact, Room, Message } from 'wechaty';
import { ContactImpl } from 'wechaty/impls';
import qrcodeTerminal from 'qrcode-terminal';
import { jobWxBotConfig } from '../package.json';
import { InternJobProvider } from './providers/internship-job-provider';
import { NGJobProvider } from './providers/new-graduate-job-provider';
import path from 'path';
import os from 'os';
import * as fs from 'fs';
import { InternshipJobProvider } from './providers/internship-job-provider';
import { NewGraduateJobProvider } from './providers/new-graduate-job-provider';
import { FileSystemService } from './file-system-service';
import { TopicsLocal } from './types';
import { CommandHandler } from './command-handler';

const wechaty = WechatyBuilder.build();
let targetRooms: Room[] = [];
const internJob = new InternJobProvider(jobWxBotConfig);
const newGradJob = new NGJobProvider(jobWxBotConfig);
const internJob = new InternshipJobProvider(jobWxBotConfig);
const newGradJob = new NewGraduateJobProvider(jobWxBotConfig);

const homeDir = os.homedir();
const configDir = path.join(homeDir, '.job-wx-bot');
const registeredTopicsPath = path.join(configDir, 'registered-topics.json');

if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// Initialize FileSystemService
FileSystemService.initialize();

function displayStartupBanner() {
const banner = `
Expand Down Expand Up @@ -49,15 +42,14 @@ async function updateRegisteredTopics(validRooms: Room[]) {
const validTopics: TopicsLocal = {
topics: await Promise.all(validRooms.map(async (room) => await room.topic())),
};
fs.writeFileSync(registeredTopicsPath, JSON.stringify(validTopics, null, 2));
FileSystemService.writeGlobalJSON('registered-topics.json', validTopics);
}

async function getTargetRooms(): Promise<Room[]> {
let roomTopics = new Set(jobWxBotConfig.rooms);

if (fs.existsSync(registeredTopicsPath)) {
const data = fs.readFileSync(registeredTopicsPath, 'utf8');
const topicsLocal: TopicsLocal = JSON.parse(data);
if (FileSystemService.globalFileExists('registered-topics.json')) {
const topicsLocal = FileSystemService.readGlobalJSON<TopicsLocal>('registered-topics.json');
if (topicsLocal.topics) topicsLocal.topics.forEach((topic: string) => roomTopics.add(topic));
}

Expand Down Expand Up @@ -97,16 +89,29 @@ wechaty
'\x1b[36m%s\x1b[0m',
`🚀 ${targetRooms.length} target room(s) found. Bot is ready!`,
);

const executeCommandInAllRooms = async (command: string, silent: boolean) => {
for (const room of targetRooms) {
await commandHandler.handleCommand(
{
text: () => `@BOT ${command}`,
room: () => room,
} as any,
silent,
);
}
};

setInterval(
() => commandHandler.handleCommand({ command: 'intern' } as any),
() => executeCommandInAllRooms('intern', true),
jobWxBotConfig.minsCheckInterval * 60 * 1000,
);
setInterval(
() => commandHandler.handleCommand({ command: 'ng' } as any),
() => executeCommandInAllRooms('ng', true),
jobWxBotConfig.minsCheckInterval * 60 * 1000,
);
await commandHandler.handleCommand({ command: 'intern' } as any);
await commandHandler.handleCommand({ command: 'ng' } as any);
await executeCommandInAllRooms('intern', true);
await executeCommandInAllRooms('ng', true);
} else {
console.log('\x1b[31m%s\x1b[0m', '❌ No target rooms found. Bot cannot operate.');
}
Expand Down
6 changes: 3 additions & 3 deletions src/providers/internship-job-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { Job } from '../types';
import { BaseJobProvider } from './job-provider-base';

/**
* @class InternJobProvider
* @class InternshipJobProvider
* @extends {BaseJobProvider}
* @description Provides internship job listings for students
* @source https://github.com/SimplifyJobs/Summer2025-Internships
*/
export class InternJobProvider extends BaseJobProvider {
export class InternshipJobProvider extends BaseJobProvider {
readonly jobType = 'INTERN';
protected githubUrl =
'https://raw.githubusercontent.com/SimplifyJobs/Summer2025-Internships/dev/README.md';
Expand Down Expand Up @@ -84,7 +84,7 @@ export class InternJobProvider extends BaseJobProvider {
return role.replace(/[🛂🇺🇸🔒]/g, '').trim();
}

private cleanCompanyName(company: string): string {
protected cleanCompanyName(company: string): string {
return company.replace(/\*\*\[(.*?)\].*?\*\*/, '$1');
}

Expand Down
Loading

0 comments on commit 2532c07

Please sign in to comment.