From eaca399926e4f16ea3497998b9c971b62145fb8d Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:47:17 +0900 Subject: [PATCH 01/17] =?UTF-8?q?cal=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 11 +++++++++++ packages/misc/index.js | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 packages/misc/commands/cal.ts diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts new file mode 100644 index 00000000..8c61a041 --- /dev/null +++ b/packages/misc/commands/cal.ts @@ -0,0 +1,11 @@ +import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; + +export default SimpleSlashCommandBuilder.create( + 'cal', + 'カレンダーを表示します', +).build(async (interaction) => { + await interaction.reply({ + content: '開発中', + ephemeral: true, + }); +}); diff --git a/packages/misc/index.js b/packages/misc/index.js index 991ce3b2..9975ead2 100644 --- a/packages/misc/index.js +++ b/packages/misc/index.js @@ -10,7 +10,10 @@ class MiscFeature { }).forEach((file) => { const ext = path.extname(file.name); if (!file.isFile() || (ext != '.js' && ext != '.ts')) return; - const cmds = require(path.join(__dirname, 'commands', file.name)); + let cmds = require(path.join(__dirname, 'commands', file.name)); + if ('default' in cmds) { + cmds = cmds.default; + } CommandManager.default.addCommands(cmds); }); } From a159f62730f6225d471716ba9d2b2d0686cf09ec Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:34:02 +0900 Subject: [PATCH 02/17] =?UTF-8?q?1=E5=88=97=E3=81=AE=E3=82=AB=E3=83=AC?= =?UTF-8?q?=E3=83=B3=E3=83=80=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 31 ++++++++++++++++++++--- packages/misc/util/calendar.ts | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 packages/misc/util/calendar.ts diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 8c61a041..b7c72fc4 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,11 +1,34 @@ import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; +import { Day, MonthCalendar } from '../util/calendar'; export default SimpleSlashCommandBuilder.create( 'cal', 'カレンダーを表示します', ).build(async (interaction) => { - await interaction.reply({ - content: '開発中', - ephemeral: true, - }); + const calendar = new MonthCalendar(); + const dates = []; + const size = calendar.size; + for (let i = 1; i <= size; i++) { + dates.push(`${dayToString(calendar.dayOf(i))} ${String(i).padStart(2)}`); + } + await interaction.reply('```' + dates.join('\n') + '```'); }); + +function dayToString(day: Day) { + switch (day) { + case Day.Sunday: + return 'Su'; + case Day.Monday: + return 'Mo'; + case Day.Tuesday: + return 'Tu'; + case Day.Wednesday: + return 'We'; + case Day.Thursday: + return 'Th'; + case Day.Friday: + return 'Fr'; + case Day.Saturday: + return 'Sa'; + } +} diff --git a/packages/misc/util/calendar.ts b/packages/misc/util/calendar.ts new file mode 100644 index 00000000..637ee4be --- /dev/null +++ b/packages/misc/util/calendar.ts @@ -0,0 +1,45 @@ +export const Day = Object.freeze({ + Sunday: 0, + Monday: 1, + Tuesday: 2, + Wednesday: 3, + Thursday: 4, + Friday: 5, + Saturday: 6, +}); + +export type Day = (typeof Day)[keyof typeof Day]; + +export class MonthCalendar { + public readonly month: number; + + public readonly year: number; + + public readonly size: number; + + /** 0日 (1日の前日) の曜日 */ + private readonly baseDay: Day; + + /** + * 月のカレンダーを作成する。 + * @param monthIndex 0始まりの月の番号 (0~11) + * @param year 西暦 + */ + constructor(monthIndex?: number, year?: number) { + if (year == null) { + const date = new Date(); + year = date.getFullYear(); + if (monthIndex == null) { + monthIndex = date.getMonth(); + } + } + this.month = monthIndex; + this.year = year; + this.size = new Date(year, monthIndex + 1, 0).getDate(); + this.baseDay = new Date(year, monthIndex, 0).getDay() as Day; + } + + dayOf(date: number): Day { + return ((this.baseDay + date) % 7) as Day; + } +} From 05e54fa0f923fd828e74ca4d8e8552d72bfd0da8 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:37:56 +0900 Subject: [PATCH 03/17] =?UTF-8?q?Day=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 23 +++++++++++------------ packages/misc/util/calendar.ts | 33 +++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index b7c72fc4..a9993436 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,5 +1,5 @@ import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; -import { Day, MonthCalendar } from '../util/calendar'; +import { DayOfWeek, MonthCalendar } from '../util/calendar'; export default SimpleSlashCommandBuilder.create( 'cal', @@ -7,28 +7,27 @@ export default SimpleSlashCommandBuilder.create( ).build(async (interaction) => { const calendar = new MonthCalendar(); const dates = []; - const size = calendar.size; - for (let i = 1; i <= size; i++) { - dates.push(`${dayToString(calendar.dayOf(i))} ${String(i).padStart(2)}`); + for (const day of calendar.days()) { + dates.push(`${dayToString(day.day)} ${String(day.date).padStart(2)}`); } await interaction.reply('```' + dates.join('\n') + '```'); }); -function dayToString(day: Day) { +function dayToString(day: DayOfWeek) { switch (day) { - case Day.Sunday: + case DayOfWeek.Sunday: return 'Su'; - case Day.Monday: + case DayOfWeek.Monday: return 'Mo'; - case Day.Tuesday: + case DayOfWeek.Tuesday: return 'Tu'; - case Day.Wednesday: + case DayOfWeek.Wednesday: return 'We'; - case Day.Thursday: + case DayOfWeek.Thursday: return 'Th'; - case Day.Friday: + case DayOfWeek.Friday: return 'Fr'; - case Day.Saturday: + case DayOfWeek.Saturday: return 'Sa'; } } diff --git a/packages/misc/util/calendar.ts b/packages/misc/util/calendar.ts index 637ee4be..c445d5c9 100644 --- a/packages/misc/util/calendar.ts +++ b/packages/misc/util/calendar.ts @@ -1,4 +1,4 @@ -export const Day = Object.freeze({ +export const DayOfWeek = Object.freeze({ Sunday: 0, Monday: 1, Tuesday: 2, @@ -8,7 +8,25 @@ export const Day = Object.freeze({ Saturday: 6, }); -export type Day = (typeof Day)[keyof typeof Day]; +export type DayOfWeek = (typeof DayOfWeek)[keyof typeof DayOfWeek]; + +export class Day { + public readonly year: number; + + public readonly month: number; + + public readonly date: number; + + public readonly day: DayOfWeek; + + constructor(year: number, monthIndex: number, date: number) { + const dateObj = new Date(year, monthIndex, date); + this.year = dateObj.getFullYear(); + this.month = dateObj.getMonth(); + this.date = dateObj.getDate(); + this.day = dateObj.getDay() as DayOfWeek; + } +} export class MonthCalendar { public readonly month: number; @@ -17,9 +35,6 @@ export class MonthCalendar { public readonly size: number; - /** 0日 (1日の前日) の曜日 */ - private readonly baseDay: Day; - /** * 月のカレンダーを作成する。 * @param monthIndex 0始まりの月の番号 (0~11) @@ -36,10 +51,12 @@ export class MonthCalendar { this.month = monthIndex; this.year = year; this.size = new Date(year, monthIndex + 1, 0).getDate(); - this.baseDay = new Date(year, monthIndex, 0).getDay() as Day; } - dayOf(date: number): Day { - return ((this.baseDay + date) % 7) as Day; + *days(): Iterable { + const size = this.size; + for (let date = 1; date <= size; date++) { + yield new Day(this.year, this.month, date); + } } } From 18acbd805a9232f1f5c1bfe1af29b29887816b1e Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:18:36 +0900 Subject: [PATCH 04/17] =?UTF-8?q?=E8=A1=A8=E5=BD=A2=E5=BC=8F=E3=81=AE?= =?UTF-8?q?=E3=82=AB=E3=83=AC=E3=83=B3=E3=83=80=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 36 +++++++++++----------------------- packages/misc/util/calendar.ts | 32 ++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index a9993436..c5afaae8 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,33 +1,19 @@ import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; -import { DayOfWeek, MonthCalendar } from '../util/calendar'; +import { MonthCalendar } from '../util/calendar'; export default SimpleSlashCommandBuilder.create( 'cal', 'カレンダーを表示します', ).build(async (interaction) => { const calendar = new MonthCalendar(); - const dates = []; - for (const day of calendar.days()) { - dates.push(`${dayToString(day.day)} ${String(day.date).padStart(2)}`); - } - await interaction.reply('```' + dates.join('\n') + '```'); + const days = Array.from(calendar.weeks()) + .map((week) => + week + .map((day) => + calendar.includes(day) ? String(day.date).padStart(2) : ' ', + ) + .join(' '), + ) + .join('\n'); + await interaction.reply('```Su Mo Tu We Th Fr Sa\n' + days + '```'); }); - -function dayToString(day: DayOfWeek) { - switch (day) { - case DayOfWeek.Sunday: - return 'Su'; - case DayOfWeek.Monday: - return 'Mo'; - case DayOfWeek.Tuesday: - return 'Tu'; - case DayOfWeek.Wednesday: - return 'We'; - case DayOfWeek.Thursday: - return 'Th'; - case DayOfWeek.Friday: - return 'Fr'; - case DayOfWeek.Saturday: - return 'Sa'; - } -} diff --git a/packages/misc/util/calendar.ts b/packages/misc/util/calendar.ts index c445d5c9..2aadaa91 100644 --- a/packages/misc/util/calendar.ts +++ b/packages/misc/util/calendar.ts @@ -26,8 +26,14 @@ export class Day { this.date = dateObj.getDate(); this.day = dateObj.getDay() as DayOfWeek; } + + add(days: number) { + return new Day(this.year, this.month, this.date + days); + } } +export type Week = { [K in DayOfWeek]: Day } & Array; + export class MonthCalendar { public readonly month: number; @@ -53,10 +59,28 @@ export class MonthCalendar { this.size = new Date(year, monthIndex + 1, 0).getDate(); } - *days(): Iterable { - const size = this.size; - for (let date = 1; date <= size; date++) { - yield new Day(this.year, this.month, date); + firstDay() { + return new Day(this.year, this.month, 1); + } + + includes(day: Day) { + if (day.year == this.year && day.month == this.month) { + return true; } } + + *weeks(): Iterable { + const monthFirst = this.firstDay(); + let weekFirst = monthFirst.add(-monthFirst.day); + do { + const week = [weekFirst]; + let day = weekFirst; + for (let i = 1; i < 7; i++) { + day = day.add(1); + week[i] = day; + } + yield week as Week; + weekFirst = weekFirst.add(7); + } while (this.includes(weekFirst)); + } } From 7e3c092b4b5004fee3ed0ba7895f9a39584d505f Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:10:13 +0900 Subject: [PATCH 05/17] =?UTF-8?q?=E8=A8=80=E8=AA=9E=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=82=92=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- language/default.json | 5 +++++ packages/misc/commands/cal.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/language/default.json b/language/default.json index 39fdeccc..54f56542 100644 --- a/language/default.json +++ b/language/default.json @@ -117,6 +117,11 @@ } }, "commands": { + "cal": { + "name": "cal", + "description": "カレンダーを表示します", + "dayLabels": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] + }, "check": { "name": "check", "description": "Ping Checker", diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index c5afaae8..9e329a60 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,9 +1,10 @@ import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; +import { LANG } from '../../../util/languages'; import { MonthCalendar } from '../util/calendar'; export default SimpleSlashCommandBuilder.create( - 'cal', - 'カレンダーを表示します', + LANG.commands.cal.name, + LANG.commands.cal.description, ).build(async (interaction) => { const calendar = new MonthCalendar(); const days = Array.from(calendar.weeks()) @@ -15,5 +16,7 @@ export default SimpleSlashCommandBuilder.create( .join(' '), ) .join('\n'); - await interaction.reply('```Su Mo Tu We Th Fr Sa\n' + days + '```'); + await interaction.reply( + '```' + LANG.commands.cal.dayLabels.join(' ') + '\n' + days + '```', + ); }); From fe168050681fb8a269a6d2308a0daaea2866ffe3 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:23:23 +0900 Subject: [PATCH 06/17] =?UTF-8?q?canvas=E3=81=AB=E6=8F=8F=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 40 +++++++++++++++++------- packages/misc/util/canvasUtils.ts | 52 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 packages/misc/util/canvasUtils.ts diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 9e329a60..38d8f6ef 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,22 +1,40 @@ +import { createCanvas } from 'canvas'; import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; import { LANG } from '../../../util/languages'; import { MonthCalendar } from '../util/calendar'; +import { BoundingBox, CanvasTable } from '../util/canvasUtils'; export default SimpleSlashCommandBuilder.create( LANG.commands.cal.name, LANG.commands.cal.description, ).build(async (interaction) => { const calendar = new MonthCalendar(); - const days = Array.from(calendar.weeks()) - .map((week) => - week - .map((day) => - calendar.includes(day) ? String(day.date).padStart(2) : ' ', - ) - .join(' '), - ) - .join('\n'); - await interaction.reply( - '```' + LANG.commands.cal.dayLabels.join(' ') + '\n' + days + '```', + const days = Array.from(calendar.weeks()).map((week) => + week.map((day) => day.date.toString().padStart(2)), ); + const table = [LANG.commands.cal.dayLabels, ...days]; + console.log(table); + const canvas = createCanvas(800, 400); + const ctx = canvas.getContext('2d'); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, 800, 400); + const canvasTable = new CanvasTable(table, new BoundingBox(0, 0, 800, 400)); + canvasTable.color = 'black'; + canvasTable.renderTo(ctx); + await interaction.reply({ + files: [ + { + attachment: canvas.toBuffer(), + name: 'calendar.png', + }, + ], + embeds: [ + { + title: 'カレンダー', + image: { + url: 'attachment://calendar.png', + }, + }, + ], + }); }); diff --git a/packages/misc/util/canvasUtils.ts b/packages/misc/util/canvasUtils.ts new file mode 100644 index 00000000..dd709c32 --- /dev/null +++ b/packages/misc/util/canvasUtils.ts @@ -0,0 +1,52 @@ +import { CanvasRenderingContext2D } from 'canvas'; +import { formatTable } from '../../../util/strings'; + +function requireNonnegative(x: number, name: string): number { + if (x < 0) { + throw new RangeError(`${name} must not be less than ${x}`); + } + return x; +} + +export class BoundingBox { + readonly x: number; + + readonly y: number; + + readonly width: number; + + readonly height: number; + + constructor(x: number, y: number, width: number, height: number) { + this.x = requireNonnegative(x, 'x'); + this.y = requireNonnegative(y, 'y'); + this.width = requireNonnegative(width, 'width'); + this.height = requireNonnegative(height, 'height'); + } +} + +export class CanvasTable { + private cells: string[][]; + + private boundingBox: BoundingBox; + + public color: string; + + constructor(cells: string[][], boundingBox: BoundingBox) { + this.cells = cells; + this.boundingBox = boundingBox; + } + + renderTo(ctx: CanvasRenderingContext2D) { + ctx.save(); + ctx.font = '48px monospace'; + ctx.fillStyle = this.color; + ctx.textBaseline = 'top'; + ctx.fillText( + formatTable(this.cells), + this.boundingBox.x, + this.boundingBox.y, + ); + ctx.restore(); + } +} From 1a94feead461208f915a7a9dacaaf5b51cfd8113 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:24:26 +0900 Subject: [PATCH 07/17] =?UTF-8?q?=E3=83=AD=E3=82=B0=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 38d8f6ef..4a26095d 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -13,7 +13,6 @@ export default SimpleSlashCommandBuilder.create( week.map((day) => day.date.toString().padStart(2)), ); const table = [LANG.commands.cal.dayLabels, ...days]; - console.log(table); const canvas = createCanvas(800, 400); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'white'; From 3f010a35e9bb87b4bf8c4ff2c658b9930d7c842d Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:09:56 +0900 Subject: [PATCH 08/17] =?UTF-8?q?=E5=9D=87=E7=AD=89=E3=81=AB=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/util/canvasUtils.ts | 57 +++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/misc/util/canvasUtils.ts b/packages/misc/util/canvasUtils.ts index dd709c32..a401c622 100644 --- a/packages/misc/util/canvasUtils.ts +++ b/packages/misc/util/canvasUtils.ts @@ -25,28 +25,63 @@ export class BoundingBox { } } -export class CanvasTable { - private cells: string[][]; +export class CanvasTextBox { + public text: string; - private boundingBox: BoundingBox; + public boundingBox: BoundingBox; public color: string; - constructor(cells: string[][], boundingBox: BoundingBox) { - this.cells = cells; + constructor(text: string, boundingBox: BoundingBox) { + this.text = text; this.boundingBox = boundingBox; } renderTo(ctx: CanvasRenderingContext2D) { ctx.save(); - ctx.font = '48px monospace'; + ctx.font = '24px serif'; ctx.fillStyle = this.color; ctx.textBaseline = 'top'; - ctx.fillText( - formatTable(this.cells), - this.boundingBox.x, - this.boundingBox.y, - ); + ctx.fillText(this.text, this.boundingBox.x, this.boundingBox.y); ctx.restore(); } } + +export class CanvasTable { + private cells: CanvasTextBox[][]; + + private boundingBox: BoundingBox; + + public color: string; + + constructor(cells: string[][], boundingBox: BoundingBox) { + const rowCount = cells.length; + const columnCount = cells[0].length; + const cellWidth = boundingBox.width / columnCount; + const cellHeight = boundingBox.height / rowCount; + this.cells = cells.map((row, i) => + row.map( + (cell, j) => + new CanvasTextBox( + cell, + new BoundingBox( + boundingBox.x + cellWidth * j, + boundingBox.y + cellHeight * i, + boundingBox.width, + boundingBox.height, + ), + ), + ), + ); + this.boundingBox = boundingBox; + } + + renderTo(ctx: CanvasRenderingContext2D) { + for (const row of this.cells) { + for (const cell of row) { + cell.color = this.color; + cell.renderTo(ctx); + } + } + } +} From 403fc5a0d9aca084d62704cd3f994176829da0a6 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 19:06:37 +0900 Subject: [PATCH 09/17] =?UTF-8?q?=E6=9B=9C=E6=97=A5=E3=81=AE=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 35 +++++++++++++++++++++------ packages/misc/util/canvasUtils.ts | 40 +++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 4a26095d..bcbec594 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,23 +1,44 @@ import { createCanvas } from 'canvas'; import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; import { LANG } from '../../../util/languages'; -import { MonthCalendar } from '../util/calendar'; -import { BoundingBox, CanvasTable } from '../util/canvasUtils'; +import { DayOfWeek, MonthCalendar } from '../util/calendar'; +import { BoundingBox, CanvasTable, InlineText } from '../util/canvasUtils'; + +function dayColor(day: DayOfWeek) { + switch (day) { + case DayOfWeek.Sunday: + return 'red'; + case DayOfWeek.Saturday: + return 'blue'; + default: + return 'black'; + } +} export default SimpleSlashCommandBuilder.create( LANG.commands.cal.name, LANG.commands.cal.description, ).build(async (interaction) => { const calendar = new MonthCalendar(); - const days = Array.from(calendar.weeks()).map((week) => - week.map((day) => day.date.toString().padStart(2)), - ); - const table = [LANG.commands.cal.dayLabels, ...days]; + const table = [ + LANG.commands.cal.dayLabels.map((e, i) => { + const text = new InlineText(e); + text.color = dayColor(i as DayOfWeek); + return text; + }), + ...Array.from(calendar.weeks()).map((week) => + week.map((day) => { + const text = new InlineText(day.date.toString().padStart(2)); + text.color = dayColor(day.day); + return text; + }), + ), + ]; const canvas = createCanvas(800, 400); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, 800, 400); - const canvasTable = new CanvasTable(table, new BoundingBox(0, 0, 800, 400)); + const canvasTable = new CanvasTable(table, new BoundingBox(50, 50, 750, 350)); canvasTable.color = 'black'; canvasTable.renderTo(ctx); await interaction.reply({ diff --git a/packages/misc/util/canvasUtils.ts b/packages/misc/util/canvasUtils.ts index a401c622..2547239f 100644 --- a/packages/misc/util/canvasUtils.ts +++ b/packages/misc/util/canvasUtils.ts @@ -1,4 +1,4 @@ -import { CanvasRenderingContext2D } from 'canvas'; +import { CanvasRenderingContext2D, CanvasTextBaseline } from 'canvas'; import { formatTable } from '../../../util/strings'; function requireNonnegative(x: number, name: string): number { @@ -25,24 +25,45 @@ export class BoundingBox { } } -export class CanvasTextBox { +export class InlineText { public text: string; - public boundingBox: BoundingBox; + public color: string = 'black'; - public color: string; + public font: string = '24px serif'; - constructor(text: string, boundingBox: BoundingBox) { + constructor(text: string) { + this.text = text; + } + + renderTo( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + maxWidth?: number, + ) { + ctx.save(); + ctx.fillStyle = this.color; + ctx.font = this.font; + ctx.fillText(this.text, x, y, maxWidth); + ctx.restore(); + } +} + +export class CanvasTextBox { + public text: InlineText; + + public boundingBox: BoundingBox; + + constructor(text: InlineText, boundingBox: BoundingBox) { this.text = text; this.boundingBox = boundingBox; } renderTo(ctx: CanvasRenderingContext2D) { ctx.save(); - ctx.font = '24px serif'; - ctx.fillStyle = this.color; ctx.textBaseline = 'top'; - ctx.fillText(this.text, this.boundingBox.x, this.boundingBox.y); + this.text.renderTo(ctx, this.boundingBox.x, this.boundingBox.y); ctx.restore(); } } @@ -54,7 +75,7 @@ export class CanvasTable { public color: string; - constructor(cells: string[][], boundingBox: BoundingBox) { + constructor(cells: InlineText[][], boundingBox: BoundingBox) { const rowCount = cells.length; const columnCount = cells[0].length; const cellWidth = boundingBox.width / columnCount; @@ -79,7 +100,6 @@ export class CanvasTable { renderTo(ctx: CanvasRenderingContext2D) { for (const row of this.cells) { for (const cell of row) { - cell.color = this.color; cell.renderTo(ctx); } } From f3c9378ac30368645d8cf801d2ebba270e39d79d Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 19:52:53 +0900 Subject: [PATCH 10/17] =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- language/default.json | 17 ++++++++++++++++- packages/misc/commands/cal.ts | 7 +++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/language/default.json b/language/default.json index 54f56542..ccb8e974 100644 --- a/language/default.json +++ b/language/default.json @@ -120,7 +120,22 @@ "cal": { "name": "cal", "description": "カレンダーを表示します", - "dayLabels": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] + "dayLabels": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], + "monthNames": [ + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月" + ], + "monthYear": "${year}年${month}" }, "check": { "name": "check", diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index bcbec594..01299478 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -1,6 +1,6 @@ import { createCanvas } from 'canvas'; import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; -import { LANG } from '../../../util/languages'; +import { LANG, strFormat } from '../../../util/languages'; import { DayOfWeek, MonthCalendar } from '../util/calendar'; import { BoundingBox, CanvasTable, InlineText } from '../util/canvasUtils'; @@ -50,7 +50,10 @@ export default SimpleSlashCommandBuilder.create( ], embeds: [ { - title: 'カレンダー', + title: strFormat(LANG.commands.cal.monthYear, { + month: LANG.commands.cal.monthNames[calendar.month], + year: calendar.year, + }), image: { url: 'attachment://calendar.png', }, From 8842612a76d0e8ecfef0481bb577dfdb0d6c3c27 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:11:08 +0900 Subject: [PATCH 11/17] =?UTF-8?q?CanvasTextBox=E5=86=85=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=AD=E3=82=B9=E3=83=88=E4=BD=8D=E7=BD=AE=E3=82=92=E8=AA=BF?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 7 +++-- packages/misc/util/canvasUtils.ts | 45 +++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 01299478..af6c32c6 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -28,7 +28,7 @@ export default SimpleSlashCommandBuilder.create( }), ...Array.from(calendar.weeks()).map((week) => week.map((day) => { - const text = new InlineText(day.date.toString().padStart(2)); + const text = new InlineText(day.date.toString()); text.color = dayColor(day.day); return text; }), @@ -36,9 +36,8 @@ export default SimpleSlashCommandBuilder.create( ]; const canvas = createCanvas(800, 400); const ctx = canvas.getContext('2d'); - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, 800, 400); - const canvasTable = new CanvasTable(table, new BoundingBox(50, 50, 750, 350)); + new BoundingBox(0, 0, 800, 400).fill(ctx, 'white'); + const canvasTable = new CanvasTable(table, new BoundingBox(50, 50, 700, 300)); canvasTable.color = 'black'; canvasTable.renderTo(ctx); await interaction.reply({ diff --git a/packages/misc/util/canvasUtils.ts b/packages/misc/util/canvasUtils.ts index 2547239f..6fbaaf78 100644 --- a/packages/misc/util/canvasUtils.ts +++ b/packages/misc/util/canvasUtils.ts @@ -1,5 +1,4 @@ -import { CanvasRenderingContext2D, CanvasTextBaseline } from 'canvas'; -import { formatTable } from '../../../util/strings'; +import { CanvasRenderingContext2D } from 'canvas'; function requireNonnegative(x: number, name: string): number { if (x < 0) { @@ -23,6 +22,20 @@ export class BoundingBox { this.width = requireNonnegative(width, 'width'); this.height = requireNonnegative(height, 'height'); } + + stroke(ctx: CanvasRenderingContext2D, color: string) { + ctx.save(); + ctx.strokeStyle = color; + ctx.strokeRect(this.x, this.y, this.width, this.height); + ctx.restore(); + } + + fill(ctx: CanvasRenderingContext2D, color: string) { + ctx.save(); + ctx.fillStyle = color; + ctx.fillRect(this.x, this.y, this.width, this.height); + ctx.restore(); + } } export class InlineText { @@ -55,6 +68,10 @@ export class CanvasTextBox { public boundingBox: BoundingBox; + public align: 'center' = 'center'; + + public verticalAlign: 'middle' = 'middle'; + constructor(text: InlineText, boundingBox: BoundingBox) { this.text = text; this.boundingBox = boundingBox; @@ -63,9 +80,27 @@ export class CanvasTextBox { renderTo(ctx: CanvasRenderingContext2D) { ctx.save(); ctx.textBaseline = 'top'; - this.text.renderTo(ctx, this.boundingBox.x, this.boundingBox.y); + ctx.textAlign = this.align; + ctx.textBaseline = this.verticalAlign; + this.text.renderTo(ctx, this.getX(), this.getY()); ctx.restore(); } + + private getX() { + const boundingBox = this.boundingBox; + switch (this.align) { + case 'center': + return boundingBox.x + boundingBox.width / 2; + } + } + + private getY() { + const boundingBox = this.boundingBox; + switch (this.verticalAlign) { + case 'middle': + return boundingBox.y + boundingBox.height / 2; + } + } } export class CanvasTable { @@ -88,8 +123,8 @@ export class CanvasTable { new BoundingBox( boundingBox.x + cellWidth * j, boundingBox.y + cellHeight * i, - boundingBox.width, - boundingBox.height, + cellWidth, + cellHeight, ), ), ), From 1f56f789a7af123affed77e3ae51b6b513fe31dc Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:22:43 +0900 Subject: [PATCH 12/17] =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=81=AB=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=88=E3=83=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index af6c32c6..6379cc69 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -2,7 +2,12 @@ import { createCanvas } from 'canvas'; import { SimpleSlashCommandBuilder } from '../../../common/SimpleCommand'; import { LANG, strFormat } from '../../../util/languages'; import { DayOfWeek, MonthCalendar } from '../util/calendar'; -import { BoundingBox, CanvasTable, InlineText } from '../util/canvasUtils'; +import { + BoundingBox, + CanvasTable, + CanvasTextBox, + InlineText, +} from '../util/canvasUtils'; function dayColor(day: DayOfWeek) { switch (day) { @@ -37,9 +42,15 @@ export default SimpleSlashCommandBuilder.create( const canvas = createCanvas(800, 400); const ctx = canvas.getContext('2d'); new BoundingBox(0, 0, 800, 400).fill(ctx, 'white'); - const canvasTable = new CanvasTable(table, new BoundingBox(50, 50, 700, 300)); - canvasTable.color = 'black'; - canvasTable.renderTo(ctx); + const title = strFormat(LANG.commands.cal.monthYear, { + month: LANG.commands.cal.monthNames[calendar.month], + year: calendar.year, + }); + const titleStyle = new InlineText(title); + titleStyle.color = 'black'; + titleStyle.font = '48px serif'; + new CanvasTextBox(titleStyle, new BoundingBox(50, 0, 700, 100)).renderTo(ctx); + new CanvasTable(table, new BoundingBox(50, 100, 700, 300)).renderTo(ctx); await interaction.reply({ files: [ { @@ -49,10 +60,7 @@ export default SimpleSlashCommandBuilder.create( ], embeds: [ { - title: strFormat(LANG.commands.cal.monthYear, { - month: LANG.commands.cal.monthNames[calendar.month], - year: calendar.year, - }), + title: strFormat(title), image: { url: 'attachment://calendar.png', }, From 9d09f716e12fb70dcce0c9089ef6e7c1a5b5a434 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:32:17 +0900 Subject: [PATCH 13/17] =?UTF-8?q?=E4=BB=96=E3=81=AE=E6=9C=88=E3=81=AE?= =?UTF-8?q?=E6=97=A5=E4=BB=98=E3=82=92=E8=96=84=E3=81=8F=E3=80=81=E4=BB=8A?= =?UTF-8?q?=E6=97=A5=E3=82=92=E5=A4=AA=E5=AD=97=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 7 +++++++ packages/misc/util/calendar.ts | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 6379cc69..597cf7f0 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -25,6 +25,7 @@ export default SimpleSlashCommandBuilder.create( LANG.commands.cal.description, ).build(async (interaction) => { const calendar = new MonthCalendar(); + const today = new Date(); const table = [ LANG.commands.cal.dayLabels.map((e, i) => { const text = new InlineText(e); @@ -35,6 +36,12 @@ export default SimpleSlashCommandBuilder.create( week.map((day) => { const text = new InlineText(day.date.toString()); text.color = dayColor(day.day); + if (!calendar.includes(day)) { + text.color = 'gray'; + } + if (day.is(today)) { + text.font = 'bold 24px serif'; + } return text; }), ), diff --git a/packages/misc/util/calendar.ts b/packages/misc/util/calendar.ts index 2aadaa91..c8188510 100644 --- a/packages/misc/util/calendar.ts +++ b/packages/misc/util/calendar.ts @@ -30,6 +30,14 @@ export class Day { add(days: number) { return new Day(this.year, this.month, this.date + days); } + + is(date: Date): boolean { + return ( + this.year == date.getFullYear() && + this.month == date.getMonth() && + this.date == date.getDate() + ); + } } export type Week = { [K in DayOfWeek]: Day } & Array; From a743e62307512b9c7a379b0699b555db2a5c2614 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:47:44 +0900 Subject: [PATCH 14/17] =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E8=AA=AD=E3=81=BF=E8=BE=BC?= =?UTF-8?q?=E3=81=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ config.json.example | 4 +++- packages/misc/commands/cal.ts | 5 +++-- packages/misc/index.js | 2 ++ packages/misc/util/canvasUtils.ts | 16 ++++++++++++++-- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 410fea08..38212caa 100755 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Font file +*.ttf diff --git a/config.json.example b/config.json.example index 52beef05..ebadf3cc 100755 --- a/config.json.example +++ b/config.json.example @@ -26,5 +26,7 @@ "rconpass1": "yourpassword1234", "syslogChannel": "1151139585791901746", "notificationChannel": "1151139585791901746", - "language": "default" + "language": "default", + "fontFile": "font.ttf", + "fontFamily": "sans-serif" } \ No newline at end of file diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 597cf7f0..9c32b0b1 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -6,6 +6,7 @@ import { BoundingBox, CanvasTable, CanvasTextBox, + FONT_FAMILY, InlineText, } from '../util/canvasUtils'; @@ -40,7 +41,7 @@ export default SimpleSlashCommandBuilder.create( text.color = 'gray'; } if (day.is(today)) { - text.font = 'bold 24px serif'; + text.font = `bold 24px ${FONT_FAMILY}`; } return text; }), @@ -55,7 +56,7 @@ export default SimpleSlashCommandBuilder.create( }); const titleStyle = new InlineText(title); titleStyle.color = 'black'; - titleStyle.font = '48px serif'; + titleStyle.font = `48px ${FONT_FAMILY}`; new CanvasTextBox(titleStyle, new BoundingBox(50, 0, 700, 100)).renderTo(ctx); new CanvasTable(table, new BoundingBox(50, 100, 700, 300)).renderTo(ctx); await interaction.reply({ diff --git a/packages/misc/index.js b/packages/misc/index.js index 9975ead2..c1d1a0f0 100644 --- a/packages/misc/index.js +++ b/packages/misc/index.js @@ -2,9 +2,11 @@ const fs = require('fs'); const path = require('path'); const { CommandManager } = require('../../internal/commands'); +const { registerConfiguredFont } = require('./util/canvasUtils'); class MiscFeature { onLoad() { + registerConfiguredFont(); fs.readdirSync(path.join(__dirname, 'commands'), { withFileTypes: true, }).forEach((file) => { diff --git a/packages/misc/util/canvasUtils.ts b/packages/misc/util/canvasUtils.ts index 6fbaaf78..1f9f5297 100644 --- a/packages/misc/util/canvasUtils.ts +++ b/packages/misc/util/canvasUtils.ts @@ -1,4 +1,8 @@ -import { CanvasRenderingContext2D } from 'canvas'; +import { CanvasRenderingContext2D, registerFont } from 'canvas'; +import config from '../../../config.json'; + +const FONT_FILE = config.fontFile ?? 'font.ttf'; +export const FONT_FAMILY = config.fontFamily ?? 'serif'; function requireNonnegative(x: number, name: string): number { if (x < 0) { @@ -7,6 +11,14 @@ function requireNonnegative(x: number, name: string): number { return x; } +export function registerConfiguredFont() { + try { + registerFont(FONT_FILE, { family: FONT_FAMILY }); + } catch (e) { + console.error(e); + } +} + export class BoundingBox { readonly x: number; @@ -43,7 +55,7 @@ export class InlineText { public color: string = 'black'; - public font: string = '24px serif'; + public font: string = `24px ${FONT_FAMILY}`; constructor(text: string) { this.text = text; From 40906a97c82dc6490e6878939c2fa562fba6e13e Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:34:45 +0900 Subject: [PATCH 15/17] =?UTF-8?q?=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- language/default.json | 23 ++++++ packages/misc/commands/cal.ts | 129 +++++++++++++++++++++------------ packages/misc/util/calendar.ts | 8 +- 3 files changed, 110 insertions(+), 50 deletions(-) diff --git a/language/default.json b/language/default.json index ccb8e974..6fd6cf26 100644 --- a/language/default.json +++ b/language/default.json @@ -120,6 +120,29 @@ "cal": { "name": "cal", "description": "カレンダーを表示します", + "options": { + "month": { + "name": "month", + "description": "月" + }, + "year": { + "name": "year", + "description": "年" + }, + "weekStart": { + "name": "week_start", + "description": "週の始まりの曜日" + } + }, + "dayNames": [ + "日曜日", + "月曜日", + "火曜日", + "水曜日", + "木曜日", + "金曜日", + "土曜日" + ], "dayLabels": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], "monthNames": [ "1月", diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index 9c32b0b1..d750166b 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -24,55 +24,88 @@ function dayColor(day: DayOfWeek) { export default SimpleSlashCommandBuilder.create( LANG.commands.cal.name, LANG.commands.cal.description, -).build(async (interaction) => { - const calendar = new MonthCalendar(); - const today = new Date(); - const table = [ - LANG.commands.cal.dayLabels.map((e, i) => { - const text = new InlineText(e); - text.color = dayColor(i as DayOfWeek); - return text; - }), - ...Array.from(calendar.weeks()).map((week) => - week.map((day) => { - const text = new InlineText(day.date.toString()); - text.color = dayColor(day.day); - if (!calendar.includes(day)) { - text.color = 'gray'; - } - if (day.is(today)) { - text.font = `bold 24px ${FONT_FAMILY}`; - } +) + .addIntegerOption({ + name: LANG.commands.cal.options.month.name, + description: LANG.commands.cal.options.month.description, + choices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((value) => ({ + name: LANG.commands.cal.monthNames[value], + value, + })), + required: false, + }) + .addIntegerOption({ + name: LANG.commands.cal.options.year.name, + description: LANG.commands.cal.options.year.description, + required: false, + }) + .addIntegerOption({ + name: LANG.commands.cal.options.weekStart.name, + description: LANG.commands.cal.options.weekStart.description, + choices: Object.values(DayOfWeek).map((value) => ({ + name: LANG.commands.cal.dayNames[value], + value, + })), + required: false, + }) + .build(async (interaction, month, year, weekStart = DayOfWeek.Sunday) => { + const today = new Date(); + const calendar = new MonthCalendar( + month ?? today.getMonth(), + year ?? today.getFullYear(), + ); + const days = []; + for (let i = 0; i < 7; i++) { + days.push((weekStart + i) % 7); + } + const table = [ + days.map((i) => { + const text = new InlineText(LANG.commands.cal.dayLabels[i]); + text.color = dayColor(i as DayOfWeek); return text; }), - ), - ]; - const canvas = createCanvas(800, 400); - const ctx = canvas.getContext('2d'); - new BoundingBox(0, 0, 800, 400).fill(ctx, 'white'); - const title = strFormat(LANG.commands.cal.monthYear, { - month: LANG.commands.cal.monthNames[calendar.month], - year: calendar.year, - }); - const titleStyle = new InlineText(title); - titleStyle.color = 'black'; - titleStyle.font = `48px ${FONT_FAMILY}`; - new CanvasTextBox(titleStyle, new BoundingBox(50, 0, 700, 100)).renderTo(ctx); - new CanvasTable(table, new BoundingBox(50, 100, 700, 300)).renderTo(ctx); - await interaction.reply({ - files: [ - { - attachment: canvas.toBuffer(), - name: 'calendar.png', - }, - ], - embeds: [ - { - title: strFormat(title), - image: { - url: 'attachment://calendar.png', + ...Array.from(calendar.weeks(weekStart)).map((week) => + week.map((day) => { + const text = new InlineText(day.date.toString()); + text.color = dayColor(day.day); + if (!calendar.includes(day)) { + text.color = 'gray'; + } + if (day.is(today)) { + text.font = `bold 24px ${FONT_FAMILY}`; + } + return text; + }), + ), + ]; + const canvas = createCanvas(800, 400); + const ctx = canvas.getContext('2d'); + new BoundingBox(0, 0, 800, 400).fill(ctx, 'white'); + const title = strFormat(LANG.commands.cal.monthYear, { + month: LANG.commands.cal.monthNames[calendar.month], + year: calendar.year, + }); + const titleStyle = new InlineText(title); + titleStyle.color = 'black'; + titleStyle.font = `48px ${FONT_FAMILY}`; + new CanvasTextBox(titleStyle, new BoundingBox(50, 0, 700, 100)).renderTo( + ctx, + ); + new CanvasTable(table, new BoundingBox(50, 100, 700, 300)).renderTo(ctx); + await interaction.reply({ + files: [ + { + attachment: canvas.toBuffer(), + name: 'calendar.png', + }, + ], + embeds: [ + { + title: strFormat(title), + image: { + url: 'attachment://calendar.png', + }, }, - }, - ], + ], + }); }); -}); diff --git a/packages/misc/util/calendar.ts b/packages/misc/util/calendar.ts index c8188510..e2cb1498 100644 --- a/packages/misc/util/calendar.ts +++ b/packages/misc/util/calendar.ts @@ -77,9 +77,13 @@ export class MonthCalendar { } } - *weeks(): Iterable { + *weeks(weekStart: DayOfWeek = DayOfWeek.Sunday): Iterable { const monthFirst = this.firstDay(); - let weekFirst = monthFirst.add(-monthFirst.day); + let firstDate = weekStart - monthFirst.day + 7; // カレンダーの最初の日付 (1日以前、負になり得る) + while (firstDate > 1) { + firstDate -= 7; + } + let weekFirst = monthFirst.add(firstDate); do { const week = [weekFirst]; let day = weekFirst; From 45161d5f830aa7ee8ca73a967c6dbc8da08d5256 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:31:02 +0900 Subject: [PATCH 16/17] =?UTF-8?q?=E7=A5=9D=E6=97=A5=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/misc/commands/cal.ts | 17 +++++++++++++---- packages/misc/util/calendar.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/misc/commands/cal.ts b/packages/misc/commands/cal.ts index d750166b..bbd2af32 100644 --- a/packages/misc/commands/cal.ts +++ b/packages/misc/commands/cal.ts @@ -10,14 +10,20 @@ import { InlineText, } from '../util/canvasUtils'; +const WORKDAY_COLOR = 'black'; +const HOLIDAY_COLOR = 'red'; +const SUNDAY_COLOR = 'red'; +const SATURDAY_COLOR = 'blue'; +const EXCLUDED_COLOR = 'gray'; + function dayColor(day: DayOfWeek) { switch (day) { case DayOfWeek.Sunday: - return 'red'; + return SUNDAY_COLOR; case DayOfWeek.Saturday: - return 'blue'; + return SATURDAY_COLOR; default: - return 'black'; + return WORKDAY_COLOR; } } @@ -68,8 +74,11 @@ export default SimpleSlashCommandBuilder.create( week.map((day) => { const text = new InlineText(day.date.toString()); text.color = dayColor(day.day); + if (day.isHoliday()) { + text.color = HOLIDAY_COLOR; + } if (!calendar.includes(day)) { - text.color = 'gray'; + text.color = EXCLUDED_COLOR; } if (day.is(today)) { text.font = `bold 24px ${FONT_FAMILY}`; diff --git a/packages/misc/util/calendar.ts b/packages/misc/util/calendar.ts index e2cb1498..bd34187f 100644 --- a/packages/misc/util/calendar.ts +++ b/packages/misc/util/calendar.ts @@ -1,3 +1,5 @@ +import axios from 'axios'; + export const DayOfWeek = Object.freeze({ Sunday: 0, Monday: 1, @@ -10,6 +12,25 @@ export const DayOfWeek = Object.freeze({ export type DayOfWeek = (typeof DayOfWeek)[keyof typeof DayOfWeek]; +const HOLIDAYS_CSV = 'https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv'; + +let holidays = new Map(); + +async function getHolidays() { + const res = await axios.get(HOLIDAYS_CSV, { + responseType: 'arraybuffer', + }); + const text = new TextDecoder('shift_jis').decode(res.data); + const data = text + .split('\n') + .map((row) => row.trim()) // '\r' を削除 + .filter((row) => row != '') // 空行を削除 + .slice(1) // 見出し行を削除 + .map((row) => row.split(',') as [string, string]); + holidays = new Map(data); +} +getHolidays(); + export class Day { public readonly year: number; @@ -38,6 +59,14 @@ export class Day { this.date == date.getDate() ); } + + toString() { + return `${this.year}/${this.month + 1}/${this.date}`; + } + + isHoliday() { + return holidays.has(this.toString()); + } } export type Week = { [K in DayOfWeek]: Day } & Array; From 7e68fe9bcc4c34c35c6dbabac79838df47e36508 Mon Sep 17 00:00:00 2001 From: takejohn <105504345+takejohn@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:40:53 +0900 Subject: [PATCH 17/17] i18n --- language/en.json | 43 +++++++++++++++++++++++++++++++++++++++++++ language/ja.json | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/language/en.json b/language/en.json index 12165c33..e7a534b6 100644 --- a/language/en.json +++ b/language/en.json @@ -117,6 +117,49 @@ } }, "commands": { + "cal": { + "name": "cal", + "description": "Shows the calendar", + "options": { + "month": { + "name": "month", + "description": "Month" + }, + "year": { + "name": "year", + "description": "Year" + }, + "weekStart": { + "name": "week_start", + "description": "The days to start weeks" + } + }, + "dayNames": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ], + "dayLabels": ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], + "monthNames": [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ], + "monthYear": "${month} ${year}" + }, "check": { "name": "check", "description": "Ping Checker", diff --git a/language/ja.json b/language/ja.json index 15739be1..99a2e3c4 100644 --- a/language/ja.json +++ b/language/ja.json @@ -117,6 +117,49 @@ } }, "commands": { + "cal": { + "name": "cal", + "description": "カレンダーを表示します", + "options": { + "month": { + "name": "month", + "description": "月" + }, + "year": { + "name": "year", + "description": "年" + }, + "weekStart": { + "name": "week_start", + "description": "週の始まりの曜日" + } + }, + "dayNames": [ + "日曜日", + "月曜日", + "火曜日", + "水曜日", + "木曜日", + "金曜日", + "土曜日" + ], + "dayLabels": ["日", "月", "火", "水", "木", "金", "土"], + "monthNames": [ + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月" + ], + "monthYear": "${year}年${month}" + }, "check": { "name": "check", "description": "Ping を確認",