👋
ChatGPTが書くコードは複雑になりがちなので「手続き型で初心者にもわかりやすく短めに」と伝えるべき!
ちょっとしたコードを書いてもらうだけなのに、なんでこんなに複雑に書いちゃうんだろうね。。。
NotebookLM に食わせるため、自分用discordサーバのデータを取ってきてもらってるんですが、一回しか使わない関数や変数を多用してくるので、個人的には可読性が悪すぎ問題があります。
まずダメな例。途中まで書いたコードを渡したあとに「コードに内に記載されている $channelIds に指定したチャンネルのスレッドをafterを使って取得してください。」と伝えた結果が以下です。
foreach($channelIds as $cid=>$meta) {
$type = $meta[0]; // "TEXT" / "FORUM"
// ユーティリティ:アーカイブ済みスレッドを列挙(active は使わない)
$listArchivedThreads = function($channelId) use ($fetch) {
$threads = array();
$before = gmdate('c'); // 現在時刻より前
$guard = 0;
do {
$u = "https://discord.com/api/v10/channels/{$channelId}/threads/archived/public?limit=100&before=" . rawurlencode($before);
$j = $fetch($u);
if (!is_array($j) || empty($j['threads'])) break;
foreach ($j['threads'] as $t) {
if (!isset($t['id'])) continue;
$threads[(string)$t['id']] = $t;
}
// 次ページ用に最後のアーカイブ時刻を使う
$last = end($j['threads']);
$before = isset($last['thread_metadata']['archive_timestamp'])
? $last['thread_metadata']['archive_timestamp']
: $before;
// 無限ループ防止
$guard++;
if ($guard > 50 || empty($j['has_more'])) break;
} while (true);
return $threads;
};
// スレッド内メッセージを after で取得して $data に積む
$drainThreadMessages = function($threadId, $threadName = null) use ($fetch, $toJST, &$data, $cid, $after) {
$url = "https://discord.com/api/v10/channels/{$threadId}/messages?limit=100" . ($after ? "&after={$after}" : "");
$msgs = $fetch($url);
if (!is_array($msgs) || empty($msgs)) return;
foreach ($msgs as $m) {
if (!isset($m['id'])) continue;
$id = (string)$m['id'];
$data[$id] = array(
'user' => isset($m['author']['id']) ? (string)$m['author']['id'] : null,
'channel' => (string)$cid, // 親チャンネルID
'thread' => (string)$threadId, // スレッドID
'title' => $threadName !== null ? (string)$threadName : null,
'content' => isset($m['content']) ? (string)$m['content'] : '',
'params' => json_encode(array(
'attachments' => isset($m['attachments']) ? $m['attachments'] : array(),
'embeds' => isset($m['embeds']) ? $m['embeds'] : array(),
'message_reference' => isset($m['message_reference']) ? $m['message_reference'] : null
), JSON_UNESCAPED_UNICODE),
'created' => $toJST($m['timestamp'])
);
}
};
// ---- 通常テキストチャンネル:親メッセージ取得(非スレッド) ----
if($type === 'TEXT') {
$msgs = $fetch("https://discord.com/api/v10/channels/{$cid}/messages?limit=100" . ($after ? "&after={$after}" : ""));
$newThreadIds = array(); // 親メッセージ経由で検出した新規スレッド
if (is_array($msgs)) {
foreach ($msgs as $m) {
if (!isset($m['id'])) continue;
$id = (string)$m['id'];
// 1) 非スレッドの通常メッセージを保存
if (!isset($m['thread'])) {
$data[$id] = array(
'user' => isset($m['author']['id']) ? (string)$m['author']['id'] : null,
'channel' => (string)$cid,
'thread' => null,
'title' => null,
'content' => isset($m['content']) ? (string)$m['content'] : '',
'params' => json_encode(array(
'attachments' => isset($m['attachments']) ? $m['attachments'] : array(),
'embeds' => isset($m['embeds']) ? $m['embeds'] : array(),
'message_reference' => isset($m['message_reference']) ? $m['message_reference'] : null
), JSON_UNESCAPED_UNICODE),
'created' => $toJST($m['timestamp'])
);
}
// 2) スレッド作成のシステムメッセージ経由で新規スレッド検出(m.thread に Thread オブジェクトが入る)
if (isset($m['thread']['id'])) {
$tid = (string)$m['thread']['id'];
$newThreadIds[$tid] = isset($m['thread']['name']) ? (string)$m['thread']['name'] : null;
}
}
}
// 3) アーカイブ済みスレッドを列挙し、after より新しいものだけを対象にメッセージ取得
$archived = $listArchivedThreads($cid);
foreach ($archived as $tid => $tmeta) {
// last_message_id または thread 自体の id が after より新しいかチェック
$lastId = isset($tmeta['last_message_id']) ? (string)$tmeta['last_message_id'] : null;
$name = isset($tmeta['name']) ? (string)$tmeta['name'] : null;
$isNewByThreadId = ($after && strcmp($tid, (string)$after) > 0);
$isNewByLastMsg = ($lastId && $after && strcmp($lastId, (string)$after) > 0);
if ($after && !($isNewByThreadId || $isNewByLastMsg)) continue;
$drainThreadMessages($tid, $name);
// 既に newThreadIds にある場合は重複回避のため消しておく
unset($newThreadIds[$tid]);
}
// 4) 親メッセージ経由で見つかった “新しく作られた” スレッドからも after で取得
foreach ($newThreadIds as $tid => $name) {
$drainThreadMessages($tid, $name);
}
// ---- FORUM:ポスト=スレッド。一覧→各スレッドの after 以降を取得 ----
} elseif ($type === 'FORUM') {
$threads = $listArchivedThreads($cid);
foreach ($threads as $tid => $tmeta) {
$lastId = isset($tmeta['last_message_id']) ? (string)$tmeta['last_message_id'] : null;
$name = isset($tmeta['name']) ? (string)$tmeta['name'] : null;
// after より新しい動きがなければスキップ
$isNewByThreadId = ($after && strcmp($tid, (string)$after) > 0);
$isNewByLastMsg = ($lastId && $after && strcmp($lastId, (string)$after) > 0);
if ($after && !($isNewByThreadId || $isNewByLastMsg)) continue;
$drainThreadMessages($tid, $name);
}
}
}
いや長くない????「長いです。手続き型で初心者にもわかりやすく短めに」 と伝えました。
foreach ($channelIds as $cid => $meta) {
$type = $meta[0]; // "TEXT" / "FORUM"
// --- TEXT:親チャンネルの通常メッセージ(非スレッド)を after で取得 ---
if ($type === 'TEXT') {
$msgs = $fetch("https://discord.com/api/v10/channels/{$cid}/messages?limit=100" . ($after ? "&after={$after}" : ""));
if (is_array($msgs)) {
foreach ($msgs as $m) {
if (!isset($m['id'])) continue;
$id = (string)$m['id'];
$data[$id] = array(
'user' => isset($m['author']['id']) ? (string)$m['author']['id'] : null,
'channel' => (string)$cid,
'thread' => null,
'title' => null,
'content' => isset($m['content']) ? (string)$m['content'] : '',
'params' => json_encode(array(
'attachments' => $m['attachments'] ?? array(),
'embeds' => $m['embeds'] ?? array(),
'message_reference' => $m['message_reference'] ?? null
), JSON_UNESCAPED_UNICODE),
'created' => $toJST($m['timestamp'] ?? '')
);
}
}
}
// --- ここから「もう1個の foreach」:チャンネル配下スレッドを after で取得(TEXT/FORUM 共通) ---
// 1) アーカイブ済みスレッド一覧を before で遡って集める(Active は使わない)
$threads = array();
$before = gmdate('c');
do {
$u = "https://discord.com/api/v10/channels/{$cid}/threads/archived/public?limit=100&before=" . rawurlencode($before);
$j = $fetch($u);
if (!is_array($j) || empty($j['threads'])) break;
foreach ($j['threads'] as $t) {
if (!isset($t['id'])) continue;
$threads[(string)$t['id']] = isset($t['name']) ? (string)$t['name'] : null; // tid => name
}
$last = end($j['threads']);
$before = $last['thread_metadata']['archive_timestamp'] ?? $before;
if (empty($j['has_more'])) break;
} while (true);
// 2) 各スレッドのメッセージを after で取得して保存(ここが追加の foreach)
foreach ($threads as $tid => $tname) {
$msgs = $fetch("https://discord.com/api/v10/channels/{$tid}/messages?limit=100" . ($after ? "&after={$after}" : ""));
if (!is_array($msgs)) continue;
foreach ($msgs as $m) {
if (!isset($m['id'])) continue;
$id = (string)$m['id'];
$data[$id] = array(
'user' => isset($m['author']['id']) ? (string)$m['author']['id'] : null,
'channel' => (string)$cid, // 親チャンネル
'thread' => (string)$tid, // スレッドID
'title' => $tname, // スレッド名(FORUMはタイトル相当)
'content' => isset($m['content']) ? (string)$m['content'] : '',
'params' => json_encode(array(
'attachments' => $m['attachments'] ?? array(),
'embeds' => $m['embeds'] ?? array(),
'message_reference' => $m['message_reference'] ?? null
), JSON_UNESCAPED_UNICODE),
'created' => $toJST($m['timestamp'] ?? '')
);
}
}
}
めっちゃ見やすくなった!!!
だけどプロジェクトによる気もする
ChatGPTって、簡単なことも複雑に言いがちなので、その悪い癖が出てるのかも知れませんね。
ただ今回は個人開発かつ1ファイルしかない、簡単なバッチ処理を書いてもらう想定の評価です。組織のプロジェクトだと上の方が良い場合もあるかも知れません。個人的な感想ということで。
でも大きいプロジェクトって、Laravelとかクラス使って、もっと見やすい感じだと思うけど…笑
Discussion