👋

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