冬コミ（C91）東方同人ハッカソン　東方小説まとめをPerlとHTML5とMySQLで作ってみた。

# ０．目的と動機
冬コミ（C91）が差し迫ったある日。私は切実な悩みを抱えていた。
毎年誰かがやってくれるはずの、**「冬コミ東方小説まとめ」**が待てども待てども出てこないのだ。
どうやらいつもまとめを作成してくれている人が多忙のようである。
まぁ、年末だしね……。
### 「小説まとめ」が無いと困るのか？
小説まとめというのは、コミケや例大祭など同人イベントで小説サークルを回る上での宝の地図みたいなもの。
これがないと、
・**小説サークルは大小様々なイラスト本サークルの陰に埋もれてしまう**。
・**小説本が欲しい人も探すのに困る。（Webカタログでは役不足だ。オンリーイベントではそもそもWebカタログというものがない！）**
・また、**どういう本を出す予定なのかも理解してもらえない**ので、**集客にも影響が出る**。
という、**「頒布しようにもそもそも気付いてもらえない」**致命的な問題が発生したりする。
実はこれがあるとないとでは雲泥の差だったりする。
これが出てこないとなると、私も含め、小説サークルの主はアピールの手段を一つ失ってしまう。
（かつては**「発見！東方小説」**という非常に便利な小冊子があったりもしたが。今はその企画も廃止されてしまっており、より一層小説まとめの重要性は増していると考えられる。）
だが、これを作るというのは大変な労力であるというのもまた理解している。
どうにかして、こういうのを簡単に作れないか。それが今回作ってみた動機だ。

### ブログじゃだめなのか？
確かにこれら「～まとめ」はブログで作ってまとめるのが主流だ。
だが、大手ブログサービスを含め、大抵のレンタルブログでは**広告**が出る。
私はこれが嫌いなのだ。
別に人様がアドセンスで鞘銭を稼ごうと知ったことではないし、それに対して何を言う資格も権利も無いが。
自分で創るサービスにはできればこういうアドセンスは載せたくないと勝手に思っている。
実はブログが嫌だったのはただそれだけの理由だ。
しかし、このご時世、アドセンスが出ないレンタルブログサービスなど皆無に等しい。かといって、このようなまとめは巷にあるまとめ作成サービス（togetterなど）を使うと顰蹙を買う事もある。結構デリケートなのだ。
**目的が合致するサービスが無いなら作るしかないじゃないか。**
これは同人活動だろうが業務であろうが、クリエイティブな事を行う上では一つの真理のようなものだ。今回も私はその真理に従っただけだ。

# １．つくってみた。
まぁ論ずるよりも、実物を見てもらった方が早い。ので、成果物を以下に示す。
[C91東方小説まとめ](http://www.thtenshouki.info/c91_thnovels "C91東方小説まとめ")

ちなみに現状**スマートフォンでの表示には対応していない**。（今後の課題。）

主な機能は以下となる。
・島ごとにリンクがあり、それをクリックすることで該当の島の部分へジャンプできる
・その島で小説を頒布するサークルの一覧、新刊情報がある場合は新刊の情報を表示する。
・サークル代表者がTwitterアカウントを所持している場合は、Twitterページへのリンクを表示する。
・新刊情報がある場合、「新刊あり」と赤文字で表示する。
・サークルスペース番号にWebカタログへのリンクを表示する。
・サークル名にサークルWebページへのリンクを表示する。
・スクロールバーが移動した場合、インデックスを右下に表示する。

機能としては至ってシンプル。だが、これを手で書こうもんなら途轍もない労力だ。
ブログで書くにしてもつらい。調査の時間も合わせれば、悠に三人日は掛かるだろう。
その工数を何とか削減しようというのが今回の目的である。

ちなみに、利用用途を東方に限定する気は更々無い。ある程度どんなジャンルでも使えるように汎用的には作ってある。自分の活動ジャンルが東方なだけだ。

# ２．動作環境・開発環境
この「C91東方小説まとめ」は一見ただのHTMLページに見えるが、ソースの動的生成をしている。
バージョンによって動作が変わるようなメソッドやコマンドなどは使っていないつもりではあるが、念のため以下に環境を示す。
> OS    : FreeBSD 9.1
> Perl  : v5.8.9 built for amd64-freebsd
> MySQL : Ver 14.14 Distrib 5.5.32, for FreeBSD9.1 (amd64) using  5.2

開発環境は以下。
> OS : Microsoft Windows10 Build 1516
> Editor : 秀丸エディタ

開発に際し、何ら特殊なものは一切使用していない。
シェアウェア（秀丸エディタ）が嫌というのであれば、サクラエディタ、何ならメモ帳だっていい。

# ３．データベース
「まとめ自動生成」に欠かせないのはやはりDBだ。
といっても、そこまでリレーショナルなテーブル構成を作る気もなく。（時間も無かったので。）
こんなテーブルを１枚用意しただけだ。

```mysql:toho_novels.sql
CREATE TABLE `toho_novels` (
   `id` int(7) not null auto_increment,
   `event_name` varchar(64) not null,
   `circle_name` varchar(64) not null,
   `space_no` varchar(10) not null,
   `pen_name` varchar(64) not null,
   `new_books` varchar(256),
   `new_book_values` varchar(256),
   `url` varchar(512),
   `web_catalog_url` varchar(512),
   `twitter` varchar(512),
   `tag` varchar(256),
   PRIMARY KEY (`id`),
   KEY `event_name` (`event_name`),
   KEY `circle_name` (`circle_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
```

このテーブルの内容をプログラムの中で問い合わせて、その結果をもとにHTMLを自動生成するという訳だ。
一度構築してしまえば、以降まとめを作りたいと思い立ったらこのテーブルに、Webカタログ等で調べた元ネタ（CSVファイル）を流し込むだけでまとめ作成の準備は完了する。
Webページ自体はプログラムが作ってくれるので、まとめる人は元ネタだけ準備すれば良く、相当楽だ。

# ４．プログラム
では肝心のプログラムはどうなっているのかというと。
やはりこちらも難しい事は何一つしていない。

```perl:index.cgi
#!/usr/local/bin/perl --
use DBI;
use CGI;

$q = new CGI;

# DB接続
$d = "DBI:mysql:(DB接続先)";
$u = "(DBユーザ)";
$p = "(DBパスワード)";

$dbh = DBI->connect($d, $u, $p, {'RaiseError' => 1});
$dbh->do("set names utf8");
```
先頭にこれらのおまじないとDB接続設定をまず書く。
そして、ヒアドキュメントでヘッダ部分のHTMLを書く。テンプレートはお借りしたものを使っている。
[DESIGNED & DEVELOPED by FREEHTML5.CO](http://freehtml5.co/)

```perl:index.cgi
print << 'EOT_HEAD';
Content-Type: text/html; charset=UTF-8;

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
	<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>コミックマーケット９１　東方小説サークルまとめ</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="description" content="C91で頒布予定の東方小説についてのまとめ。突貫製作につき、品質(正確性・網羅性)に過度の期待をしないように。" />
	<meta name="keywords" content="東方, 小説, C91" />
	<meta name="author" content="東方天翔記CPUダービー処" />

  <!-- 
	//////////////////////////////////////////////////////

	FREE HTML5 TEMPLATE 
	DESIGNED & DEVELOPED by FREEHTML5.CO
		
	Website: 		http://freehtml5.co/
	Email: 			info@freehtml5.co
	Twitter: 		http://twitter.com/fh5co
	Facebook: 		https://www.facebook.com/fh5co

	//////////////////////////////////////////////////////
	 -->

  	<!-- Facebook and Twitter integration -->
	<meta property="og:title" content="コミックマーケット９１　東方小説サークルまとめ"/>
	<meta property="og:image" content=""/>
	<meta property="og:url" content="http://www.thtenshouki.info/c91_thnovels/"/>
	<meta property="og:site_name" content=""/>
	<meta property="og:description" content="C91で頒布予定の東方小説についてのまとめ。突貫製作につき、品質(正確性・網羅性)に過度の期待をしないように。"/>
	<meta name="twitter:title" content="コミックマーケット９１　東方小説サークルまとめ" />
	<meta name="twitter:image" content="" />
	<meta name="twitter:url" content="http://www.thtenshouki.info/c91_thnovels/" />
	<meta name="twitter:card" content="" />
	<!-- Animate.css -->
	<link rel="stylesheet" href="css/animate.css">
	<!-- Icomoon Icon Fonts-->
	<link rel="stylesheet" href="css/icomoon.css">
	<!-- Bootstrap  -->
	<link rel="stylesheet" href="css/bootstrap.css">

	<link rel="stylesheet" href="css/style.css">
	<link rel="stylesheet" type="text/css" href="css/pagetop.css" />


	<!-- Modernizr JS -->
	<script src="js/modernizr-2.6.2.min.js"></script>
	<!-- FOR IE9 below -->
	<!--[if lt IE 9]>
	<script src="js/respond.min.js"></script>
	<![endif]-->
	</head>
	<body>
    <p><a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.thtenshouki.info/c91_thnovels/" data-text="コミックマーケット９１　東方小説サークルまとめ" data-lang="ja">ツイート</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
	<header>
		<div class="container text-center">
			<div class="fh5co-navbar-brand">
				<h1>コミックマーケット９１　東方小説サークルまとめ</h1>
			</div>
			<nav id="fh5co-main-nav" role="navigation">
				<ul>
					<li><a href="#top">トップ</a></li>
					<li><a href="#about">このページについて</a></li>
					<li><a href="#ム">ム島</a></li>
					<li><a href="#メ">メ島</a></li>
					<li><a href="#モ">モ島</a></li>
					<li><a href="#ユ">ユ島</a></li>
					<li><a href="#リ">リ島</a></li>
					<li><a href="#ル">ル島</a></li>
				</ul>
			</nav>
		</div>
	</header>
	<div id="about" class="fh5co-parallax" style="background-image: url(images/portfolio_pic8.jpg);" data-stellar-background-ratio="0.5">
		<div class="overlay"></div>
		<div class="container">
			<div class="row">
				<div class="col-md-8 col-md-offset-2 col-sm-12 col-sm-offset-0 col-xs-12 col-xs-offset-0 text-center fh5co-table">
					<div class="fh5co-intro fh5co-table-cell">
						<h2 class="text-center">このページについて</h2>
						<p>C91で頒布予定の東方小説についてのまとめ。既刊は掲載しない。<br />基本的にWebカタログ情報ベース。記載なしは記載できない。<br /><font color="#FF0000"><b>突貫製作につき、品質(正確性・網羅性)に過度の期待をしないように。</b></font><br />掲載追加申込、修正申込、削除申請等は<a href="https://twitter.com/yuna_priest" target="_blank">こちら</a>までリプライをください</p>
						<h2 class="text-center">掲載基準</h2>
						1日目（木曜日）に参加するサークルのうち、<br />以下のいずれかを満たすサークル
						<ul>
							<li>Webカタログにて「小説サークル」であると推定される東方サークル</li>
							<li>Webカタログにて「新刊」で小説本を出すと明記がある東方サークル</li>
							<li>1日目で東方の小説新刊を委託で頒布するサークル(ジャンル不問)</li>
						</ul>
					</div>
				</div>
			</div>
		</div>
	</div><!-- end: fh5co-parallax -->
EOT_HEAD
```

> Content-Type: text/html; charset=UTF-8;

がないとバグるので要注意だ。よく忘れがち。（今回もやった。）

ヘッダー部分を書いたところで、SELECT文を一発。

```perl:index.cgi
$island = "";
$quoted_str = "SELECT circle_name, space_no, pen_name, new_books, new_book_values, url, web_catalog_url, twitter, tag FROM `toho_novels` where event_name = 'コミックマーケット９１' order by space_no;";
$sth = $dbh->prepare($quoted_str);
$sth->execute;
```

なんともダサいSELECT文だが許してほしい。時間が無かったんだ。
これで艦これまとめと東方まとめを両立するという場合は、SELECT文をちょっと考えなければならないだろう。
ここでのポイントは、$islandという変数。
この変数の値が変わるたびに島のdivを切り替えるということをしている。

取得したデータレコード数分ループする。よく見るfor文だ。
island変数は初期化時は空なので、それを利用して一番最初のDIV要素を作る。
island変数が変わるタイミングは、取得した島記号（space_noの１文字目）が現在保持している島記号と違う場合。
この場合、前の島のdivを閉じて、新しい島のdivを開く。

```perl:index.cgi
$rowcnt = $sth->rows;
# 取得したデータ件数分ループ
for ($cnt = 0; $cnt < $rowcnt; $cnt++) {
  @tmp = $sth->fetchrow_array;
  $nowisland_mark = $tmp[1];
  utf8::decode($nowisland_mark);
  $nowisland = substr($nowisland_mark, 0, 1);
  utf8::encode($nowisland);
  
  if ($island eq "") {
    $island = $nowisland;
    print << "EOT_DIV_HEAD";
	<div id="fh5co-work-section">
		<div id="$island" class="container">
			<h2>■${island}島</h2>
			<div class="row">
EOT_DIV_HEAD
  } elsif ($island ne $nowisland) {
    $island = $nowisland;
    print << "EOT_DIV";
			</div>
		</div>
	</div>
	<div id="fh5co-work-section">
		<div id="$island" class="container">
			<h2>■${island} 島</h2>
			<div class="row">
EOT_DIV

  }
```
div生成部分に続き、それぞれのスペースの明細を生成する。
と言っても、データベースから取って来た情報をテンプレートに埋めるだけ。
プログラムはこれを繰り返して、各サークルスペースの情報を生成していく。

```perl:index.cgi
  # Webカタログリンク生成
  $catalog = "<a href='$tmp[6]' target='_blank'>$tmp[1]</a>";
  # サークルHPリンク生成
  $circle = $tmp[0];
  if ($tmp[5] ne "") {
    $circle = "<a href='$tmp[5]' target='_blank'>$circle</a>";
  }
  # ツイッターリンク生成
  $twitter = "";
  if ($tmp[7] ne "") {
    $twitter = "<a href='$tmp[7]' target='_blank'><i class='icon-twitter-with-circle'></i></a>";
  }
  # タグ
  $tag = $tmp[8];
  $tag =~ s|/|　|g;
  # 新刊情報
  $newbook = $tmp[3];
  $newbook =~ s|/|<br />　　　　　　|g;
  $newbook_val = $tmp[4];
  $newbook_mark = "";
  if ($newbook ne "") {
    $newbook_mark = "<small><font color='#FF0000'><b>新刊あり</b></font></small>";
  }
  if ($newbook_val ne "") {
    $newbook_val =~ s|/|円<br />　　|g;
    $newbook_val = "$newbook_val円";
  }
  
  print << "EOT_DESC";
				<h4>　　◆$catalog　$circle　（$tmp[2] $twitter）　$newbook_mark</h4>
				<div class="desc">
					<table border="0"><tr><td><h5>　　　　　　$newbook</h5></td><td><h5>　　$newbook_val</h5></td></tr></table>
					<h6>　　　　　　　　＜タグ＞ $tag</h6>
				</div>
EOT_DESC

}

```
最後にフッター部分をヒアドキュメントで書いておしまい。

```perl:index.cgi
print << "EOT_FOOT";
			</div>
		</div>
	</div>
	<!-- jQuery -->
	<script src="js/jquery.min.js"></script>
	<!-- jQuery Easing -->
	<script src="js/jquery.easing.1.3.js"></script>
	<!-- Bootstrap -->
	<script src="js/bootstrap.min.js"></script>
	<!-- Waypoints -->
	<script src="js/jquery.waypoints.min.js"></script>
	<!-- Owl carousel -->
	<script src="js/owl.carousel.min.js"></script>
	<!-- Stellar -->
	<script src="js/jquery.stellar.min.js"></script>
	<script src="js/scroll.js"></script>
	
	<!-- Main JS (Do not remove) -->
	<script src="js/main.js"></script>

	<p class="pagetop"><a href="#ム">ム島</a> <a href="#メ">メ島</a> <a href="#モ">モ島</a> <a href="#ユ">ユ島</a> <a href="#リ">リ島</a> <a href="#ル">ル島</a> <a id="moveTop" href="#top">▲</a></p>
	</body>
</html>
EOT_FOOT

# DB切断
$dbh->disconnect();
```

# ５．かかった工数
多分元ネタ作成とDB設計とコーディング、簡単な動作確認含めて１２時間くらい。
元ネタ作成自体は３～４時間なので、一度構築してしまえば、今後はその元ネタを作成する３～４時間だけで済むという訳だ。
毎回ブログ記事書くよりは格段に楽だろうねー。

# ６．最後に
perlとMySQLさえ使えば結構簡単に作れるので、別に東方小説まとめに限らず、他ジャンルや他作品群でも使えるのが特長だったりする。
需要があればソースコードは流用して頂いて構わない。
**（但しアフィを主目的で使うのは勘弁な！あくまで同人イベント参加者の利便性向上の為に使ってくれ。）**