Fazendaaa/AnilistBot

View on GitHub
src/lib/schedule/notifications.ts

Summary

Maintainability
A
35 mins
Test Coverage
// tslint:disable: no-submodule-imports -- Just using it this way because of lack of support yet -- my intention is to work on it.
import { config } from 'dotenv';
import { internationalization } from 'main';
import { Job, scheduleJob } from 'node-schedule';
import Telegram from 'telegraf/telegram';
import { IReduceLanguage, IUserMessage, IUsersLanguage } from '.';
import { fetchNewRelease } from '../anilist/requests/newRelease';
import { IContentInfo, IDBLaterNotificationsInfo } from '../database/notifications';
import { addLaterNotifications, fetchLaterNotifications, fetchMediaNotifications, updateMediaNotifications
} from '../database/notifications/notifications';
import { INotifySubscribersResponse } from '../database/subscriptions';
import { fetchNotifySubscribers } from '../database/subscriptions/subscription';
import { IDBUserInfo } from '../database/user';
import { userInfo } from '../database/user/user';
import { handleMediaNotifyExtra } from '../telegram/extra/media';
import { dailyNotificationExtra } from '../telegram/extra/notification';
import { handleNewRelease, handleUserRelease } from '../telegram/parse/media';

config();

const telegram = new Telegram(process.env.BOT_KEY);
const eachHour = '0 0 0-23 * * *';
const eachHalfHour = '0 0,30 * * * *';

const reduceLanguage = async (reduce: IReduceLanguage, acc: Promise<IUsersLanguage>, cur: number): Promise<IUsersLanguage> => {
    return acc.then(async (newAcc) => {
        const { content_id, kind } = reduce;
        const { _id, time, language } = <IDBUserInfo> await userInfo(cur);
        const toNotifyLanguage = ('' !== language) ? language : 'en';

        if (undefined === newAcc[toNotifyLanguage]) {
            newAcc[toNotifyLanguage] = [];
        } if (null !== time) {
            addLaterNotifications({ _id, content_id, kind });
        } else {
            newAcc[toNotifyLanguage].push(_id);
        }

        return newAcc;
    });
};

const sendMedia = async ({ kind, users, content_id }: INotifySubscribersResponse): Promise<boolean> => {
    const translation = internationalization;
    const content = (true === kind) ? 'ANIME' : 'MANGA';
    const curriedReduceLanguage = (async (acc: Promise<IUsersLanguage>, cur: number) => reduceLanguage({ content_id, kind }, acc, cur));
    const usersLanguages = await users.reduce(curriedReduceLanguage, Promise.resolve({}));
    const media = await fetchNewRelease({ id: content_id, content });

    Object.keys(usersLanguages).map(async (language) => {
        const usersToNotify = usersLanguages[language];
        const message = handleNewRelease({ media, language, translation });
        const extra = handleMediaNotifyExtra({ id: content_id, request: content, language, translation });

        usersToNotify.map(async (userId) => telegram.sendMessage(userId, message, extra));
    });

    return true;
};

const userMessage = async (user: IUserMessage, acc: Promise<string>, cur: IContentInfo): Promise<string> => acc.then(async (total) => {
    const { content_id, kind } = cur;
    const { language, translation } = user;
    const content = (true === kind) ? 'ANIME' : 'MANGA';
    const data = await fetchNewRelease({ id: content_id, content });

    return `${total}\n${handleUserRelease({ media: data, language, translation })}`;
});

const sendUser = async ({ _id, media }: IDBLaterNotificationsInfo): Promise<string> => {
    if (media.length > 0) {
        const translation = internationalization;
        const { language } = <IDBUserInfo> await userInfo(_id);
        const toNotifyLanguage = ('' !== language) ? language : 'en';
        const curriedUserMessage = (async (acc: Promise<string>, cur: IContentInfo) => {
            return userMessage({ language: toNotifyLanguage, translation }, acc, cur);
        });
        const message = await media.reduce(curriedUserMessage, Promise.resolve(translation.t(toNotifyLanguage, 'userReleaseHeader')));

        return telegram.sendMessage(_id, message, dailyNotificationExtra({ language: toNotifyLanguage, translation }));
    }
};

export const mediaSchedule = (): Job => scheduleJob('Sending content upon release notification.', eachHalfHour, async () => {
    // Only 'true' because only Animes are currently supported to notify upon releases.
    const kind = true;
    const released = await fetchMediaNotifications({ kind });
    const mediaSubscribers = await Promise.all(released.map(async ({ _id }) => fetchNotifySubscribers({ kind, content_id: _id })));

    mediaSubscribers.map(sendMedia);

    updateMediaNotifications({ kind });
});

export const userSchedule = (): Job => scheduleJob('Sending content to the user desired time.', eachHour, async () => {
    const kind = true;
    const usersToNotify = await fetchLaterNotifications({ kind });

    usersToNotify.map(sendUser);
});