まずは作ったものを見ていただければ
※18歳未満の方はご利用いただけません。申し訳ございません……
作ろうと思ったきっかけ
2年ほど前にBookRainというアプリを作成しました。
こちらはOpenBDを使って取得した本のタイトルが雨のように降ってくるのを楽しむウェブサイトとなっております。
開発の経緯はこちらからどうぞ。
BookRainは楽しんで作ることができ、個人開発を公開するのは初めてで、私にとって有意義な経験となりました。
一方で、反省は以下の通りです。
- スマホで楽しめるものを作れていない。
- ユーザーからの入力を受け付けるようなものを作りたい。
この反省を踏まえて、次に作るのはクイズアプリなどが良いかなと考えていました。
実装も簡単そうで、ユーザー視点からもフォーマットがわかりやすい。
かつ、オリジナリティは出題の部分で主張できる。
あとは題材として面白そうなAPIを探していたところ、DMMの商品情報APIを見つけました。
DMMの商品情報APIについて
DMMアフィリエイトに登録して、API用のIDを発行することで利用できるようになります。
その名前の通り、DMMの商品情報を取得できるのですが、特徴的なのはFANZA(DMMのアダルトプラットフォーム)の商品についても商品情報を取得できる点です。
とにかく数も種類も豊富で、かつ各コンテンツにちゃんとジャンルや製作者の情報まで載っています。
この情報を良い切り口から提示できれば、面白いクイズが出せるのではないかなと考えていたところ、以前に2ちゃんねるで「作品の冒頭だけでNTRかどうか判断できるか」というスレッドがあったのを思い出しました。
技術選定
BookRainの時と同じくAPIを叩くだけなのだからBrythonを使ってS3,CloudFront,Route53,ACMで行こうかなと考えましたが、今回はAPIを叩くのにIDを使います。つまり、Brythonはフロントエンドなので、ID流出のリスクがあるということです。
クレジットカードの情報などと結びついているわけではないし流出したところでな、とも思いましたが、やはりなんだか気持ち悪いですし、もしかしたら今後、状況が変わるかもしれない。
かといってEC2を使うほどのものなのかなと考えたところで、サーバーレスで作るのはどうだろうと思い至りました。
zappaについて
すごく簡潔に説明するなら「S3にコードをアップしてLambdaとAPI gatewayでアプリ立ち上げるから、Flaskでアプリつくるだけでいいよ」という非常に便利なライブラリです。
サーバーを準備する必要がなく経済的で、かつバックエンドでAPIを叩くことができる。
まさに今回のアプリにぴったりでした。
開発環境はwindows,vscode,Python(3.11.9)です。
対象 | 技術 |
---|---|
フロントエンド | HTML5, skelton.css |
バックエンド | Flask |
インフラ | Amazon S3,AWS lambda, Amazon API Gateway,AWS WAF |
API | DMM 商品検索 API |
それでは、開発中に工夫したことや躓いたことを挙げていきます。
sessionについて
アプリ内で保持する情報として、作品のタイトルやジャンル、現在の正解数などが挙げられます。これらはsessionに入れておくことにしました。
使おうとしたら「シークレットキーを設定しないとsessionは使えないよ」というエラーが起きてしまいました。
実装はこちらの記事を参考にさせて頂きました。
上記のエラーについても記述があり、とてもわかりやすかったです。
flaskでのhrefでのURL指定について
ページ遷移のリンクですが、ローカルでは以下のコードで想定通りのページ遷移をしていました。
<a class="button" href="/intro">はい</a>
ところが、zappaでdeployしてからページ遷移すると{"message":"Forbidden"}が返ってきました……。
確認してみると、遷移しているURLの末尾がおかしくなっていました。
おかしいURL
https://9xswpuzxzd.execute-api.ap-northeast-1.amazonaws.com/intro
正しいURL
https://9xswpuzxzd.execute-api.ap-northeast-1.amazonaws.com/ntr/intro
これはシンプルな間違いで、flaskのurl_forを使えば解決でした。
<a class="button" href="{{ url_for('intro') }}">はい</a>
ローカルと本番とは違うのは分かっているのに、なんで同じだと思ってしまうんでしょうね……。
Lambdaでの環境変数設定と取得方法
APIのIDはLambdaに環境変数として設定しました。
取得はosライブラリで簡単にできます。
また環境変数として設定するシークレットキーについても
import secrets
secret_key = secrets.token_hex(32) # 32バイトのランダムな16進数文字列を生成
print(secret_key)
とサクッと作れますので共有しておきます。
商品をランダムに取得する
こちらはAPIの仕様の話になります。
私が作った商品情報を取得するコードは以下の通りです。
import requests
import json
import random
import os
# 環境変数の取得
api_id = os.environ.get('DMM_API_ID')
aff_id = os.environ.get('DMM_AFF_ID')
# サービスコードをキーとしたフロアコードの一覧
# 対象とするのは動画、PCゲーム、同人、FANZAブックス
sfdic = {
'doujin':['digital_doujin'],
'digital':['videoa','nikkatsu','anime'],
'pcgame':['digital_pcgame'],
'ebook':['comic','novel','photo']
}
# 選択したキーに対応するリストからランダムに値を選択
service_code = random.choice(list(sfdic.keys()))
floor_code = random.choice(sfdic[service_code])
# 最初にtotal_countsを取得する
url = f'https://api.dmm.com/affiliate/v3/ItemList?api_id={api_id}&affiliate_id={aff_id}&site=FANZA&service={service_code}&floor={floor_code}&hits=1'
res = requests.get(url)
syouhin = json.loads(res.text)
total_count = min(50000,int(syouhin['result']['total_count']))
# numberを取得
offset = random.randint(1,total_count)
url = f'https://api.dmm.com/affiliate/v3/ItemList?api_id={api_id}&affiliate_id={aff_id}&site=FANZA&service={service_code}&floor={floor_code}&hits=1&offset={offset}'
res = requests.get(url)
syouhin = json.loads(res.text)
2回問い合わせているのが工夫したところです。
なぜ2回なのかですが、offset(検索開始位置)がサービスとフロアによって最大値が異なるためです。
offsetはAPIの仕様ページでは最大50000となっていますが、50000件の商品がないサービス・フロアでoffsetを50000に指定すると、空のデータが返ってきます。
つまり、できる限り多くの問題を作りたいが、指定フロアの商品総数より大きな数を指定するのは出来ない、というわけです。
そこでレスポンスに含まれているtotal_count(何件の商品があるか)を問い合わせたあとに、offsetをその範囲内で取得するという方法をとることで、空データの取得を回避することにしました。
EDos攻撃対策
NTR judgeはデータベースを持ちません。
セッションに保存するのも、ゲームの進捗状態や問題についてです。
そういう意味ではアプリというよりは静的ウェブサイトに近いです。
とはいえ、最低限のセキュリティは担保する必要があるな、と感じたのが次の記事です。
たしかに出費は増えますが、精神衛生的にも良いかなということで、AWS WAFを導入しました。
シークレットモード(配信用モード)
突然ですが、みなさんはウーマンコミュニケーションというゲームをご存じでしょうか?
一時期、配信界隈を席巻した淫語探しゲームです。
このゲームには配信モードがあり、画面に表示しない言葉を選択することができます。
同じくNTR Judgeも性的なコンテンツを扱うアプリですので、需要がありそうな機能です。
- 出題時、タイトルが非表示。
- ボタンを押してタイトルをコピーすることで配信に乗せずにタイトル確認が可能。
- タイトルが配信できるものなら、表示ができる。
- 解答ページでの画像・動画などの詳細表示はなし。
というシークレットモードを作成しました。
タイトルのコピペボタンと、文字色を白黒で反転させるボタンをjavascriptで実装しただけですが、個人的にはこの機能が最も大きなフックになってくれると考えています。
アフィリエイト
また、通常モードには商品へのリンクがあるのですが、これをアフィリエイトのリンクにしてみました。
アフィリエイトの管理画面からクリック数とかも見れるので、これが中々に面白いです。
これ以外には広告などは置かないことにしました。理由はシンプルに、クイズの邪魔だからです。
まとめ
以上となります。
またなにかアイデアができたら、発作的にアプリを作ろうと思います。
また、この記事ではできる限り性的な表現がないようにしましたが、ガイドライン的に問題があれば、修正する可能性がございます。ご了承ください。