# WEBマンガ検索 Comicy

## 概要
複数のWEBマンガ配信サイトを横断して、マンガの検索・閲覧・お気に入り管理ができる機能。

## URL一覧

| ページ | URL | 説明 |
|--------|-----|------|
| TOP | `/comic` | フィーチャー、ランキング、本日更新、Kindleセール、ピックアップ |
| マンガ一覧 | `/comic/list` | 検索・サイト絞り込み・ジャンル絞り込み |
| マンガ詳細 | `/comic/view/{id}` | コミック情報、エピソード一覧、お気に入り、コメント |
| ランキング | `/comic/ranking` | PVランキング（7日/30日/総合） |
| 本日更新 | `/comic/today` | 当日更新されたエピソード |
| 連載スケジュール | `/comic/weekly` | 曜日別の連載一覧（タブ切替） |
| 発売予定 | `/comic/schedule` | 単行本の発売スケジュール |
| Kindleセール | `/comic/kindle-sale` | Kindleのセール情報 |
| お気に入り | `/comic/favorite` | ログインユーザーのお気に入り一覧 |
| 閲覧履歴 | `/comic/history` | 閲覧したコミックの履歴 |
| コミック管理 | `/admin/comic-management` | admin専用。サイト別統計・表示トグル・全削除 |
| コメント管理 | `/admin/comic-comment-management` | admin専用。コメント検索・ソフト削除・復元・統計 |
| 投稿管理 | `/admin/comic-tweet-management` | admin専用。本日更新コミックのTwitter自動投稿設定・即時実行 |

## 対応サイト

| サイト | スラッグ | ヘルパークラス | ジャンル取得 |
|--------|----------|---------------|-------------|
| となりのヤングジャンプ | `tonariyjump` | `Tonarinoyjump` | - |
| マガポケ | `magapoke` | `Magapoke` | ランキングページ(/ranking/ID) |
| コミックDAYS | `comic-days` | `Comicdays` | - (JSレンダリング) |
| 少年ジャンプ＋ | `jump-plus` | `Jumpplus` | - |
| サンデーうぇぶり | `sunday-webry` | `Sundaywebry` | - |
| ヤンマガWeb | `young-magazine` | `YoungMagazine` | タグページ(/tags?kind=comics) |
| コミプレ | `comi-pre` | `ComiPre` | - |
| くらげバンチ | `kurage-bunch` | `KurageBunch` | - |
| カドコミ（旧TYPE-MOONコミックエース） | `kadocomi` | `Kadocomi` | 一覧ページ(.item-tag) |
| ComicOGYAAA!! | `comic-ogyaaa` | - | __NEXT_DATA__(mainGenre) |
| ゼノン編集部 | `comic-zenon` | `ComicZenon` | - |
| コミックメテオ | `comic-meteor` | `Comicmeteor` | - |
| ヤングエースUP | `youngaceup` | `Youngaceup` | タグ一覧ページ |
| ガンガンONLINE | `ganganonline` | `Ganganonline` | ジャンルリンク |
| サイコミ | `cycomi` | `Cycomi` | ジャンルタグ |
| コミックブースト | `comicboost` | `Comicboost` | - |
| アルファポリス | `alphapolice` | `Alphapolice` | カテゴリタグ |
| pixivコミック | `pixivcomic` | `Pixivcomic` | - |
| 裏サンデー | `urasunday` | `Urasunday` | - |

## Artisanコマンド

### コミック一覧更新
```bash
# サイト指定で一覧取得＆DB登録
php artisan comic:update-comics --site=tonariyjump,magapoke

# 全サイト
php artisan comic:update-comics
```

### エピソード更新
```bash
# 曜日スケジュールに従ってエピソード更新
php artisan comic:update-sites

# サイト指定
php artisan comic:update-sites --site=magapoke

# 全件強制更新
php artisan comic:update-sites --force
```

### ジャンル情報更新
```bash
# 全対応サイトのジャンル一括更新
php artisan comic:update-genres

# サイト指定
php artisan comic:update-genres --site=magapoke
php artisan comic:update-genres --site=kadocomi
php artisan comic:update-genres --site=young-magazine
php artisan comic:update-genres --site=comic-ogyaaa

# dry-run（保存せずに確認）
php artisan comic:update-genres --site=magapoke --dry-run -v
```

#### ジャンル取得方法（サイト別）

| サイト | 方法 |
|--------|------|
| マガポケ | ジャンル別ランキング（/ranking/21〜32）からコミックの`/title/ID`でマッチング |
| カドコミ | 一覧ページの`.item-tag li`からタグ取得、`comic_sites.link`でマッチング |
| ヤンマガWeb | タグ検索（/search?kind=tags&q=#タグ名）の結果から`/comics/タイトル`でマッチング |
| ComicOGYAAA | トップページの`__NEXT_DATA__` Apollo StateからSeries.mainGenreを取得、タイトルでマッチング |

### 本日更新取得
```bash
# 各サイトの「本日更新」ページから、新規公開されたエピソードを取り込む
php artisan comic:fetch-today-updates

# サイト指定（カンマ区切り）
php artisan comic:fetch-today-updates --site=kadocomi,magapoke

# 過去日付を指定
php artisan comic:fetch-today-updates --date=2026-04-13
```

`get_today_updates($date)` を実装していないヘルパーはスキップされる。対応サイトは順次追加。

### コミックメタ情報リフレッシュ
```bash
# 公開フラグ・Amazon ASIN・ジャンルなどのメタ情報を再収集
php artisan comic:refresh-meta
```

### Kindleセール情報取得
```bash
# Kindleセール情報をAmazon Creators APIから取得
php artisan comic:fetch-kindle-sale
```

> **ホーム画面でのセール表示**: TOPページ（`/comic`）では `fetchKindleSaleItems()` で内部API経由のセールデータを表示する。タイムアウトは **5秒** に設定されており、`ConnectionException` 発生時は空配列にフォールバックしてホーム画面のレンダリングを継続する（`Log::warning` でエラーを記録）。

### 本日更新コミックのツイート予約（2026-04-17 追加）
```bash
# 設定画面（/admin/comic-tweet-management）の内容に従って予約投稿を生成
php artisan comic:tweet-updates

# 設定で無効化されていても強制実行
php artisan comic:tweet-updates --force
```

- 直近 N 日（デフォルト 7 日）のコミック PV ランキング上位 `ranking_top_count` 件（デフォルト 50）を集計
- その中から当日更新されたエピソードを `post_limit` 件（デフォルト 4）抽出
- `post_hours`（デフォルト `8,12,17,19`）の「まだ来ていない時刻」にスロット割り当て
- `exclude_keywords`（カンマ区切り）に含まれる `summary` のエピソードは除外
- `TwitterScheduledPost` に `scheduled_at` 指定で挿入（送信は既存の Twitter 予約投稿機構）
- テンプレートのプレースホルダー: `{title}` / `{summary}` / `{url}` / `{hashtags}`

### サイトマップ
- `/sitemap-comic.xml` — コミック詳細ページのサイトマップを自動生成

### APIエンドポイント
- `GET /api/kindle-sale` — Kindleセールデータ配信（内部トークン認証）

## Kindle セール API のフォールバック

TOPページ（`/comic`）で表示する Kindle セール情報は外部 API（`/api/kindle-sale`）から取得している。`ComicController::fetchKindleSaleItems()` では以下のフォールバック処理を行う:

- タイムアウトを 5 秒に設定（ユーザー体感の悪化防止）
- `ConnectionException`（接続失敗・タイムアウト）時は warning ログを残して空配列を返す
- HTTP エラー時も空配列を返す（TOPページの他セクションは正常表示される）

## bot User-Agent による autoRefresh 除外

マンガ詳細ページ（`/comic/view/{id}`）では、非管理者・未ログインユーザー向けに `check_time` が古い場合に自動リフレッシュ（JS側でスクレイピングAPIを呼び出す）が発火する。

クローラ（Googlebot、Bingbot 等）のアクセスでこの自動リフレッシュが発火すると、外部サイトへの不要なスクレイピングが大量発生するため、`ComicController::view()` で User-Agent を正規表現で判定し、bot アクセス時は `autoRefresh` を無効化している。

対象パターン: `bot`, `crawl`, `spider`, `slurp`, `bingbot`, `googlebot`, `yandex`, `baidu`, `facebookexternalhit`, `twitterbot`, `linkedinbot`, `semrush`, `ahrefs`, `mj12`, `dotbot`, `archive.org`

## スケジュール設定（routes/console.php）

```php
if (config('app.name') === 'app_comicy' || config('app.server_role') === 'nas') {
    Schedule::command('comic:update-sites')->hourly();
    Schedule::command('comic:update_comics --site=tonariyjump,magapoke')->dailyAt('00:01');
}
```

## データベース

### テーブル

| テーブル | 説明 |
|---------|------|
| `comics` | コミック本体（タイトル、著者、画像、PV、ジャンル等） |
| `comic_sites` | コミック×配信サイト（リンク、更新曜日、チェック時刻等） |
| `comic_site_episords` | エピソード（話数、公開日、サムネイル、リンク） |
| `comic_genres` | ジャンル紐付け（現在未使用、comics.genresカラムで管理） |
| `comic_views` | 閲覧履歴・PVカウント |
| `comic_favorites` | お気に入り |
| `comic_comments` | コメント |
| `comic_update_logs` | 更新ログ |
| `comic_source_sites` | コミック配信サイトのマスタ（`2026_04_17_120000_create_comic_source_sites_table.php`） |
| `setting_comic_tweets` | 本日更新ツイート自動投稿設定（シングルトン行、`2026_04_16_110000_create_setting_comic_tweets_table.php`） |

### 主要カラム（comics）

| カラム | 説明 |
|--------|------|
| `genres` | ジャンル（カンマ区切り。例: `アクション,ファンタジー`） |
| `pv` | 累計ページビュー |
| `oneshot_flg` | 読み切りフラグ |
| `publish_flg` | 公開フラグ |

### 主要カラム（comic_sites）

| カラム | 説明 |
|--------|------|
| `schedule` | 更新曜日（月〜日） |
| `update_timing` | 連載状態（連載中、完結、読み切り等） |
| `update_text` | 更新テキスト（例: `4月7日(火)更新!`） |
| `check_time` | 最終スクレイピング日時 |
| `next_update_date` | 次回更新予定日（`2026_04_14_100003_add_next_update_date_to_comic_sites_table.php`） |

### 主要カラム（comic_site_episords）

| カラム | 説明 |
|--------|------|
| `paid_flg` | 有料エピソードフラグ（`2026_04_14_100002_add_paid_flg_to_comic_site_episords_table.php`） |
| `expire_time` | 無料公開期限（`2026_04_14_100004_add_expire_time_to_comic_site_episords_table.php`） |
| `image_local_name` | ローカル保存したサムネイル名（`2026_04_13_200142_add_image_local_name_to_comic_site_episords_table.php`） |

### 補修マイグレーション（本番DB整合）

- `2026_04_14_000001_fix_comic_views_id_auto_increment.php`
- `2026_04_14_200001_fix_comic_site_episords_missing_columns.php`
- `2026_04_14_200002_fix_comic_site_episords_id_auto_increment.php`
- `2026_04_14_300001_add_index_to_comic_site_episords_link.php`
- `2026_04_14_100000_rename_tmca_slug_to_kadocomi.php` / `2026_04_14_100001_rename_tmca_site_name_to_kadocomi.php`（TMCA → Kadocomi リネーム）

## HTTP 通信の共通化

全コミックサイトヘルパーの HTTP GET リクエストは `ComicCommon::safeHttpGet()` に統一されている（Jumpplus / Kadocomi / Magapoke は個別にタイムアウト処理を実装）。

| 項目 | 内容 |
|------|------|
| タイムアウト | `ComicCommon::HTTP_TIMEOUT`（15秒） |
| SSL検証 | 無効（`withoutVerifying()`） |
| エラー処理 | `ConnectionException` を catch し、warning ログを残して `null` を返す |
| 用途 | 各ヘルパーの `get_items()` / `get_item_detail()` での外部サイトアクセス |

呼び出し元は戻り値 `null` チェックで接続失敗を判定し、空配列を返すかスキップする。

#### 2026-04-20 全ヘルパー統一

以下15ヘルパーが `ComicCommon::safeHttpGet()` に移行済み: Alphapolice, ComiPre, ComicZenon, Comicboost, Comicdays, Comicmeteor, Cycomi, Ganganonline, KurageBunch, Sundaywebry, Tonarinoyjump, YoungMagazine, Youngaceup, Kadocomi（部分）, Magapoke（部分）。

`ComicCommon::getTodayUpdatesFromAtom()` のタイムアウトも 30秒 → 15秒（`HTTP_TIMEOUT`）に統一。`ComicCommon::downloadComicImage()` も `ConnectionException` をキャッチして接続タイムアウトを warning ログ化。

## ファイル構成

```
app/
├── Console/Commands/Comic/
│   ├── UpdateComicsCommand.php         # コミック一覧更新
│   ├── UpdateComicSitesCommand.php     # エピソード更新
│   ├── UpdateComicGenresCommand.php    # ジャンル情報更新
│   ├── FetchTodayUpdatesCommand.php    # 本日更新取得（kadocomi等）
│   ├── RefreshComicMetaCommand.php     # メタ情報リフレッシュ
│   ├── FetchKindleSaleCommand.php      # Kindleセール情報取得
│   └── TweetComicUpdatesCommand.php    # 本日更新コミックのTwitter予約投稿生成（2026-04-17）
├── Foundation/Helpers/Comic/
│   ├── ComicCommon.php               # 共通処理（クエリ、更新ロジック）
│   ├── Tonarinoyjump.php             # 各サイトのスクレイパー
│   ├── Magapoke.php
│   ├── Comicdays.php
│   ├── Jumpplus.php
│   ├── Sundaywebry.php
│   ├── YoungMagazine.php
│   ├── ComiPre.php
│   ├── KurageBunch.php
│   ├── Kadocomi.php                    # 旧 Tmca.php（2026-04-14 リネーム）
│   ├── Youngaceup.php
│   ├── Ganganonline.php
│   ├── Cycomi.php
│   ├── ComicZenon.php
│   ├── Comicboost.php
│   ├── Comicmeteor.php
│   ├── Alphapolice.php
│   ├── Pixivcomic.php
│   └── Urasunday.php
├── Http/Controllers/
│   ├── Comic/ComicController.php     # メインコントローラ
│   ├── Comic/ComicApiController.php  # API（Kindleセール配信）
│   ├── Comic/ComicSitemapController.php # サイトマップXML
│   ├── Admin/ComicManagementController.php
│   ├── Admin/ComicCommentManagementController.php # コメント管理画面
│   └── Admin/ComicTweetManagementController.php   # 自動投稿設定・即時実行（2026-04-17）
├── Jobs/Comic/
│   ├── UpdateComicSiteJob.php        # エピソード更新ジョブ
│   ├── ComicScrapeSitesJob.php       # 全サイト一括スクレイピング
│   └── FetchKindleItemsJob.php       # Kindleアイテム取得ジョブ
└── Models/Comic/
    ├── Comic.php
    ├── ComicSite.php
    ├── ComicSiteEpisord.php
    └── ...

resources/
├── css/comic/
│   ├── comic-all.css                 # 統合エントリーポイント
│   ├── comic.css                     # 共通・サイドバー
│   ├── comic-home.css
│   ├── comic-index.css
│   ├── comic-view.css
│   ├── comic-ranking.css
│   ├── comic-weekly.css
│   ├── comic-sale.css
│   └── comic-schedule.css
└── views/comic/
    ├── top.blade.php
    ├── index.blade.php
    ├── view.blade.php
    ├── ranking.blade.php
    ├── today.blade.php
    ├── weekly-schedule.blade.php
    ├── schedule.blade.php
    ├── favorite.blade.php
    ├── history.blade.php
    └── parts/
        ├── sidebar.blade.php
        ├── comic_card.blade.php
        └── comic_row.blade.php
```
