Global-IP-Detection-Change-.../main.py
2025-11-10 15:20:41 +00:00

559 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)