在 Manus 中运行任何 Skill
一键导入
一键导入
一键在 Manus 中运行任何 Skill
开始使用$pwd:
discord-bot
// Oceanic.jsを使用したDiscord Botの実装方法。Bot初期化、メッセージ送信、Embed作成、イベントハンドリングについて説明。Discord連携機能の実装時に参照。
$ git log --oneline --stat
stars:0
forks:0
updated:2026年3月30日 10:31
SKILL.md
// Oceanic.jsを使用したDiscord Botの実装方法。Bot初期化、メッセージ送信、Embed作成、イベントハンドリングについて説明。Discord連携機能の実装時に参照。
[HINT] 下载包含 SKILL.md 和所有相关文件的完整技能目录
| name | discord-bot |
| description | Oceanic.jsを使用したDiscord Botの実装方法。Bot初期化、メッセージ送信、Embed作成、イベントハンドリングについて説明。Discord連携機能の実装時に参照。 |
Oceanic.jsは、高速で型安全なDiscord APIライブラリです。
pnpm add oceanic.js
pnpm add -D @types/node typescript
import { Client, Message, TextChannel } from 'oceanic.js';
export class DiscordBot {
private client: Client;
private channelId: string;
constructor(token: string, channelId: string) {
this.client = new Client({
auth: `Bot ${token}`,
gateway: {
intents: ['GUILDS', 'GUILD_MESSAGES']
}
});
this.channelId = channelId;
this.setupEventHandlers();
}
private setupEventHandlers(): void {
this.client.on('ready', () => {
console.log(`Botがログインしました: ${this.client.user?.tag}`);
});
this.client.on('error', (error) => {
console.error('Discord Clientエラー:', error);
});
}
async connect(): Promise<void> {
try {
await this.client.connect();
} catch (error) {
console.error('Bot接続エラー:', error);
throw error;
}
}
async disconnect(): Promise<void> {
await this.client.disconnect();
}
getClient(): Client {
return this.client;
}
}
async sendMessage(content: string): Promise<void> {
try {
const channel = await this.client.rest.channels.get(this.channelId);
if (!channel || channel.type !== 0) { // 0 = GUILD_TEXT
throw new Error('指定されたチャンネルがテキストチャンネルではありません');
}
await this.client.rest.channels.createMessage(this.channelId, {
content: content
});
} catch (error) {
console.error('メッセージ送信エラー:', error);
throw error;
}
}
import { CreateMessageOptions, Embed } from 'oceanic.js';
async sendEmbed(embed: Embed): Promise<void> {
try {
await this.client.rest.channels.createMessage(this.channelId, {
embeds: [embed]
});
} catch (error) {
console.error('Embed送信エラー:', error);
throw error;
}
}
import { Embed, EmbedField } from 'oceanic.js';
import { EarthquakeInfo } from '../earthquake/types';
export class EarthquakeEmbedFormatter {
static createEarthquakeEmbed(info: EarthquakeInfo): Embed {
const intensity = this.convertIntensity(info.earthquake.maxScale || 0);
const color = this.getColorByIntensity(info.earthquake.maxScale || 0);
const fields: EmbedField[] = [];
// 震源地情報
if (info.earthquake.hypocenter) {
fields.push({
name: '📍 震源地',
value: info.earthquake.hypocenter.name || '不明',
inline: true
});
}
// マグニチュード
if (info.earthquake.hypocenter.magnitude) {
fields.push({
name: '📊 マグニチュード',
value: `M${info.earthquake.hypocenter.magnitude}`,
inline: true
});
}
// 深さ
if (info.earthquake.hypocenter.depth) {
fields.push({
name: '⬇️ 深さ',
value: `約${info.earthquake.hypocenter.depth}km`,
inline: true
});
}
// 最大震度
if (info.earthquake.maxScale) {
fields.push({
name: '⚠️ 最大震度',
value: `震度${intensity}`,
inline: false
});
}
// 津波情報
if (info.earthquake.domesticTsunami) {
const tsunamiInfo = this.getTsunamiText(info.earthquake.domesticTsunami);
fields.push({
name: '🌊 津波の心配',
value: tsunamiInfo,
inline: false
});
}
// 観測地点(上位10件)
if (info.points && info.points.length > 0) {
const topPoints = info.points
.sort((a, b) => b.scale - a.scale)
.slice(0, 10)
.map(point => {
const scale = this.convertIntensity(point.scale);
return `\`震度${scale}\` ${point.pref} ${point.addr}`;
})
.join('\n');
fields.push({
name: '📌 主な観測地点',
value: topPoints,
inline: false
});
}
const embed: Embed = {
title: '🚨 地震情報',
description: `**${info.earthquake.time}** 頃、地震がありました。`,
color: color,
fields: fields,
timestamp: new Date(info.issue.time).toISOString(),
footer: {
text: `情報源: ${info.issue.source}`
}
};
return embed;
}
private static convertIntensity(scale: number): string {
const intensityMap: Record<number, string> = {
10: '1',
20: '2',
30: '3',
40: '4',
45: '5弱',
50: '5強',
55: '6弱',
60: '6強',
70: '7'
};
return intensityMap[scale] || '不明';
}
private static getColorByIntensity(scale: number): number {
// 震度に応じた色分け
if (scale >= 60) return 0xFF0000; // 赤 (震度6弱以上)
if (scale >= 50) return 0xFF6600; // オレンジ (震度5強以上)
if (scale >= 45) return 0xFFCC00; // 黄色 (震度5弱以上)
if (scale >= 40) return 0x00FF00; // 緑 (震度4以上)
return 0x00BFFF; // 青 (震度3以下)
}
private static getTsunamiText(tsunamiType: string): string {
const tsunamiMap: Record<string, string> = {
'None': 'なし',
'Unknown': '不明',
'Checking': '調査中',
'NonEffective': '若干の海面変動の可能性あり',
'Watch': '津波注意報',
'Warning': '津波警報'
};
return tsunamiMap[tsunamiType] || tsunamiType;
}
}
// メインエントリーポイント
import { DiscordBot } from './bot/client';
import { EarthquakeWebSocket } from './earthquake/websocket';
import { EarthquakeEmbedFormatter } from './formatter/embed';
const bot = new DiscordBot(
process.env.DISCORD_BOT_TOKEN!,
process.env.DISCORD_CHANNEL_ID!
);
const earthquakeWs = new EarthquakeWebSocket();
// 地震情報受信時の処理
earthquakeWs.on('earthquake', async (data) => {
const embed = EarthquakeEmbedFormatter.createEarthquakeEmbed(data);
await bot.sendEmbed(embed);
});
// Bot起動
await bot.connect();
earthquakeWs.connect();
Oceanic.jsでは、必要な権限を intents で指定します。
const client = new Client({
auth: `Bot ${token}`,
gateway: {
intents: [
'GUILDS', // サーバー情報の取得
'GUILD_MESSAGES' // メッセージの送信
]
}
});
this.client.on('error', (error) => {
console.error('Discord Client エラー:', error);
// 必要に応じて通知やログ記録
});
this.client.on('disconnect', () => {
console.warn('Discord Botが切断されました');
// 再接続処理
});
トークンの管理
.env ファイルを .gitignore に追加するレート制限対策
エラー処理
Embedのデザイン
パフォーマンス
Bot を Discord サーバーに追加する際、以下の権限が必要です:
招待URLの生成例:
https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=51200&scope=bot