-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
174 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import Browser from 'zombie' | ||
import EodiroMailer from '../eodiro-mailer' | ||
import { JSDOM as JsDom } from 'jsdom' | ||
import appRoot from 'app-root-path' | ||
import fs from 'fs' | ||
import wait from '../wait' | ||
|
||
export type TitleBuilder = ( | ||
/** A single notice item */ noticeItemElement: HTMLElement | Element | ||
) => string | ||
|
||
export type FeedOptions = { | ||
/** | ||
* Minutes | ||
* @default 10 | ||
*/ | ||
interval?: number | ||
} | ||
|
||
export interface Subscriber { | ||
id: string | ||
link: string | ||
noticeItemSelector: string | ||
titleBuilder: TitleBuilder | ||
} | ||
|
||
export type LastNotice = Record<string, string> | ||
|
||
const lastNoticeFilePath = appRoot.resolve('/.eodiro/last_notice.json') | ||
|
||
export class CauNoticeWatcher { | ||
private feedOptions: FeedOptions | ||
private subscribers: Subscriber[] = [] | ||
private shouldStop = false | ||
private browser: any | ||
private lastNotice: LastNotice | ||
|
||
constructor(feedOptions?: FeedOptions) { | ||
if (!feedOptions) { | ||
feedOptions = { | ||
interval: 10, | ||
} | ||
} else if (!feedOptions?.interval) { | ||
feedOptions.interval = 10 | ||
} | ||
|
||
this.feedOptions = feedOptions | ||
this.browser = new Browser() | ||
this.lastNotice = this.loadLastNoticeFile() | ||
} | ||
|
||
private loadLastNoticeFile() { | ||
let lastNotice: LastNotice | ||
|
||
if (!fs.existsSync(lastNoticeFilePath)) { | ||
lastNotice = {} | ||
fs.writeFileSync(lastNoticeFilePath, JSON.stringify(lastNotice, null, 2)) | ||
} else { | ||
lastNotice = JSON.parse(fs.readFileSync(lastNoticeFilePath, 'utf8')) | ||
} | ||
|
||
return lastNotice | ||
} | ||
|
||
private writeLastNoticeFile() { | ||
fs.writeFileSync( | ||
lastNoticeFilePath, | ||
JSON.stringify(this.lastNotice, null, 2) | ||
) | ||
} | ||
|
||
private getLastNotice(subscriber: Subscriber) { | ||
return this.lastNotice[subscriber.id] | ||
} | ||
|
||
private updateLastNotice(subscriber: Subscriber, title: string) { | ||
this.lastNotice[subscriber.id] = title | ||
} | ||
|
||
public subscribe(subscriber: Subscriber) { | ||
this.subscribers.push(subscriber) | ||
} | ||
|
||
public async watch() { | ||
if (this.shouldStop) { | ||
return | ||
} | ||
|
||
for (const subscriber of this.subscribers) { | ||
await this.processSubscriber(subscriber) | ||
} | ||
|
||
// Recursive function call after the interval | ||
await wait(this.feedOptions.interval * 60 * 1000) | ||
this.watch() | ||
} | ||
|
||
private async processSubscriber(subscriber: Subscriber) { | ||
const notices = Array.from(await this.visit(1, subscriber)) | ||
|
||
if (notices.length === 0) { | ||
return | ||
} | ||
|
||
const lastNoticeIndex = notices.indexOf(this.getLastNotice(subscriber)) | ||
if (lastNoticeIndex !== -1) { | ||
for (let i = lastNoticeIndex - 1; i >= 0; i--) { | ||
EodiroMailer.sendMail({ | ||
from: '"어디로 알림" <[email protected]>', | ||
to: '[email protected]', | ||
subject: notices[i], | ||
}) | ||
} | ||
} | ||
|
||
this.updateLastNotice(subscriber, notices[0]) | ||
this.writeLastNoticeFile() | ||
} | ||
|
||
private async visit( | ||
page: number, | ||
subscriber: Subscriber | ||
): Promise<Set<string>> { | ||
return new Promise((resolve) => { | ||
const notices: Set<string> = new Set() | ||
|
||
this.browser.visit(subscriber.link, null, () => { | ||
const body = new JsDom(this.browser.html(subscriber.noticeItemSelector)) | ||
.window.document.body | ||
const noticeElms = body.querySelectorAll(subscriber.noticeItemSelector) | ||
|
||
for (const noticeElm of Array.from(noticeElms)) { | ||
const title = subscriber.titleBuilder(noticeElm) || '' | ||
|
||
notices.add(title) | ||
} | ||
|
||
resolve(notices) | ||
}) | ||
}) | ||
} | ||
|
||
public stop() { | ||
this.shouldStop = true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Subscriber } from '..' | ||
|
||
export const cau: Subscriber = { | ||
id: 'cau', | ||
link: 'https://www.cau.ac.kr/cms/FR_CON/index.do?MENU_ID=100#page1', | ||
noticeItemSelector: '.typeNoti', | ||
titleBuilder: (noticeElm) => { | ||
const mark = noticeElm.querySelector('.mark_noti').textContent.trim() | ||
const title = noticeElm.querySelector('a').textContent.trim() | ||
|
||
return `${mark} ${title}` | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './cau' |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,10 @@ import chalk from 'chalk' | |
const log = console.log | ||
|
||
interface MailOption { | ||
/** | ||
* "name" \<[email protected]\> | ||
*/ | ||
from?: string | ||
subject: string | ||
to: string | ||
html?: string | ||
|
@@ -40,10 +44,8 @@ export default class EodiroMailer { | |
// TODO: Asynchronous | ||
static sendMail(options: MailOption): void { | ||
this.transporter.sendMail({ | ||
from: '"어디로" <[email protected]>', | ||
subject: options.subject, | ||
to: options.to, | ||
html: options.html, | ||
from: '"어디로" <[email protected]>', | ||
...options, | ||
}) | ||
} | ||
} |