Skip to content
This repository has been archived by the owner on Mar 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #40 from proshunsuke/v5
Browse files Browse the repository at this point in the history
櫻坂分を増やした
  • Loading branch information
proshunsuke authored Mar 12, 2021
2 parents ad9066c + ae70c41 commit 8ba1fec
Show file tree
Hide file tree
Showing 20 changed files with 413 additions and 42 deletions.
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ build: install
watch:
ENV=local yarn webpack watch

server:
yarn run functions-framework --target=getKeyakiSchedule --source=./gcpFunctions/getKeyakiSchedule
server/keyaki:
yarn run functions-framework --target=getKeyakiSchedule --source=./gcpFunctions/getKeyakiSchedule --port 8080

server/sakura:
yarn run functions-framework --target=getSakuraSchedule --source=./gcpFunctions/getSakuraSchedule --port 8081

run/setSchedule:
node -e 'require("./dist/index.js");global.setSchedule();'
Expand Down
62 changes: 62 additions & 0 deletions gcpFunctions/getSakuraSchedule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const puppeteer = require('puppeteer');
let page;

async function getBrowserPage() {
// Launch headless Chrome. Turn off sandbox so Chrome can run under root.
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
return browser.newPage();
}

exports.getSakuraSchedule = async (req, res) => {

if (!page) {
page = await getBrowserPage();
}

await page.goto('https://sakurazaka46.com/s/s46/media/list?dy=' + req.query['date']);

const scheduleEvents = await page.evaluate("scheduleEvents");

const scheduleDateList = Array.from(new Set(scheduleEvents.map((scheduleEvent) => scheduleEvent.start.replace(/-/g, ''))));

const result = [];

for await (const scheduleDate of scheduleDateList) {
await page.goto('https://sakurazaka46.com/s/s46/media/list?dy=' + scheduleDate);
const scheduleContexts = await page.$$('.com-arclist-part li.box');
for (const scheduleContext of scheduleContexts) {
const type = await (await scheduleContext.$('.type')).evaluate((el) => el.textContent) || 'その他';
const times = await (await scheduleContext.$('.times')).evaluate((el) => el.textContent);
const modalCount = (await (await scheduleContext.$('.js-schedule-detail-modal')).evaluate((el) => el.href)).replace(/^.*#/g, '');
const date = await (await page.$(`.${modalCount} .date.wf-a`)).evaluate((el) => el.textContent);
const title = await (await page.$(`.${modalCount} .title`)).evaluate((el) => el.textContent);
const members = await Promise.all(
(await page.$$(`.${modalCount} .members.fx > li`)).map(async (liElement) => await liElement.evaluate((el) => el.textContent))
);
const scheduleObject = {title: title, date: date, type: type};
if (times) {
const startTime = formattedDate(date, times.replace(/~.*/, ''));
const endTimeHourMin = times.replace(/.*~/, '');
const endTime = endTimeHourMin ? formattedDate(date, endTimeHourMin) : startTime;
scheduleObject.startTime = startTime;
scheduleObject.endTime = endTime;
}
if (members.length !== 0) scheduleObject.description = `メンバー: ${members.join(', ')}`;
result.push(scheduleObject);
}
}

res.send(result);
};

function formattedDate(date, hourMin) {
if ((new Date(`${date} ${hourMin}`)).toString() !== 'Invalid Date') return `${date} ${hourMin}`;
const hour = parseInt(hourMin.replace(/:.*/, ''));
const min = parseInt(hourMin.replace(/.*:/, ''));
const nextDayHour = hour - 24;
const targetDate = new Date(date);
targetDate.setDate(targetDate.getDate() + 1);
targetDate.setHours(nextDayHour);
targetDate.setMinutes(min);
return `${targetDate.getFullYear()}.${(targetDate.getMonth()+1).toString().padStart(2, '0')}.${targetDate.getDate().toString().padStart(2, '0')} ${targetDate.getHours().toString().padStart(2, '0')}:${targetDate.getMinutes().toString().padStart(2, '0')}`
}
10 changes: 9 additions & 1 deletion src/calendar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ScheduleInterface, SiteCalendarInterface } from './calendarInterface';
import Retry from './lib/retry';
import Counter from './lib/counter';

export default class Calendar {
/**
Expand All @@ -10,6 +11,8 @@ export default class Calendar {
Retry.retryable(3, () => {
event.deleteEvent();
});
const counter = Counter.getInstance;
counter.incrementDeleteEventCallCount();
if (process.env.ENV === 'production') {
Utilities.sleep(500); // API制限に引っかかってそうなのでsleepする
}
Expand Down Expand Up @@ -47,10 +50,15 @@ export default class Calendar {
} else {
CalendarApp.getCalendarById(calendarId).createAllDayEvent(
schedule.title,
new Date(schedule.date)
new Date(schedule.date),
{
description: schedule.description,
}
);
}
});
const counter = Counter.getInstance;
counter.incrementCreateEventCallCount();
console.info(
`予定を作成しました。日付: ${schedule.date}, タイトル: ${schedule.title}`
);
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ global.createSetScheduleTrigger = (): void => {

global.setSchedule = async (): Promise<void> => {
console.info('スケジュール更新を開始します');
await Schedule.setSchedule();
await Schedule.setSchedule(dayjs());
console.info('スケジュール更新が完了しました');
};

Expand Down
36 changes: 36 additions & 0 deletions src/lib/counter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export default class Counter {
private static instance: Counter;

private createEventCallCount;

private deleteEventCallCount;

private constructor() {
this.createEventCallCount = 0;
this.deleteEventCallCount = 0;
}

public static get getInstance(): Counter {
if (!this.instance) {
this.instance = new Counter();
}

return this.instance;
}

public incrementCreateEventCallCount(): void {
this.createEventCallCount += 1;
}

public getCreateEventCallCount(): number {
return this.createEventCallCount;
}

public incrementDeleteEventCallCount(): void {
this.deleteEventCallCount += 1;
}

public getDeleteEventCallCount(): number {
return this.deleteEventCallCount;
}
}
2 changes: 1 addition & 1 deletion src/lib/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default class Retry {
return func();
} catch (e) {
lastError = e;
const message = e instanceof Error ? e.message : 'error';
const { message } = e as Error;
console.info(
`エラーが起きました。リトライを行います。message: ${message}, リトライ${i.toString()}回目`
);
Expand Down
27 changes: 23 additions & 4 deletions src/lib/trigger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import dayjs from 'dayjs';

const TARGET_DATE_KEY = 'target_date';
const TARGET_SITE_NAME_KEY = 'site_name';
export const TERMINATION_MINUTES = 4;
const TRIGGER_FUNCTION_NAME = 'setSchedule';
const TRIGGER_DURATION: number = 60 * 1000; // 1分後
const TRIGGER_DURATION: number = 60 * 2000; // 2分後

export default class Trigger {
/**
Expand All @@ -18,11 +19,13 @@ export default class Trigger {
/**
*
* @param {dayjs.Dayjs} targetDate
* @param {string} siteName
*/
static setTrigger(targetDate: dayjs.Dayjs): void {
static setTrigger(targetDate: dayjs.Dayjs, siteName?: string): void {
if (process.env.ENV !== 'production') return;
const properties = PropertiesService.getScriptProperties();
properties.setProperty(TARGET_DATE_KEY, targetDate.format('YYYY-MM-DD'));
if (siteName) properties.setProperty(TARGET_SITE_NAME_KEY, siteName);
ScriptApp.newTrigger(TRIGGER_FUNCTION_NAME)
.timeBased()
.after(TRIGGER_DURATION)
Expand All @@ -35,16 +38,32 @@ export default class Trigger {
*/
static getTargetDateProperty(): string | null {
if (process.env.ENV !== 'production') return null;
const properties: GoogleAppsScript.Properties.Properties = PropertiesService.getScriptProperties();
const properties = PropertiesService.getScriptProperties();
return properties.getProperty(TARGET_DATE_KEY);
}

static deleteTargetDateProperty(): void {
if (process.env.ENV !== 'production') return;
const properties: GoogleAppsScript.Properties.Properties = PropertiesService.getScriptProperties();
const properties = PropertiesService.getScriptProperties();
properties.deleteProperty(TARGET_DATE_KEY);
}

/**
*
* @returns {string | null}
*/
static getTargetSiteNameProperty(): string | null {
if (process.env.ENV !== 'production') return null;
const properties = PropertiesService.getScriptProperties();
return properties.getProperty(TARGET_SITE_NAME_KEY);
}

static deleteTargetSiteNameProperty(): void {
if (process.env.ENV !== 'production') return;
const properties = PropertiesService.getScriptProperties();
properties.deleteProperty(TARGET_SITE_NAME_KEY);
}

static deleteTriggers(): void {
if (process.env.ENV !== 'production') return;
ScriptApp.getProjectTriggers().forEach(
Expand Down
19 changes: 7 additions & 12 deletions src/oneMonthSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Calendar from './calendar';
import { ScheduleInterface, SiteCalendarInterface } from './calendarInterface';
import Retry from './lib/retry';
import 'regenerator-runtime';
import Counter from './lib/counter';

export default class OneMonthSchedule {
/**
Expand Down Expand Up @@ -57,7 +58,7 @@ export default class OneMonthSchedule {
date: dayjs.Dayjs,
siteCalendarIds: SiteCalendarInterface[]
) {
let deleteEventCallCount = 0;
const counter = Counter.getInstance;
siteCalendarIds.forEach((siteCalendarId) => {
if (process.env.ENV !== 'production') return;
const calendarApp = Retry.retryable(3, () =>
Expand All @@ -70,12 +71,11 @@ export default class OneMonthSchedule {
const events = calendarApp.getEventsForDay(targetDate.toDate());
// eslint-disable-next-line @typescript-eslint/no-loop-func
events.forEach((event) => {
deleteEventCallCount += 1;
try {
Calendar.deleteEvent(event);
} catch (e) {
console.error(
`カレンダー削除に失敗しました。失敗するまでに実行された回数: ${deleteEventCallCount.toString()}`
`カレンダー削除に失敗しました。失敗するまでに実行された回数: ${counter.getDeleteEventCallCount()}`
);
throw e;
}
Expand All @@ -86,7 +86,7 @@ export default class OneMonthSchedule {
console.info(
`${date.format(
'YYYY年MM月'
)}分のカレンダー削除実行回数${deleteEventCallCount.toString()}`
)}分時点のカレンダー削除実行回数${counter.getDeleteEventCallCount()}`
);
}

Expand All @@ -101,22 +101,17 @@ export default class OneMonthSchedule {
date: dayjs.Dayjs,
calendarIds: SiteCalendarInterface[]
) {
let createEventCallCount = 0;
const counter = Counter.getInstance;
scheduleList.forEach((schedule: ScheduleInterface) => {
createEventCallCount += 1;
try {
Calendar.createEvent(schedule, calendarIds);
} catch (e) {
console.error(
`カレンダー作成に失敗しました。失敗するまでに実行された回数: ${createEventCallCount.toString()}`
`カレンダー作成に失敗しました。失敗するまでに実行された回数: ${counter.getCreateEventCallCount()}`
);
throw e;
}
});
console.info(
`${date.format(
'YYYY年MM月'
)}分のカレンダー作成実行回数: ${createEventCallCount.toString()}`
);
console.info(`${date.format('YYYY年MM月')}分時点のカレンダー作成回数: ${counter.getCreateEventCallCount()}`);
}
}
8 changes: 5 additions & 3 deletions src/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import dayjs from 'dayjs';
import KeyakiSiteSchedule from './sites/keyakizaka/keyakiSiteSchedule';
import SakuraSiteSchedule from './sites/sakurazaka/sakuraSiteSchedule';
import { SiteScheduleInterface } from './sites/siteSchedule';

export default class Schedule {
static async setSchedule(): Promise<void> {
static async setSchedule(startDate: dayjs.Dayjs): Promise<void> {
const siteScheduleList: SiteScheduleInterface[] = [
new KeyakiSiteSchedule(),
// new KeyakiSiteSchedule
new SakuraSiteSchedule(),
];
for await (const siteSchedule of siteScheduleList) {
await siteSchedule.setSiteSchedule();
await siteSchedule.setSiteSchedule(startDate);
}
}
}
5 changes: 5 additions & 0 deletions src/sites/keyakizaka/keyakiSiteSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ export default class KeyakiSiteSchedule extends SiteSchedule {
siteCalendarIds(): SiteCalendarInterface[] {
return keyakiCalendarIds;
}

// eslint-disable-next-line class-methods-use-this
siteName(): string {
return 'keyakizaka';
}
}
57 changes: 57 additions & 0 deletions src/sites/sakurazaka/sakuraObjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { SiteCalendarInterface } from '../../calendarInterface';

export const getSakuraCalendarUrl =
process.env.ENV === 'production'
? 'https://asia-northeast1-augc-260709.cloudfunctions.net/getSakuraSchedule?date='
: 'http://localhost:8081?date=';

export const sakuraCalendarIds: SiteCalendarInterface[] = [
{
type: 'アルバム',
calendarId: '0ivcdulcqpm0majeaqo0f1bml8@group.calendar.google.com',
},
{
type: 'イベント',
calendarId: 'ulksj6q2hr6hvvre7jqk2rghe4@group.calendar.google.com',
},
{
type: 'シングル',
calendarId: 'rf2qon3acq2g8fj1iuvngmp7tg@group.calendar.google.com',
},
{
type: 'その他',
calendarId: '06ol8jcjk0r5bviarevjicta70@group.calendar.google.com',
},
{
type: 'テレビ',
calendarId: '14elrf80nstbrahsfe2iuem8fg@group.calendar.google.com',
},
{
type: 'メディア',
calendarId: 'ivej29993ugnjb20l077n233i4@group.calendar.google.com',
},
{
type: 'ラジオ',
calendarId: 'f01lrnkgl42eqfbs1k97u7mrdc@group.calendar.google.com',
},
{
type: 'リリース',
calendarId: 'oo8hkuk4udrflu06337hq42jqo@group.calendar.google.com',
},
{
type: '映画',
calendarId: 'hbflgqvcrjvd9c1a07q5t93ork@group.calendar.google.com',
},
{
type: '雑誌',
calendarId: '2veim8rg9o7k2js0jtng8i2dug@group.calendar.google.com',
},
{
type: '新聞',
calendarId: 'g3puqreu4a67quqqu7ueo58l5k@group.calendar.google.com',
},
{
type: '誕生日',
calendarId: '02mgt618voeueel3gonuc62nrs@group.calendar.google.com',
},
];
Loading

0 comments on commit 8ba1fec

Please sign in to comment.