ブログの過去記事の文字数が分かるツールをAIに作らせたよ

# はじめに

ブログを書いていると「そういえば俺、今までどんだけ記事書いて来とったっけ～？」とふと思ったことはありませんか？
ブログサービスには編集画面に文字数カウンター機能があるところも多いので、過去記事をちまちま開いて文字数カウンターの値を見に行くみたいなことをやったりしていないでしょうか？
少ないうちはそれでも良いでしょうが、書いてきた記事の数が多くなってくると手作業での確認も不可能になってきます
しかし世の中のブログサービスにはバックアップの機能を持っているサービスがあります
このバックアップファイルを上手く利用して、どうにか文字数をカウントできないものか......

そんな時に役立つのが、ChatGPT!
ChatGPTに頼んでブログのバックアップファイルから文字数をカウントするツールを作ってもらいました
今回は僕が普段適当な記事を書き捨てているはてなブログの例で紹介します


# 出来たもの

さて、まずは最初に出来たものをお見せしましょう
はてなブログのバックアップエクスポート機能で出力させたバックアップのテキストファイルを食わせてあげると表形式で記事のタイトルと文字数を出力します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2275128/6d2c1cd0-e5fc-ae70-92a1-1639dcef02b4.png)
......
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2275128/013a03a3-1eed-a43a-625a-93c6288e9a5e.png)

一番下まで行くと、文字数を集計しており、20万字も書いていたのか！というのが分かります
コードをぺたぺた貼ってるとは言え、かなりの文字数文章を書いています
今までは「何となく沢山書いたよな～。知らんけど」で終わっていましたが、このツールを使うことで具体的に自分が書いた文字数を知ることができます

後から見返したい需要とかさらに分析したい可能性も考えられるので、CSVでダウンロードできるようにもなっています


# 作らせたコード

さて、このツールはChatGPTに作らせていると言いました
作らせ方はめんどくさいので説明は省きますが、そこまで手の込んだ指示をするまでもなく、いい感じに作っておいてレベルで何度か修正を重ねれば使えるものが出てきました

出来たコードだけここに貼り付けておきます

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>記事文字数解析ツール</title>
    <style>
        table {
            border-collapse: collapse;
            width: 100%;
        }

        th, td {
            border: 1px solid black;
            padding: 8px;
            text-align: left;
        }

        th {
            background-color: #f2f2f2;
        }

        #totalCharCount {
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <h1>記事文字数解析ツール</h1>
    <input type="file" id="fileInput" accept=".txt">
    <button onclick="downloadCsv()">CSV出力</button>
    <button onclick="downloadTotal()">集計結果ダウンロード</button>
    <div id="outputTable"></div>
    <div id="totalCharCount"></div>

    <script>
        document.getElementById('fileInput').addEventListener('change', handleFileSelect);

        function handleFileSelect(event) {
            const file = event.target.files[0];
            if (file) {
                const reader = new FileReader();

                reader.onload = function(e) {
                    const content = e.target.result;
                    const articles = parseMarkdown(content);
                    displayResults(articles);
                };

                reader.readAsText(file);
            }
        }

        function stripHtmlTags(html) {
            const doc = new DOMParser().parseFromString(html, 'text/html');
            return doc.body.textContent || "";
        }

        function parseMarkdown(content) {
            const lines = content.split('\n');
            const articles = [];
            let currentArticle = { title: '', charCount: 0, body: '', status: '' };
            let isBody = false;

            for (const line of lines) {
                if (line.startsWith('TITLE:')) {
                    // New article detected
                    if (currentArticle.title !== '') {
                        articles.push(currentArticle);
                    }

                    currentArticle = {
                        title: line.replace(/^TITLE:/, '').trim(),
                        charCount: 0,
                        body: '',
                        status: ''
                    };
                    isBody = false;
                } else if (line.startsWith('BODY:')) {
                    isBody = true;
                } else if (line.startsWith('STATUS:')) {
                    currentArticle.status = line.replace(/^STATUS:/, '').trim();
                } else if (isBody) {
                    const strippedLine = stripHtmlTags(line);
                    currentArticle.charCount += strippedLine.length;
                    currentArticle.body += strippedLine + '\n';
                }
            }

            // Add the last article
            if (currentArticle.title !== '') {
                articles.push(currentArticle);
            }

            return articles;
        }

        function displayResults(articles) {
            const table = document.createElement('table');
            const headerRow = table.insertRow();
            const titleHeader = headerRow.insertCell(0);
            const charCountHeader = headerRow.insertCell(1);
            const statusHeader = headerRow.insertCell(2);

            titleHeader.textContent = 'タイトル';
            charCountHeader.textContent = '文字数';
            statusHeader.textContent = '公開状態';

            let totalCharCountPublished = 0;
            let totalCharCountDraft = 0;

            for (const article of articles) {
                const row = table.insertRow();
                const titleCell = row.insertCell(0);
                const charCountCell = row.insertCell(1);
                const statusCell = row.insertCell(2);

                titleCell.textContent = article.title;
                charCountCell.textContent = article.charCount;
                statusCell.textContent = article.status;

                // Aggregate char count for both published and draft articles
                if (article.status.toLowerCase() === 'publish') {
                    totalCharCountPublished += article.charCount;
                }

                if (article.status.toLowerCase() === 'draft') {
                    totalCharCountDraft += article.charCount;
                }
            }

            // Display table
            document.getElementById('outputTable').innerHTML = '';
            document.getElementById('outputTable').appendChild(table);

            // Display total char count for both published and draft articles above the table
            const totalCharCountDiv = document.getElementById('totalCharCount');
            totalCharCountDiv.innerHTML = '';
            totalCharCountDiv.textContent = `公開記事の文字数合計: ${totalCharCountPublished}\n非公開記事 (Draft) の文字数合計: ${totalCharCountDraft}`;
        }

        function downloadCsv() {
            const table = document.querySelector('table');
            const rows = Array.from(table.querySelectorAll('tr'));

            const csvContent = rows.map(row => {
                const columns = Array.from(row.children).map(cell => cell.textContent);
                return columns.join(',');
            }).join('\n');

            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const link = document.createElement('a');

            link.href = URL.createObjectURL(blob);
            link.download = 'articles.csv';
            link.click();
        }

        function downloadTotal() {
            const totalCharCountDiv = document.getElementById('totalCharCount');
            const content = totalCharCountDiv.textContent;

            const blob = new Blob([content], { type: 'text/plain;charset=utf-8;' });
            const link = document.createElement('a');

            link.href = URL.createObjectURL(blob);
            link.download = 'total.txt';
            link.click();
        }
    </script>
</body>
</html>
```

それにしても、このコードを一瞬で出力してくれるChatGPTってすごい
ほんとに使い方次第だなと思いました


# おわりに

ちなみに、今回ははてなブログのバックアップファイルで試してみましたが、noteでも同じようなことが出来ることを確認しています
おそらくバックアップファイルさえ出せればどのブログサービスでも出来ると思われます
ただし、バックアップファイルのファイル形式が違うので、サービスごとにChatGPTにプログラムを作らせる必要はあると思います