This commit is contained in:
uttili 2025-11-10 15:20:41 +00:00
commit bd3eb883c7

559
main.py Normal file
View File

@ -0,0 +1,559 @@
import discord
from discord.ext import commands, tasks
from discord import app_commands
from discord.ui import Button, View
import aiohttp
import asyncio
import platform
import psutil
import os
from datetime import datetime
import logging
# ロギングの設定
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("bot.log"),
logging.StreamHandler()
]
)
# BOTの設定
TOKEN = 'あなたのDiscord Bot Token' # BOTトークン
CHANNEL_ID = 000000000000000000 # チャンネルID
CHECK_INTERVAL = 30 # 間隔(秒)
LOG_FILE = 'ip_changes.log' # IPアドレス変更ログファイル
last_ip = None
status_message = None
initialization_complete = False
intents = discord.Intents.all()
intents.message_content = True
intents.reactions = True
bot = commands.Bot(command_prefix='!', intents=intents)
# グローバルIPを取得
async def get_global_ip():
try:
async with aiohttp.ClientSession() as session:
async with session.get('https://api.ipify.org/?format=json', timeout=10) as response:
if response.status == 200:
data = await response.json()
return data['ip']
else:
logging.error(f"IPの取得に失敗しました: ステータスコード {response.status}")
return None
except Exception as e:
logging.error(f"IPの取得中にエラーが発生しました: {e}")
return None
# ログにIPアドレス変更を記録
def log_ip_change(old_ip, new_ip):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] IP変更: {old_ip} -> {new_ip}\n"
try:
with open(LOG_FILE, 'a', encoding='utf-8') as log_file:
log_file.write(log_entry)
except Exception as e:
logging.error(f"ログの書き込み中にエラーが発生しました: {e}")
# サーバーのステータスを取得
def get_server_status():
try:
# CPU使用率
cpu_percent = psutil.cpu_percent(interval=1)
# メモリ使用率
memory = psutil.virtual_memory()
memory_percent = memory.percent
memory_used = round(memory.used / (1024 * 1024 * 1024), 2) # GB
memory_total = round(memory.total / (1024 * 1024 * 1024), 2) # GB
# ディスク使用率
disk = psutil.disk_usage('/')
disk_percent = disk.percent
disk_used = round(disk.used / (1024 * 1024 * 1024), 2) # GB
disk_total = round(disk.total / (1024 * 1024 * 1024), 2) # GB
# システム稼働時間
boot_time = datetime.fromtimestamp(psutil.boot_time())
uptime = datetime.now() - boot_time
days = uptime.days
hours, remainder = divmod(uptime.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
uptime_str = f"{days}{hours}時間 {minutes}{seconds}"
# OSバージョン
os_info = platform.platform()
return {
"cpu_percent": cpu_percent,
"memory_percent": memory_percent,
"memory_used": memory_used,
"memory_total": memory_total,
"disk_percent": disk_percent,
"disk_used": disk_used,
"disk_total": disk_total,
"uptime": uptime_str,
"os_info": os_info
}
except Exception as e:
logging.error(f"サーバーステータスの取得中にエラーが発生しました: {e}")
return None
# 埋め込みメッセージ
def create_ip_embed(ip, old_ip=None, color=discord.Color.green()):
if not ip:
return discord.Embed(
title="⚠️ エラー",
description="グローバルIPアドレスの取得に失敗しました",
color=discord.Color.red()
)
embed = discord.Embed(
title="🌐 グローバルIPアドレス",
description=f"**`{ip}`**",
color=color,
timestamp=datetime.now()
)
if old_ip and old_ip != ip:
embed.add_field(name="以前のIPアドレス", value=f"`{old_ip}`", inline=False)
embed.add_field(name="状態", value="⚠️ IPアドレスが変更されました", inline=False)
embed.color = discord.Color.orange()
embed.set_footer(text="最終更新")
return embed
# サーバーステータスの埋め込みメッセージ
def create_status_embed(status_data):
if not status_data:
return discord.Embed(
title="⚠️ エラー",
description="サーバーステータスの取得に失敗しました",
color=discord.Color.red()
)
embed = discord.Embed(
title="🖥️ サーバーステータス",
color=discord.Color.blue(),
timestamp=datetime.now()
)
# CPU使用率のプログレスバー
cpu_bar = create_progress_bar(status_data["cpu_percent"])
embed.add_field(name="CPU使用率", value=f"{cpu_bar} {status_data['cpu_percent']}%", inline=False)
# メモリ使用率のプログレスバー
memory_bar = create_progress_bar(status_data["memory_percent"])
embed.add_field(
name="メモリ使用率",
value=f"{memory_bar} {status_data['memory_percent']}%\n`{status_data['memory_used']}GB / {status_data['memory_total']}GB`",
inline=False
)
# ディスク使用率のプログレスバー
disk_bar = create_progress_bar(status_data["disk_percent"])
embed.add_field(
name="ディスク使用率",
value=f"{disk_bar} {status_data['disk_percent']}%\n`{status_data['disk_used']}GB / {status_data['disk_total']}GB`",
inline=False
)
# システム情報
embed.add_field(name="システム稼働時間", value=f"`{status_data['uptime']}`", inline=False)
embed.add_field(name="OS情報", value=f"`{status_data['os_info']}`", inline=False)
embed.set_footer(text="最終更新")
return embed
# プログレスバーを作成
def create_progress_bar(percent, bar_length=15):
filled_length = int(bar_length * percent / 100)
empty_length = bar_length - filled_length
if percent < 60:
color = "🟢" # 緑
elif percent < 80:
color = "🟡" # 黄
else:
color = "🔴" # 赤
bar = "" * filled_length + "" * empty_length
return f"[{bar}] {color}"
class IPControlView(View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="今すぐ更新", style=discord.ButtonStyle.primary, custom_id="refresh_ip")
async def refresh_button(self, interaction: discord.Interaction, button: discord.ui.Button):
global last_ip
await interaction.response.defer(ephemeral=True)
current_ip = await get_global_ip()
if current_ip is None:
await interaction.followup.send("IPアドレスの取得に失敗しました", ephemeral=True)
return
if last_ip and last_ip != current_ip:
log_ip_change(last_ip, current_ip)
old_ip = last_ip
last_ip = current_ip
embed = create_ip_embed(current_ip, old_ip)
await interaction.message.edit(embed=embed, view=self)
await interaction.followup.send("IPアドレス情報を更新しました", ephemeral=True)
@discord.ui.button(label="サーバーステータス", style=discord.ButtonStyle.success, custom_id="server_status")
async def status_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.defer(ephemeral=True)
status_data = get_server_status()
if status_data is None:
await interaction.followup.send("サーバーステータスの取得に失敗しました", ephemeral=True)
return
embed = create_status_embed(status_data)
view = CloseView()
await interaction.followup.send(embed=embed, view=view, ephemeral=False)
@discord.ui.button(label="PROXMOX", style=discord.ButtonStyle.secondary, custom_id="proxmox_link")
async def proxmox_button(self, interaction: discord.Interaction, button: discord.ui.Button):
global last_ip
await interaction.response.defer(ephemeral=True)
if not last_ip:
await interaction.followup.send("IPアドレスが取得されていません", ephemeral=True)
return
proxmox_url = f"https://{last_ip}:8006"
await interaction.followup.send(
f"Proxmox管理画面にアクセス\n"
f"[Proxmox管理画面]({proxmox_url})\n\n"
f"または直接アクセス: `{proxmox_url}`",
ephemeral=True
)
class CloseView(View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="閉じる", style=discord.ButtonStyle.danger, custom_id="close_status")
async def close_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.defer(ephemeral=True)
await interaction.message.delete()
await interaction.followup.send("ステータス表示を終了しました", ephemeral=True)
async def clear_channel_messages():
try:
channel = bot.get_channel(CHANNEL_ID)
if channel is None:
logging.error(f"チャンネルが見つかりません: {CHANNEL_ID}")
return False
deleted = 0
logging.info("チャンネル内のメッセージを削除中")
try:
logging.info("最近のメッセージを一削除中")
deleted_messages = await channel.purge(limit=100, bulk=True)
deleted += len(deleted_messages)
logging.info(f"{len(deleted_messages)}件のメッセージの削除完了")
remaining = []
async for message in channel.history(limit=10):
remaining.append(message)
if remaining:
logging.info(f"残り{len(remaining)}件の古いメッセージを削除中")
for message in remaining:
await message.delete()
deleted += 1
logging.info(f"メッセージを削除(合計: {deleted}")
await asyncio.sleep(0.5) # レート制限回避
except discord.Forbidden:
logging.error("Discordのメッセージ削除権限がありません")
return False
except discord.HTTPException as e:
logging.error(f"一括削除中にHTTPエラーが発生: {e}")
try:
async for message in channel.history(limit=100):
await message.delete()
deleted += 1
logging.info(f"個別削除:メッセージを削除(合計: {deleted}")
await asyncio.sleep(0.5)
except Exception as e2:
logging.error(f"個別削除中にエラーが発生: {e2}")
logging.info(f"{deleted}件のメッセージを削除")
return True
except Exception as e:
logging.error(f"チャンネル内メッセージ削除中にエラーが発生: {e}")
return False
@bot.event
async def on_ready():
global initialization_complete
logging.info(f'{bot.user}')
logging.info(f'BOT ID: {bot.user.id}')
logging.info(f'送信先チャンネルID: {CHANNEL_ID}')
logging.info(f'間隔: {CHECK_INTERVAL}秒 ({CHECK_INTERVAL/60}分)')
logging.info('------')
logging.info('グローバルIP検出・変更通知BOT')
logging.info('© 2025 uttili_external_system')
logging.info('https://uttili.com')
logging.info('------')
if not initialization_complete:
logging.info("チャンネル内メッセージを削除中")
await clear_channel_messages()
logging.info("メッセージを送信中")
await send_initial_message()
if not check_ip_change.is_running():
check_ip_change.start()
logging.info("IP検出開始")
initialization_complete = True
logging.info("初期化が完了")
else:
if not check_ip_change.is_running():
check_ip_change.start()
logging.info("IP検出再開")
@tasks.loop(seconds=CHECK_INTERVAL)
async def check_ip_change():
global last_ip, status_message
try:
current_ip = await get_global_ip()
if current_ip is None:
logging.warning("IPアドレスの取得に失敗")
return
if last_ip is None:
last_ip = current_ip
logging.info(f"初期IPアドレスを確定: {current_ip}")
if status_message is None:
await send_initial_message()
else:
await update_ip_message(current_ip)
elif last_ip != current_ip:
old_ip = last_ip
last_ip = current_ip
log_ip_change(old_ip, current_ip)
await update_ip_message(current_ip, old_ip)
logging.info(f"IPアドレスの変更検出: {old_ip} -> {current_ip}")
else:
await update_ip_message(current_ip)
logging.debug(f"IPアドレスに変更なし: {current_ip}")
except Exception as e:
logging.error(f"IP検出中に予期しないエラーが発生: {e}")
@check_ip_change.before_loop
async def before_check_ip_change():
await bot.wait_until_ready()
logging.info("BOT起動")
@check_ip_change.error
async def check_ip_change_error(error):
logging.error(f"IP検出中にエラーが発生: {error}")
if not check_ip_change.is_running():
check_ip_change.restart()
logging.info("IP検出再開")
async def send_initial_message():
global last_ip, status_message
try:
if status_message is not None:
logging.info("既にメッセージが存在するためスキップ")
return
channel = bot.get_channel(CHANNEL_ID)
if channel is None:
logging.error(f"チャンネル不明: {CHANNEL_ID}")
return
current_ip = await get_global_ip()
if current_ip is None:
logging.error("IPアドレスの取得に失敗しました")
return
last_ip = current_ip
embed = create_ip_embed(current_ip)
view = IPControlView()
status_message = await channel.send(embed=embed, view=view)
logging.info(f"メッセージを送信 Message ID: {status_message.id}")
except Exception as e:
logging.error(f"メッセージ送信中にエラー: {e}")
async def update_ip_message(current_ip, old_ip=None):
global status_message
try:
if status_message is None:
await send_initial_message()
return
channel = bot.get_channel(CHANNEL_ID)
if channel is None:
logging.error(f"チャンネル不明: {CHANNEL_ID}")
return
try:
message = await channel.fetch_message(status_message.id)
except discord.NotFound:
logging.warning("ステータスメッセージ不明。新規作成開始。")
status_message = None # status_messageリセット
await send_initial_message()
return
except Exception as e:
logging.error(f"メッセージ取得中にエラーが発生: {e}")
status_message = None
await send_initial_message()
return
color = discord.Color.orange() if old_ip and old_ip != current_ip else discord.Color.green()
embed = create_ip_embed(current_ip, old_ip, color)
view = IPControlView()
await message.edit(embed=embed, view=view)
if old_ip and old_ip != current_ip:
logging.info(f"IPメッセージを更新: {old_ip} -> {current_ip}")
else:
logging.debug(f"IPメッセージのタイムスタンプを更新: {current_ip}")
except discord.NotFound:
logging.warning("メッセージ不明。新規作成開始。")
status_message = None
await send_initial_message()
except discord.Forbidden:
logging.error("メッセージの編集権限がないため動作不可能")
except discord.HTTPException as e:
logging.error(f"メッセージ更新中にHTTPエラーが発生: {e}")
await asyncio.sleep(5)
try:
if status_message:
channel = bot.get_channel(CHANNEL_ID)
message = await channel.fetch_message(status_message.id)
embed = create_ip_embed(current_ip, old_ip, discord.Color.green())
view = IPControlView()
await message.edit(embed=embed, view=view)
except Exception:
status_message = None
await send_initial_message()
except Exception as e:
logging.error(f"メッセージの更新中に予期しないエラーが発生: {e}")
status_message = None
await send_initial_message()
@bot.command(name="ipreset")
async def ipreset(ctx):
global status_message, initialization_complete
if not ctx.author.guild_permissions.administrator:
await ctx.send("❌ このコマンドは管理者権限が必要です", delete_after=5)
return
await ctx.message.delete()
await clear_channel_messages()
status_message = None
initialization_complete = False
initialization_complete = True
await send_initial_message()
if not check_ip_change.is_running():
check_ip_change.start()
logging.info("IP検出を開始")
await ctx.send("✅ チャンネル内を削除し、ステータスを表示しました", delete_after=5)
@bot.command(name="ipclear")
async def ipclear(ctx):
global status_message
if not ctx.author.guild_permissions.administrator:
await ctx.send("❌ このコマンドは管理者権限が必要です", delete_after=5)
return
await ctx.message.delete()
success = await clear_channel_messages()
if success:
status_message = None
await send_initial_message()
await ctx.send("✅ チャンネルをクリアしました", delete_after=5)
else:
await ctx.send("❌ チャンネルのクリアに失敗しました", delete_after=5)
@bot.command(name="ipstatus")
async def ipstatus(ctx):
await ctx.message.delete()
task_status = "実行中" if check_ip_change.is_running() else "停止中"
ip_status = f"`{last_ip}`" if last_ip else "未取得"
status_msg = await ctx.send(
f"**IPチェック状態**\n"
f"タスク: {task_status}\n"
f"現在のIP: {ip_status}\n"
f"更新間隔: {CHECK_INTERVAL}",
delete_after=10
)
if __name__ == "__main__":
logging.info("BOTを起動中")
logging.info("グローバルIPの監視開始")
try:
import psutil
except ImportError:
logging.warning("psutilモジュールがインストールされていない")
logging.warning("サーバーステータス機能を使用するには、以下のコマンドでインストール:")
logging.warning("pip install psutil")
logging.warning("続行しますが、サーバーステータス機能は機能しない")
bot.run(TOKEN)