194 Commits

Author SHA1 Message Date
eai04191
5e9fa05be5 spoilerを画像にだけ適用するように変更 2020-01-28 01:47:40 +09:00
shibafu
9f565798c0 Update npm packages 2020-01-26 17:08:36 +09:00
shibafu
ef23f1b12e Merge pull request #316 from shikorism/fix/295-trim-tag-space
タグの入力確定時にtrimする
2020-01-18 16:22:51 +09:00
shibafu
2edabfa38f タグの入力確定時にtrimする 2020-01-18 00:31:51 +09:00
shibafu
ad65475037 Update composer packages 2020-01-18 00:11:03 +09:00
shibafu
e429b2c054 Merge pull request #313 from eai04191/feature/resolver-dlsite-fix
DLsiteResolver の修正と更新
2020-01-10 09:43:24 +09:00
shibafu
cec9ffc5ac Merge pull request #314 from eai04191/feature/happy-new-year
著作権表示の更新
2020-01-10 09:01:08 +09:00
eai04191
07b315e8af 著作権表示の更新 2020-01-10 04:27:56 +09:00
eai04191
1948d5235e .btn_followを動的作成するようになったため削除クラス名を.add_followに変更 2020-01-10 03:58:24 +09:00
eai04191
78a255c1e3 fixtureを更新 2020-01-10 03:58:07 +09:00
shibafu
d9cf5e54e3 Merge pull request #308 from shikorism/fix/force-testing-env
phpunit.xml内の環境変数を強制適用する
2019-12-30 16:04:03 +09:00
shibafu
e7db04fd55 Merge pull request #309 from shikorism/fix/307-monthly-term
月間チェックイン回数グラフの期間セレクターが、直近1年の範囲しか選択できないバグを修正
2019-12-19 00:56:52 +09:00
shibafu
37a10b7354 月間チェックイン回数グラフの期間セレクターが、直近1年の範囲しか選択できないバグを修正 2019-12-16 22:56:11 +09:00
shibafu
170492b39d 本来テストにいらない処理を消す 2019-12-15 20:07:25 +09:00
shibafu
ea3f2e595f phpunit.xml内の環境変数を強制適用する
docker-compose.ymlで指定した環境変数で上書きされてしまうため、こうする
2019-12-15 20:07:06 +09:00
shibafu
6ea360bc4e Merge branch 'master' into develop
cherry-pick部分のコンフリクト解消のため、ours merge
2019-12-12 20:35:47 +09:00
shibafu
251d7b9108 Merge pull request #299 from shikorism/feature/263-deactivate-account
アカウント削除
2019-12-12 20:25:58 +09:00
shibafu
bba945113b deactivated_usersに登録されていることをチェックし忘れていた 2019-12-11 01:42:15 +09:00
shibafu
695f457505 テストをしましょう 2019-12-11 01:32:55 +09:00
shibafu
00345eedca Merge pull request #303 from unarist/fix/ap-actor
MastodonのプロフィールURLをいい感じに表示する
2019-12-10 23:43:52 +09:00
shibafu
19ef4d2bbe Merge pull request #305 from eai04191/feature/resolver-pixiv-en
PixivResolver 英語版に対応する
2019-12-10 23:42:15 +09:00
shibafu
6577a032e1 Merge pull request #304 from eai04191/feature/resolver-hentai-foundry
HentaiFoundryResolverを追加
2019-12-10 23:40:01 +09:00
eai04191
010a0a9c8f 英語版Pixivに対応する 2019-12-10 13:12:37 +09:00
eai04191
cdc6335a06 HentaiFoundryResolverを追加 2019-12-09 01:35:44 +09:00
eai04191
03633440a6 jakeasmith/http_build_urlを追加 2019-12-09 01:05:28 +09:00
unarist
67ae0e159f ActivityPubResolverはNoteだけ処理するように 2019-11-24 21:18:19 +09:00
shibafu
73ee9f108b 過去に使用されたユーザー名で登録できないようにする 2019-11-16 00:26:52 +09:00
shibafu
f132955be7 アカウント削除画面の追加 2019-11-16 00:26:38 +09:00
shibafu
3420e053fc Merge pull request #297 from shikorism/fix/admin-menu-in-sp-layout
管理画面へのリンクがモバイルで表示されないバグの修正
2019-11-14 21:15:09 +09:00
shibafu
a01bc6989e 管理画面へのリンクがモバイルで表示されないバグの修正 2019-11-14 00:59:23 +09:00
shibafu
a71aa0c3b6 Merge pull request #296 from shikorism/fix/295-drop-empty-value-tag
空文字列のタグは保存せず捨てる
2019-11-13 00:39:26 +09:00
shibafu
26be8a086e 空文字列のタグは保存せず捨てる 2019-11-09 23:19:28 +09:00
shibafu
af5de3ee14 Merge pull request #294 from eai04191/feature/resolver-dlsite-affiliate-url
DLsiteResolver 新しいアフィリエイトURL構造に対応
2019-11-09 18:40:01 +09:00
eai04191
c8cee80144 冗長な評価を削除 2019-11-09 18:28:47 +09:00
eai04191
d8e170ff85 DLsiteの新しいアフィリエイトURL構造に対応 2019-11-08 05:36:40 +09:00
shibafu
9c101dfb7b Merge pull request #293 from eai04191/feature/resolver-xtube-fix
XtubeResolver 修正
2019-11-05 08:17:34 +09:00
eai04191
12fd228e75 array_mapのcallbackをシンプルにする 2019-11-05 05:59:00 +09:00
eai04191
dc0eb0a548 tagの各要素をtrim()する 2019-11-04 14:24:09 +09:00
eai04191
16ed4482f4 OGPを吐くようになっていたのでOGPResolverでimageを取得するように変更 2019-11-04 14:23:30 +09:00
eai04191
57a847baf5 Update fixture 2019-11-04 14:01:42 +09:00
shibafu
332b6d7dd0 こっちのほうがマシ 2019-10-14 02:24:39 +09:00
shibafu
99a92c6106 チェックインの編集は本人のみ可能
(cherry picked from commit bcb5abb161)
2019-10-14 02:17:02 +09:00
shibafu
bcb5abb161 チェックインの編集は本人のみ可能 2019-10-14 02:03:57 +09:00
shibafu
4ca6f00c1b Merge pull request #288 from MitarashiDango/feature_tag_list
タグ一覧画面を追加
2019-10-11 00:33:25 +09:00
shibafu
7b8811b894 Merge pull request #281 from shikorism/fix/279-unauthorized-ajax-call
非ログイン状態でいいねしようとした時にログインを促す
2019-10-11 00:31:24 +09:00
shibafu
c7e261d06b Merge pull request #289 from shikorism/fix/nijie-unescape-after-json-decode
NijieResolver: JSONデコード後にHTMLエンティティのデコードを行う
2019-10-04 00:32:57 +09:00
shibafu
b3c98613e7 テストの追加 2019-10-03 23:34:57 +09:00
shibafu
f7a5948e8e Merge pull request #292 from kb10uy/add-kb10uyshortstoryserverresolver
Kb10uyShortStoryServerResolver の追加
2019-10-03 23:22:22 +09:00
Yuu Kobayashi
d1abca5416 成否判定を除去 2019-09-29 18:03:34 +09:00
Yuu Kobayashi
579708389a Kb10uyShortStoryServerResolver とテストを追加 2019-09-29 11:33:05 +09:00
MitarashiDango
900e4c94a7 怒られたのでなおした... 2019-09-25 23:46:06 +09:00
MitarashiDango
a434a45e4a タグ一覧の件数カウント条件を検索機能と揃える 2019-09-25 23:39:36 +09:00
shibafu
e6657a0756 Merge pull request #291 from shikorism/develop
Release 20190925.2210
2019-09-25 22:20:39 +09:00
shibafu
4f6bb0ac15 Merge pull request #290 from eai04191/feature/resolver-pixiv-new-url
PixivResolver 新URL形式に対応
2019-09-25 22:07:55 +09:00
eai04191
e27a848b08 Pixiv新URL形式に対応 2019-09-25 08:42:28 +09:00
shibafu
8772facadf JSONデコード後にHTMLエンティティのデコードを行う
JSONデコード前にHTMLエンティティのデコードを行ってしまうと " なども解除されてしまい、JSONとして不正な入力になる。
html_decode_entityのオプションで除外しても良いが、改行以外は一応JSONとして正当はなずなので、安全に倒してJSONとしてのデコードを済ませてから処理する。
2019-09-22 01:44:14 +09:00
MitarashiDango
d5ee59825f 年齢確認ダイアログ表示時のブラーをタグ一覧へ適用させる 2019-09-19 00:09:41 +09:00
shibafu
4192f22af5 Merge pull request #287 from shikorism/fix/seiga-partially-tag-test
NicoSeigaResolverTest: ロックされているタグのみを検証する
2019-09-18 22:23:25 +09:00
MitarashiDango
dd07940aea スマホ向けレイアウトへタグ一覧ボタンを追加 2019-09-16 14:02:19 +09:00
MitarashiDango
78bb7dae28 タグ一覧画面を追加 2019-09-16 13:24:22 +09:00
shibafu
e4890f65ae 頻繁に変更される部分を完全一致判定してもきりがないので、ロックされているタグのみを検証する 2019-09-16 09:55:29 +09:00
shibafu
2454a24ee2 Merge pull request #286 from shikorism/username-hint
ユーザー名は変更できないことを明記する
2019-09-15 02:19:09 +09:00
shibafu
7858bd0a5f ユーザー名は変更不能である旨を登録画面に明記する 2019-09-15 02:10:54 +09:00
shibafu
ce8855510c ユーザー名は変更不能です 2019-09-15 02:06:10 +09:00
shibafu
c42a3d2657 Merge pull request #285 from shikorism/fix/hide-private-like-in-like-users
いいね非公開のユーザーが、チェックイン画面のいいね件数欄に露出してしまう不具合の修正
2019-09-15 02:02:42 +09:00
shibafu
7cb6dd4754 いいね非公開のユーザーが、チェックイン画面のいいね件数欄に露出する不具合の修正 2019-09-15 01:55:17 +09:00
shibafu
bf1c4e7a21 Merge pull request #284 from eai04191/feature/resolver-fanza-improve
FanzaResolverに機能・テストを追加
2019-09-13 01:18:26 +09:00
shibafu
7a56072765 cosmetic change 2019-09-13 01:13:15 +09:00
eai04191
d6e0512dae 返り値の型を指定 2019-09-13 01:05:09 +09:00
eai04191
1edc70fc4c リンクのみをタグにするようにCSSセレクタを修正 2019-09-13 01:03:08 +09:00
shibafu
a20f690cdd Merge pull request #283 from eai04191/feature/resolver-fantiaResolver-improve
FantiaResolverを更新・テストを追加
2019-09-12 23:37:29 +09:00
shibafu
eab901d56d Merge pull request #282 from eai04191/feature/test-cienResolver
CienResolverのテストを追加
2019-09-12 23:05:58 +09:00
eai04191
8c88d60034 私が悪うございました 2019-09-12 11:58:21 +09:00
eai04191
599e3f9557 FanzaResolverに機能・テストを追加
動画、同人、電子書籍、PCゲームにてタグ取得に対応
2019-09-12 11:39:18 +09:00
eai04191
a9a0f3b99a FantiaResolverを更新・テストを追加
APIを見つけたのでAPIを使用するように
タグを追加
テストを追加
2019-09-12 06:56:28 +09:00
eai04191
5fc0c6c1b6 CienResolverのテストを追加 2019-09-12 05:26:03 +09:00
shibafu
5642e73391 401 Unauthorizedはよくないね 2019-09-12 00:00:34 +09:00
shibafu
92847fefe0 Merge pull request #280 from eai04191/feature/remove-status-200
不要な成否判定の削除
2019-09-10 23:40:21 +09:00
shibafu
178ed02d00 Merge pull request #276 from eai04191/feature/fix-resolver-deviantart
DeviantArtResolverのfixture・テストを更新
2019-09-10 23:40:01 +09:00
shibafu
3381965896 Merge pull request #270 from MitarashiDango/feature_add_okazu_spoiler
チェックインに対してセンシティブフラグを付与可能とする対応を実施
2019-09-10 23:39:45 +09:00
eai04191
dc8a70291d URL置換を削除 タグを追加
なんかもともと入っているurlが十分大きいので置換する必要がないことに気が付きました
2019-09-10 23:04:21 +09:00
shibafu
a10acdd481 モックがHTTPステータスコードに応じた例外を投げるようにする 2019-09-10 22:35:36 +09:00
shibafu
2c4eaccf43 OGPResolverTestをrevert、Guzzleの例外を期待するように変更 2019-09-10 22:34:24 +09:00
shibafu
141d5ce77c Merge pull request #278 from shikorism/feature/nicoseiga-tags
ニコニコ静画のタグを取得する & テストの追加
2019-09-10 20:20:30 +09:00
eai04191
ccade6ff9f 怒られたので直した(小学生並みの感想) 2019-09-10 07:33:29 +09:00
eai04191
033784bfc8 不要な成否判定の削除
ref: c0b76e5
2019-09-10 07:03:46 +09:00
eai04191
db39ee35c2 不要な判定の削除
ref: c0b76e5
2019-09-10 01:27:35 +09:00
shibafu
fb6c1a0574 Merge pull request #277 from eai04191/feature/test-fix-xtube
XtubeResolverの取得方法を変更・テストを更新
2019-09-10 01:05:26 +09:00
shibafu
59aec2c038 不要な成否判定をやめる 2019-09-10 01:01:47 +09:00
shibafu
d45898931a Merge pull request #274 from eai04191/feature/test-fix-dlsiteresolver
DLsiteResolverのfixture・テストを更新
2019-09-10 00:52:53 +09:00
shibafu
fb50881e74 Merge pull request #275 from eai04191/feature/test-fix-toranoanaresolver
ToranoanaResolverのfixture・テストを更新
2019-09-10 00:51:27 +09:00
shibafu
40fedf59d4 ニコニコ静画のテストの追加 2019-09-10 00:47:32 +09:00
shibafu
b2014a3db7 ニコニコ静画のサムネイルをhttpsで取得する 2019-09-10 00:47:32 +09:00
shibafu
f3a4f682a8 ニコニコ静画のタグを取得する 2019-09-10 00:47:31 +09:00
eai04191
c0b76e522b 200でなかったときのテストを削除
4xxや5xxの場合は`\GuzzleHttp\Exception\BadResponseException`が発生するので、今まであったelseのところには到達しなかった
2019-09-09 13:52:32 +09:00
eai04191
0f530099b4 データの取得方法をAPIからスクレイピングに変更
より多くのタグと高画質なサムネイルを取得するように変更
2019-09-09 13:51:07 +09:00
eai04191
eecace33bd 2桁以上のcdnのURLに対応できるように正規表現を修正 2019-09-09 10:58:20 +09:00
eai04191
d049a6f631 すべてwixmpを使用するようになったので分ける必要がなくなった
ついでに$data['thumbnail_url']なら常にオプションが付いているのでオプションを含んでいるか判別する必要がなくなった
2019-09-09 10:30:10 +09:00
eai04191
4add9a87cc Update Fixture 2019-09-09 10:20:39 +09:00
eai04191
c898487a20 Update Tests 2019-09-09 10:01:26 +09:00
eai04191
03cb2b0728 Update Toranoana Fixture 2019-09-09 09:59:08 +09:00
eai04191
3c0b65ff8c Update Tests 2019-09-09 09:47:42 +09:00
eai04191
b367009c5c Update DLsite Fixture 2019-09-09 09:35:38 +09:00
shibafu
22150d0e7a 同じオカズでチェックインする際にセンシティブフラグを継承する 2019-09-08 16:32:37 +09:00
MitarashiDango
2b98267fa8 設定項目のアイコン変更 2019-09-08 02:39:18 +09:00
MitarashiDango
a7972046ef fix stylelint error 2019-09-07 21:19:31 +09:00
MitarashiDango
524d00d0ed オーバーレイのスタイルを修正 2019-09-07 21:06:06 +09:00
MitarashiDango
06cc18565e チェックインに対してセンシティブフラグを付与可能とする対応を実施 2019-09-07 19:57:44 +09:00
shibafu
ff6de777d7 Merge pull request #268 from shikorism/develop
Release 20190906.0000
2019-09-06 00:06:31 +09:00
shibafu
743272f8d6 Merge pull request #266 from shikorism/feature/262-update-mail-address
メールアドレスを変更可能にする
2019-09-04 23:50:07 +09:00
shibafu
784fb43ae9 Merge pull request #267 from shikorism/package-update-20190904
Package update (20190904)
2019-09-04 23:49:50 +09:00
shibafu
de23a37ab3 Update composer packages 2019-09-04 22:04:37 +09:00
shibafu
72fc84a42c Update npm packages 2019-09-04 21:56:35 +09:00
shibafu
f59aa750e4 Merge pull request #265 from shikorism/dependabot/npm_and_yarn/mixin-deep-1.3.2
Bump mixin-deep from 1.3.1 to 1.3.2
2019-09-04 20:54:18 +09:00
shibafu
66f4c45f5c メールアドレスを変更可能にする 2019-09-04 20:52:26 +09:00
dependabot[bot]
c204a7e934 Bump mixin-deep from 1.3.1 to 1.3.2
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-09-04 11:42:19 +00:00
shibafu
aa87a8f070 Merge pull request #261 from eai04191/feature/resolver-dlsite-affiliate
DLsiteResolver アフィリエイトURLに対応
2019-09-01 18:05:16 +09:00
Aoi Irie
ab20ca5370 Update app/MetadataResolver/MetadataResolver.php
マージをミスるって

マジ

Co-Authored-By: shibafu <shibafu528@gmail.com>
2019-09-01 16:55:37 +09:00
Aoi Irie
bd84f29a27 Merge branch 'develop' into feature/resolver-dlsite-affiliate 2019-09-01 03:43:37 +09:00
shibafu
ac2077af49 Merge pull request #259 from eai04191/feature/resolver-nijie
NijieResolver 修正など
2019-08-31 03:12:34 +09:00
shibafu
27970e3ac5 Merge pull request #260 from eai04191/feature/resolver-iwara-improve
IwaraResolver 拡張など
2019-08-31 03:12:17 +09:00
shibafu
4940b7a9ca Merge pull request #252 from MitarashiDango/fix_tag_input_android
タグ入力確定時のフォールバック処理を追加
2019-08-29 00:18:07 +09:00
eai04191
5e02a8ab7a Iwara側のURL構造変更に追従 2019-08-29 00:01:17 +09:00
eai04191
a088444626 textの引数を指定して例外を回避する 2019-08-29 00:00:03 +09:00
eai04191
8ef9a1f8f4 Update fixture 2019-08-28 23:57:23 +09:00
eai04191
150a8152a4 指摘箇所の修正 2019-08-28 23:48:53 +09:00
shibafu
5ac1bae73f Update README.md 2019-08-28 23:25:48 +09:00
Aoi Irie
1bec21f15f Merge branch 'develop' into feature/resolver-dlsite-affiliate 2019-08-28 00:31:20 +09:00
shibafu
2c1976fd2b Merge pull request #256 from eai04191/feature/fix-deviantart
DeviantArtResolverでoEmbed APIを使用するように変更
2019-08-27 21:52:35 +09:00
eai04191
13c3407a4e テストを更新 2019-08-27 21:40:48 +09:00
eai04191
91e6cea79a wixmpのURL変換の修正
常にjpgを使用する
1024pxの画像を使用する
q_が付いていない場合に対応
2019-08-27 21:36:44 +09:00
eai04191
ac40a411da クエリの前に追加するように修正 2019-08-27 20:49:04 +09:00
shibafu
e2aa47151b Merge pull request #255 from eai04191/feature/resolver-dlsite-striptags
DLsiteResolverのdescriptionをstrip_tagsに通す
2019-08-27 20:05:42 +09:00
eai04191
dd38f4e0eb アフィリエイト先が不正なURLのテストを追加 2019-08-27 00:27:37 +09:00
eai04191
17bc8cebbf アフィリエイトURLに対応 2019-08-26 23:33:52 +09:00
eai04191
4f23a9404b 名前変更 2019-08-22 07:47:42 +09:00
eai04191
b7eafd881f descriptionが存在しない場合に対応 2019-08-22 07:28:28 +09:00
eai04191
0a994884a0 作者名をタグに追加する 2019-08-22 05:24:25 +09:00
eai04191
9926cc3357 役に立たないタグを含めない 2019-08-22 05:17:23 +09:00
eai04191
5069f20b50 タグ対応, images対応, テスト更新, その他 2019-08-21 05:11:36 +09:00
eai04191
93387f1ff5 assertEquals撲滅委員会 2019-08-21 03:53:32 +09:00
eai04191
7baf51fc09 書き直し, タグ対応, テスト更新 2019-08-21 03:40:47 +09:00
shibafu
0e3878a808 Merge pull request #257 from eai04191/feature/test-pixiv--this-item-has-been-deleted--very-sad
PixivResolver 作品削除に伴うテスト差し替え
2019-08-20 22:19:15 +09:00
shibafu
4c0b245574 Merge branch 'feature/249-install-dom-crawlar' into develop 2019-08-20 20:52:57 +09:00
shibafu
0a0047c4c3 symfony/css-selector を依存関係に追加
refs #249
2019-08-20 20:52:12 +09:00
shibafu
831d1668ef Merge pull request #258 from shikorism/feature/249-install-dom-crawlar
symfony/dom-crawler を依存関係に追加
2019-08-20 20:50:10 +09:00
shibafu
51c8199283 symfony/dom-crawler を依存関係に追加
refs #249
2019-08-20 20:46:28 +09:00
shibafu
78a1bdfb30 Merge pull request #254 from shikorism/feature/php-7.3
DockerfileおよびCI環境をPHP 7.3にアップデート
2019-08-20 20:44:39 +09:00
eai04191
ceff57f9f6 作品削除に伴いテスト差し替え 2019-08-20 11:31:50 +09:00
eai04191
bc2f8662fc oEmbed APIを使用するように変更 2019-08-20 11:14:44 +09:00
eai04191
2112087e89 nbspをspaceに置換 2019-08-20 01:40:02 +09:00
eai04191
c93ccb43c8 strip_tags追加 2019-08-20 00:52:00 +09:00
shibafu
58ae1bc1c1 DockerfileおよびCI環境をPHP 7.3にアップデート 2019-08-13 17:04:06 +09:00
shibafu
f7c9e83b12 Merge pull request #251 from shikorism/schedule-ci
定期的に実際のリクエストを伴うMetadataResolverのテストを実行する
2019-08-10 12:36:31 +09:00
shibafu
a1850b666b 定期テストではMetadataResolverのテストのみを実行する 2019-08-10 12:18:36 +09:00
MitarashiDango
8594caade1 タグ入力確定時のフォールバック処理を追加 2019-08-09 00:57:14 +09:00
shibafu
dddb47f68a Merge pull request #248 from eai04191/feature/unknown-territory
Add XtubeResolver
2019-08-08 00:26:17 +09:00
shibafu
1b2b043be2 Merge pull request #246 from shikorism/feature/monthly-graph-term
月間チェックイングラフの表示期間を選択可能にする
2019-08-08 00:23:15 +09:00
shibafu
c535153e1f 定期的に実際のリクエストを伴うMetadataResolverのテストを実行する 2019-08-08 00:14:59 +09:00
eai04191
830de3a5e3 Add XtubeResolver 2019-08-06 22:44:38 +09:00
shibafu
ab46117138 対象年の表記を yyyy年 にした 2019-08-04 01:33:54 +09:00
shibafu
3c2fec21a0 月間チェックイングラフの対象年を切り替えられるようにした 2019-08-04 01:31:53 +09:00
shibafu
370d1cc01b 月間グラフのデータ整形をクライアントサイドでやらせる 2019-08-04 00:57:35 +09:00
shibafu
5517cd5fab Merge pull request #244 from shikorism/prestissimo
prestissimoのインストールをDockerfileに含める
2019-08-03 17:17:34 +09:00
shibafu
3c083a7c60 Merge pull request #243 from shikorism/fix/235-tora
ToranoanaResolverで、OGP画像の代わりになりそうなサムネ画像を取得する
2019-08-03 17:06:56 +09:00
shibafu
fa6b8b87af READMEからprestissimoのインストールを削除 2019-08-03 01:00:15 +09:00
shibafu
d290bf4107 prestissimoを予めインストールしておく 2019-08-03 00:59:53 +09:00
shibafu
b274c6bc40 Cookieを付与しなくてもデータを取得できたので、付与しないように変更 2019-08-03 00:26:12 +09:00
shibafu
018532f01f ToranoanaResolverのテストを追加 2019-08-03 00:16:21 +09:00
shibafu
e4ef935dd2 OGP画像が全て代替イメージになっていたため、サムネイルのスクレイピングを実施 2019-08-02 23:48:56 +09:00
shibafu
dabae9f251 Merge pull request #242 from shikorism/develop
Release 20190725.2018
2019-07-25 20:19:19 +09:00
shibafu
fb84a1d416 Merge pull request #241 from shikorism/dependabot/npm_and_yarn/lodash-4.17.15
Bump lodash from 4.17.11 to 4.17.15
2019-07-25 20:17:24 +09:00
dependabot[bot]
358580a15e Bump lodash from 4.17.11 to 4.17.15
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-25 11:12:45 +00:00
shibafu
000b89f380 Merge pull request #237 from eai04191/feature/card-round-fix
カードの角丸を修正
2019-07-14 01:06:51 +09:00
shibafu
c5cbad4475 Merge pull request #238 from shikorism/feature/disable-inserted-tag
入力済みのタグ候補の背景色を変更する
2019-07-14 01:06:02 +09:00
shibafu
f4abb08921 編集モードの初期表示時に入力済みタグが反映されていなかったので、現在のタグを配信させるイベントを定義 2019-07-04 22:34:28 +09:00
shibafu
38eb0348f9 入力済みのタグ候補の背景色を変更する 2019-07-04 22:33:28 +09:00
eai04191
4f5595dae0 vue側も追従 2019-07-04 21:28:54 +09:00
eai04191
733e97bc58 card-img類削除、.cardにoverflow付与 2019-07-04 21:28:26 +09:00
shibafu
c4768ded38 Revert "Merge pull request #138 from eai04191/feature/bootstrap-custom"
This reverts commit 077731495c.
2019-07-04 20:58:59 +09:00
eai04191
598d27f6b8 Revert "Merge pull request #153 from shikorism/fix/140"
This reverts commit d044b6db20.
2019-07-04 20:55:33 +09:00
shibafu
bcf78df2fc Merge pull request #228 from shikorism/develop
Release 20190629.1945
2019-06-29 19:50:51 +09:00
shibafu
3f9de593d6 Merge pull request #192 from shikorism/develop
Release 20190530.2249
2019-05-30 22:51:59 +09:00
shibafu
2efb387b6a Merge pull request #183 from shikorism/develop
Release 20190421.1742
2019-04-21 17:44:01 +09:00
shibafu
ed295cb7bc Merge pull request #182 from shikorism/develop
Release 20190421.0218
2019-04-21 02:18:44 +09:00
shibafu
0c18965ade Merge pull request #180 from shikorism/develop
Release 20190421.0114
2019-04-21 01:23:03 +09:00
shibafu
6b1ccc52e5 Merge pull request #171 from shikorism/develop
Release 20190407.0014
2019-04-07 00:15:38 +09:00
shibafu
5153de54d2 Merge pull request #161 from shikorism/develop
Release 20190321.1905
2019-03-21 19:08:56 +09:00
shibafu
03fcc424d8 Merge pull request #126 from shikorism/develop
Release 20190307.2250
2019-03-07 22:51:27 +09:00
shibafu
f09ae32b00 Merge pull request #97 from shikorism/develop
Release 20190216.0130
2019-02-16 01:31:41 +09:00
shibafu
e36b9c7c1b Merge pull request #93 from shikorism/develop
Release 20190212.2300
2019-02-12 23:01:33 +09:00
shibafu
f5f4cbb5b6 Merge pull request #67 from shikorism/develop
Release 20190123.0045
2019-01-23 00:45:51 +09:00
shibafu
8c73bda2ac Merge pull request #66 from shikorism/develop
Release 20190123.0030
2019-01-23 00:31:08 +09:00
shibafu
09482ca2c5 Merge pull request #45 from shikorism/develop
Release 20190117.0100
2019-01-17 01:05:04 +09:00
136 changed files with 60163 additions and 8609 deletions

View File

@@ -1,9 +1,9 @@
version: 2
version: 2.1
jobs:
executors:
build:
docker:
- image: circleci/php:7.1-node-browsers
- image: circleci/php:7.3-node-browsers
environment:
APP_DEBUG: true
APP_ENV: testing
@@ -17,38 +17,75 @@ jobs:
POSTGRES_DB: tissue
POSTGRES_USER: tissue
POSTGRES_PASSWORD: tissue
commands:
initialize:
steps:
- checkout
- run: sudo apt update
- run: sudo apt install -y libpq-dev
- run: sudo docker-php-ext-install zip
- run: sudo docker-php-ext-install pdo_pgsql
restore_composer:
steps:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
- run: composer install -n --prefer-dist
save_composer:
steps:
- save_cache:
key: v1-dependencies-{{ checksum "composer.json" }}
paths:
- ./vendor
restore_npm:
steps:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run: yarn install
save_npm:
steps:
- save_cache:
key: v1-dependencies-{{ checksum "package.json" }}
paths:
- ./node_modules
- ~/.yarn
- run: php artisan migrate
jobs:
build:
executor: build
steps:
- initialize
- restore_composer
- run: composer install -n --prefer-dist
- save_composer
- restore_npm
- run: yarn install
- save_npm
- run: yarn run prod
- persist_to_workspace:
root: .
paths:
- public
test:
executor: build
steps:
- initialize
- restore_composer
- restore_npm
- attach_workspace:
at: .
- run: php artisan migrate
# Run linter
- run:
command: |
@@ -79,3 +116,51 @@ jobs:
- run:
command: bash <(curl -s https://codecov.io/bash) -f /tmp/phpunit/coverage.xml
when: always
test_resolver:
executor: build
environment:
TEST_USE_HTTP_MOCK: false
steps:
- initialize
- restore_composer
- attach_workspace:
at: .
- run: php artisan migrate
# Run unit test
- run:
command: |
mkdir -p /tmp/phpunit
./vendor/bin/phpunit --testsuite MetadataResolver --log-junit /tmp/phpunit/phpunit.xml --coverage-clover=/tmp/phpunit/coverage.xml
when: always
- store_test_results:
path: /tmp/phpunit
- store_artifacts:
path: /tmp/phpunit/coverage.xml
workflows:
version: 2.1
test:
jobs:
- build
- test:
requires:
- build
scheduled_resolver_test:
triggers:
- schedule:
cron: "4 0 * * 1"
filters:
branches:
only:
- develop
jobs:
- build
- test_resolver:
requires:
- build

View File

@@ -1,6 +1,6 @@
FROM node:10-jessie as node
FROM php:7.1-apache
FROM php:7.3-apache
ENV APACHE_DOCUMENT_ROOT /var/www/html/public
@@ -10,6 +10,7 @@ RUN apt-get update \
&& pecl install xdebug \
&& curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer \
&& composer global require hirak/prestissimo \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
&& a2enmod rewrite

View File

@@ -12,7 +12,7 @@ a.k.a. shikorism.net
## 実行環境
- PHP 7.1
- PHP 7.3
- PostgreSQL 9.6
## 開発環境の構築
@@ -36,7 +36,6 @@ docker-compose up -d
4. Composer と yarn を使い必要なライブラリをインストールします。
```
docker-compose exec web composer global require hirak/prestissimo
docker-compose exec web composer install
docker-compose exec web yarn install
```

18
app/DeactivatedUser.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* 削除済Userのユーザー名履歴
*/
class DeactivatedUser extends Model
{
public $incrementing = false;
protected $keyType = 'string';
protected $fillable = [
'name'
];
}

View File

@@ -15,7 +15,7 @@ class Ejaculation extends Model
protected $fillable = [
'user_id', 'ejaculated_date',
'note', 'geo_latitude', 'geo_longitude', 'link',
'is_private'
'is_private', 'is_too_sensitive'
];
protected $dates = [
@@ -56,6 +56,7 @@ class Ejaculation extends Model
},
'likes.user' => function ($query) {
$query->where('is_protected', false)
->where('private_likes', false)
->orWhere('id', Auth::id());
}
])
@@ -72,11 +73,25 @@ class Ejaculation extends Model
$query->latest()->take(10);
},
'likes.user' => function ($query) {
$query->where('is_protected', false);
$query->where('is_protected', false)
->where('private_likes', false);
}
])
->withCount('likes')
->addSelect(DB::raw('0 as is_liked'));
}
}
/**
* このチェックインと同じ情報を流用してチェックインするためのURLを生成
* @return string
*/
public function makeCheckinURL(): string
{
return route('checkin', [
'link' => $this->link,
'tags' => $this->textTags(),
'is_too_sensitive' => $this->is_too_sensitive,
]);
}
}

View File

@@ -48,7 +48,7 @@ class RegisterController extends Controller
protected function validator(array $data)
{
$rules = [
'name' => 'required|string|regex:/^[a-zA-Z0-9_-]+$/u|max:15|unique:users',
'name' => 'required|string|regex:/^[a-zA-Z0-9_-]+$/u|max:15|unique:users|unique:deactivated_users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed'
];

View File

@@ -21,7 +21,8 @@ class EjaculationController extends Controller
'link' => $request->input('link', ''),
'tags' => $request->input('tags', ''),
'note' => $request->input('note', ''),
'is_private' => $request->input('is_private', 0) == 1
'is_private' => $request->input('is_private', 0) == 1,
'is_too_sensitive' => $request->input('is_too_sensitive', 0) == 1
];
return view('ejaculation.checkin')->with('defaults', $defaults);
@@ -56,13 +57,18 @@ class EjaculationController extends Controller
'ejaculated_date' => Carbon::createFromFormat('Y/m/d H:i', $inputs['date'] . ' ' . $inputs['time']),
'note' => $inputs['note'] ?? '',
'link' => $inputs['link'] ?? '',
'is_private' => $request->has('is_private') ?? false
'is_private' => $request->has('is_private') ?? false,
'is_too_sensitive' => $request->has('is_too_sensitive') ?? false
]);
$tagIds = [];
if (!empty($inputs['tags'])) {
$tags = explode(' ', $inputs['tags']);
foreach ($tags as $tag) {
if ($tag === '') {
continue;
}
$tag = Tag::firstOrCreate(['name' => $tag]);
$tagIds[] = $tag->id;
}
@@ -104,6 +110,8 @@ class EjaculationController extends Controller
{
$ejaculation = Ejaculation::findOrFail($id);
$this->authorize('edit', $ejaculation);
return view('ejaculation.edit')->with(compact('ejaculation'));
}
@@ -111,6 +119,8 @@ class EjaculationController extends Controller
{
$ejaculation = Ejaculation::findOrFail($id);
$this->authorize('edit', $ejaculation);
$inputs = $request->all();
$validator = Validator::make($inputs, [
@@ -137,13 +147,18 @@ class EjaculationController extends Controller
'ejaculated_date' => Carbon::createFromFormat('Y/m/d H:i', $inputs['date'] . ' ' . $inputs['time']),
'note' => $inputs['note'] ?? '',
'link' => $inputs['link'] ?? '',
'is_private' => $request->has('is_private') ?? false
'is_private' => $request->has('is_private') ?? false,
'is_too_sensitive' => $request->has('is_too_sensitive') ?? false
])->save();
$tagIds = [];
if (!empty($inputs['tags'])) {
$tags = explode(' ', $inputs['tags']);
foreach ($tags as $tag) {
if ($tag === '') {
continue;
}
$tag = Tag::firstOrCreate(['name' => $tag]);
$tagIds[] = $tag->id;
}
@@ -160,6 +175,9 @@ class EjaculationController extends Controller
public function destroy($id)
{
$ejaculation = Ejaculation::findOrFail($id);
$this->authorize('edit', $ejaculation);
$user = User::findOrFail($ejaculation->user_id);
$ejaculation->tags()->detach();
$ejaculation->delete();

View File

@@ -2,9 +2,14 @@
namespace App\Http\Controllers;
use App\DeactivatedUser;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class SettingController extends Controller
{
@@ -18,10 +23,18 @@ class SettingController extends Controller
$inputs = $request->all();
$validator = Validator::make($inputs, [
'display_name' => 'required|string|max:20',
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore(Auth::user()->email, 'email')
],
'bio' => 'nullable|string|max:160',
'url' => 'nullable|url|max:2000'
], [], [
'display_name' => '名前',
'email' => 'メールアドレス',
'bio' => '自己紹介',
'url' => 'URL'
]);
@@ -32,6 +45,7 @@ class SettingController extends Controller
$user = Auth::user();
$user->display_name = $inputs['display_name'];
$user->email = $inputs['email'];
$user->bio = $inputs['bio'] ?? '';
$user->url = $inputs['url'] ?? '';
$user->save();
@@ -57,6 +71,51 @@ class SettingController extends Controller
return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');
}
public function deactivate()
{
return view('setting.deactivate');
}
public function destroyUser(Request $request)
{
// パスワードチェック
$validated = $request->validate([
'password' => 'required|string'
]);
if (!Hash::check($validated['password'], Auth::user()->getAuthPassword())) {
throw ValidationException::withMessages([
'password' => 'パスワードが正しくありません。'
]);
}
// データの削除
set_time_limit(0);
DB::transaction(function () {
$user = Auth::user();
// 関連レコードの削除
// TODO: 別にDELETE文相当のクエリを一発発行するだけでもいい
foreach ($user->ejaculations as $ejaculation) {
$ejaculation->delete();
}
foreach ($user->likes as $like) {
$like->delete();
}
// 先にログアウトしないとユーザーは消せない
Auth::logout();
// ユーザーの削除
$user->delete();
// ユーザー名履歴に追記
DeactivatedUser::create(['name' => $user->name]);
});
return view('setting.deactivated');
}
// ( ◠‿◠ )☛ここに気づいたか・・・消えてもらう ▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ
// public function password()
// {

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class TagController extends Controller
{
public function index()
{
$tags = Tag::select(DB::raw(
<<<'SQL'
tags.name,
count(*) AS "checkins_count"
SQL
))
->join('ejaculation_tag', 'tags.id', '=', 'ejaculation_tag.tag_id')
->join('ejaculations', 'ejaculations.id', '=', 'ejaculation_tag.ejaculation_id')
->join('users', 'users.id', '=', 'ejaculations.user_id')
->where('ejaculations.is_private', false)
->where(function ($query) {
$query->where('users.is_protected', false);
if (Auth::check()) {
$query->orWhere('users.id', Auth::id());
}
})
->groupBy('tags.name')
->orderByDesc('checkins_count')
->orderBy('tags.name')
->paginate(100);
return view('tag.index', compact('tags'));
}
}

View File

@@ -30,6 +30,7 @@ id,
ejaculated_date,
note,
is_private,
is_too_sensitive,
link,
to_char(lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC), 'YYYY/MM/DD HH24:MI') AS before_date,
to_char(ejaculated_date - (lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)), 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span
@@ -109,13 +110,6 @@ SQL
}
}
// 月間グラフ用の配列初期化
$month = Carbon::now()->firstOfMonth()->subMonth(11); // 直近12ヶ月
for ($i = 0; $i < 12; $i++) {
$monthlySum[$month->format('Y/m')] = 0;
$month->addMonth();
}
foreach ($groupByDay as $data) {
$date = Carbon::createFromFormat('Y/m/d', $data->date);
$yearAndMonth = $date->format('Y/m');
@@ -123,9 +117,7 @@ SQL
$dailySum[$date->timestamp] = $data->count;
$yearlySum[$date->year] += $data->count;
$dowSum[$date->dayOfWeek] += $data->count;
if (isset($monthlySum[$yearAndMonth])) {
$monthlySum[$yearAndMonth] += $data->count;
}
$monthlySum[$yearAndMonth] = ($monthlySum[$yearAndMonth] ?? 0) + $data->count;
}
foreach ($groupByHour as $data) {
@@ -136,8 +128,7 @@ SQL
$graphData = [
'dailySum' => $dailySum,
'dowSum' => $dowSum,
'monthlyKey' => array_keys($monthlySum),
'monthlySum' => array_values($monthlySum),
'monthlySum' => $monthlySum,
'yearlyKey' => array_keys($yearlySum),
'yearlySum' => array_values($yearlySum),
'hourlyKey' => array_keys($hourlySum),
@@ -161,6 +152,7 @@ id,
ejaculated_date,
note,
is_private,
is_too_sensitive,
link,
to_char(lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC), 'YYYY/MM/DD HH24:MI') AS before_date,
to_char(ejaculated_date - (lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)), 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span

View File

@@ -37,6 +37,10 @@ class ActivityPubResolver implements Resolver, Parser
$activityOrObject = json_decode($json, true);
$object = $activityOrObject['object'] ?? $activityOrObject;
if ($object['type'] !== 'Note') {
throw new UnsupportedContentException('Unsupported object type: ' . $object['type']);
}
$metadata = new Metadata();
$metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : '';

View File

@@ -25,8 +25,7 @@ class CienResolver extends MetadataResolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata = $this->ogpResolver->parse((string) $res->getBody());
// 画像URLから有効期限の起点を拾う
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $params);
@@ -36,8 +35,5 @@ class CienResolver extends MetadataResolver
$metadata->expires_at = Carbon::createFromTimestamp($params['px-time'])->addHour(1);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -52,6 +52,22 @@ class DLsiteResolver implements Resolver
public function resolve(string $url): Metadata
{
//アフィリエイトの場合は普通のURLに変換
// ID型
if (preg_match('~/dlaf/=(/.+/.+)?/link/~', $url)) {
preg_match('~www\.dlsite\.com/(?P<genre>.+)/dlaf/=(/.+/.+)?/link/work/aid/(?P<AffiliateId>.+)/id/(?P<titleId>..\d+)(\.html)?~', $url, $matches);
$url = "https://www.dlsite.com/{$matches['genre']}/work/=/product_id/{$matches['titleId']}.html";
}
// URL型
if (strpos($url, '/dlaf/=/aid/') !== false) {
preg_match('~www\.dlsite\.com/.+/dlaf/=/aid/.+/url/(?P<url>.+)~', $url, $matches);
$affiliateUrl = urldecode($matches['url']);
if (preg_match('~www\.dlsite\.com/.+/(work|announce)/=/product_id/..\d+(\.html)?~', $affiliateUrl, $matches)) {
$url = $affiliateUrl;
} else {
throw new \RuntimeException("アフィリエイト先のリンクがDLsiteのタイトルではありません: $affiliateUrl");
}
}
//スマホページの場合はPCページに正規化
if (strpos($url, '-touch') !== false) {
@@ -59,7 +75,6 @@ class DLsiteResolver implements Resolver
}
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@@ -72,8 +87,8 @@ class DLsiteResolver implements Resolver
preg_match('~ \[([^\[\]]*)\] (予告作品 )?\| DLsite(がるまに)?$~', $metadata->title, $match);
$makers = explode(' ', $match[1]);
//フォローボタン(.btn_follow)はテキストを含んでしまうことがあるので要素を削除しておく
$followButtonNode = $xpath->query('//*[@class="btn_follow"]')->item(0);
//フォローボタン(.add_follow)はテキストを含んでしまうことがあるので要素を削除しておく
$followButtonNode = $xpath->query('//*[@class="add_follow"]')->item(0);
$followButtonNode->parentNode->removeChild($followButtonNode);
// maker, makerHeadを探す
@@ -82,7 +97,8 @@ class DLsiteResolver implements Resolver
// #work_makerから「makerを含むテキスト」を持つ要素を持つtdを探す
// 作者名単体の場合もあるし、"作者A / 作者B"のようになることもある
$makersNode = $xpath->query('//*[@id="work_maker"]//*[contains(text(), "' . $makers[0] . '")]/ancestor::td')->item(0);
$makers = trim($makersNode->textContent);
// nbspをspaceに置換
$makers = trim(str_replace("\xc2\xa0", ' ', $makersNode->textContent));
// makersHaed
// $makerNode(td)に対するthを探す
@@ -97,10 +113,11 @@ class DLsiteResolver implements Resolver
// OGP説明文から定型文を消す
if (strpos($url, 'dlsite.com/eng/') || strpos($url, 'dlsite.com/ecchi-eng/')) {
$metadata->description = trim(preg_replace('~DLsite.+ is a download shop for .+With a huge selection of products, we\'re sure you\'ll find whatever tickles your fancy\. DLsite is one of the greatest indie contents download shops in Japan\.$~', '', $metadata->description));
$metadata->description = preg_replace('~DLsite.+ is a download shop for .+With a huge selection of products, we\'re sure you\'ll find whatever tickles your fancy\. DLsite is one of the greatest indie contents download shops in Japan\.$~', '', $metadata->description);
} else {
$metadata->description = trim(preg_replace('~「DLsite.+」は.+のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」$~', '', $metadata->description));
$metadata->description = preg_replace('~「DLsite.+」は.+のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」$~', '', $metadata->description);
}
$metadata->description = trim(strip_tags($metadata->description));
// 整形
$metadata->description = $makersHead . ': ' . $makers . PHP_EOL . $metadata->description;
@@ -108,8 +125,5 @@ class DLsiteResolver implements Resolver
$metadata->tags = $this->extractTags($res->getBody());
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -23,35 +23,17 @@ class DeviantArtResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get('https://backend.deviantart.com/oembed?url=' . $url);
$data = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$node = $xpath->query('//*[@id="pimp-preload"]/following-sibling::div//img')->item(0);
$srcset = $node->getAttribute('srcset');
$srcset_array = explode('w,', $srcset);
$src = end($srcset_array);
$src = preg_replace('~ \d+w$~', '', $src);
if (preg_match('~\.wixmp\.com$~', parse_url($src)['host'])) {
// アスペクト比を保ったまま、縦か横が最大700pxになるように変換する。
// Ref: https://support.wixmp.com/en/article/image-service-3835799
if (strpos($src, '/v1/fill/')) {
$src = preg_replace('~/v1/fill/w_\d+,h_\d+,q_\d+,strp~', '/v1/fit/w_700,h_700,q_70,strp', $src);
} else {
$src = $src . '/v1/fit/w_700,h_700,q_70,strp/image.jpg';
$metadata->title = $data['title'] ?? '';
$metadata->description = 'By ' . $data['author_name'];
$metadata->image = $data['url'];
if (isset($data['tags'])) {
$metadata->tags = explode(', ', $data['tags']);
}
}
$metadata->image = $src;
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -24,7 +24,6 @@ class FC2ContentsResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@@ -37,8 +36,5 @@ class FC2ContentsResolver implements Resolver
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -3,7 +3,6 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
class FantiaResolver implements Resolver
{
@@ -11,46 +10,31 @@ class FantiaResolver implements Resolver
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
public function __construct(Client $client)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
preg_match("~\d+~", $url, $match);
$postId = $match[0];
preg_match("~posts/(\d+)~", $url, $match);
$postId = $match[1];
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get("https://fantia.jp/api/v1/posts/{$postId}");
$data = json_decode(str_replace('\r\n', '\n', (string) $res->getBody()), true);
$post = $data['post'];
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$tags = array_map(function ($tag) {
return $tag['name'];
}, $post['tags']);
$node = $xpath->query("//meta[@property='twitter:image']")->item(0);
$ogpUrl = $node->getAttribute('content');
// 投稿に画像がない場合ogp.jpgでない場合のみ大きい画像に変換する
if ($ogpUrl != 'http://fantia.jp/images/ogp.jpg') {
preg_match("~https://fantia\.s3\.amazonaws\.com/uploads/post/file/{$postId}/ogp_(.*?)\.(jpg|png)~", $ogpUrl, $match);
$uuid = $match[1];
$extension = $match[2];
// 大きい画像に変換
$metadata->image = "https://c.fantia.jp/uploads/post/file/{$postId}/main_{$uuid}.{$extension}";
}
$metadata = new Metadata();
$metadata->title = $post['title'] ?? '';
$metadata->description = 'サークル: ' . $post['fanclub']['fanclub_name_with_creator_name'] . PHP_EOL . $post['comment'];
$metadata->image = str_replace('micro', 'main', $post['thumb_micro']) ?? '';
$metadata->tags = array_merge($tags, [$post['fanclub']['creator_name']]);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class FanzaResolver implements Resolver
{
@@ -21,17 +22,84 @@ class FanzaResolver implements Resolver
$this->ogpResolver = $ogpResolver;
}
/**
* arrayの各要素をtrim・スペースの_置換をした後、重複した値を削除してキーを詰め直す
*
* @param array $array
*
* @return array 処理されたarray
*/
public function array_finish(array $array): array
{
$array = array_map('trim', $array);
$array = array_map((function ($value) {
return str_replace(' ', '_', $value);
}), $array);
$array = array_unique($array);
$array = array_values($array);
return $array;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image);
$metadata->description = str_replace('<>', '', $metadata->description);
$html = (string) $res->getBody();
$crawler = new Crawler($html);
// 動画
if (preg_match('~www\.dmm\.co\.jp/digital/(videoa|videoc|anime)/-/detail~', $url)) {
$metadata = new Metadata();
$metadata->title = trim($crawler->filter('#title')->text(''));
$metadata->description = trim($crawler->filter('.box-rank+table+div+div')->text(''));
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $crawler->filter('meta[property="og:image"]')->attr('content'));
$metadata->tags = $this->array_finish($crawler->filter('.box-rank+table a:not([href="#review"])')->extract(['_text']));
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
// 同人
if (mb_strpos($url, 'www.dmm.co.jp/dc/doujin/-/detail/') !== false) {
$genre = $this->array_finish($crawler->filter('.m-productInformation a:not([href="#update-top"])')->extract(['_text']));
$genre = array_filter($genre, (function ($text) {
return !preg_match('~OFF対象$~', $text);
}));
$metadata = new Metadata();
$metadata->title = $crawler->filter('meta[property="og:title"]')->attr('content');
$metadata->description = trim($crawler->filter('.summary__txt')->text(''));
$metadata->image = $crawler->filter('meta[property="og:image"]')->attr('content');
$metadata->tags = array_merge($genre, [$crawler->filter('.circleName__txt')->text('')]);
return $metadata;
}
// 電子書籍
if (mb_strpos($url, 'book.dmm.co.jp/detail/') !== false) {
$metadata = new Metadata();
$metadata->title = trim($crawler->filter('#title')->text(''));
$metadata->description = trim($crawler->filter('.m-boxDetailProduct__info__story')->text(''));
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $crawler->filter('meta[property="og:image"]')->attr('content'));
$metadata->tags = $this->array_finish($crawler->filter('.m-boxDetailProductInfoMainList__description__list__item, .m-boxDetailProductInfo__list__description__item a')->extract(['_text']));
return $metadata;
}
// PCゲーム
if (mb_strpos($url, 'dlsoft.dmm.co.jp/detail/') !== false) {
$metadata = new Metadata();
$metadata->title = trim($crawler->filter('#title')->text(''));
$metadata->description = trim($crawler->filter('.area-detail-read .text-overflow')->text(''));
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $crawler->filter('meta[property="og:image"]')->attr('content'));
$metadata->tags = $this->array_finish($crawler->filter('.area-bskt table a:not([href="#review"])')->extract(['_text']));
return $metadata;
}
// 上で特に対応しなかったURL 画像の置換くらいはしておく
$metadata = $this->ogpResolver->parse($html);
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image);
return $metadata;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use Symfony\Component\DomCrawler\Crawler;
class HentaiFoundryResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
private function nbsp2space(string $string): string
{
return str_replace("\xc2\xa0", ' ', $string);
}
private function br2nl(string $string): string
{
return str_replace('<br>', PHP_EOL, $string);
}
public function resolve(string $url): Metadata
{
$res = $this->client->get(
http_build_url($url, ['query' => 'enterAgree=1']),
['cookies' => new CookieJar()]
);
$metadata = new Metadata();
$crawler = new Crawler((string) $res->getBody());
$author = $crawler->filter('#picBox .boxtitle a')->text();
$description = trim(strip_tags($this->nbsp2space($this->br2nl($crawler->filter('.picDescript')->html()))));
$metadata->title = $crawler->filter('#picBox .boxtitle .imageTitle')->text();
$metadata->description = 'by ' . $author . PHP_EOL . $description;
$metadata->image = 'https:' . $crawler->filter('img[src^="//picture"]')->attr('src');
$metadata->tags = $crawler->filter('a[rel="tag"]')->extract('_text');
return $metadata;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class IwaraResolver implements Resolver
{
@@ -19,51 +20,41 @@ class IwaraResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$metadata = new Metadata();
$html = (string) $res->getBody();
$crawler = new Crawler($html);
// find title
foreach ($xpath->query('//title') as $node) {
$content = $node->textContent;
if (!empty($content)) {
$metadata->title = $content;
break;
}
$infoElements = $crawler->filter('#video-player + div, .field-name-field-video-url + div, .field-name-field-images + div');
$title = $infoElements->filter('h1.title')->text();
$author = $infoElements->filter('.username')->text();
$description = $infoElements->filter('.field-type-text-with-summary')->text('');
$tags = $infoElements->filter('a[href^="/videos"], a[href^="/images"]')->extract('_text');
// 役に立たないタグを削除する
$tags = array_values(array_diff($tags, ['Uncategorized', 'Other']));
array_push($tags, $author);
$metadata->title = $title;
$metadata->description = '投稿者: ' . $author . PHP_EOL . $description;
$metadata->tags = $tags;
// iwara video
if ($crawler->filter('#video-player')->count()) {
$metadata->image = 'https:' . $crawler->filter('#video-player')->attr('poster');
}
// find thumbnail
foreach ($xpath->query('//*[@id="video-player"]') as $node) {
$poster = $node->getAttribute('poster');
if (!empty($poster)) {
if (strpos($poster, '//') === 0) {
$poster = 'https:' . $poster;
}
$metadata->image = $poster;
break;
}
}
if (empty($metadata->image)) {
// YouTube embedded?
foreach ($xpath->query('//div[@class="embedded-video"]//iframe') as $node) {
$src = $node->getAttribute('src');
if (preg_match('~youtube\.com/embed/(\S+)\?~', $src, $matches) !== -1) {
// youtube
if ($crawler->filter('iframe[src^="//www.youtube.com"]')->count()) {
if (preg_match('~youtube\.com/embed/(\S+)\?~', $crawler->filter('iframe[src^="//www.youtube.com"]')->attr('src'), $matches) === 1) {
$youtubeId = $matches[1];
$iwaraThumbUrl = 'https://i.iwara.tv/sites/default/files/styles/thumbnail/public/video_embed_field_thumbnails/youtube/' . $youtubeId . '.jpg';
$metadata->image = 'https://img.youtube.com/vi/' . $youtubeId . '/maxresdefault.jpg';
}
}
$metadata->image = $iwaraThumbUrl;
break;
}
}
// images
if ($crawler->filter('.field-name-field-images')->count()) {
$metadata->image = 'https:' . $crawler->filter('.field-name-field-images a')->first()->attr('href');
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class Kb10uyShortStoryServerResolver implements Resolver
{
protected const EXCLUDED_TAGS = ['R-15', 'R-18'];
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
$html = (string) $res->getBody();
$crawler = new Crawler($html);
$infoElement = $crawler->filter('div.post-info');
$metadata = new Metadata();
$metadata->title = $infoElement->filter('h1')->text();
$metadata->description = trim($infoElement->filter('p.summary')->text());
$metadata->tags = array_values(array_diff($infoElement->filter('ul.tags > li.tag > a')->extract('_text'), self::EXCLUDED_TAGS));
return $metadata;
}
}

View File

@@ -24,14 +24,12 @@ class KomifloResolver implements Resolver
$id = $matches[1];
$res = $this->client->get('https://api.komiflo.com/content/id/' . $id);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$metadata->title = $json['content']['data']['title'] ?? '';
$metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') .
' - ' .
($json['content']['parents'][0]['data']['title'] ?? '?');
' - ' . ($json['content']['parents'][0]['data']['title'] ?? '?');
$metadata->image = 'https://t.komiflo.com/564_mobile_large_3x/' . $json['content']['named_imgs']['cover']['filename'];
// 作者情報
@@ -49,8 +47,5 @@ class KomifloResolver implements Resolver
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -27,7 +27,6 @@ class MelonbooksResolver implements Resolver
$cookieJar = CookieJar::fromArray(['AUTH_ADULT' => '1'], 'www.melonbooks.co.jp');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@@ -65,8 +64,5 @@ class MelonbooksResolver implements Resolver
$metadata->description = trim($description);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -14,10 +14,13 @@ class MetadataResolver implements Resolver
'~komiflo\.com(/#!)?/comics/(\\d+)~' => KomifloResolver::class,
'~www\.melonbooks\.co\.jp/detail/detail\.php~' => MelonbooksResolver::class,
'~ec\.toranoana\.(jp|shop)/(tora|joshi)(_[rd]+)?/(ec|digi)/item/~' => ToranoanaResolver::class,
'~iwara\.tv/videos/.*~' => IwaraResolver::class,
'~iwara\.tv/(videos|images)/.*~' => IwaraResolver::class,
'~www\.dlsite\.com/.*/(work|announce)/=/product_id/..\d+(\.html)?~' => DLsiteResolver::class,
'~www\.dlsite\.com/.*/dlaf/=(/.+/.+)?/link/work/aid/.+(/id)?/..\d+(\.html)?~' => DLsiteResolver::class,
'~www\.dlsite\.com/.*/dlaf/=/aid/.+/url/.+~' => DLsiteResolver::class,
'~dlsite\.jp/...tw/..\d+~' => DLsiteResolver::class,
'~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class,
'~www\.pixiv\.net/(en/)?artworks/\d+~' => PixivResolver::class,
'~www\.pixiv\.net/user/\d+/series/\d+~' => PixivResolver::class,
'~fantia\.jp/posts/\d+~' => FantiaResolver::class,
'~dmm\.co\.jp/~' => FanzaResolver::class,
@@ -28,6 +31,9 @@ class MetadataResolver implements Resolver
'~www\.plurk\.com\/p\/.*~' => PlurkResolver::class,
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
'~store\.steampowered\.com/app/\d+~' => SteamResolver::class,
'~www\.xtube\.com/video-watch/.*-\d+$~'=> XtubeResolver::class,
'~ss\.kb10uy\.org/posts/\d+$~' => Kb10uyShortStoryServerResolver::class,
'~www\.hentai-foundry\.com/pictures/user/.+/\d+/.+~'=> HentaiFoundryResolver::class,
];
public $mimeTypes = [
@@ -43,16 +49,19 @@ class MetadataResolver implements Resolver
{
foreach ($this->rules as $pattern => $class) {
if (preg_match($pattern, $url) === 1) {
try {
/** @var Resolver $resolver */
$resolver = app($class);
return $resolver->resolve($url);
} catch (UnsupportedContentException $e) {
}
}
}
$result = $this->resolveWithAcceptHeader($url);
if ($result !== null) {
return $result;
try {
return $this->resolveWithAcceptHeader($url);
} catch (UnsupportedContentException $e) {
}
if (isset($this->defaultResolver)) {
@@ -65,7 +74,7 @@ class MetadataResolver implements Resolver
throw new \UnexpectedValueException('URL not matched.');
}
public function resolveWithAcceptHeader(string $url): ?Metadata
public function resolveWithAcceptHeader(string $url): Metadata
{
try {
// Rails等はAcceptに */* が入っていると、ブラウザの適当なAcceptヘッダだと判断して全部無視してしまう。
@@ -110,6 +119,6 @@ class MetadataResolver implements Resolver
// 5xx は変なAcceptが原因かもしれないので無視してフォールバック
}
return null;
throw new UnsupportedContentException();
}
}

View File

@@ -27,7 +27,6 @@ class NarouResolver implements Resolver
$cookieJar = CookieJar::fromArray(['over18' => 'yes'], '.syosetu.com');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->description = '';
@@ -53,8 +52,5 @@ class NarouResolver implements Resolver
$metadata->description = implode(' / ', $description);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class NicoSeigaResolver implements Resolver
{
@@ -24,16 +25,18 @@ class NicoSeigaResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$html = (string)$res->getBody();
$metadata = $this->ogpResolver->parse($html);
$crawler = new Crawler($html);
// タグ
$excludeTags = ['R-15'];
$metadata->tags = array_values(array_diff($crawler->filter('.tag')->extract(['_text']), $excludeTags));
// ページURLからサムネイルURLに変換
preg_match('~http://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches);
$metadata->image = "http://lohas.nicoseiga.jp/thumb/${matches[1]}l?";
preg_match('~https?://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches);
$metadata->image = "https://lohas.nicoseiga.jp/thumb/${matches[1]}l?";
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class NijieResolver implements Resolver
{
@@ -30,27 +31,33 @@ class NijieResolver implements Resolver
$url = preg_replace('~view_popup\.php~', 'view.php', $url);
}
$client = $this->client;
$res = $client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get($url);
$html = (string) $res->getBody();
$metadata = $this->ogpResolver->parse($html);
$crawler = new Crawler($html);
$json = $crawler->filter('script[type="application/ld+json"]')->first()->text();
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$dataNode = $xpath->query('//script[substring(@type, string-length(@type) - 3, 4) = "json"]');
foreach ($dataNode as $node) {
// 改行がそのまま入っていることがあるのでデコード前にエスケープが必要
$imageData = json_decode(preg_replace('/\r?\n/', '\n', $node->nodeValue), true);
if (isset($imageData['thumbnailUrl']) && !ends_with($imageData['thumbnailUrl'], '.gif') && !ends_with($imageData['thumbnailUrl'], '.mp4')) {
$metadata->image = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']);
break;
}
$data = json_decode(preg_replace('/\r?\n/', '\n', $json), true);
// DomCrawler内でjson内の日本語がHTMLエンティティに変換されるので、全要素に対してhtml_entity_decode
array_walk_recursive($data, function (&$v) {
$v = html_entity_decode($v);
});
$metadata->title = $data['name'];
$metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description'];
if (
isset($data['thumbnailUrl']) &&
!ends_with($data['thumbnailUrl'], '.gif') &&
!ends_with($data['thumbnailUrl'], '.mp4')
) {
// サムネイルからメイン画像に
$metadata->image = str_replace('__rs_l160x160/', '', $data['thumbnailUrl']);
}
$metadata->tags = $crawler->filter('#view-tag span.tag_name')->extract('_text');
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -18,12 +18,7 @@ class OGPResolver implements Resolver, Parser
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
return $this->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
return $this->parse($this->client->get($url)->getBody());
}
public function parse(string $html): Metadata

View File

@@ -25,7 +25,6 @@ class PatreonResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $query);
@@ -35,8 +34,5 @@ class PatreonResolver implements Resolver
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -38,27 +38,26 @@ class PixivResolver implements Resolver
{
if (preg_match('~www\.pixiv\.net/user/\d+/series/\d+~', $url, $matches)) {
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->image = $this->proxize($metadata->image);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
$page = 0;
if (preg_match('~www\.pixiv\.net/(en/)?artworks/(?P<illustId>\d+)~', $url, $matches)) {
$illustId = $matches['illustId'];
} else {
parse_str(parse_url($url, PHP_URL_QUERY), $params);
$illustId = $params['illust_id'];
$page = 0;
// 漫画ページページ数はmanga_bigならあるかも
if ($params['mode'] === 'manga_big' || $params['mode'] === 'manga') {
$page = $params['page'] ?? 0;
}
}
$res = $this->client->get('https://www.pixiv.net/ajax/illust/' . $illustId);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
@@ -68,7 +67,7 @@ class PixivResolver implements Resolver
// ページ数の指定がある場合は画像URLをそのページにする
if ($page != 0) {
$metadata->image = str_replace('_p0', '_p'.$page, $metadata->image);
$metadata->image = str_replace('_p0', '_p' . $page, $metadata->image);
}
// タグ
@@ -82,8 +81,5 @@ class PixivResolver implements Resolver
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -24,7 +24,6 @@ class PlurkResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@@ -37,8 +36,5 @@ class PlurkResolver implements Resolver
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -24,7 +24,6 @@ class SteamResolver implements Resolver
$appid = $matches[1];
$res = $this->client->get('https://store.steampowered.com/api/appdetails/?l=japanese&appids=' . $appid);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
if ($json[$appid]['success'] === false) {
throw new \RuntimeException("API response [$appid][success] is false: $url");
@@ -37,8 +36,5 @@ class SteamResolver implements Resolver
$metadata->image = $data['header_image'] ?? '';
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -24,13 +24,17 @@ class ToranoanaResolver implements Resolver
public function resolve(string $url): Metadata
{
$cookieJar = CookieJar::fromArray(['adflg' => '0'], 'ec.toranoana.jp');
$res = $this->client->get($url);
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
return $this->ogpResolver->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$imgNode = $xpath->query('//*[@id="preview"]//img')->item(0);
if ($imgNode !== null) {
$metadata->image = $imgNode->getAttribute('src');
}
return $metadata;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\MetadataResolver;
use Exception;
/**
* このResolverやParserが対応していないサイトであったことを表わします。
*/
class UnsupportedContentException extends Exception
{
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class XtubeResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
if (preg_match('~www\.xtube\.com/video-watch/.*-(\d+)$~', $url) !== 1) {
throw new \RuntimeException("Unmatched URL Pattern: $url");
}
$res = $this->client->get($url);
$html = (string) $res->getBody();
$metadata = $this->ogpResolver->parse($html);
$crawler = new Crawler($html);
$metadata->title = trim($crawler->filter('.underPlayerRateForm h1')->text(''));
$metadata->description = trim($crawler->filter('.fullDescription ')->text(''));
$metadata->image = str_replace('m=eSuQ8f', 'm=eaAaaEFb', $metadata->image);
$metadata->image = str_replace('240X180', 'original', $metadata->image);
$metadata->tags = array_map('trim', $crawler->filter('.tagsCategories a')->extract('_text'));
return $metadata;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Policies;
use App\Ejaculation;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class EjaculationPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* @return void
*/
public function __construct()
{
//
}
public function edit(User $user, Ejaculation $ejaculation): bool
{
return $user->id === $ejaculation->user_id;
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Providers;
use App\Ejaculation;
use App\Policies\EjaculationPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
@@ -14,6 +16,7 @@ class AuthServiceProvider extends ServiceProvider
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Ejaculation::class => EjaculationPolicy::class,
];
/**

View File

@@ -53,6 +53,11 @@ class User extends Authenticatable
return Auth::check() && $this->id === Auth::user()->id;
}
public function ejaculations()
{
return $this->hasMany(Ejaculation::class);
}
public function likes()
{
return $this->hasMany(Like::class);

View File

@@ -10,10 +10,13 @@
"doctrine/dbal": "^2.9",
"fideloper/proxy": "~3.3",
"guzzlehttp/guzzle": "^6.3",
"jakeasmith/http_build_url": "^1.0",
"laravel/framework": "5.5.*",
"laravel/tinker": "~1.0",
"misd/linkify": "^1.1",
"staudenmeir/eloquent-eager-limit": "^1.0"
"staudenmeir/eloquent-eager-limit": "^1.0",
"symfony/css-selector": "^4.3",
"symfony/dom-crawler": "^4.3"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.1",

1929
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Ejaculation;
use Faker\Generator as Faker;
$factory->define(Ejaculation::class, function (Faker $faker) {
return [
'ejaculated_date' => $faker->date('Y-m-d H:i:s'),
'note' => $faker->text,
];
});

View File

@@ -0,0 +1,10 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use Faker\Generator as Faker;
$factory->define(App\Like::class, function (Faker $faker) {
return [
//
];
});

View File

@@ -1,24 +0,0 @@
<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
|
*/
/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class, function (Faker\Generator $faker) {
static $password;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
];
});

View File

@@ -0,0 +1,21 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class, function (Faker\Generator $faker) {
static $password;
return [
'name' => substr($faker->userName, 0, 15),
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
'display_name' => substr($faker->name, 0, 20),
'is_protected' => false,
'accept_analytics' => false,
'private_likes' => false,
];
});
$factory->state(App\User::class, 'protected', [
'is_protected' => true,
]);

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIsTooSensitiveToEjaculations extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('ejaculations', function (Blueprint $table) {
$table->boolean('is_too_sensitive')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('ejaculations', function (Blueprint $table) {
$table->dropColumn('is_too_sensitive');
});
}
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDeactivatedUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('deactivated_users', function (Blueprint $table) {
$table->string('name', 15);
$table->timestamps();
$table->primary('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('deactivated_users');
}
}

View File

@@ -16,6 +16,7 @@
"cal-heatmap": "^3.3.10",
"chart.js": "^2.7.1",
"cross-env": "^5.2.0",
"date-fns": "^1.30.1",
"husky": "^1.3.1",
"jquery": "^3.2.1",
"js-cookie": "^2.2.0",

View File

@@ -16,6 +16,10 @@
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="MetadataResolver">
<directory suffix="Test.php">./tests/Unit/MetadataResolver</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
@@ -23,9 +27,9 @@
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="APP_ENV" value="testing" force="true"/>
<env name="CACHE_DRIVER" value="array" force="true"/>
<env name="SESSION_DRIVER" value="array" force="true"/>
<env name="QUEUE_DRIVER" value="sync" force="true"/>
</php>
</phpunit>

View File

@@ -85,6 +85,9 @@ $(() => {
if (xhr.status === 409) {
callback(JSON.parse(xhr.responseText));
return;
} else if (xhr.status === 401) {
alert('いいねするためにはログインしてください。');
return;
}
console.error(xhr);
@@ -92,4 +95,12 @@ $(() => {
});
}
});
$(document).on('click', '.card-spoiler-wrap', function (event) {
const $this = $(this);
const $card = $this.siblings(".card-link");
$card.removeClass("card-spoiler");
$card.find('.card-img-spoiler-overlay').remove();
$this.remove();
});
});

View File

@@ -11,7 +11,7 @@
</div>
<div v-else-if="state === MetadataLoadState.Success" class="row no-gutters">
<div v-if="hasImage" class="col-4 justify-content-center align-items-center">
<img :src="metadata.image" alt="Thumbnail" class="card-img-top-to-left bg-secondary">
<img :src="metadata.image" alt="Thumbnail" class="w-100 bg-secondary">
</div>
<div :class="descClasses">
<div class="card-body">
@@ -20,8 +20,8 @@
<p class="card-text mb-2" style="font-size: small;">タグ候補<br><span class="text-secondary">(クリックするとタグ入力欄にコピーできます)</span></p>
<ul class="list-inline d-inline">
<li v-for="tag in suggestions"
class="list-inline-item badge badge-primary metadata-tag-item"
@click="addTag(tag)"><span class="oi oi-tag"></span> {{ tag }}</li>
:class="tagClasses(tag)"
@click="addTag(tag.name)"><span class="oi oi-tag"></span> {{ tag.name }}</li>
</ul>
</template>
</div>
@@ -54,6 +54,11 @@
}[],
};
type Suggestion = {
name: string,
used: boolean,
}
@Component
export default class MetadataPreview extends Vue {
@Prop() readonly state!: MetadataLoadState;
@@ -62,16 +67,38 @@
// for use in v-if
private readonly MetadataLoadState = MetadataLoadState;
tags: string[] = [];
created() {
bus.$on("change-tag", (tags: string[]) => this.tags = tags);
bus.$emit("resend-tag");
}
addTag(tag: string) {
bus.$emit("add-tag", tag);
}
get suggestions() {
tagClasses(s: Suggestion) {
return {
"list-inline-item": true,
"badge": true,
"badge-primary": !s.used,
"badge-secondary": s.used,
"metadata-tag-item": true,
};
}
get suggestions(): Suggestion[] {
if (this.metadata === null) {
return [];
}
return this.metadata.tags.map(t => t.name);
return this.metadata.tags.map(t => {
return {
name: t.name,
used: this.tags.indexOf(t.name) !== -1
};
});
}
get hasImage() {
@@ -90,10 +117,7 @@
<style lang="scss" scoped>
.link-card-mini {
$height: 150px;
.row > div {
overflow: hidden;
}
.row > div:first-child {
display: flex;

View File

@@ -16,7 +16,7 @@
</template>
<script lang="ts">
import {Vue, Component, Prop} from "vue-property-decorator";
import {Vue, Component, Prop, Watch} from "vue-property-decorator";
import {bus} from "../checkin";
@Component
@@ -31,6 +31,7 @@
created() {
bus.$on("add-tag", (tag: string) => this.tags.indexOf(tag) === -1 && this.tags.push(tag));
bus.$on("resend-tag", () => bus.$emit("change-tag", this.tags));
}
onKeyDown(event: KeyboardEvent) {
@@ -40,11 +41,19 @@
case 'Enter':
case ' ':
if ((event as any).isComposing !== true) {
this.tags.push(this.buffer);
this.tags.push(this.buffer.trim());
this.buffer = "";
}
event.preventDefault();
break;
case 'Unidentified':
// 実際にテキストボックスに入力されている文字を見に行く (フォールバック処理)
if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') {
this.tags.push(this.buffer.trim());
this.buffer = "";
event.preventDefault();
}
break;
}
} else if (event.key === "Enter") {
// 誤爆防止
@@ -56,6 +65,11 @@
this.tags.splice(index, 1);
}
@Watch("tags")
onTagsChanged() {
bus.$emit("change-tag", this.tags);
}
get containerClass(): object {
return {
"form-control": true,

View File

@@ -0,0 +1,5 @@
$('#deactivate-form').on('submit', function () {
if (!confirm('本当にアカウントを削除してもよろしいですか?')) {
return false;
}
});

View File

@@ -1,9 +1,12 @@
import CalHeatMap from 'cal-heatmap';
import Chart from 'chart.js';
import {addMonths, format, startOfMonth, subMonths} from 'date-fns';
const graphData = JSON.parse(document.getElementById('graph-data').textContent);
function createLineGraph(id, labels, data) {
const context = document.getElementById(id).getContext('2d');
new Chart(context, {
return new Chart(context, {
type: 'line',
data: {
labels: labels,
@@ -62,7 +65,22 @@ function createBarGraph(id, labels, data) {
});
}
const graphData = JSON.parse(document.getElementById('graph-data').textContent);
/**
* @param {Date} from
*/
function createMonthlyGraphData(from) {
const keys = [];
const values = [];
for (let i = 0; i < 12; i++) {
const current = addMonths(from, i);
const yearAndMonth = format(current, 'YYYY/MM');
keys.push(yearAndMonth);
values.push(graphData.monthlySum[yearAndMonth] || 0);
}
return {keys, values};
}
new CalHeatMap().init({
itemSelector: '#cal-heatmap',
@@ -76,7 +94,41 @@ new CalHeatMap().init({
legend: [1, 2, 3, 4]
});
createLineGraph('monthly-graph', graphData.monthlyKey, graphData.monthlySum);
// 直近1年の月間グラフのデータを準備
const monthlyTermFrom = subMonths(startOfMonth(new Date()), 11);
const {keys: monthlyKey, values: monthlySum} = createMonthlyGraphData(monthlyTermFrom);
const monthlyGraph = createLineGraph('monthly-graph', monthlyKey, monthlySum);
createLineGraph('yearly-graph', graphData.yearlyKey, graphData.yearlySum);
createBarGraph('hourly-graph', graphData.hourlyKey, graphData.hourlySum);
createBarGraph('dow-graph', ['日', '月', '火', '水', '木', '金', '土'], graphData.dowSum);
// 月間グラフの期間セレクターを準備
const monthlyTermSelector = document.getElementById('monthly-term');
const earliestYear = [...new Set(Object.keys(graphData.monthlySum).map(v => v.substr(0, 4)))].shift();
for (let year = earliestYear; year <= new Date().getFullYear(); year++) {
const opt = document.createElement('option');
opt.setAttribute('value', year);
opt.textContent = `${year}`;
monthlyTermSelector.insertBefore(opt, monthlyTermSelector.firstChild);
}
if (monthlyTermSelector.children.length) {
monthlyTermSelector.selectedIndex = 0;
}
monthlyTermSelector.addEventListener('change', function (e) {
let monthlyTermFrom;
if (e.target.selectedIndex === 0) {
// 今年のデータを表示する時は、直近12ヶ月を表示
monthlyTermFrom = subMonths(startOfMonth(new Date()), 11);
} else {
// 過去のデータを表示する時は、選択年の1〜12月を表示
monthlyTermFrom = new Date(e.target.value, 0, 1);
}
const {keys, values} = createMonthlyGraphData(monthlyTermFrom);
monthlyGraph.data.labels = keys;
monthlyGraph.data.datasets[0].data = values;
monthlyGraph.update();
});

View File

@@ -1,19 +0,0 @@
.card-img-left {
width: 100%;
@include border-left-radius($card-inner-border-radius);
}
.card-img-right {
width: 100%;
@include border-right-radius($card-inner-border-radius);
}
.card-img-top-to-left {
width: 100%;
@include media-breakpoint-down(md) {
@include border-top-radius($card-inner-border-radius);
}
@include media-breakpoint-up(lg) {
@include border-left-radius($card-inner-border-radius);
}
}

View File

@@ -3,7 +3,6 @@ $primary: #e53fb1;
// Bootstrap
@import "~bootstrap/scss/bootstrap";
@import "bootstrap-custom";
// Open Iconic
@import "~open-iconic/font/css/open-iconic-bootstrap";
@@ -14,3 +13,6 @@ $primary: #e53fb1;
// Components
@import "components/ejaculation";
@import "components/link-card";
// Tag
@import "tag/index";

View File

@@ -1,4 +1,6 @@
.link-card {
overflow: hidden;
.row > div {
max-height: 400px;
overflow: hidden;
@@ -28,4 +30,32 @@
.card-text {
white-space: pre-line;
}
.card-spoiler-wrap {
position: absolute;
z-index: 1000;
width: 100%;
height: 100%;
cursor: pointer;
}
.card-spoiler img{
z-index: 1;
filter: blur(15px) grayscale(100%);
}
.card-img-spoiler-overlay {
position: absolute;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
.warning-text {
padding: 10px;
user-select: none;
background-color: rgba(240, 240, 240, 0.8);
border-radius: 5px;
}
}
}

22
resources/assets/sass/tag/_index.scss vendored Normal file
View File

@@ -0,0 +1,22 @@
.tags {
& > .btn-tag {
width: 100%;
.tag-name {
display: inline-block;
max-width: 80%;
overflow: hidden;
line-height: 40px;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.checkins-count {
display: inline-block;
line-height: 40px;
white-space: nowrap;
vertical-align: middle;
}
}
}

View File

@@ -77,3 +77,4 @@
#navbarAccountDropdownSp {
max-width: calc(100vw - 5em);
}

View File

@@ -24,6 +24,7 @@
<div class="form-group">
<label for="name"><span class="oi oi-person"></span> ユーザー名</label>
<input id="name" name="name" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" type="text" value="{{ old('name') }}" required>
<small class="form-text text-muted">半角英数字と一部記号が使用できます。一度決めたら変更できません。</small>
@if ($errors->has('name'))
<div class="invalid-feedback">{{ $errors->first('name') }}</div>

View File

@@ -19,7 +19,7 @@
<!-- okazu link -->
@if (!empty($ejaculation->link))
<div class="row mx-0">
@component('components.link-card', ['link' => $ejaculation->link])
@component('components.link-card', ['link' => $ejaculation->link, 'is_too_sensitive' => $ejaculation->is_too_sensitive])
@endcomponent
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
@@ -49,7 +49,7 @@
<div class="ejaculation-actions">
<button type="button" class="btn btn-link text-secondary"
data-toggle="tooltip" data-placement="bottom"
title="同じオカズでチェックイン" data-href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload"></span></button>
title="同じオカズでチェックイン" data-href="{{ $ejaculation->makeCheckinURL() }}"><span class="oi oi-reload"></span></button>
<button type="button" class="btn btn-link text-secondary like-button"
data-toggle="tooltip" data-placement="bottom" data-trigger="hover"
title="いいね" data-id="{{ $ejaculation->id }}" data-liked="{{ (bool)$ejaculation->is_liked }}"><span class="oi oi-heart {{ $ejaculation->is_liked ? 'text-danger' : '' }}"></span><span class="like-count">{{ $ejaculation->likes_count ? $ejaculation->likes_count : '' }}</span></button>

View File

@@ -1,8 +1,16 @@
<div class="card link-card mb-2 px-0 col-12 d-none" style="font-size: small;">
<a class="text-dark card-link" href="{{ $link }}" target="_blank" rel="noopener">
@if ($is_too_sensitive)
<div class="card-spoiler-wrap"> </div>
@endif
<a class="text-dark card-link {{ $is_too_sensitive ? 'card-spoiler' : '' }}" href="{{ $link }}" target="_blank" rel="noopener">
<div class="row no-gutters">
<div class="col-12 col-md-6 justify-content-center align-items-center">
<img src="" alt="Thumbnail" class="card-img-top-to-left bg-secondary">
@if ($is_too_sensitive)
<div class="card-img-spoiler-overlay">
<span class="warning-text">クリックまたはタップで表示</span>
</div>
@endif
<img src="" alt="Thumbnail" class="w-100 bg-secondary">
</div>
<div class="col-12 col-md-6">
<div class="card-body">

View File

@@ -85,6 +85,12 @@
<span class="oi oi-lock-locked"></span> このチェックインを非公開にする
</label>
</div>
<div class="custom-control custom-checkbox mb-3">
<input id="isTooSensitive" name="is_too_sensitive" type="checkbox" class="custom-control-input" {{ old('is_too_sensitive') || $defaults['is_too_sensitive'] ? 'checked' : '' }}>
<label class="custom-control-label" for="isTooSensitive">
<span class="oi oi-warning"></span> チェックイン対象のオカズをより過激なオカズとして設定する
</label>
</div>
</div>
</div>

View File

@@ -86,6 +86,12 @@
<span class="oi oi-lock-locked"></span> このチェックインを非公開にする
</label>
</div>
<div class="custom-control custom-checkbox mb-3">
<input id="isTooSensitive" name="is_too_sensitive" type="checkbox" class="custom-control-input" {{ (is_bool(old('is_too_sensitive')) ? old('is_too_sensitive') : $ejaculation->is_too_sensitive) ? 'checked' : '' }}>
<label class="custom-control-label" for="isTooSensitive">
<span class="oi oi-warning"></span> チェックイン対象のオカズをより過激なオカズとして設定する
</label>
</div>
</div>
</div>

View File

@@ -47,7 +47,7 @@
<!-- okazu link -->
@if (!empty($ejaculation->link))
<div class="row mx-0">
@component('components.link-card', ['link' => $ejaculation->link])
@component('components.link-card', ['link' => $ejaculation->link, 'is_too_sensitive' => $ejaculation->is_too_sensitive])
@endcomponent
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
@@ -75,7 +75,7 @@
@endif
<!-- actions -->
<div class="ejaculation-actions">
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload"></span></button>
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ $ejaculation->makeCheckinURL() }}"><span class="oi oi-reload"></span></button>
<button type="button" class="btn btn-link text-secondary like-button" data-toggle="tooltip" data-placement="bottom" data-trigger="hover" title="いいね" data-id="{{ $ejaculation->id }}" data-liked="{{ (bool)$ejaculation->is_liked }}"><span class="oi oi-heart {{ $ejaculation->is_liked ? 'text-danger' : '' }}"></span><span class="like-count">{{ $ejaculation->likes_count ? $ejaculation->likes_count : '' }}</span></button>
@if ($user->isMe())
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="修正" data-href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil"></span></button>

View File

@@ -54,6 +54,9 @@
<a href="{{ route('user.likes', ['name' => Auth::user()->name]) }}" class="dropdown-item">いいね</a>
<div class="dropdown-divider"></div>
<a href="{{ route('setting') }}" class="dropdown-item">設定</a>
@can ('admin')
<a href="{{ route('admin.dashboard') }}" class="dropdown-item">管理</a>
@endcan
<a href="{{ route('logout') }}" class="dropdown-item" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">ログアウト</a>
</div>
</div>
@@ -79,6 +82,9 @@
<li class="nav-item {{ stripos(Route::currentRouteName(), 'user.okazu') === 0 ? 'active' : ''}}">
<a class="nav-link" href="{{ route('user.okazu', ['name' => Auth::user()->name]) }}">オカズ</a>
</li>
<li class="nav-item {{ stripos(Route::currentRouteName(), 'tag') === 0 ? 'active' : ''}}">
<a class="nav-link" href="{{ route('tag') }}">タグ一覧</a>
</li>
{{--<li class="nav-item">
<a class="nav-link" href="{{ route('ranking') }}">ランキング</a>
</li>--}}
@@ -137,6 +143,13 @@
<a class="btn btn-{{ stripos(Route::currentRouteName(), 'user.okazu') === 0 ? 'primary' : 'outline-secondary'}}" href="{{ route('user.okazu', ['name' => Auth::user()->name]) }}" role="button">オカズ</a>
</div>
</div>
<div class="row mt-2">
<div class="col">
<a class="btn btn-{{ stripos(Route::currentRouteName(), 'tag') === 0 ? 'primary' : 'outline-secondary'}}" href="{{ route('tag') }}" role="button">タグ一覧</a>
</div>
<div class="col">
</div>
</div>
{{-- <div class="row mt-2">
<div class="col">
<a class="btn btn-outline-secondary" href="{{ route('ranking') }}">ランキング</a>
@@ -201,7 +214,7 @@
@yield('content')
<footer class="tis-footer mt-4">
<div class="container p-3 p-md-4">
<p>Copyright (c) 2017-2019 shikorism.net</p>
<p>Copyright (c) 2017-2020 shikorism.net</p>
<ul class="list-inline">
<li class="list-inline-item"><a href="https://github.com/shibafu528" class="text-dark">Admin(@shibafu528)</a></li>
<li class="list-inline-item"><a href="https://github.com/shikorism/tissue" class="text-dark">GitHub</a></li>

View File

@@ -10,6 +10,8 @@
href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.privacy' ? 'active' : '' }}"
href="{{ route('setting.privacy') }}"><span class="oi oi-shield mr-1"></span> プライバシー</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.deactivate' ? 'active' : '' }}"
href="{{ route('setting.deactivate') }}"><span class="oi oi-trash mr-1"></span> アカウントの削除</a>
{{--<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.password' ? 'active' : '' }}"
href="{{ route('setting.password') }}"><span class="oi oi-key mr-1"></span> パスワード</a>--}}
</div>

View File

@@ -0,0 +1,32 @@
@extends('setting.base')
@section('title', 'アカウントの削除')
@section('tab-content')
<h3>アカウントの削除</h3>
<hr>
<p>Tissueからあなたのアカウントに関する情報を削除します。</p>
<div class="alert alert-danger">
<h4 class="alert-heading"><span class="oi oi-warning"></span> 警告</h4>
<p><strong>削除はすぐに実行され、取り消すことはできません!</strong></p>
<p class="my-0">なりすましを防止するため、あなたのユーザー名はサーバーに記録されます。今後、同じユーザー名を使って再登録することはできません。</p>
</div>
<form id="deactivate-form" action="{{ route('setting.deactivate.destroy') }}" method="post">
{{ csrf_field() }}
<div class="form-group">
<p>上記の条件に同意してアカウントを削除する場合は、パスワードを入力して削除ボタンを押してください。</p>
<input name="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" required>
@if ($errors->has('password'))
<div class="invalid-feedback">{{ $errors->first('password') }}</div>
@endif
</div>
<button type="submit" class="btn btn-danger mt-2">削除</button>
</form>
@endsection
@push('script')
<script src="{{ mix('js/setting/deactivate.js') }}"></script>
@endpush

View File

@@ -0,0 +1,16 @@
@extends('layouts.base')
@section('title', 'アカウント削除完了')
@section('content')
<div class="container">
<h3>アカウントを削除しました</h3>
<hr>
<p>Tissueをご利用いただき、ありがとうございました。</p>
<p class="my-5 text-center"><a class="btn btn-link" href="{{ route('home') }}">トップページへ</a></p>
</div>
@endsection
@push('script')
<script src="{{ mix('js/setting/deactivate.js') }}"></script>
@endpush

View File

@@ -29,12 +29,15 @@
</div>
<input id="name" name="name" type="text" class="form-control" value="{{ Auth::user()->name }}" disabled>
</div>
<small class="form-text text-muted">現在は変更できません。</small>
<small class="form-text text-muted">変更することはできません。</small>
</div>
<div class="from-group mt-3">
<label for="name">メールアドレス</label>
<input id="name" name="name" type="text" class="form-control" value="{{ Auth::user()->email }}" disabled>
<small class="form-text text-muted">現在は変更できません。</small>
<label for="email">メールアドレス</label>
<input id="email" name="email" type="email" class="form-control {{ $errors->has('email') ? ' is-invalid' : '' }}" value="{{ old('email') ?? Auth::user()->email }}">
@if ($errors->has('email'))
<div class="invalid-feedback">{{ $errors->first('email') }}</div>
@endif
</div>
<div class="form-group mt-3">
<label for="bio">自己紹介</label>

View File

@@ -0,0 +1,20 @@
@extends('layouts.base')
@section('title', 'タグ一覧')
@section('content')
<div class="container pb-1">
<h2 class="mb-3">タグ一覧</h2>
<p class="text-secondary">公開チェックインに付けられているタグを、チェックイン数の多い順で表示しています。</p>
<div class="container-fluid">
<div class="row mx-1">
@foreach($tags as $tag)
<div class="col-12 col-lg-6 col-xl-3 py-3 text-break tags">
<a href="{{ route('search', ['q' => $tag->name]) }}" class="btn btn-outline-primary btn-tag" title="{{ $tag->name }}"><span class="tag-name">{{ $tag->name }}</span> <span class="checkins-count">({{ $tag->checkins_count }})</span></a>
</div>
@endforeach
</div>
{{ $tags->links(null, ['className' => 'mt-4 justify-content-center']) }}
</div>
</div>
@endsection

View File

@@ -53,7 +53,7 @@
<!-- okazu link -->
@if (!empty($ejaculation->link))
<div class="row mx-0">
@component('components.link-card', ['link' => $ejaculation->link])
@component('components.link-card', ['link' => $ejaculation->link, 'is_too_sensitive' => $ejaculation->is_too_sensitive])
@endcomponent
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
@@ -81,7 +81,7 @@
@endif
<!-- actions -->
<div class="ejaculation-actions">
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload"></span></button>
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ $ejaculation->makeCheckinURL() }}"><span class="oi oi-reload"></span></button>
<button type="button" class="btn btn-link text-secondary like-button" data-toggle="tooltip" data-placement="bottom" data-trigger="hover" title="いいね" data-id="{{ $ejaculation->id }}" data-liked="{{ (bool)$ejaculation->is_liked }}"><span class="oi oi-heart {{ $ejaculation->is_liked ? 'text-danger' : '' }}"></span><span class="like-count">{{ $ejaculation->likes_count ? $ejaculation->likes_count : '' }}</span></button>
@if ($user->isMe())
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="修正" data-href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil"></span></button>

View File

@@ -15,7 +15,14 @@
<h5 class="my-4">Shikontribution graph</h5>
<div id="cal-heatmap" class="tis-contribution-graph"></div>
<hr class="my-4">
<h5 class="my-4">月間チェックイン回数</h5>
<div class="row my-4">
<div class="col-12 col-lg-6 d-flex align-items-center">
<h5 class="my-0">月間チェックイン回数</h5>
</div>
<div class="col-12 col-lg-6 mt-2 mt-lg-0">
<select id="monthly-term" class="form-control"></select>
</div>
</div>
<canvas id="monthly-graph" class="w-100"></canvas>
<hr class="my-4">
<h5 class="my-4">年間チェックイン回数</h5>

View File

@@ -36,6 +36,8 @@ Route::middleware('auth')->group(function () {
Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update');
Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy');
Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update');
Route::get('/setting/deactivate', 'SettingController@deactivate')->name('setting.deactivate');
Route::post('/setting/deactivate', 'SettingController@destroyUser')->name('setting.deactivate.destroy');
// Route::get('/setting/password', 'SettingController@password')->name('setting.password');
});
@@ -46,6 +48,8 @@ Route::redirect('/search', '/search/checkin', 301);
Route::get('/search/checkin', 'SearchController@index')->name('search');
Route::get('/search/related-tag', 'SearchController@relatedTag')->name('search.related-tag');
Route::get('/tag', 'TagController@index')->name('tag');
Route::middleware('can:admin')
->namespace('Admin')
->prefix('admin')

View File

@@ -0,0 +1,46 @@
<?php
namespace Tests\Feature;
use App\Ejaculation;
use App\Like;
use App\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Symfony\Component\DomCrawler\Crawler;
use Tests\TestCase;
class SettingTest extends TestCase
{
public function testDestroyUser()
{
$user = factory(User::class)->create();
$ejaculation = factory(Ejaculation::class)->create(['user_id' => $user->id]);
$anotherUser = factory(User::class)->create();
$anotherEjaculation = factory(Ejaculation::class)->create(['user_id' => $anotherUser->id]);
$like = factory(Like::class)->create([
'user_id' => $user->id,
'ejaculation_id' => $anotherEjaculation->id,
]);
$anotherLike = factory(Like::class)->create([
'user_id' => $anotherUser->id,
'ejaculation_id' => $ejaculation->id,
]);
$response = $this->actingAs($user)
->followingRedirects()
->post('/setting/deactivate', ['password' => 'secret']);
$response->assertStatus(200)
->assertViewIs('setting.deactivated');
$this->assertGuest();
$this->assertDatabaseMissing('users', ['id' => $user->id]);
$this->assertDatabaseMissing('ejaculations', ['id' => $ejaculation->id]);
$this->assertDatabaseMissing('likes', ['id' => $like->id]);
$this->assertDatabaseMissing('likes', ['id' => $anotherLike->id]);
$this->assertDatabaseHas('deactivated_users', ['name' => $user->name]);
}
}

17
tests/MyAsserts.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
namespace Tests;
trait MyAsserts
{
/**
* assertArraySubset()がdeprecatedって本当ですか 配列の中に所定の値が全て含まれていることを検証します。
* @param array $expected
* @param array $actual
* @param string $message
*/
public function assertArrayContains(array $expected, array $actual, string $message = '')
{
$this->assertSame($expected, array_intersect($actual, $expected), $message);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\CienResolver;
use Tests\TestCase;
class CienResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function test()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Cien/test.html');
$this->createResolver(CienResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ci-en.dlsite.com/creator/2462/article/87502');
$this->assertSame('進捗とボツ立ち絵', $metadata->title);
$this->assertSame('ドット製D ACTを製作しています。' . PHP_EOL . '恐ろしい存在に襲われる絶望感や、被虐的な官能がテーマです。', $metadata->description);
$this->assertStringStartsWith('https://media.ci-en.jp/private/attachment/creator/00002462/a7afd3b02a6d1caa6afe6a3bf5550fb6a42aefba686f17a0a2f63c97fd6867ab/image-800.jpg?px-time=', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://media.ci-en.jp/private/attachment/creator/00002462/a7afd3b02a6d1caa6afe6a3bf5550fb6a42aefba686f17a0a2f63c97fd6867ab/image-800.jpg?px-time=1568231879&px-hash=70c57e9a73d5afb4ac5363d1f37a851af8e0cb1f', $metadata->image);
$this->assertSame(1568235479, $metadata->expires_at->timestamp);
$this->assertSame('https://ci-en.dlsite.com/creator/2462/article/87502', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testWithNoTimestamp()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Cien/testWithNoTimestamp.html');
$this->createResolver(CienResolver::class, $responseText);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Parameter "px-time" not found. Image=https://ci-en.dlsite.com/assets/img/common/logo_Ci-en_R18.svg Source=https://ci-en.dlsite.com/');
$this->resolver->resolve('https://ci-en.dlsite.com/');
}
}

View File

@@ -5,6 +5,7 @@ namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\Resolver;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Monolog\Handler\AbstractHandler;
@@ -41,7 +42,7 @@ trait CreateMockedResolver
$mockResponse = new Response($status, $headers, $responseText);
$this->handler = new MockHandler([$mockResponse]);
$client = new Client(['handler' => $this->handler]);
$client = new Client(['handler' => HandlerStack::create($this->handler)]);
$this->resolver = app()->make($resolverClass, ['client' => $client]);
return $this->resolver;

View File

@@ -44,7 +44,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('ことのはアムリラート', $metadata->title);
$this->assertEquals('メーカー名: SukeraSparo' . PHP_EOL . '異世界へと迷い込んだ凜に救いの手を差し伸べるルカ――。これは、ふたりが手探りの意思疎通(ことのは)で織りなす、もどかしくも純粋な……女の子同士の物語。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/professional/VJ012000/VJ011276_img_main.jpg', $metadata->image);
$this->assertEquals(['少女', '日常/生活', '純愛', '百合'], $metadata->tags);
$this->assertEquals(['日常/生活', '純愛', '百合', '少女'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/soft/work/=/product_id/VJ011276.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -60,7 +60,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('快楽ヒストリエ', $metadata->title);
$this->assertEquals('著者: 火鳥' . PHP_EOL . '天地創造と原初の人類を描いた「創世編」をはじめ、英雄たちの偉業を大真面目に考証した正真正銘の学術コミック全15編。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/books/BJ139000/BJ138581_img_main.jpg', $metadata->image);
$this->assertEquals(['おっぱい', 'ロリ', 'ショタ', '', '男性/おやじ', '女王様/お姫様', '王子様/王子系', '戦士', 'セーラー服', '着物/和服', '青年コミック', 'ギャグ', 'コメディ', '歴史/時代物', '褐色/日焼け', '爺'], $metadata->tags);
$this->assertEquals(['おっぱい', '青年コミック', 'ギャグ', 'コメディ', '歴史/時代物', 'ロリ', 'ショタ', '', 'おやじ', '女王様/お姫様', '王子様/王子系', '戦士', 'セーラー服', '着物/和服', '褐色/日焼け'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/comic/work/=/product_id/BJ138581.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -76,7 +76,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('催眠術で新婚人妻マナカさんとエッチしよう', $metadata->title);
$this->assertEquals('サークル名: デルタブレード' . PHP_EOL . '催眠術で新婚人妻マナカさんの愛する夫にすり替わって子作りラブラブエッチをするCG集です。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ206000/RJ205445_img_main.jpg', $metadata->image);
$this->assertEquals(['断面図', '人妻', '中出し', '妊娠/孕ませ', '催眠', '口内射精'], $metadata->tags);
$this->assertEquals(['断面図', '中出し', '妊娠/孕ませ', '催眠', '口内射精', '人妻'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/maniax/work/=/product_id/RJ205445.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -92,7 +92,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('euphoria HDリマスター Best Price版', $metadata->title);
$this->assertEquals('ブランド名: CLOCK UP' . PHP_EOL . 'インモラルハードコアADV「euphoria」が高解像度1024×768版、「euphoria HDリマスター」となって登場', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/professional/VJ009000/VJ008455_img_main.jpg', $metadata->image);
$this->assertEquals(['アブノーマル', '幼なじみ', '女教師', '退廃/背徳/インモラル', '拘束', '強制/無理矢理', 'スカトロ', 'アヘ顔', '拷問', '血液/流血', '狂気'], $metadata->tags);
$this->assertEquals(['マニアック/変態', 'アヘ顔', '退廃/背徳/インモラル', '拘束', '強制/無理矢理', 'スカトロ', '幼なじみ', '女教師', '拷問', '血液/流血', '狂気'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/pro/work/=/product_id/VJ008455.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -106,9 +106,9 @@ class DLsiteResolverTest extends TestCase
$metadata = $this->resolver->resolve('https://www.dlsite.com/books/work/=/product_id/BJ191317.html');
$this->assertEquals('永遠娘 vol.6', $metadata->title);
$this->assertEquals('著者: あまがえる / 玉之けだま / びんせん / 甘露アメ / 源五郎 / すみやお / 宇宙烏賊 / 毒茸人 / あやね / ガロウド / ハードボイルドよし子 / 夜歌 / 黒青郎君' . PHP_EOL . '君の命はどんな味なのだろうな?', $metadata->description);
$this->assertEquals('著者: あまがえる / 玉之けだま / びんせん / 甘露アメ / 源五郎 / すみやお / 宇宙烏賊 / 毒茸人 / あやね / ガロウド / ハードボイルドよし子 / 夜歌 / 黒青郎君' . PHP_EOL . '君の命はどんな味なのだろうな?', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/books/BJ192000/BJ191317_img_main.jpg', $metadata->image);
$this->assertEquals(['ツンデレ', 'ロリ', '妖怪', '人外娘/モンスター娘', 'セーラー服', 'メイド', 'ストッキング', 'ファンタジー', 'ぶっかけ', '中出し', '近親相姦', 'アヘ顔', '口内射精'], $metadata->tags);
$this->assertEquals(['アヘ顔', 'ファンタジー', 'ぶっかけ', '中出し', '近親相姦', '口内射精', 'ツンデレ', 'ロリ', '妖怪', '人外娘/モンスター娘', 'セーラー服', 'メイド', 'ストッキング'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/books/work/=/product_id/BJ191317.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -124,7 +124,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('体イク教師', $metadata->title);
$this->assertEquals('サークル名: Dusk' . PHP_EOL . '思い込みの激しい体育教師に執着されるお話', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ218000/RJ217995_img_main.jpg', $metadata->image);
$this->assertEquals(['教師', '中出し', '陵辱', '変態', '強制/無理矢理', 'レイプ'], $metadata->tags);
$this->assertEquals(['マニアック/変態', '中出し', '陵辱', '強制/無理矢理', 'レイプ', '教師'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/girls/work/=/product_id/RJ217995.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -140,7 +140,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('×××レクチャー', $metadata->title);
$this->assertEquals('著者: 江口尋' . PHP_EOL . '昔、告白してくれた地味な同級生・瀬尾は超人気セクシー男優になっていて!?', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/books/BJ171000/BJ170641_img_main.jpg', $metadata->image);
$this->assertEquals(['メガネ', '芸能人/アイドル/モデル', '俺様', 'ラブコメ', 'ラブラブ/あまあま', 'ティーンズラブ', '調教', '褐色/日焼け'], $metadata->tags);
$this->assertEquals(['ラブコメ', 'ラブラブ/あまあま', 'ティーンズラブ', '調教', 'メガネ', '芸能人/アイドル/モデル', '俺様', '褐色/日焼け'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/girls-pro/work/=/product_id/BJ170641.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -156,7 +156,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('秘密に堕つ', $metadata->title);
$this->assertEquals('サークル名: ナゲットぶん投げ屋さん' . PHP_EOL . 'とある村に越してきた新婚夫婦。村の集会所で行われた歓迎会で犯される花婿。村の男達に犯され続けた花婿にある変化が…?', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ245000/RJ244977_img_main.jpg', $metadata->image);
$this->assertEquals(['既婚者', '中出し', '強制/無理矢理', 'レイプ', 'モブ姦'], $metadata->tags);
$this->assertEquals(['中出し', '強制/無理矢理', 'レイプ', 'モブ姦', '既婚者'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/bl/work/=/product_id/RJ244977.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -172,7 +172,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]', $metadata->title);
$this->assertEquals('Circle: Triangle!' . PHP_EOL . 'You go with a girl of your first love and enjoy going to haunted places and her massage, ear cleaning, sleep sharing etc. (CV: Yui Asami)', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_main.jpg', $metadata->image);
$this->assertEquals(['Healing', 'Binaural', 'ASMR', 'Childhood Friend', 'Ear Cleaning', 'Romance'], $metadata->tags);
$this->assertEquals(['Healing', 'Binaural', 'ASMR', 'Ear Cleaning', 'Lovey Dovey/Sweet Love', 'Childhood Friend'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/eng/work/=/product_id/RE228866.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -188,7 +188,7 @@ class DLsiteResolverTest extends TestCase
$this->assertEquals('NEKOPARA vol.1', $metadata->title);
$this->assertEquals('Circle: NEKO WORKs' . PHP_EOL . 'Chocolat and Vanilla star in a rich adult eroge series with E-mote system and animated H scenes', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ145000/RJ144678_img_main.jpg', $metadata->image);
$this->assertEquals(['Moe', 'Master and Servant', 'Funny Love Story', 'Nekomimi (Cat Ears)'], $metadata->tags);
$this->assertEquals(['Moe', 'Love Comedy/Romcom', 'Master and Servant', 'Nekomimi (Cat Ears)'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/ecchi-eng/work/=/product_id/RE144678.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -226,4 +226,94 @@ class DLsiteResolverTest extends TestCase
$this->assertSame('https://dlsite.jp/howtw/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testOldAffiliateLink()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
$this->createResolver(DLsiteResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.dlsite.com/home/dlaf/=/link/work/aid/eai04191/id/RJ221761.html');
$this->assertEquals('ひつじ、数えてあげるっ', $metadata->title);
$this->assertEquals('サークル名: Butterfly Dream' . PHP_EOL . '眠れないあなたに彼女が羊を数えてくれる音声です。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ222000/RJ221761_img_main.jpg', $metadata->image);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testSnsAffiliateLink()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
$this->createResolver(DLsiteResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.dlsite.com/home/dlaf/=/t/s/link/work/aid/eai04191/id/RJ221761.html');
$this->assertEquals('ひつじ、数えてあげるっ', $metadata->title);
$this->assertEquals('サークル名: Butterfly Dream' . PHP_EOL . '眠れないあなたに彼女が羊を数えてくれる音声です。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ222000/RJ221761_img_main.jpg', $metadata->image);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testAffiliateLink()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
$this->createResolver(DLsiteResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.dlsite.com/home/dlaf/=/t/t/link/work/aid/eai04191/id/RJ221761.html');
$this->assertEquals('ひつじ、数えてあげるっ', $metadata->title);
$this->assertEquals('サークル名: Butterfly Dream' . PHP_EOL . '眠れないあなたに彼女が羊を数えてくれる音声です。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ222000/RJ221761_img_main.jpg', $metadata->image);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testAffiliateUrl()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
$this->createResolver(DLsiteResolver::class, $responseText);
$metadata = $this->resolver->resolve('http://www.dlsite.com/home/dlaf/=/aid/eai04191/url/https%3A%2F%2Fwww.dlsite.com%2Fhome%2Fwork%2F=%2Fproduct_id%2FRJ221761.html');
$this->assertEquals('ひつじ、数えてあげるっ', $metadata->title);
$this->assertEquals('サークル名: Butterfly Dream' . PHP_EOL . '眠れないあなたに彼女が羊を数えてくれる音声です。', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ222000/RJ221761_img_main.jpg', $metadata->image);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testAffiliateBadUrl()
{
$this->createResolver(DLsiteResolver::class, '');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('アフィリエイト先のリンクがDLsiteのタイトルではありません: https://www.dlsite.com/home/');
$this->resolver->resolve('http://www.dlsite.com/home/dlaf/=/aid/eai04191/url/https%3A%2F%2Fwww.dlsite.com%2Fhome%2F');
}
public function testHTMLdescription()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHTMLdescription.html');
$this->createResolver(DLsiteResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.dlsite.com/books/work/=/product_id/BJ123822.html');
$this->assertEquals('獣○彼女カタログ', $metadata->title);
$this->assertEquals('著者: チキコ / MUJIN編集部' . PHP_EOL . '【DLsite.com独占販売】 エロ漫画界騒然、1冊まるごと獣○オンリー単行本! 人間チ×ポは出てきませんっ!!', $metadata->description);
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/books/BJ124000/BJ123822_img_main.jpg', $metadata->image);
$this->assertEquals(['断面図', '中出し', 'フェラチオ', '複数プレイ/乱交', '異種姦', '制服', '水着', 'メイド', '巫女', '軍服', '巨乳/爆乳', '処女', '褐色/日焼け'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/books/work/=/product_id/BJ123822.html', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\DeviantArtResolver;
use Tests\TestCase;
class DeviantArtResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testMature()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/DeviantArt/mature.json');
$this->createResolver(DeviantArtResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.deviantart.com/gatanii69/art/R-15-mabel-and-will-update-686016962');
$this->assertSame('R-15 mabel and will update', $metadata->title);
$this->assertSame('By gatanii69', $metadata->description);
$this->assertStringStartsWith('https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/6854f36d-8010-4cd0-9d62-0cf9b7829764/dbcfq2q-d78c9f6e-dced-4e5c-a345-2a1bfd5d7620.jpg', $metadata->image);
$this->assertSame(['nsfw', 'reversefalls', 'gravityfalls', 'gravityfallsfanart', 'mabelpines', 'billcipher', 'reversemabel', 'willcipher', 'reversebill', 'reversebillcipher', 'mawill'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://backend.deviantart.com/oembed?url=https://www.deviantart.com/gatanii69/art/R-15-mabel-and-will-update-686016962', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\FantiaResolver;
use Tests\TestCase;
class FantiaResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function test()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Fantia/test.json');
$this->createResolver(FantiaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://fantia.jp/posts/206561');
$this->assertSame('召喚士アルドラ', $metadata->title);
$this->assertSame('サークル: サークルぬるま湯 (ナナナナ)' . PHP_EOL . 'コミッション' . PHP_EOL . 'クイーンズブレイドリベリオンの召喚士アルドラです。', $metadata->description);
$this->assertSame('https://c.fantia.jp/uploads/post/file/206561/main_dbcc59e5-4090-4650-b969-8855a721c6a5.jpg', $metadata->image);
$this->assertSame(['ふたなり', '超乳', '超根', 'クイーンズブレイド', 'ナナナナ'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://fantia.jp/api/v1/posts/206561', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\FanzaResolver;
use Tests\TestCase;
class FanzaResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
/**
* @dataProvider provider
*/
public function test($filename, $url, $title, $description, $image, $tags)
{
$responseText = file_get_contents(__DIR__ . "/../../fixture/Fanza/{$filename}");
$this->createResolver(FanzaResolver::class, $responseText);
$metadata = $this->resolver->resolve($url);
$this->assertSame($title, $metadata->title);
$this->assertSame($description, $metadata->description);
$this->assertSame($image, $metadata->image);
$this->assertSame($tags, $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame($url, (string) $this->handler->getLastRequest()->getUri());
}
}
public function provider()
{
return [
'動画 digital/videoa' => [
'digital_videoa.html',
'https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=ssni00558/',
'巨乳姉妹2人とただひたすらセックスに明け暮れた両親不在の3日間',
'「お姉ちゃんもヤりなよ。すごい気持ちいいよ、セックス」ボクには父親が再婚してできた義理の妹たちがいる。名前はみはるとしおん。ある週末、父と母が外出して家を空けると、僕と妹たちの関係が大きく変わった。姉のみはるの前で妹のしおんと肉体関係を持つとそのままみはるともSEX。そして僕たちは両親がいない3日間、ただただSEXを楽しんだんだ。※ 配信方法によって収録内容が異なる場合があります。',
'https://pics.dmm.co.jp/digital/video/ssni00558/ssni00558pl.jpg',
['夕美しおん', '羽咲みはる', '朝霧浄', 'エスワン_ナンバーワンスタイル', 'S1_NO.1_STYLE', 'ハイビジョン', '独占配信', '制服', 'ドラマ', '巨乳', '美少女', 'ギリモザ', '姉・妹']
],
'素人動画 digital/videoc' => [
'digital_videoc.html',
'https://www.dmm.co.jp/digital/videoc/-/detail/=/cid=sweet015/',
'ねる',
'鉄板オナ素材的ハイシコリティもうサンプルは見ていただけましたかそうなんです非の打ち所まるで無し恋するキラッキラの瞳愛嬌抜群の純真笑顔Gカップ巨乳にむっちむちの恵体モザイク越しにも伝わってしまう雑誌グラビア級の美少女ルックスこのスペックなのに自分に自信が持てない系のウブっ子触れただけで濡れだす敏感ボディねっとりDキスから嬉しそうに大量唾液をゴク飲みする程度には恋愛洗脳済み溢れ出るガマン汁を丁寧に舐めとるラブいフェラビックビク痙攣しながら困り顔で何度も何度も連続イキ絶頂※ 配信方法によって収録内容が異なる場合があります。' . PHP_EOL . '特集:' . PHP_EOL . PHP_EOL . 'FANZAオリジナル『素人ホイホイZ/素人ホイホイsweet』',
'https://pics.dmm.co.jp/digital/amateur/sweet015/sweet015jp.jpg',
['素人ホイホイsweet', '独占配信', '巨乳', '制服', '清楚', '美少女', '女子校生', 'ハイビジョン']
],
'アニメ digital/anime' => [
'digital_anime.html',
'https://www.dmm.co.jp/digital/anime/-/detail/=/cid=h_1379jdxa57513/',
'性活週間 THE ANIMATION 第1巻',
'めちゃシコ美少女マスター・みちきんぐの初単行本が' . PHP_EOL . '『ヌーディストビーチに修学旅行で?』『リアルエロゲシチュエーション』など' . PHP_EOL . '大ヒットシリーズを手掛けたアダルトアニメ界の新進気鋭クリエイター' . PHP_EOL . '「小原和大」によって待望のOVA化' . PHP_EOL . '私と姉体験してみない?' . PHP_EOL . 'c2019 みちきんぐ/GOT/ピンクパイナップル※ 配信方法によって収録内容が異なる場合があります。',
'https://pics.dmm.co.jp/digital/video/h_1379jdxa57513/h_1379jdxa57513pl.jpg',
['性活週間_THE_ANIMATION', 'ピンクパイナップル', 'Pink_Pineapple', 'ハイビジョン', '中出し', 'フェラ', '巨乳', '姉・妹']
],
'同人' => [
'doujin.html',
'https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_115139/',
'美少女拉致って性教育',
'ハ○エースでおさげ髪美少女を拉致って、凌辱する内容です。' . PHP_EOL . '汚っさん×美少女モノ。' . PHP_EOL . '表紙込み総ページ数28p内本文27p' . PHP_EOL . '表紙大きさ1200×1719' . PHP_EOL . '本文大きさ1200×1694',
'https://doujin-assets.dmm.co.jp/digital/comic/d_115139/d_115139pr.jpg',
['美少女拉致って性教育', 'オリジナル', '制服', '男性向け', 'ミニ系', '少女', '屋外', '中出し', '成人向け', 'みくろぺえじ'],
],
'電子書籍' => [
'book.html',
'https://book.dmm.co.jp/detail/b104atint00313/',
'少女×少女×少女',
'少女達が乱舞する…!' . PHP_EOL . '天上家。俺が捨てたあの家…祭子から「母が亡くなった」と電話を受けて、俺は妹達を救うために帰って行くが…。そこで待っていたのは、運命に逆らえず妹達との果てしなき乱交の宴だった…。' . PHP_EOL . '透明感溢れる魅力的なキャラクター、緻密に描きこまれた世界、そしてそのスタイルからは想像できないハードかつ長大なエロ描写!赤月みゅうとのセカンド単行本。',
'https://ebook-assets.dmm.co.jp/digital/e-book/b104atint00313/b104atint00313pl.jpg',
['赤月みゅうと', 'MUJIN編集部', '少女×少女×少女', 'MUJIN_COMICS', 'ティーアイネット', 'アダルトコミック単行本', '単行本', '美少女', '中出し', '3P・4P', 'ハーレム']
],
'PCゲーム' => [
'dlsoft.html',
'https://dlsoft.dmm.co.jp/detail/views_0630/',
'姫と穢欲のサクリファイス',
'ソリデ国――国家間戦争に勝利し発展した大国は、一人の男によって襲撃される。国王に強い恨みを抱き、復讐のために行動を起こした主人公・カルドは使役している‘‘悪魔’’の力を借りて城を掌握。国政や国民には興味を示さず、国王への復讐として悪魔達の能力を使って王女・フィアナへの調教を開始する。',
'https://pics.dmm.co.jp/digital/pcgame/views_0630/views_0630pl.jpg',
['B-銀河', '遊丸', '瑠奈璃亜', 'はっとりまさき', '蒼瀬', '木下じゃっく', '御導はるか', '薄迷', '犬童飛沫', '星天誠', '紅ぴえろ', 'エスクード', 'お姫様', '辱め', 'デモ・体験版あり', 'ファンタジー']
],
'未対応' => [
'nosupport.html',
'http://www.dmm.co.jp/ppm/video/-/detail/=/cid=h_275tdsu00032/',
'素人のお姉さん!!「チ○ポを洗う」お仕事してみませんか? 2',
'パーツモデルの募集と思い面接に訪れた素人娘達に、初めての『チ●ポ』を洗うお仕事してもらいました!『エッチとかじゃなくて…洗うだけなら…』自らに言い聞かせる様に出演承諾した彼女...',
'http://pics.dmm.co.jp/digital/video/h_275tdsu00032/h_275tdsu00032pl.jpg',
[]
]
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\HentaiFoundryResolver;
use Tests\TestCase;
class HentaiFoundryResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function test()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/HentaiFoundry/illust.html');
$this->createResolver(HentaiFoundryResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.hentai-foundry.com/pictures/user/DevilHS/723498/Witchcraft');
$this->assertSame('Witchcraft', $metadata->title);
$this->assertSame('by DevilHS' . PHP_EOL . 'gift for Liru', $metadata->description);
$this->assertEquals(['witch', 'futa'], $metadata->tags);
$this->assertSame('https://pictures.hentai-foundry.com/d/DevilHS/723498/DevilHS-723498-Witchcraft.png', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.hentai-foundry.com/pictures/user/DevilHS/723498/Witchcraft?enterAgree=1', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\IwaraResolver;
use Tests\TestCase;
class IwaraResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testVideo()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Iwara/video.html');
$this->createResolver(IwaraResolver::class, $responseText);
$url = 'https://ecchi.iwara.tv/videos/wqlwatgmvhqg40kg';
$metadata = $this->resolver->resolve($url);
$this->assertEquals('Cakeface【鈴谷、プリンツ】', $metadata->title);
$this->assertEquals('投稿者: kuro@vov' . PHP_EOL . 'Thank you for watching!いつもありがとうございます' . PHP_EOL . 'こっそり微修正…' . PHP_EOL . 'Model鈴谷&プリンツ つみだんご様 罪袋BCD様' . PHP_EOL . '(いずれも改変)クレジット漏れゴメンナサイ。。。' . PHP_EOL, $metadata->description);
$this->assertEquals(['KanColle', 'kuro@vov'], $metadata->tags);
$this->assertEquals('https://i.iwara.tv/sites/default/files/videos/thumbnails/238591/thumbnail-238591_0004.jpg', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame($url, (string) $this->handler->getLastRequest()->getUri());
}
}
public function testYouTube()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Iwara/youtube.html');
$this->createResolver(IwaraResolver::class, $responseText);
$url = 'https://iwara.tv/videos/z4dn6fag4iko08o0';
$metadata = $this->resolver->resolve($url);
$this->assertEquals('むちむち天龍ちゃんで君色に染まる', $metadata->title);
$this->assertEquals('投稿者: kochira' . PHP_EOL . 'Ray-cast test. Still trying to figure out how Ray-cast works so I\'m sorry if anything looks off.' . PHP_EOL . 'Unauthorized reproduction prohibited (無断転載は禁止です/未經授權禁止複製)' . PHP_EOL, $metadata->description);
$this->assertEquals(['KanColle', 'kochira'], $metadata->tags);
$this->assertEquals('https://img.youtube.com/vi/pvA5Db082yo/maxresdefault.jpg', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame($url, (string) $this->handler->getLastRequest()->getUri());
}
}
public function testImages()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Iwara/images.html');
$this->createResolver(IwaraResolver::class, $responseText);
$url = 'https://iwara.tv/images/%E9%8F%A1%E9%9F%B3%E3%82%8A%E3%82%9318%E6%AD%B3';
$metadata = $this->resolver->resolve($url);
$this->assertEquals('鏡音りん18歳', $metadata->title);
$this->assertEquals('投稿者: Tonjiru Lion' . PHP_EOL . '今回はあんまエロくないです。' . PHP_EOL, $metadata->description);
$this->assertEquals(['Vocaloid', 'Tonjiru Lion'], $metadata->tags);
$this->assertEquals('https://i.iwara.tv/sites/default/files/photos/jing_yin_rin18sui_a.png', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame($url, (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\Kb10uyShortStoryServerResolver;
use Tests\TestCase;
class Kb10uyShortStoryServerResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testNormalPost()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Kb10uyShortStoryServer/tomone.html');
$this->createResolver(Kb10uyShortStoryServerResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ss.kb10uy.org/posts/14');
$this->assertSame('朋音「は、はぁ?おむつ?」', $metadata->title);
$this->assertSame('自炊したおかずってやつです。とりあえずこのSSの中ではkb10uyの彼女は朋音ってことにしといてください。そうじゃないと出す男が決定できないので。', $metadata->description);
$this->assertSame(['妄想', 'kb10uy', '岩永朋音', 'おむつ'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://ss.kb10uy.org/posts/14', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\NicoSeigaResolver;
use Tests\MyAsserts;
use Tests\TestCase;
class NicoSeigaResolverTest extends TestCase
{
use CreateMockedResolver, MyAsserts;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testSeiga()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/NicoSeiga/seiga.html');
$this->createResolver(NicoSeigaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://seiga.nicovideo.jp/seiga/im9623750');
$this->assertSame('シャミ子 / まとけち さんのイラスト', $metadata->title);
$this->assertSame('シャミ子が悪いんだよ・・・', $metadata->description);
$this->assertSame('https://lohas.nicoseiga.jp/thumb/9623750l?', $metadata->image);
$this->assertArrayContains(['アニメ', 'まちカドまぞく', 'シャミ子', 'シャドウミストレス優子', '吉田優子', '危機管理フォーム'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://seiga.nicovideo.jp/seiga/im9623750', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testShunga()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/NicoSeiga/shunga.html');
$this->createResolver(NicoSeigaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://seiga.nicovideo.jp/seiga/im9232798');
$this->assertSame('ベッドのゆかりさん / せゆーら/Se-U-Ra さんのイラスト', $metadata->title);
$this->assertSame('待つ側の方がつよいってスマブラが伝えてきたので', $metadata->description);
$this->assertSame('https://lohas.nicoseiga.jp/thumb/9232798l?', $metadata->image);
$this->assertArrayContains(['結月ゆかり', 'VOICEROID'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://seiga.nicovideo.jp/seiga/im9232798', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -25,10 +25,10 @@ class NijieResolverTest extends TestCase
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://nijie.info/view.php?id=66384');
$this->assertEquals('チンポップくんの日常ep.1「チンポップくんと釣り」 | ニジエ運営', $metadata->title);
$this->assertEquals("メールマガジン漫画のバックナンバー第一話です!\r\n最新話はメールマガジンより配信中です。", $metadata->description);
$this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image);
$this->assertNotRegExp('~/diff/main/~', $metadata->image);
$this->assertSame('チンポップくんの日常ep.1「チンポップくんと釣り」', $metadata->title);
$this->assertSame('投稿者: ニジエ運営' . PHP_EOL . 'メールマガジン漫画のバックナンバー第一話です!' . PHP_EOL . '最新話はメールマガジンより配信中です。', $metadata->description);
$this->assertSame('https://pic.nijie.net/04/nijie_picture/38_20131130155623.png', $metadata->image);
$this->assertSame(['ニジエたん', '釣り', 'チンポップ君の日常', '公式漫画'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=66384', (string) $this->handler->getLastRequest()->getUri());
}
@@ -41,10 +41,10 @@ class NijieResolverTest extends TestCase
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://nijie.info/view.php?id=202707');
$this->assertEquals('ニジエ壁紙 | ニジエ運営', $metadata->title);
$this->assertEquals("ニジエのPCとiphone用(4.7inch推奨)の壁紙です。\r\n保存してご自由にお使いくださいませ。", $metadata->description);
$this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image);
$this->assertNotRegExp('~/diff/main/~', $metadata->image);
$this->assertSame('ニジエ壁紙', $metadata->title);
$this->assertSame('投稿者: ニジエ運営' . PHP_EOL . 'ニジエのPCとiphone用(4.7inch推奨)の壁紙です。' . PHP_EOL . '保存してご自由にお使いくださいませ。', $metadata->description);
$this->assertSame('https://pic.nijie.net/03/nijie_picture/38_20170209185801_0.png', $metadata->image);
$this->assertSame(['ニジエたん', '壁紙'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=202707', (string) $this->handler->getLastRequest()->getUri());
}
@@ -57,9 +57,10 @@ class NijieResolverTest extends TestCase
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://nijie.info/view.php?id=9537');
$this->assertEquals('ニジエがgifに対応したんだってね 奥さん | 黒末アプコ', $metadata->title);
$this->assertEquals('アニメgifとか専門外なのでよくわかりませんでした', $metadata->description);
$this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image);
$this->assertSame('ニジエがgifに対応したんだってね 奥さん', $metadata->title);
$this->assertSame('投稿者: 黒末アプコ' . PHP_EOL . 'アニメgifとか専門外なのでよくわかりませんでした', $metadata->description);
$this->assertStringStartsWith('https://nijie.info/pic/logo/nijie_logo_og.png', $metadata->image);
$this->assertSame(['おっぱい', '陥没乳首', '眼鏡', 'GIFアニメ', 'ぶるんぶるん', 'アニメgif'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=9537', (string) $this->handler->getLastRequest()->getUri());
}
@@ -72,74 +73,79 @@ class NijieResolverTest extends TestCase
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://nijie.info/view.php?id=256283');
$this->assertEquals('てすと | ニジエ運営', $metadata->title);
$this->assertEquals("H264動画てすと あとで消します\r\n\r\n今の所、H264コーデックのみ、出力時に音声なしにしないと投稿できません\r\n動画は勝手にループします", $metadata->description);
$this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image);
$this->assertSame('てすと', $metadata->title);
$this->assertSame('投稿者: ニジエ運営' . PHP_EOL . 'H264動画てすと あとで消します' . PHP_EOL . PHP_EOL . '今の所、H264コーデックのみ、出力時に音声なしにしないと投稿できません' . PHP_EOL . '動画は勝手にループします', $metadata->description);
$this->assertStringStartsWith('https://nijie.info/pic/logo/nijie_logo_og.png', $metadata->image);
$this->assertSame([], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=256283', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testStandardPictureSp()
public function testViewPopup()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testStandardPictureResponse.html');
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://nijie.info/view_popup.php?id=66384');
$this->assertSame('チンポップくんの日常ep.1「チンポップくんと釣り」', $metadata->title);
$this->assertSame('投稿者: ニジエ運営' . PHP_EOL . 'メールマガジン漫画のバックナンバー第一話です!' . PHP_EOL . '最新話はメールマガジンより配信中です。', $metadata->description);
$this->assertSame('https://pic.nijie.net/04/nijie_picture/38_20131130155623.png', $metadata->image);
$this->assertSame(['ニジエたん', '釣り', 'チンポップ君の日常', '公式漫画'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=66384', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testSp()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testStandardPictureResponse.html');
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://sp.nijie.info/view.php?id=66384');
$this->assertEquals('チンポップくんの日常ep.1「チンポップくんと釣り」 | ニジエ運営', $metadata->title);
$this->assertEquals("メールマガジン漫画のバックナンバー第一話です!\r\n最新話はメールマガジンより配信中です。", $metadata->description);
$this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image);
$this->assertNotRegExp('~/diff/main/~', $metadata->image);
$this->assertSame('チンポップくんの日常ep.1「チンポップくんと釣り」', $metadata->title);
$this->assertSame('投稿者: ニジエ運営' . PHP_EOL . 'メールマガジン漫画のバックナンバー第一話です!' . PHP_EOL . '最新話はメールマガジンより配信中です。', $metadata->description);
$this->assertSame('https://pic.nijie.net/04/nijie_picture/38_20131130155623.png', $metadata->image);
$this->assertSame(['ニジエたん', '釣り', 'チンポップ君の日常', '公式漫画'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=66384', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testMultiplePictureSp()
public function testSpViewPopup()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testMultiplePictureResponse.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testStandardPictureResponse.html');
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://sp.nijie.info/view.php?id=202707');
$this->assertEquals('ニジエ壁紙 | ニジエ運営', $metadata->title);
$this->assertEquals("ニジエのPCとiphone用(4.7inch推奨)の壁紙です。\r\n保存してご自由にお使いくださいませ。", $metadata->description);
$this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image);
$this->assertNotRegExp('~/diff/main/~', $metadata->image);
$metadata = $this->resolver->resolve('https://sp.nijie.info/view_popup.php?id=66384');
$this->assertSame('チンポップくんの日常ep.1「チンポップくんと釣り」', $metadata->title);
$this->assertSame('投稿者: ニジエ運営' . PHP_EOL . 'メールマガジン漫画のバックナンバー第一話です!' . PHP_EOL . '最新話はメールマガジンより配信中です。', $metadata->description);
$this->assertSame('https://pic.nijie.net/04/nijie_picture/38_20131130155623.png', $metadata->image);
$this->assertSame(['ニジエたん', '釣り', 'チンポップ君の日常', '公式漫画'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=202707', (string) $this->handler->getLastRequest()->getUri());
$this->assertSame('https://nijie.info/view.php?id=66384', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testAnimationGifSp()
public function testHasHtmlInAuthorProfile()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testAnimationGifResponse.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testHasHtmlInAuthorProfileResponse.html');
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://nijie.info/view.php?id=9537');
$this->assertEquals('ニジエがgifに対応したんだってね 奥さん | 黒末アプコ', $metadata->title);
$this->assertEquals('アニメgifとか専門外なのでよくわかりませんでした', $metadata->description);
$this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image);
$metadata = $this->resolver->resolve('https://nijie.info/view.php?id=285698');
$this->assertSame('JK文化祭コスプレ喫茶', $metadata->title);
$this->assertSame('投稿者: ままままま' . PHP_EOL .
'https://www.pixiv.net/fanbox/creator/32045169' . PHP_EOL .
'ピクシブのファンボックスでこっちに上げてた一次創作のノリでえっちなやつ描いてます' . PHP_EOL .
'二次創作のえっちなやつは相変わらずこっち' . PHP_EOL . '健全目なのはついったー', $metadata->description);
$this->assertSame('https://pic.nijie.net/02/nijie_picture/540086_20181028112046_0.png', $metadata->image);
$this->assertSame(['バニーガール'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=9537', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testMp4MovieSp()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testMp4MovieResponse.html');
$this->createResolver(NijieResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://sp.nijie.info/view.php?id=256283');
$this->assertEquals('てすと | ニジエ運営', $metadata->title);
$this->assertEquals("H264動画てすと あとで消します\r\n\r\n今の所、H264コーデックのみ、出力時に音声なしにしないと投稿できません\r\n動画は勝手にループします", $metadata->description);
$this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://nijie.info/view.php?id=256283', (string) $this->handler->getLastRequest()->getUri());
$this->assertSame('https://nijie.info/view.php?id=285698', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -3,7 +3,7 @@
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\OGPResolver;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\BadResponseException;
use Tests\TestCase;
class OGPResolverTest extends TestCase
@@ -14,7 +14,7 @@ class OGPResolverTest extends TestCase
{
$this->createResolver(OGPResolver::class, '', [], 404);
$this->expectException(\RuntimeException::class);
$this->expectException(BadResponseException::class);
$this->resolver->resolve('http://example.com/404');
}

View File

@@ -40,13 +40,13 @@ class PixivResolverTest extends TestCase
$this->createResolver(PixivResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.pixiv.net/member_illust.php?mode=medium&illust_id=74939802');
$this->assertEquals('T-20S', $metadata->title);
$this->assertEquals('投稿者: amssc' . PHP_EOL . 'JUST FOR FUN' . PHP_EOL . '现在可以做到游戏内立绘修改拉!立绘动态皮肤都可以支持,想要资助获得新技术请站内信联系我。', $metadata->description);
$this->assertEquals('https://i.pixiv.cat/img-master/img/2019/05/28/01/16/24/74939802_p0_master1200.jpg', $metadata->image);
$this->assertEquals(['巨乳', '乳', 'lastorigin', 'Last_Origin', 'T-20S', 'おっぱい', '라스트오리진', '노움'], $metadata->tags);
$metadata = $this->resolver->resolve('https://www.pixiv.net/member_illust.php?mode=medium&illust_id=75899985');
$this->assertEquals('コミッション絵33', $metadata->title);
$this->assertEquals('投稿者: ナゼ(NAZE)' . PHP_EOL . 'Leak' . PHP_EOL . PHP_EOL . 'Character:アリッサさん(依頼主のオリキャラ)', $metadata->description);
$this->assertEquals('https://i.pixiv.cat/img-master/img/2019/07/25/13/02/59/75899985_p0_master1200.jpg', $metadata->image);
$this->assertEquals(['巨乳', '乳', '巨乳首', '母乳'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.pixiv.net/ajax/illust/74939802', (string) $this->handler->getLastRequest()->getUri());
$this->assertSame('https://www.pixiv.net/ajax/illust/75899985', (string) $this->handler->getLastRequest()->getUri());
}
}
@@ -65,4 +65,36 @@ class PixivResolverTest extends TestCase
$this->assertSame('https://www.pixiv.net/ajax/illust/46713544', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testArtworkUrl()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Pixiv/illust.json');
$this->createResolver(PixivResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.pixiv.net/artworks/68188073');
$this->assertEquals('coffee break', $metadata->title);
$this->assertEquals('投稿者: 裕' . PHP_EOL, $metadata->description);
$this->assertEquals('https://i.pixiv.cat/img-master/img/2018/04/12/00/01/28/68188073_p0_master1200.jpg', $metadata->image);
$this->assertEquals(['オリジナル', 'カフェ', '眼鏡', 'イヤホン', 'ぱっつん', '艶ぼくろ', '眼鏡っ娘', 'オリジナル5000users入り'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.pixiv.net/ajax/illust/68188073', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testArtworkUrlEn()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Pixiv/illust.json');
$this->createResolver(PixivResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.pixiv.net/en/artworks/68188073');
$this->assertEquals('coffee break', $metadata->title);
$this->assertEquals('投稿者: 裕' . PHP_EOL, $metadata->description);
$this->assertEquals('https://i.pixiv.cat/img-master/img/2018/04/12/00/01/28/68188073_p0_master1200.jpg', $metadata->image);
$this->assertEquals(['オリジナル', 'カフェ', '眼鏡', 'イヤホン', 'ぱっつん', '艶ぼくろ', '眼鏡っ娘', 'オリジナル5000users入り'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.pixiv.net/ajax/illust/68188073', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\ToranoanaResolver;
use Tests\TestCase;
class ToranoanaResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testTora()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testTora.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.shop/tora/ec/item/040030720152');
$this->assertEquals('新・古明地喫茶~そしてまた扉は開く~', $metadata->title);
$this->assertEquals('サークル【ツキギのとこ】(槻木こうすけ)発行の「新・古明地喫茶~そしてまた扉は開く~」を買うなら、とらのあな全年齢向け通販!', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.shop/tora/ec/item/040030720152', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testToraR()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testToraR.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.jp/tora_r/ec/item/040030720174');
$this->assertEquals('お姉ちゃんが妹のぱんつでひとりえっちしてました。', $metadata->title);
$this->assertEquals('サークル【没後】RYO発行の「お姉ちゃんが妹のぱんつでひとりえっちしてました。」を買うなら、とらのあな成年向け通販', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.jp/tora_r/ec/item/040030720174', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testToraD()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testToraD.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.shop/tora_d/digi/item/042000013358');
$this->assertEquals('虎の穴ラボの薄い本。vol 1.5', $metadata->title);
$this->assertEquals('サークル【虎の穴ラボ】虎の穴ラボエンジニアチーム発行の「虎の穴ラボの薄い本。vol 1.5」を買うなら、とらのあな全年齢向け電子書籍通販!', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.shop/tora_d/digi/item/042000013358', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testToraRD()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testToraRD.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.jp/tora_rd/digi/item/042000013181');
$this->assertEquals('放課後のお花摘み', $metadata->title);
$this->assertEquals('サークル【給食泥棒】(村雲)発行の「放課後のお花摘み」を買うなら、とらのあな成年向け電子書籍通販!', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.jp/tora_rd/digi/item/042000013181', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testJoshi()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testJoshi.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.shop/joshi/ec/item/040030702729');
$this->assertEquals('円卓のクソ漫画', $metadata->title);
$this->assertEquals('サークル【地獄のすなぎもカーニバル】(槌田)発行の「円卓のクソ漫画」を買うなら、とらのあな女子部全年齢向け通販!', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.shop/joshi/ec/item/040030702729', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testJoshiR()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testJoshiR.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.jp/joshi_r/ec/item/040030730126');
$this->assertEquals('リバースナイトリバース', $metadata->title);
$this->assertEquals('サークル【雨傘サイクル】(チャリリズム)発行の「リバースナイトリバース」を買うなら、とらのあな女子部成年向け通販!', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.jp/joshi_r/ec/item/040030730126', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testJoshiD()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testJoshiD.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.shop/joshi_d/digi/item/042000012192');
$this->assertEquals('超幸運ガール審神者GOLDEN', $metadata->title);
$this->assertEquals('サークル【Day Of The Dead】ほんちゅ発行の「超幸運ガール審神者GOLDEN」を買うなら、とらのあな女子部全年齢向け電子書籍通販', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.shop/joshi_d/digi/item/042000012192', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testJoshiRD()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Toranoana/testJoshiRD.html');
$this->createResolver(ToranoanaResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://ec.toranoana.jp/joshi_rd/digi/item/042000013472');
$this->assertEquals('UBWの裏側で非公式に遠坂凛をナデナデする本', $metadata->title);
$this->assertEquals('サークル【阿仁谷組】阿仁谷ユイジ発行の「UBWの裏側で非公式に遠坂凛をナデナデする本」を買うなら、とらのあな女子部成年向け電子書籍通販', $metadata->description);
$this->assertRegExp('~ecdnimg\.toranoana\.jp/ec/img/.*\.jpg~', $metadata->image);
if ($this->shouldUseMock()) {
$this->assertSame('https://ec.toranoana.jp/joshi_rd/digi/item/042000013472', (string) $this->handler->getLastRequest()->getUri());
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\XtubeResolver;
use Tests\TestCase;
class XtubeResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function test()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Xtube/video.html');
$this->createResolver(XtubeResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.xtube.com/video-watch/homegrown-big-tits-18634762');
$this->assertEquals('Homegrown Big Tits', $metadata->title);
$this->assertEquals('Dedicated to the fans of the beautiful amateur women with big natural tits. All user submitted - you can see big boob amateur hotties fucking and sucking as their tits bounce and sway.', $metadata->description);
$this->assertRegExp('~https://cdn\d+-s-hw-e5\.xtube\.com/m=eaAaaEFb/videos/201302/07/RF4Nk-S774-/original/1\.jpg~', $metadata->image);
$this->assertEquals(['Amateur', 'Blowjob', 'Big Boobs', 'bigtits', 'homeg'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.xtube.com/video-watch/homegrown-big-tits-18634762', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testNotMatch()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Unmatched URL Pattern: https://www.xtube.com/gallery/black-celebs-free-7686657');
$this->createResolver(XtubeResolver::class, '');
$this->resolver->resolve('https://www.xtube.com/gallery/black-celebs-free-7686657');
}
}

678
tests/fixture/Cien/test.html vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,602 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta name="google-site-verification" content="4UtUmaro4aJIR94PZdv-GoliXlDvtUVFL03-9CTh68s" />
<meta charset="UTF-8">
<meta name="viewport" id="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
<meta name="csrf-token" content="1UAeVYZqG3XqR5XwRi0MXYJn3zIf51glrKZKY2gp">
<meta name="app-auth-check" content="0">
<meta property="og:title" content="Ci-en">
<meta property="og:type" content="website">
<meta property="og:url" content="http://ci-en.dlsite.com">
<meta property="og:image" content="https://ci-en.dlsite.com/assets/img/common/logo_Ci-en_R18.svg">
<meta property="og:site_name" content="Ci-en">
<meta property="og:description" content="好きの気持ちは、カタチで伝えよう。">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Ci-en">
<meta name="twitter:description" content="好きの気持ちは、カタチで伝えよう。">
<meta name="twitter:image:src" content="https://ci-en.dlsite.com/assets/img/common/logo_Ci-en_R18.svg">
<meta name="description" content="好きの気持ちは、カタチで伝えよう。">
<meta name="keyword" content="Ci-en">
<meta name="sentry-public-dsn" content="7319f62f11fe408b932254c5fe87eb64@sentry.io/301968">
<meta name="sentry-release" content="fd2635a6350eda85e4dbec5559f0172e7f8086df">
<meta name="app-locale" content="ja">
<title>好きの気持ちは、カタチで伝えよう。 - Ci-en</title>
<link media="all" type="text/css" rel="stylesheet" href="https://ci-en.dlsite.com/assets/css/app.css?1567667013">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="https://www.dlsite.com/assets/share/css/universal/universal.css">
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-NNPHW5Z');</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-109913020-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-109913020-1', {
'send_page_view': false,
'custom_map': {
'dimension1':'logined',
},
});
gtag('set', 'linker', {
'accept_incoming': true,
'domains': ['ci-en.net','ci-en.dlsite.com']
});
gtag('event', 'page_view', {
'logined': '',
'has_creator': '0',
});
</script>
</head>
<body class="global-layout p-topPage ">
<!-- グローバルヘッダー -->
<div class="l-eisysGroupHeader type-cien">
<vue-global-header
account-settings-url="https://login.dlsite.com/user/self?redirect_uri=https%3A%2F%2Fci-en.dlsite.com%2Flogout&amp;lang=ja"
is-adult="1"
user-id=""
creator-id=""
></vue-global-header>
</div>
<header class="global-layout-item type-header">
<div class="header-inner">
<div class="cien-logo type-r18">
<a href="/">Ci-en</a>
</div>
<form method="GET" action="https://ci-en.dlsite.com/search" accept-charset="UTF-8" class="hd-searchBox">
<input type="text" class="hd-searchInput" name="keyword" placeholder="クリエイターを検索">
<input type="submit" class="hd-searchButton" value="&#xe90f;">
</form>
<div class="nav-drawer type-menu">
<input id="nav-inputMenu" type="checkbox" class="nav-unshown">
<label id="nav-open" for="nav-inputMenu">
<span></span>
</label>
<label class="nav-unshown icon-navClose" id="nav-close" for="nav-inputMenu"><span></span></label>
<div class="nav-content type-left">
<div class="navEntry">
<p class="text">DLsiteアカウントをお持ちの方はログインできます。</p>
<div class="btnBox">
<a href="https://ci-en.dlsite.com/login" class="btn type-basic">ログイン</a>
<a href="https://ci-en.dlsite.com/login" class="btn type-important">新規登録</a>
</div>
<p class="notice">株式会社エイシスが運営しているサービスをDLsiteアカウント一つでご利用いただけます。</p>
</div>
<ul class="nav-submenuList">
<li class="nav-submenuList-item"><a href="https://ci-en.dlsite.com/about/supporter">Ci-enとは</a></li>
<li class="nav-submenuList-item"><a href="https://ci-en.dlsite.com/about/creator">クリエイター登録</a></li>
<li class="nav-submenuList-item"><a href="https://ci-en.dlsite.com/about/faq">よくある質問</a></li>
</ul>
</div>
</div>
<div class="globalNav-wrap">
<ul class="globalNav is-guest">
<li class="globalNav-item type-bell">
<a href="https://ci-en.dlsite.com/mypage/activity">
<span class="globalNav-icon">通知</span>
</a>
</li>
<li class="globalNav-item type-searchBox">
<form method="GET" action="https://ci-en.dlsite.com/search" accept-charset="UTF-8" class="hd-search">
<input type="submit" class="hd-searchButton" value="&#xe90f;">
<input type="text" class="hd-searchInput" name="keyword" placeholder="クリエイターを検索">
</form>
</li>
<li class="globalNav-item type-search ">
<a href="https://ci-en.dlsite.com/search/top">
<span class="globalNav-icon"></span>
</a>
</li>
<li class="globalNav-item type-signup">
<a href="https://ci-en.dlsite.com/login">Ci-enをはじめる</a>
</li>
<li class="globalNav-item type-mypage">
<a href="https://ci-en.dlsite.com/mypage">
<span class="globalNav-icon">マイページ</span>
</a>
</li>
<li class="globalNav-item type-help">
<a href="https://ci-en.dlsite.com/about/faq">
<span class="globalNav-icon">ヘルプ</span>
</a>
</li>
</ul>
</div>
</div>
</header>
<section class="global-layout-item type-contentsNav">
</section>
<section id="detail" class="global-layout-item type-contents">
<section class="grid-container inner-layout">
<div class="topHeroArea" onload="console.log('loaded');">
<h1 class="topCatchcopy">
<div class="catchcopy-item type-first"></div>
<div class="catchcopy-item type-last"></div>
</h1>
<div id="top-heroarea-mainimg" class="topHeroArea-mainImg">
<div class="mainImg-item item-twinkleStar"></div>
<div class="mainImg-item item-twinkleStar1"></div>
<div class="mainImg-item item-twinkleStar2"></div>
<div class="mainImg-item item-star"></div>
<div class="topHeroArea-gradeFilter"></div>
<div class="mainImg-item item-wood"></div>
<div class="mainImg-item item-donguri"></div>
<div class="mainImg-item item-present"></div>
<div class="mainImg-item item-letter"></div>
</div>
</div>
<div class="grid-item grid-main">
<div class="topIntroArea">
<h1 class="topIntroArea-heading"></h1>
<div class="topIntroArea-textGroup">
<p class="text">新しいものを作るのは、簡単なことではありません。<span>思いを形にするには時間と手間、そして資金が必要です。</span></p>
<p class="text">Ci-enで好きなクリエイターを支援すれば、<span>その収益を創作活動に活かすことができるようになります。</span></p>
<p class="text">クリエイターも支援者も、誰もが創作を楽しめる世界に参加してみませんか?</p>
</div>
<div class="topIntroArea-btn">
<a href="https://login.dlsite.com/register?redirect_uri=https%3A%2F%2Fci-en.dlsite.com&amp;lang=ja" class="btn type-important-confirm">Ci-enをはじめる</a>
</div>
</div>
<div class="topAboutCienArea">
<div class="topAboutCienArea-main"></div>
<div class="topAboutCienArea-btn">
<a href="https://ci-en.dlsite.com/about/supporter" class="btn type-confirm">もっと知りたい方はこちら</a>
</div>
</div>
<div id="follow" class="topLetsFollowArea">
<div class="topLetsFollowArea-heading"></div>
<div class="topLetsFollowArea-body">
<div class="topShowcase type-popularCreator">
<div class="topShowcase-heading">
<a href="//ci-en.net#follow" class="btn type-topRatingChange">全年齢に切替</a>
</div>
<div class="topShowcase-body">
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00000208/6742fc1b379a180e4485cdeff9a086d535725683ca882155b400bc65ab13ed3e/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00000208/6f0dac0278bbb547e97b0deddd2aad22043d6b9ce8cde868b99e543d7dc1ec9f/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">ONEONE1</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/208" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00001145/4f39516e4f22c76b45443b5567789419d8d0ea985958cd26adea88cf79c95fd3/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00001145/5c18c657f97e23ea73a700784f55c2e34b1871e532b45e32118ee57a6c2cb677/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">同人サークルGyu!</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/1145" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00000057/a8c0cf4f84fc374e9ba5891ee2a158a69067eee0d3601a66e1e00489df4df25d/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00000057/72fbd8b3e2124f88de11866d21a23c6e4ff375e62dba50c517f537655ff2e981/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">クリメニア</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/57" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00001321/5e327eb84b4ce6a36be637729b000ba74a9b27f35273f08a30b86e464ee1e25e/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00001321/60557adcbb149f7494c6aed36f3d44373896a971b997af3e4e02a650f70f5cbe/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">Hypnotic Yanh</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=8">音声作品</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/1321" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00000391/f0134aaa1e2174efabc30e024c973024f34064e0f6ab6738477564c34170ae3b/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00000391/c98d97cf4d4af1c6aad452150693a06a63e6f4323e21ad0848e83f910a949b80/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">シロクマの嫁(伊ヶ崎綾香)</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=8">音声作品</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/391" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00001058/14ccafc478078692f53a62c0e2ea722d55dd018945d44c31e55bdcc237ee9944/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00001058/3ac1827236a6fce3d5d7d9142dd4e77e3b732b63db18af81e5c74818572d7b10/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">鉱油/73号坑道</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/1058" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00000190/0ebc04b8de8d6e42f6c5bf020936bff79bdfa29ab71f1ea2eff547f42fd9caa7/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00000190/e5e01272dac25575a00b651adf8d04524d91a87ff534bc4391dbabea404e6a49/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">ぽいずん</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/190" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00002004/b568c5bcc1108db1276c32b550140f5d539a92c589f3bd8fc16163fba56eb50a/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00002004/aab5ff3de14c0361715b4a16cc3cc6961cac72b84762d514e6fc38c40dda81e6/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">あいすシチュー</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/2004" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00001191/b0ca242c5095531d78a95f3bc4e15a5b642b33c808f91af1cf95723dee7a4543/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00001191/950a61d5e3648e8c01c31a9a3ee127c8cb8e5e015379feb45d409f61acf9cf5a/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">みこにそみ</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/1191" class="creatorCard-link"></a>
</div>
<div class="mod-creatorCard at-topPage">
<div class="creatorCard-header">
<img src="https://media.ci-en.jp/public/cover/creator/00000944/8b3b9c5cc1024bf0b532b9a0db168db7600a77ed6fbeaf15eb4c56656659666d/image-990-c.jpg" alt="">
</div>
<div class="creatorCard-body">
<dt class="creatorCard-thumb">
<div class="accountIcon type-cerator size-m">
<img src="https://media.ci-en.jp/public/icon/creator/00000944/9dc7439b9801ac6f0e66e92f8980fb2771cb8ab2f8a05e230c753d0f953ce4e0/image-200-c.jpg" alt="">
</div>
</dt>
<dd class="creatorCard-name">D-LIS-ディーリス</dd>
<dd class="creatorCard-tag">
<ul class="tagList type-creator">
<li class="tagList-item type-creator">
<a class="item-tag type-activityGenre" href="/search?categoryId=9">ゲーム</a>
</li>
</ul>
</dd>
</div>
<a href="https://ci-en.dlsite.com/creator/944" class="creatorCard-link"></a>
</div>
</div>
</div>
<div class="topShowcase type-searchByGenre">
<div class="topShowcase-heading"></div>
<div class="topShowcase-body">
<div class="topGenre-item">
<a href="/search?categoryId=9" class="topGenre-link">
ゲーム
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=1" class="topGenre-link">
イラスト
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=2" class="topGenre-link">
漫画
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=8" class="topGenre-link">
音声作品
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=3" class="topGenre-link">
小説
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=7" class="topGenre-link">
声優・歌い手
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=12" class="topGenre-link">
映像・アニメ
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=17" class="topGenre-link">
その他
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=10" class="topGenre-link">
YouTuber・実況
</a>
</div>
<div class="topGenre-item">
<a href="/search?categoryId=14" class="topGenre-link">
VR
</a>
</div>
</div>
</div>
</div>
</div>
<div class="topRegisterCreatorArea">
<div class="topRegisterCreatorArea-body">
<div class="topRegisterCreatorArea-main">
<div class="topRegisterCreatorArea-mainImg"></div>
<div class="topRegisterCreatorArea-btn">
<a href="https://ci-en.dlsite.com/about/creator" class="btn type-important-confirm">クリエイター登録について</a>
</div>
</div>
</div>
</div>
<div class="topRegisterArea">
<div class="topRegisterArea-body">
<div class="topRegisterArea-main">
<p class="topRegisterArea-text">DLsiteアカウントをお持ちの方はログインできます。</p>
<div class="btnBox">
<a href="https://ci-en.dlsite.com/login" class="btn type-confirm">ログイン</a>
<a href="https://login.dlsite.com/register?redirect_uri=https%3A%2F%2Fci-en.dlsite.com&amp;lang=ja" class="btn type-important-confirm" target="_blank">新規登録</a>
</div>
<p class="topRegisterArea-annotation">株式会社エイシスが運営しているサービスをDLsiteアカウント一つでご利用いただけます。</p>
</div>
</div>
</div>
</div>
</section>
</section>
<footer class="global-layout-item type-footer">
<div class="gotoTOPContainer">
<a href="#" class="ankerlink">ページトップ</a>
</div>
<div class="globalFooter">
<div class="footerContainer innerSpaceFooter">
<dl class="footerNav itemNum1">
<dt class="footerNav-title">Ci-enについて</dt>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/about/supporter" class="footerLink">Ci-enとは</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/about/creator" class="footerLink">クリエイター登録</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/about/faq" class="footerLink">よくある質問(支援者)</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/about/creator-faq" class="footerLink">よくある質問(クリエイター)</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/inquiry" class="footerLink">お問い合わせ</a></dd>
<dd class="footerNav-item"><a href="http://info.ci-en.net" target="_blank" class="footerLink">お知らせブログ</a></dd>
</dl>
<dl class="footerNav itemNum2">
<dt class="footerNav-title">運営情報</dt>
<dd class="footerNav-item"><a href="http://www.eisys.co.jp/company/company-info.html" target="_blank" class="footerLink">会社概要</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/legal/regulation" class="footerLink">利用規約</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/legal/law" class="footerLink">特定商取引法に基づく表示</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/legal/censorship" class="footerLink">コンプライアンスポリシー</a></dd>
<dd class="footerNav-item"><a href="https://ci-en.dlsite.com/legal/privacy" class="footerLink">個人情報の取り扱いについて</a></dd>
</dl>
<div class="l-eisysGroupFooter type-cien is-sponly">
<div class="eisysGroupFooterInner">
<p class="eisysGroupFooterHeading">関連サービス</p>
<ul class="eisysGroupFooterService">
<li class="eisysGroupFooterService-link type-dlsite">
<a href="https://www.dlsite.com/maniax-touch/?utm_campaign=cien&amp;utm_medium=text&amp;utm_content=sp_globalfooter"><span>ダウンロードショップ</span>DLsite</a>
</li>
<li class="eisysGroupFooterService-link type-nijiyome">
<a href="https://www.nijiyome.jp/?en=cien&amp;em=text&amp;et=sp_globalfooter"><span>オンラインゲームサイト</span>にじよめ</a>
</li>
<li class="eisysGroupFooterService-link type-channel">
<a href="https://ch.dlsite.com/?from=sp_globalfooter_cien"><span>二次元コミュニティサイト</span>DLチャンネル</a>
</li>
<li class="eisysGroupFooterService-link type-chobit">
<a href="https://chobit.cc/?from=sp_globalfooter_cien"><span>無料体験版サイト</span>chobit</a>
</li>
<li class="eisysGroupFooterService-link type-triokini">
<a href="https://triokini.com/how_to_use?from=sp_globalfooter_cien"><span>即売会取り置きサイト</span>トリオキニ</a>
</li>
<li class="eisysGroupFooterService-link type-studio">
<a href="https://dlsitestudio.com/?from=sp_globalfooter_cien"><span>音声収録スタジオ</span>DLsiteスタジオ</a>
</li>
</ul>
</div>
</div>
</div>
<div class="snsArea">
<p class="heading">SNS公式アカウント</p>
<a href="https://twitter.com/cien_info?lang=ja" class="twitter_link" target="_blink" rel="nofollow noopener"></a>
</div>
<p class="copyright">&copy; 2018 Ci-en</p>
</div>
</footer>
<script src="https://ci-en.dlsite.com/assets/js/vendor.bundle.js?1568167511"></script>
<script src="https://ci-en.dlsite.com/assets/js/app.bundle.js?1568167511"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:og="http://ogp.me/ns#" xmlns:mixi="http://mixi-platform.com/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=991">
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta name="google-site-verification" content="S2Jzwn_Dm4hGoyTfPnxEUSKnbHSuT73N6SZbTanWbEM" />
<meta charset="utf-8">
<meta name="viewport" content="width=1024">
<meta http-equiv="Content-Style-Type" content="text/css">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta name="google-site-verification" content="S2Jzwn_Dm4hGoyTfPnxEUSKnbHSuT73N6SZbTanWbEM">
<link rel="apple-touch-icon" href="/images/web/common/apple_touch_icon_eng_57x57.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/web/common/apple_touch_icon_eng_72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/web/common/apple_touch_icon_eng_76x76.png">
@@ -13,12 +13,12 @@
<link rel="apple-touch-icon" sizes="120x120" href="/images/web/common/apple_touch_icon_eng_120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/web/common/apple_touch_icon_eng_144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/web/common/apple_touch_icon_eng_152x152.png">
<meta name="msapplication-config" content="/browserconfig_eng.xml" />
<meta name="msapplication-config" content="/browserconfig_eng.xml">
<link rel="shortcut icon" href="/images/web/common/favicon.ico">
<link rel="canonical" href="https://www.dlsite.com/eng/work/=/product_id/RE228866.html" />
<link rel="canonical" href="https://www.dlsite.com/eng/work/=/product_id/RE228866.html">
<link rel="alternate" media="only screen and (max-width: 640px)" href="https://www.dlsite.com/eng-touch/work/=/product_id/RE228866.html" class="alternate_smartphone" />
<link rel="alternate" hreflang="en" href="https://www.dlsite.com/eng/work/=/product_id/RE228866.html" />
<link rel="alternate" hreflang="ja" href="https://www.dlsite.com/home/work/=/product_id/RJ228866.html" /> <meta name="description" content="You go with a girl of your first love and enjoy going to haunted places and her massage, ear cleaning, sleep sharing etc. (CV: Yui Asami)DLsite Doujin is a download shop for doujinshi &amp; indie games. With a huge selection of products, we&#039;re sure you&#039;ll find whatever tickles your fancy. DLsite is one of the greatest indie contents download shops in Japan.">
@@ -27,37 +27,44 @@
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@DLsite">
<meta name="twitter:image:src" content="https://img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_main.jpg" />
<meta name="twitter:image:src" content="https://img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_main.jpg">
<meta property="og:title" content="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing] [Triangle!] | DLsite" />
<meta property="og:type" content="website" />
<meta property="og:description" content="You go with a girl of your first love and enjoy going to haunted places and her massage, ear cleaning, sleep sharing etc. (CV: Yui Asami)DLsite Doujin is a download shop for doujinshi &amp; indie games. With a huge selection of products, we&#039;re sure you&#039;ll find whatever tickles your fancy. DLsite is one of the greatest indie contents download shops in Japan." />
<meta property="og:url" content="https://www.dlsite.com/eng/work/=/product_id/RE228866.html" />
<meta property="og:image" content="https://img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_sam.jpg" />
<meta property="og:site_name" content="DLsite" />
<meta property="fb:app_id" content="226115600829997" />
<meta property="mixi:device-smartphone" content="https://www.dlsite.com/eng-touch/work/=/product_id/RE228866.html" />
<meta property="og:title" content="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing] [Triangle!] | DLsite">
<meta property="og:type" content="website">
<meta property="og:description" content="You go with a girl of your first love and enjoy going to haunted places and her massage, ear cleaning, sleep sharing etc. (CV: Yui Asami)DLsite Doujin is a download shop for doujinshi &amp; indie games. With a huge selection of products, we&#039;re sure you&#039;ll find whatever tickles your fancy. DLsite is one of the greatest indie contents download shops in Japan.">
<meta property="og:url" content="https://www.dlsite.com/eng/work/=/product_id/RE228866.html">
<meta property="og:image" content="https://img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_sam.jpg">
<meta property="og:site_name" content="DLsite">
<meta property="fb:app_id" content="226115600829997">
<meta property="mixi:device-smartphone" content="https://www.dlsite.com/eng-touch/work/=/product_id/RE228866.html">
<script>/dlsite_dozen=/.test(document.cookie) || (document.cookie = 'dlsite_dozen=' + Math.floor(Math.random() * 12) + '; path=/; max-age=63072000')</script>
<script>
if ( ! /dlsite_dozen=/.test(document.cookie)) {
document.cookie = 'dlsite_dozen=' + Math.floor(Math.random() * 12) + '; domain=.dlsite.com; path=/; max-age=63072000'
}
if ( ! /uniqid=/.test(document.cookie)) {
document.cookie = 'uniqid=' + Math.random().toString(36).slice(-10) + '; domain=.dlsite.com; path=/; max-age=63072000'
}
</script>
<link rel="stylesheet" href="/css/reset.css?1459221919" type="text/css" id="reset" />
<link rel="stylesheet" href="/css/default_eng.css?1559631936" type="text/css" id="default" />
<link rel="stylesheet" href="/css/reset.css?1570688439" type="text/css" id="reset" />
<link rel="stylesheet" href="/css/default_eng.css?1575516922" type="text/css" id="default" />
<link rel="stylesheet" href="/css/layout_2col_work_eng.css?1552988739" type="text/css" id="layout_2col_work" />
<link rel="stylesheet" href="/css/common_eng.css?1559631936" type="text/css" id="common" />
<link rel="stylesheet" href="/css/switch_eng.css?1551748184" type="text/css" id="switch" />
<link rel="stylesheet" href="/css/suggest.css?1559631936" type="text/css" id="suggest" />
<link rel="stylesheet" href="/css/header_campaign_banner.css?1559723711" type="text/css" id="header_campaign_banner" />
<link rel="stylesheet" href="/css/common_eng.css?1578290029" type="text/css" id="common" />
<link rel="stylesheet" href="/css/switch_eng.css?1568103394" type="text/css" id="switch" />
<link rel="stylesheet" href="/css/suggest.css?1565749496" type="text/css" id="suggest" />
<link rel="stylesheet" href="/css/header_campaign_banner.css?1575516922" type="text/css" id="header_campaign_banner" />
<link rel="stylesheet" href="/assets/share/css/universal/universal.css?" type="text/css" id="universal" />
<link rel="stylesheet" href="/css/work_template_eng.css?1559631936" type="text/css" id="work_template" />
<link rel="stylesheet" href="/css/work_template_eng.css?1575597029" type="text/css" id="work_template" />
<link rel="stylesheet" href="/css/work_slider.css?1559631936" type="text/css" id="work_slider" />
<script type="text/javascript" src="/js/libs/libraries-pack.js?1502345903"></script>
<script type="text/javascript" src="/js/dlsite_util.js?1557910552"></script>
<script type="text/javascript" src="/js/dlsite_util.js?1561689497"></script>
<script type="text/javascript" src="/js/slide_menu.js?1511946236"></script>
<script type="text/javascript" src="/js/dlsite_suggest.js?1550024738"></script>
<script type="text/javascript" src="/js/dlsite_suggest.js?1565749496"></script>
<script type="text/javascript" src="/js/dlsite_trigger.js?1544681008"></script>
<script type="text/javascript" src="/js/jquery.slideproduct.js?1524728551"></script>
<script type="text/javascript" src="/js/jquery.slideproduct.js?1574232820"></script>
<script type="text/javascript" src="/js/dlsite_img_filter.js?1544681008"></script>
<script type="text/javascript" src="/js/dlsite/work/work_logger.js?1538649197"></script>
<script type="text/javascript" src="/js/dlsite_thumb_img_popup.js?1487739431"></script>
@@ -68,16 +75,10 @@
<!-- End Google Tag Manager -->
<!--[if IE 6]>
<script type="text/javascript" src="/js/DD_belatedPNG.js"></script>
<script>
DD_belatedPNG.fix('a,img,div,li,td,h1,h2,h3,h4,h5,table,p');
</script>
<![endif]-->
<!--[if lte IE 6]>
<script type="text/javascript" src="minmax.js"></script>
<![endif]-->
</head>
<body class="style_eng">
@@ -90,7 +91,7 @@ $.extend({
if ($.useAdultcheck) return;
$.useAdultcheck = true;
var s = document.createElement('script');
s.src = '/js/adultcheck.js?1553837147';
s.src = '/js/adultcheck.js?1566881566';
s.defer = true;
document.querySelector('head').appendChild(s);
}
@@ -106,6 +107,44 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-NTSRG2&gtm_auth=Z_ubU9a2b3yGEFSh9Hdtkg&gtm_preview=env-12&gtm_cookies_win=x" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<!--data-vue-component="header-banner"-->
<div data-vue-component="header-banner" data-vue-async="true" data-section_name="campaign_header_banner">
<div v-if="loading" class="hd_cp_banner type_1bn">
<ul class="cp_bn_list">
<li class="cp_bn_item type_15">
<a href="https://www.dlsite.com/eng/campaign/sale201912">
<div class="cp_bn_inner">
<div class="cp_bn_reminder">
<div class="cp_bn_reminder_content"><i class="cp_bn_reminder_period type_date">~ FEB 3,</i><i class="cp_bn_reminder_period type_time">14:00</i></div>
</div>
<div class="cp_bn"><img src="/modpub/images/banner/floor_header/bn_doujin_sale_max90per_eng.png"></div>
<div class="cp_bn_work blank"></div>
</div>
</a>
</li>
</ul>
</div>
<div v-else-if="is_show_frame" :class="style" class="hd_cp_banner" v-cloak>
<ul class="cp_bn_list">
<li v-for="campaign in campaigns" :class="campaign.style" class="cp_bn_item">
<a :href="campaign.url">
<div class="cp_bn_inner">
<div class="cp_bn_reminder">
<div class="cp_bn_reminder_content" v-html="campaign.reminder.content"></div>
</div>
<div class="cp_bn"><img :src="campaign.img"></div>
<div class="cp_bn_work" :class="{blank: campaign.outline == null}" v-html="campaign.outline"></div>
</div>
</a>
</li>
</ul>
</div>
</div>
<div class="l-eisysGroupHeader type-dlsite" data-section_name="global_header">
<div class="eisysGroupHeaderInner">
<ul class="eisysGroupHeaderService">
@@ -130,7 +169,7 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
</div>
<ul class="eisysGroupHeaderLinkNav">
<li>
<a href="https://login.dlsite.com/user/self?lang=en&redirect_uri=https://www.dlsite.com/eng/" target="_blank">Account Management</a>
<a rel="noopener" href="https://login.dlsite.com/user/self?lang=en&redirect_uri=https://www.dlsite.com/eng/" target="_blank">Account Management</a>
</li>
<li>
<a class="logout" href="https://ssl.dlsite.com/eng/logout">Log out</a>
@@ -183,12 +222,12 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
<!-- 検索 -->
<div class="globalSearch">
<form class="globalSearch-form" action="https://www.dlsite.com/eng/fs" method="post">
<input name="_qf__fulltext_search" type="hidden" value="" />
<input name="_layout" type="hidden" value="fs" />
<input name="_site" type="hidden" value="eng" />
<input name="_form_id" type="hidden" value="FulltextSearchProductForm" />
<input name="_view" type="hidden" value="input" />
<input name="from" type="hidden" value="fs.header" id="header_search_from" />
<input name="_qf__fulltext_search" type="hidden" value="">
<input name="_layout" type="hidden" value="fs">
<input name="_site" type="hidden" value="eng">
<input name="_form_id" type="hidden" value="FulltextSearchProductForm">
<input name="_view" type="hidden" value="input">
<input name="from" type="hidden" value="fs.header" id="header_search_from">
<div class="globalSearchSelect">
<div class="globalSearchSelect-lable">
<span v-text="searchCategories[selected]">All</span>
@@ -200,7 +239,7 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
</select>
</div>
<div class="globalSearchForm">
<input name="keyword" type="search" id="search_text" maxlength="255" autocomplete="off" aria-autocomplete="list" placeholder="You can search items in both English and Japanese." />
<input name="keyword" type="search" id="search_text" ref="keyword" maxlength="255" autocomplete="off" aria-autocomplete="list" placeholder="You can search items in both English and Japanese.">
</div>
<button id="search_button" class="globalSearchBtn" type="submit" @click="saveSelected()"><i>Search</i></button>
</form>
@@ -208,27 +247,30 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
<!-- アイコンメニュー -->
<ul class="globalNav">
<li class="globalNav-item type-point"><a href="https://www.dlsite.com/eng/mypage/user/buy/point"><i>Buy Points</i></a></li>
<li class="globalNav-item type-point"><a href="https://ssl.dlsite.com/eng/mypage/user/buy/point"><i>Buy Points</i></a></li>
<li class="globalNav-item type-favorite">
<a :href="hasUnboughtFavorites ? 'https://www.dlsite.com/eng/mypage/wishlist' : 'https://www.dlsite.com/eng/mypage/wishlist'"><i>Favorites</i></a><template v-if="hasUnboughtFavorites" v-cloak><a href="https://www.dlsite.com/eng/mypage/wishlist/=/discount/1" class="notificationBadge" >On SALE</a></template>
</li>
<li class="globalNav-item type-cart"><a href="https://www.dlsite.com/eng/cart"><i>Cart</i></a><span v-if="cartActives.length" v-cloak class="cartBadge" v-text="Math.min(cartActives.length, 100)"></span></li>
<li class="globalNav-item type-play"><a href="https://play.dlsite.com/eng/" target="_blank"><i>My Items</i></a></li>
<li class="globalNav-item type-play"><a rel="noopener" href="https://play.dlsite.com/eng/" target="_blank"><i>My Items</i></a></li>
<li class="globalNav-item type-mypage"><a href="https://ssl.dlsite.com/eng/mypage"><i>My Page</i></a></li>
</ul>
<!-- /アイコンメニュー -->
<!-- ガイドメニュー -->
<div class="globalGuide"><a href="#" class="globalGuide-btn" @click.stop.prevent="toggleMenu()"><i>Help</i></a>
<div class="globalGuideLink" :class="{ 'is-active': isActive }">
<ul class="globalGuideLink-item">
<li class="link"><a href="https://www.dlsite.com/eng/faq/=/type/user" target="_blank"><i>Help / FAQ</i></a></li>
<li class="link"><a href="https://www.dlsite.com/eng/welcome"><i>New to DLsite?</i></a></li>
<li class="link"><a href="https://www.dlsite.com/eng/circle/invite"><i>Submit Your Works</i></a></li>
<li class="link"><a href="https://www.dlsite.com/eng/guide/payment"><i>About Payment Method</i></a></li>
<li class="link"><a href="https://www.dlsite.com/eng/mypage/aboutpoint"><i>About Points</i></a></li>
<div class="header_guide hover_menu">
<a href="javascript:void(0)" class="header_guide_btn"><i>Help</i></a>
<div class="dropdown_list">
<div class="dropdown_list_inner">
<ul class="menu_list">
<li class="menu_list_item"><a rel="noopener" href="https://www.dlsite.com/eng/faq/=/type/user" target="_blank"><i>Help / FAQ</i></a></li>
<li class="menu_list_item"><a href="https://www.dlsite.com/eng/welcome"><i>New to DLsite?</i></a></li>
<li class="menu_list_item"><a href="https://www.dlsite.com/eng/circle/invite"><i>Submit Your Works</i></a></li>
<li class="menu_list_item"><a href="https://www.dlsite.com/eng/guide/payment"><i>About Payment Method</i></a></li>
<li class="menu_list_item"><a href="https://www.dlsite.com/eng/mypage/aboutpoint"><i>About Points</i></a></li>
</ul>
</div>
</div>
</div>
<!-- /ガイドメニュー -->
</div>
<div class="headerCore-main">
@@ -241,7 +283,7 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
<a href="https://ssl.dlsite.com/eng/mypage/coupon/list" class="coupon-text">Your coupons:<span class="number" v-text="coupons.length">-</span></a>
<transition>
<div v-if="noticeCoupons && displayCouponAlert" v-cloak class="couponNotification">
<p><a href="https://ssl.dlsite.com/eng/mypage/coupon/list">Your coupons will expire soon.</a></a></p>
<p><a href="https://ssl.dlsite.com/eng/mypage/coupon/list">Your coupons will expire soon.</a></p>
<p class="couponNotification-close" @click.stop.prevent="confirmCoupon()">close</p>
</div>
</transition>
@@ -252,6 +294,8 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
<div class="floorNavLink"><div class="floorNavLink-item type-adult"><a href="https://www.dlsite.com/ecchi-eng/">View R18 Products</a></div></div>
</div>
<div class="floorSubNav">
<div class="floorSubNav-item">
<ul class="headerNav">
<li class="headerNav-item">
<a v-if="isRankingFilterCondition" v-cloak href="https://www.dlsite.com/eng/ranking/month?date=30d">Ranking</a>
@@ -266,6 +310,8 @@ if ((dlsite.isAdult() && dlsite.getId() !== 'circle' && !(dlsite.isFemale() && d
</ul>
</div>
</div>
</div>
</div>
</header>
@@ -285,54 +331,40 @@ if ($.cookie('loginchecked') >= 1) {
</div>
<!-- /header -->
<!--data-vue-component="header-banner"-->
<div data-vue-component="header-banner" data-vue-async="true" data-section_name="campaign_header_banner">
<div v-if="loading"></div>
<div v-else-if="is_show_frame" :class="style" class="hd_cp_banner" v-cloak>
<ul class="cp_bn_list">
<li v-for="campaign in campaigns" :class="campaign.style" class="cp_bn_item">
<a :href="campaign.url">
<div class="cp_bn_inner">
<div class="cp_bn_reminder">
<div class="cp_bn_reminder_content" v-html="campaign.reminder.content"></div>
</div>
<div class="cp_bn"><img :src="campaign.img"></div>
<div class="cp_bn_work" :class="{blank: campaign.outline == null}" v-html="campaign.outline"></div>
</div>
</a>
</li>
</ul>
</div>
</div>
<!-- top_wrapper -->
<div id="top_wrapper" class="clearfix" data-section_name="top_wrapper">
<ul class="topicpath" itemscope="" itemtype="https://schema.org/BreadcrumbList">
<li class="topicpath_item" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemscope="" itemtype="https://schema.org/Thing" itemprop="item" href="https://www.dlsite.com/eng/">
<span itemprop="name">eng</span>
<span itemprop="name">
Doujin
</span>
</a>
<meta itemprop="position" content="1">
</li>
<li class="topicpath_item" itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a itemscope="" itemtype="http://schema.org/Thing" itemprop="item" href="https://www.dlsite.com/eng/circle/list">
<span itemprop="name">Circle Database</span>
</a>
<meta itemprop="position" content="2">
</li>
<li class="topicpath_item" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemscope="" itemtype="https://schema.org/Thing" itemprop="item" href="https://www.dlsite.com/eng/circle/profile/=/maker_id/RG14177.html">
<span itemprop="name">Triangle!</span>
</a>
<meta itemprop="position" content="2">
<meta itemprop="position" content="3">
</li>
<li class="topicpath_item" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemscope="" itemtype="https://schema.org/Thing" itemprop="item" href="https://www.dlsite.com/eng/fsr/=/keyword_work_name/Your+Pure+Girlfriend+SRI0000016970/order/release_d/from/work.series">
<span itemprop="name">Your Pure Girlfriend</span>
<span itemprop="name">Your Pure Girlfriend series</span>
</a>
<meta itemprop="position" content="3">
<meta itemprop="position" content="4">
</li>
<li class="topicpath_item" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemscope="" itemtype="https://schema.org/Thing" itemprop="item" href="#">
<span itemprop="name">With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]</span>
</a>
<meta itemprop="position" content="4">
<meta itemprop="position" content="5">
</li>
</ul>
@@ -341,14 +373,14 @@ if ($.cookie('loginchecked') >= 1) {
<span class="icon_lead_01 type_exclusive" title="Exclusive">Exclusive</span>
</div>
<h1 itemprop="name" id="work_name">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE228866.html" itemprop="url">With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]</a>
</h1>
<span class="link_twitter"><a href="https://twitter.com/share" class="twitter-share-button" data-url="https://dlsite.jp/enwtw/RE228866" data-text="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]/Triangle!" data-lang="en" data-hashtags="DLsite">Tweet</a>
<span class="link_twitter"><a href="https://twitter.com/share" class="twitter-share-button" data-url="https://dlsite.jp/enwtw/RE228866" data-text="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]/Triangle! 50%OFF Til Feb. 3, 2 p.m. (JST)" data-lang="en" data-hashtags="DLsite">Tweet</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>
</span></div>
</div>
@@ -363,17 +395,17 @@ if ($.cookie('loginchecked') >= 1) {
<!-- main_inner -->
<div id="main_inner">
<div id="work_header" data-section_name="work_header">
<div id="work_left" data-vue-component="product-evaluate" data-product-id="RE228866">
<table cellspacing="0" id="work_value">
<div id="work_left">
<table cellspacing="0" id="work_value" data-vue-component="product-evaluate" data-product-id="RE228866">
<tr>
<td class="work_rankin">
<table cellspacing="0">
<tr>
<td v-if="product.ranks.total" v-cloak class="crown_total" :title="'Total ranking (' + product.ranks.total.rank_date + ') / ' + product.ranks.total.rank">&nbsp;</td>
<td v-if="product.ranks.year" v-cloak class="crown_year" :title="'Year ' + product.ranks.year.rank_date + ' ranking / ' + product.ranks.year.rank">&nbsp;</td>
<td v-if="product.ranks.month" v-cloak class="crown_month" :title="'Monthly chart (' + product.ranks.month.rank_date + ') / ' + product.ranks.month.rank">&nbsp;</td>
<td v-if="product.ranks.week" v-cloak class="crown_week" :title="'Weekly chart (' + product.ranks.week.rank_date + ') / ' + product.ranks.week.rank">&nbsp;</td>
<td v-if="product.ranks.day" v-cloak class="crown_hour" :title="'Daily chart (' + product.ranks.day.rank_date + ') / ' + product.ranks.day.rank">&nbsp;</td>
<td v-if="product.ranks.total" v-cloak class="crown_total" :title="$t('product.evaluate.ranking_total' , [ product.ranks.total.rank_date, product.ranks.total.rank ])">&nbsp;</td>
<td v-if="product.ranks.year" v-cloak class="crown_year" :title="$t('product.evaluate.ranking_year' , [ product.ranks.year.rank_date , product.ranks.year.rank ])">&nbsp;</td>
<td v-if="product.ranks.month" v-cloak class="crown_month" :title="$t('product.evaluate.ranking_monthly', [ product.ranks.month.rank_date, product.ranks.month.rank ])">&nbsp;</td>
<td v-if="product.ranks.week" v-cloak class="crown_week" :title="$t('product.evaluate.ranking_weekly' , [ product.ranks.week.rank_date , product.ranks.week.rank ])">&nbsp;</td>
<td v-if="product.ranks.day" v-cloak class="crown_hour" :title="$t('product.evaluate.ranking_daily' , [ product.ranks.day.rank_date , product.ranks.day.rank ])">&nbsp;</td>
</tr>
</table>
</td>
@@ -452,19 +484,27 @@ if ($.cookie('loginchecked') >= 1) {
</div>
</td>
<td v-if="product.review_count" v-cloak class="work_review"><a class="_review_count" href="#review_link"><div title="レビューあり">({{ product.review_count }})</div></a></td>
<td class="work_dl" v-if="product.dl_count !== undefined && product.dl_count > 0" v-cloak><div>Purchased:&nbsp;<span class="_dl_count">{{ product.dl_count }}</span>&nbsp;times</div></td>
<td class="work_dl" v-if="product.wishlist_count !== undefined && product.wishlist_count > 0" v-cloak><div>Favorited:&nbsp;<span>{{ product.wishlist_count }}</span></div></td>
<td v-if="product.review_count" v-cloak class="work_review">
<a class="_review_count" href="#review_link"><div :title="$t('product.evaluate.with_review')" v-text="product.review_count"></div></a>
</td>
<!-- 販売 / DL数 -->
<td class="work_dl" v-if="product.dl_count !== undefined && product.dl_count > 0" v-cloak>
<div v-html="$t('product.evaluate.purchase_count', [ product.dl_count ])"></div>
</td>
<!-- お気に入り数 -->
<td class="work_dl" v-if="product.wishlist_count !== undefined && product.wishlist_count > 0" v-cloak>
<div v-html="$t('product.evaluate.favorite_count', [ product.wishlist_count ])"></div>
</td>
</tr>
</table>
<product-slider product_id="RE228866" inline-template>
<div data-vue-component="product-slider" data-product-id="RE228866">
<div class="product-slider">
<!-- Sample image data -->
<div ref="product_slider_data" class="product-slider-data">
<div data-src="//img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_main.jpg" data-width="560" data-height="420" data-thumb="//img.dlsite.jp/resize/images2/work/doujin/RJ229000/RJ228866_img_main_300x300.jpg"></div>
<div data-src="//img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_main.jpg" data-width="560" data-height="420" data-thumb="//img.dlsite.jp/resize/images2/work/doujin/RJ229000/RJ228866_img_main_240x240.jpg"></div>
<div data-src="//img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_smp1.jpg" data-width="560" data-height="420" data-thumb="//img.dlsite.jp/resize/images2/work/doujin/RJ229000/RJ228866_img_smp1_100x100.jpg"></div>
<div data-src="//img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_smp2.jpg" data-width="560" data-height="420" data-thumb="//img.dlsite.jp/resize/images2/work/doujin/RJ229000/RJ228866_img_smp2_100x100.jpg"></div>
</div>
@@ -512,9 +552,10 @@ if ($.cookie('loginchecked') >= 1) {
</div>
<div v-cloak class="work_slider_comp" v-if="items.length > 1">
<a href="https://www.dlsite.com/eng/popup/=/file/smp1/product_id/RE228866.html" target="_blank">Display in HTML format</a>
<a rel="noopener" href="https://www.dlsite.com/eng/popup/=/file/smp1/product_id/RE228866.html" target="_blank">Display in HTML format</a>
<span>{{ __('totalImages', {count: items.length}) }}</span>
<!-- 枚数 -->
<span v-t="{ path: 'product.slider.totalImages', args: [items.length] }"></span>
</div>
<!-- Popup viewer -->
@@ -524,7 +565,7 @@ if ($.cookie('loginchecked') >= 1) {
<div class="slider_popup_rightpane">
<div class="slider_popup_sidebar">
<template v-for="(item, index) in items">
<div target="_blank" :class="{active:(index === swiper.realIndex)}" @click="slideTo(index, false)">
<div rel="noopener" target="_blank" :class="{active:(index === swiper.realIndex)}" @click="slideTo(index, false)">
<img :src="item.thumb.src" alt="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing] [Triangle!]">
@@ -549,15 +590,15 @@ if ($.cookie('loginchecked') >= 1) {
</div>
<div class="slider_popup_tool">
<input id="target1" class="checkbox" name="target" type="checkbox" value="1" v-model="alwaysActualSize" @change="toggleActualSize" @click="toggleActualSize">
<label for="target1" class="checkbox-label">{{ __('alwaysActual') }}</label>
<span class="slider_popup_description">{{ __('tools') }}</span>
<label for="target1" class="checkbox-label" v-t="'product.slider.alwaysActual'"></label>
<span class="slider_popup_description" v-t="'product.slider.tools'"></span>
</div>
</div>
</div>
</div>
</div>
</product-slider>
</div>
</div>
@@ -585,7 +626,7 @@ if ($.cookie('loginchecked') >= 1) {
<tr>
<th>Series</th>
<td><a href="https://www.dlsite.com/eng/fsr/=/keyword_work_name/%22Your+Pure+Girlfriend%22+SRI0000016970/ana_flg/all/order/release_d/from/work.series">Your Pure Girlfriend</a></td>
<td><a href="https://www.dlsite.com/eng/fsr/=/keyword_work_name/Your+Pure+Girlfriend+SRI0000016970/ana_flg/all/order/release_d/from/work.series">Your Pure Girlfriend</a></td>
</tr>
@@ -608,17 +649,20 @@ if ($.cookie('loginchecked') >= 1) {
</td>
</tr>
<tr>
<th>Option</th>
<th>Language</th>
<td>
<div class="work_genre"><a href="https://www.dlsite.com/eng/fsr/=/work_category%5B0%5D/doujin/options/SND/from/icon.work"><span class="icon_SND" title="Inc. Voice">Inc. Voice</span></a><a href="https://www.dlsite.com/eng/fsr/=/work_category%5B0%5D/doujin/options/TRI/from/icon.work"><span class="icon_TRI" title="Trial">Trial</span></a></div>
<div class="work_genre">
</div>
</td>
</tr>
<tr><th>Genre</th><td><div class="main_genre"><a href="https://www.dlsite.com/eng/fsr/=/genre/056/from/work.genre">Healing</a><a href="https://www.dlsite.com/eng/fsr/=/genre/496/from/work.genre">Binaural</a><a href="https://www.dlsite.com/eng/fsr/=/genre/497/from/work.genre">ASMR</a><a href="https://www.dlsite.com/eng/fsr/=/genre/222/from/work.genre">Childhood Friend</a><a href="https://www.dlsite.com/eng/fsr/=/genre/442/from/work.genre">Ear Cleaning</a><a href="https://www.dlsite.com/eng/fsr/=/genre/004/from/work.genre">Romance</a><a href="https://www.dlsite.com/eng/fsr/=/genre/306/from/work.genre">Romance</a><a href="https://www.dlsite.com/eng/fsr/=/genre/494/from/work.genre">Romance</a></div></td></tr>
<tr><th>Genre</th><td><div class="main_genre"><a href="https://www.dlsite.com/eng/fsr/=/genre/056/from/work.genre">Healing</a><a href="https://www.dlsite.com/eng/fsr/=/genre/496/from/work.genre">Binaural</a><a href="https://www.dlsite.com/eng/fsr/=/genre/497/from/work.genre">ASMR</a><a href="https://www.dlsite.com/eng/fsr/=/genre/442/from/work.genre">Ear Cleaning</a><a href="https://www.dlsite.com/eng/fsr/=/genre/004/from/work.genre">Lovey Dovey/Sweet Love</a><a href="https://www.dlsite.com/eng/fsr/=/genre/222/from/work.genre">Childhood Friend</a>
</div></td></tr>
<tr><th>File Size</th><td><div class="main_genre">1.35GB</div></td></tr>
@@ -668,7 +712,7 @@ if ($.cookie('loginchecked') >= 1) {
<div class="title_01 clearfix"><h2>Contents</h2></div>
<div itemprop="description" class="work_article work_story">** Special Site **<br />
<a rel="nofollow" href="http://www.miyuki-web.net/hatsukoiobake/" target="_blank">http://www.miyuki-web.net/hatsukoiobake/</a><br />
<a rel="noopener nofollow" href="http://www.miyuki-web.net/hatsukoiobake/" target="_blank">http://www.miyuki-web.net/hatsukoiobake/</a><br />
<br />
"Yui Asami meets First Love"<br />
<br />
@@ -683,7 +727,7 @@ To look silly in public may affect your social status.<br />
[Synopsis]<br />
<br />
* Prequel:<br />
<a href="http://www.dlsite.com/eng/work/=/product_id/RE180674.html" target="_blank">http://www.dlsite.com/eng/work/=/product_id/RE180674.html</a><br />
<a rel="noopener" href="http://www.dlsite.com/eng/work/=/product_id/RE180674.html" target="_blank">http://www.dlsite.com/eng/work/=/product_id/RE180674.html</a><br />
<br />
Having got in a relationship, you and Natsumi go to a mountain for camping.<br />
However...there is a thing that traumatized young Natsumi in the mountain...!?<br />
@@ -727,7 +771,7 @@ Bonuses:<br />
<br />
[Credits]<br />
CV: Yui Asami<br />
Script: TOMOYA HIRATA (Project E.L.C)<br />
Script: Project E.L.C<br />
Illustration: Yatomi<br />
Web Design: Guzuri Takamachi / Aoi Kazuki<br />
Logo Design: juda53 / SAKANAC<br />
@@ -1035,11 +1079,10 @@ jQuery(function($){
<div class="work_article" id="work_review" data-section_name="work_review">
<!-- review_head -->
<div class="review_head clearfix">
<div class="review_head">
<p class="float_l review_count"><span class="fs20">0</span> user reviews</p>
</div>
<!-- /review_head -->
<!-- work_review_list -->
<table id="work_review_list" cellspacing="0">
@@ -1092,6 +1135,7 @@ jQuery(function($){
</div>
<!-- /main -->
</div>
<!-- /wrapper -->
@@ -1116,7 +1160,7 @@ jQuery(function($){
<div v-if="product.is_discount || product.is_pointup || rentaled" class="campaign_info">
<p v-if="rentaled" class="type_rental"><span>レンタル期間中割引<span class="limit">あと{{ rentaled.limit }}</span><span class="period">{{ rentaled.period }}まで</span></span></p>
<p v-if="product.is_discount" class="type_sale">
<a v-if="product.discount_to" :href="product.discount_to" :title="product.discount_rate == 100 ? 'Free' : product.discount_rate + '%OFF'" target="_blank"><span>{{ product.discount_rate == 100 ? 'Free' : product.discount_rate + '%OFF' }}<span class="period" v-if="product.discount_end_date">Til {{ product.discount_end_date }}</span></span></a>
<a rel="noopener" v-if="product.discount_to" :href="product.discount_to" :title="product.discount_rate == 100 ? 'Free' : product.discount_rate + '%OFF'" target="_blank"><span>{{ product.discount_rate == 100 ? 'Free' : product.discount_rate + '%OFF' }}<span class="period" v-if="product.discount_end_date">Til {{ product.discount_end_date }}</span></span></a>
<span v-else>{{ product.discount_rate == 100 ? 'Free' : product.discount_rate + '%OFF' }}<span class="period" v-if="product.discount_end_date">Til {{ product.discount_end_date }}</span></span>
</p>
</div>
@@ -1157,8 +1201,8 @@ jQuery(function($){
</div>
</div>
<div class="work_buy_body">
<p v-if="user.os == 'Mac'" class="work_buy_message">Macではレンタル版は<br />プレイできません。</p>
<p v-if="user.ua.os.name == 'Windows' && /^(Vista|XP|2000|ME|98(SE)?|95|3\.1)$/.test(user.ua.os.version)" v-cloak class="work_buy_message">レンタル版をプレイするには<br />Windows 7以上が必要です。</p>
<p v-if="user.os == 'Mac'" class="work_buy_message">Macではレンタル版は<br>プレイできません。</p>
<p v-if="user.ua.os.name == 'Windows' && /^(Vista|XP|2000|ME|98(SE)?|95|3\.1)$/.test(user.ua.os.version)" v-cloak class="work_buy_message">レンタル版をプレイするには<br>Windows 7以上が必要です。</p>
<ul class="guide_rental">
<li v-if="product.on_sale">レンタル期間中は販売価格から{{ rental.price }}円引きで通常購入できます。[ <a href="https://www.dlsite.com/eng/guide/rental">レンタルとは</a> ]</li>
<li v-else>こちらの作品はレンタル販売のみとなります。[ <a href="https://www.dlsite.com/eng/guide/rental">レンタルとは</a> ]</li>
@@ -1182,7 +1226,7 @@ jQuery(function($){
</template>
<template v-else>
<template v-if="is_bought">
<p class="work_stream"><a href="https://play.dlsite.com/eng/?workno=RE228866" class="btn_st" :class="{ disabled: ! product.dlsiteplay_work || product.dl_format == 16 }" title="Open in DLsite Play" target="_blank">Open in DLsite Play</a></p>
<p class="work_stream"><a rel="noopener" href="https://play.dlsite.com/eng/?workno=RE228866" class="btn_st" :class="{ disabled: ! product.dlsiteplay_work || product.dl_format == 16 }" title="Open in DLsite Play" target="_blank">Open in DLsite Play</a></p>
<p class="work_cart"><a :href="product.down_url" class="btn_dl" :class="{ disabled: product.dl_format == 17 }">Download</a></p>
</template>
<template v-else-if="is_already">
@@ -1212,10 +1256,16 @@ jQuery(function($){
<!-- お気に入り -->
<p v-if="is_check" class="work_favorite"><a class="btn_favorite">Add to My Favorites</a></p>
<p v-else-if="customer_id && ! is_favorite" class="work_favorite"><a @click.prevent="addFavorite({ product_id:product.product_id })" href="https://www.dlsite.com/eng/mypage/wishlist/=/product_id/RE228866.html" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }">{{ is_favorite ? 'Already in My Favorites' : 'Add to My Favorites' }}</a></p>
<p v-else-if="! user.customer_id && ! is_favorite" class="work_favorite"><a @click.prevent="showPopup()" href="https://www.dlsite.com/eng/mypage/wishlist/=/product_id/RE228866.html" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }">{{ is_favorite ? 'Already in My Favorites' : 'Add to My Favorites' }}</a></p>
<p v-else-if="! user.customer_id && ! is_favorite" class="work_favorite"><a :href="link.favorite" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }">{{ is_favorite ? 'Already in My Favorites' : 'Add to My Favorites' }}</a></p>
<p v-else-if="user.customer_id && ! is_favorite" class="work_favorite"><a @click.prevent="addFavorite({ product_id:product.product_id })" href="https://www.dlsite.com/eng/mypage/wishlist/=/product_id/RE228866.html" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }">{{ is_favorite ? 'Already in My Favorites' : 'Add to My Favorites' }}</a></p>
<p v-else class="work_favorite"><a href="https://www.dlsite.com/eng/mypage/wishlist/=/product_id/RE228866.html" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }">{{ is_favorite ? 'Already in My Favorites' : 'Add to My Favorites' }}</a></p>
<!-- 購入済み作品の場合、マイ評価ページへのリンクを表示する -->
<template v-if="is_bought" v-cloak>
<div v-if="is_myrated" class="work_rating" v-cloak><a href="https://www.dlsite.com/eng/mypage/short-review" class="btn_rating_in">Already Rated</a></div>
<div v-else class="work_rating" v-cloak><a href="https://www.dlsite.com/eng/mypage/short-review" class="btn_rating">Rate This Product</a></div>
</template>
<div v-show="is_display_cart_popup" class="add_cart_popup_container">
<div class="add_cart_popup">
<p class="add_cart_title">Added to Cart</p>
@@ -1226,7 +1276,6 @@ jQuery(function($){
</div>
</template>
<login-guidance :is-display-popup="is_display_popup" :type="'work'"></login-guidance>
</div>
</div>
@@ -1238,7 +1287,7 @@ jQuery(function($){
<div class="work_buy_body">
<div class="work_buy_label">Price</div>
<div class="work_buy_content">
<span class="price">$8.95&nbsp;/&nbsp;&euro;7.94<i class="work_estimation">(estimation)</i><i class="work_jpy">972 JPY</i></span>
<span class="price">$4.53&nbsp;/&nbsp;&euro;4.08<i class="work_estimation">(estimation)</i><i class="work_jpy">495 JPY</i></span>
</div>
</div>
</div>
@@ -1255,7 +1304,7 @@ jQuery(function($){
<div class="cart_box_body">
<div class="cart_box_body_inner">
<div class="work_thumb">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_sam.jpg" alt="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing] [Triangle!]" title="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing] [Triangle!]" class="target_type" />
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ229000/RJ228866_img_sam.jpg" alt="With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing] [Triangle!]" class="target_type" />
</div>
<template v-if="product.is_rental">
@@ -1304,7 +1353,6 @@ jQuery(function($){
<p v-else-if="! user.customer_id && ! is_favorite" class="work_favorite"><a @click.prevent="showPopup()" href="https://www.dlsite.com/eng/mypage/wishlist/=/product_id/RE228866.html" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }">{{ is_favorite ? 'Already in My Favoritesみ' : 'Add to My Favorites' }}</a></p>
<p v-else class="work_favorite"><a href="https://www.dlsite.com/eng/mypage/wishlist/=/product_id/RE228866.html" class="btn_favorite" :class="{ btn_favorite_in: is_favorite }" data-link-name="float">{{ is_favorite ? 'Already in My Favorites' : 'Add to My Favorites' }}</a></p>
<login-guidance :is-display-popup="is_display_popup" :type="'work'"></login-guidance>
</div>
</div>
@@ -1317,26 +1365,29 @@ jQuery(function($){
<ul>
<li v-if="(gift && gift.title)">
<p class="label bonus_code">{{ gift.title }}</p>
<p class="period" v-if="gift.distribute_end_str">{{ gift.distribute_end_str }}</p>
<p class="distribution_period" v-if="gift.distribute_end_str">Offered until {{ gift.distribute_end_str }}</p>
<p class="body" v-html="gift.description"></p>
</li>
<li v-for="coupon in coupons">
<p class="label coupon" :class="{ type_work: coupon.issue.product_id, type_maker: coupon.issue.maker_id, type_price: ! coupon.issue.product_id && ! coupon.issue.maker_id }">{{ coupon.coupon_name }}</p>
<p class="period" v-if="coupon.end_date_str">{{ coupon.end_date_str }}</p>
<p v-if="coupon.end_date_str" class="distribution_period" :class="{ type_work: coupon.issue.product_id, type_maker: coupon.issue.maker_id, type_price: ! coupon.issue.product_id && ! coupon.issue.maker_id }">Offered until {{ coupon.end_date_str }}</p>
<p class="body" v-html="coupon.info"></p>
<p v-if="coupon.user_limit_date" class="period"><span>Expires on</span>{{ coupon.user_limit_date }}</p>
<p v-else-if="coupon.limit_days_day" class="period">Expires {{ coupon.limit_days_day }} days from receipt</p>
</li>
</ul>
</div>
<p v-if="product.is_rental" class="guide_message">レンタルでは購入特典は<br>付与されません。</p>
<ul class="guide_list">
<li><a href="https://www.dlsite.com/eng/faq/detail/=/type/user/mid/5/did/297" target="_blank">About Purchase Bonus</a></li>
<li><a rel="noopener" href="https://www.dlsite.com/eng/faq/detail/=/type/user/mid/5/did/297" target="_blank">About Purchase Bonus</a></li>
</ul>
</div>
<div id="work_device_guide">
<p class="separate_title"><span>Compatible Devices</span></p>
<table>
<div class="work_device_table_wrap">
<table class="work_device_table">
<thead>
<tr>
<th class="dev"></th>
@@ -1346,23 +1397,21 @@ jQuery(function($){
</thead>
<tbody>
<tr>
<td class="label">PC</td>
<td class="label icon_pc">PC</td>
<td v-if="user.os == 'Mac'" v-cloak><span class="dev_play"></span></td>
<td v-else><span class="dev_play"></span></td>
<td><span class="dev_play"></span></td>
</tr>
<tr>
<td class="label">iOS</td>
<td><span class="dev_play_no"></span></td>
<td><span class="dev_play"></span></td>
</tr>
<tr>
<td class="label">Android</td>
<td><span class="dev_play_no"></span></td>
<td class="label icon_sp">Smartphone</td>
<td>
<span class="dev_play_no"></span>
</td>
<td><span class="dev_play"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="os_popup" :class="{ active: is_display_machine_list }">
@@ -1372,7 +1421,7 @@ jQuery(function($){
<tbody>
<tr>
<td>Windows</td>
<td>Windows7 / Windows8 / Windows8.1 / Windows10</td>
<td>-</td>
</tr>
<tr>
<td>Mac</td>
@@ -1402,7 +1451,7 @@ jQuery(function($){
<ul class="guide_list">
<li>
<a href="https://www.dlsite.com/eng/circle/affiliate/link/work/=/product_id/RE228866.html" v-if="isCircleLogin">For Circles</a>
<a href="https://www.dlsite.com/eng/user/affiliate/link/work/=/product_id/RE228866.html" v-else>For Members</a>
<a href="https://www.dlsite.com/eng/user/affiliate/link/work/=/product_id/RE228866.html" v-else>Create Affiliate Links</a>
</li>
</ul>
</div>
@@ -1418,45 +1467,94 @@ jQuery(function($){
<div class="list_head"><h4>Releases</h4></div>
<table cellspacing="0" class="same_work">
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE180674.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ181000/RJ180674_img_sam_mini.jpg" alt="Fireworks With First Love Girlfriend [Ear Cleaning] [Fall Asleep] [Triangle!]" title="Fireworks With First Love Girlfriend [Ear Cleaning] [Fall Asleep] [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE180674.html">Fireworks With First Love Girlfriend [Ear Cleaning] [Fall Asleep]</a></span>
<span class="work_price">$8.95</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE180674.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ181000/RJ180674_img_sam_mini.jpg" alt="Fireworks With First Love Girlfriend [Ear Cleaning] [Fall Asleep] [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ181000/RJ180674_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="Fireworks With First Love Girlfriend [Ear Cleaning] [Fall Asleep] [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE180674.html">Fireworks With First Love Girlfriend [Ear Cleaning] [Fall Asleep]</a></span>
<span class="work_price">$4.53</span>
</td>
</tr>
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE176184.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ177000/RJ176184_img_sam_mini.jpg" alt="Hanikami Commute Date-chu! [Soothing Audio] [Triangle!]" title="Hanikami Commute Date-chu! [Soothing Audio] [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE176184.html">Hanikami Commute Date-chu! [Soothing Audio]</a></span>
<span class="work_price">$6.96</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE176184.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ177000/RJ176184_img_sam_mini.jpg" alt="Hanikami Commute Date-chu! [Soothing Audio] [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ177000/RJ176184_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="Hanikami Commute Date-chu! [Soothing Audio] [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE176184.html">Hanikami Commute Date-chu! [Soothing Audio]</a></span>
<span class="work_price">$3.52</span>
</td>
</tr>
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073910.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073910_img_sam_mini.jpg" alt="Trianthology! ACE [Triangle!]" title="Trianthology! ACE [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073910.html">Trianthology! ACE</a></span>
<span class="work_price">$2.98</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE073910.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073910_img_sam_mini.jpg" alt="Trianthology! ACE [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073910_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="Trianthology! ACE [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073910.html">Trianthology! ACE</a></span>
<span class="work_price">$1.51</span>
</td>
</tr>
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073917.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073917_img_sam_mini.jpg" alt="Trianthology! X [Triangle!]" title="Trianthology! X [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073917.html">Trianthology! X</a></span>
<span class="work_price">$3.98</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE073917.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073917_img_sam_mini.jpg" alt="Trianthology! X [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073917_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="Trianthology! X [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073917.html">Trianthology! X</a></span>
<span class="work_price">$2.01</span>
</td>
</tr>
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073911.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073911_img_sam_mini.jpg" alt="Misato 4Coma Anthology [Triangle!]" title="Misato 4Coma Anthology [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073911.html">Misato 4Coma Anthology</a></span>
<span class="work_price">$2.98</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE073911.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073911_img_sam_mini.jpg" alt="Misato 4Coma Anthology [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073911_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="Misato 4Coma Anthology [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073911.html">Misato 4Coma Anthology</a></span>
<span class="work_price">$1.51</span>
</td>
</tr>
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073920.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073920_img_sam_mini.jpg" alt="TRIF: trif [Triangle!]" title="TRIF: trif [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073920.html">TRIF: trif</a></span>
<span class="work_price">$14.93</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE073920.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073920_img_sam_mini.jpg" alt="TRIF: trif [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ074000/RJ073920_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="TRIF: trif [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE073920.html">TRIF: trif</a></span>
<span class="work_price">$7.56</span>
</td>
</tr>
<tr>
<td class="work_img"><a href="https://www.dlsite.com/eng/work/=/product_id/RE081812.html"><img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ082000/RJ081812_img_sam_mini.jpg" alt="See You Again in the SMILE [Triangle!]" title="See You Again in the SMILE [Triangle!]" class="target_type" /></a></td>
<td class="name"> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE081812.html">See You Again in the SMILE </a></span>
<span class="work_price">$3.98</span>
<td class="work_img">
<a href="https://www.dlsite.com/eng/work/=/product_id/RE081812.html" data-vue-component="thumb-img-popup">
<img src="//img.dlsite.jp/modpub/images2/work/doujin/RJ082000/RJ081812_img_sam_mini.jpg" alt="See You Again in the SMILE [Triangle!]" @mouseenter="showPopupImg" ref="popup_img" class="target_type" />
<div v-cloak class="work_img_popover flip">
<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7" :src="is_show ? '//img.dlsite.jp/modpub/images2/work/doujin/RJ082000/RJ081812_img_main.jpg' : 'data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'" alt="See You Again in the SMILE [Triangle!]">
</div>
</a>
</td>
<td class="name"> <div class="icon_wrap"><span class="icon_lead_01 type_sale">50%OFF</span></div> <span class="work_name"><a href="https://www.dlsite.com/eng/work/=/product_id/RE081812.html">See You Again in the SMILE </a></span>
<span class="work_price">$2.01</span>
</td>
</tr>
</table>
@@ -1513,97 +1611,103 @@ jQuery(function($){
<!-- footer -->
<div id="footer" data-section_name="footer">
<div class="pagetop_block clearfix">
<p class="pagetop"><a href="#header">Back to Top</a></p>
</div>
<div class="footer_link_01">
<ul>
<li><a href="https://www.dlsite.com/">DLsite Home Page</a></li>
<li><a href="https://www.dlsite.com/eng/">Doujin</a></li>
<li><a href="https://www.dlsite.com/ecchi-eng/">Adult Doujin</a></li>
<li class="sp_switch"><a id="_touch_link" href="https://www.dlsite.com/eng-touch/work/=/product_id/RE228866.html" data-platform="touch">DLsite Mobile Site</a></li>
<div class="footer_floor_nav">
<ul class="floor_list">
<li class="floor_list_item"><a href="https://www.dlsite.com/">DLsite Home Page</a></li>
<li class="floor_list_item"><a href="https://www.dlsite.com/eng/">Doujin</a></li>
<li class="floor_list_item"><a href="https://www.dlsite.com/ecchi-eng/">Adult Doujin</a></li>
<li class="floor_list_item sp_switch"><a id="_touch_link" href="https://www.dlsite.com/eng-touch/work/=/product_id/RE228866.html" data-platform="touch">For Smartphone</a></li>
</ul>
</div>
<div class="footer_section">
<div class="section_container clearfix">
<ul class="link_list">
<li class="list_item">
<div class="footer_section_inner">
<div class="link_list_wrap">
<div class="link_list_box col_2">
<div class="label">About DLsite</div>
<dl>
<dd><a href="https://www.eisys.co.jp/company/company-overview.html" target="_blank">About our Company</a></dd>
<dd><a href="https://eisys.talentcld.com/" target="_blank">Career Information</a></dd>
<dd><a href="https://twitter.com/DLsiteEnglish" target="_blank" class="twitter">Twitter</a> / <a href="https://www.facebook.com/DLsite-English-159690760755693/" target="_blank" class="facebook">Facebook</a> / <a href="http://dlsite-english.tumblr.com/" target="_blank" class="tumblr">Tumblr</a></dd>
</dl>
</li>
<li class="list_item">
<div class="label">Payment / Points</div>
<dl>
<dd><a href="https://www.dlsite.com/eng/guide/payment">Payment Methods</a></dd>
<dd><a href="https://www.dlsite.com/eng/mypage/aboutpoint">About Points</a></dd>
<dd><a href="https://www.dlsite.com/eng/mypage/aboutpoint#gp3">How to Buy Points</a></dd>
</dl>
</li>
<li class="list_item">
<div class="label">Help / Guide</div>
<dl>
<dd><a href="https://www.dlsite.com/eng/welcome">New to DLsite?</a></dd>
<dd><a href="https://www.dlsite.com/eng/faq/=/type/user" target="_blank">Frequently Asked Questions</a></dd>
<dd><a href="https://www.dlsite.com/eng/opinion/contribution">Product Request / Feedback</a></dd>
<dd><a href="https://ssl.dlsite.com/eng/mypage/setting/mail">Newsletter</a></dd>
<dd><a href="https://www.dlsite.com/eng/sitemap">Site Map</a></dd>
</dl>
</li>
<li class="list_item">
<div class="label">DLsite Services</div>
<dl>
<dd><a href="https://www.dlsite.com/eng/circle/invite">Submit Your Works</a></dd>
<dd><a href="https://www.dlsite.com/eng/guide/affiliate">Affiliate Program</a></dd>
</dl>
</li>
<ul class="link_list">
<li class="link_list_item"><a rel="noopener" href="https://www.eisys.co.jp/company/company-overview.html" target="_blank">About our Company</a></li>
<li class="link_list_item"><a rel="noopener" href="https://eisys.talentcld.com/" target="_blank">Career Information</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/user/regulations">User Agreement</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/guide/law">Legal Statement (ASCT)</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/guide/settlement">Legal Statement (PSA)</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/guide/privacy">Privacy Policy</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/mosaic">Compliance Policy</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/guide/copy">Copyright</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/banners">Link to DLsite</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/sitemap">Site Map</a></li>
</ul>
</div>
<div class="section_container type_multilingual clearfix">
<div class="link_list_box">
<div class="label">Payment / Points</div>
<ul class="link_list">
<li class="list_item">
<div class="label">Global Guide</div>
<dl>
<dd class="list_item"><a href="https://www.dlsite.com/eng/welcome">English</a></dd>
<dd class="list_item"><a href="https://www.dlsite.com/eng/welcome/deu">Deutsch</a></dd>
<dd class="list_item"><a href="https://www.dlsite.com/eng/welcome/fra">Français</a></dd>
<dd class="list_item"><a href="https://www.dlsite.com/eng/welcome/ita">Italiano</a></dd>
<dd class="list_item"><a href="https://www.dlsite.com/eng/welcome/esp">Español</a></dd>
<dd class="list_item"><a href="https://www.dlsite.com/eng/welcome/chi">繁體中文</a></dd>
</dl>
</li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/guide/payment">Payment Methods</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/mypage/aboutpoint">About Points</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/mypage/aboutpoint#gp3">How to Buy Points</a></li>
</ul>
<div class="language_container">
<div class="label">Language</div>
<select name="language" id="language_select" onchange="location.href=value;">
<option value="https://www.dlsite.com/home/" >日本語</option>
<option value="https://www.dlsite.com/eng/" selected>English</option>
<option value="https://www.dlsite.com.tw/home/">繁體中文</option>
</select>
</div>
<div class="link_list_box">
<div class="label">Help / Guide</div>
<ul class="link_list">
<li class="link_list_item"><a href="https://www.dlsite.com/eng/welcome">New to DLsite?</a></li>
<li class="link_list_item"><a rel="noopener" href="https://www.dlsite.com/eng/faq/=/type/user" target="_blank">Frequently Asked Questions</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/opinion/contribution">Product Request / Feedback</a></li>
</ul>
</div>
<div class="link_list_box">
<div class="label">DLsite Services</div>
<ul class="link_list">
<li class="link_list_item"><a href="https://www.dlsite.com/eng/circle/invite">Submit Your Works</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/guide/affiliate">Affiliate Program</a></li>
<li class="link_list_item"><a href="https://ssl.dlsite.com/eng/mypage/setting/mail">Newsletter</a></li>
</ul>
</div>
</div>
</div>
<div class="footer_section_inner sns">
<div class="label">Official SNS Accounts</div>
<ul class="footer_sns">
<li class="footer_sns_item"><a rel="noopener" href="https://twitter.com/DLsiteEnglish" target="_blank" class="twitter">Twitter</a></li>
<li class="footer_sns_item"><a rel="noopener" href="https://www.facebook.com/DLsite-English-159690760755693/" target="_blank" class="facebook">Facebook</a></li>
<li class="footer_sns_item"><a rel="noopener" href="http://dlsite-english.tumblr.com/" target="_blank" class="tumblr">Tumblr</a></li>
<li class="footer_sns_item"><a rel="noopener" href="https://www.instagram.com/dlsite_english/" target="_blank" class="instagram">Instagram</a></li>
<li class="footer_sns_item"><a rel="noopener" href="https://www.youtube.com/channel/UCGKtTGBPGmB5d9jg-fIZc8w" target="_blank" class="youtube">Youtube</a></li>
<li class="footer_sns_item"><a rel="noopener" href="https://discordapp.com/channels/555918616793710592/" target="_blank" class="discord">Discord</a></li>
</ul>
</div>
<div class="footer_section_inner multilingual">
<div class="link_list_wrap">
<div class="link_list_box">
<div class="label">International</div>
<ul class="link_list type_horizontal">
<li class="link_list_item"><a href="https://www.dlsite.com/home/">日本語</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/">English</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com.tw/home/">繁體中文</a></li>
</ul>
</div>
<div class="link_list_box">
<div class="label">Global Guide</div>
<ul class="link_list type_horizontal">
<li class="link_list_item"><a href="https://www.dlsite.com/eng/welcome">English</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/welcome/deu">Deutsch</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/welcome/fra">Français</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/welcome/ita">Italiano</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/eng/welcome/esp">Español</a></li>
<li class="link_list_item"><a href="https://www.dlsite.com/home/welcome">中文</a></li>
</ul>
</div>
</div>
</div>
</div>
<div id="copyright">
<div class="container clearfix">
<div id="system">Recommended browsers: The latest version of Internet Explorer, Microsoft Edge, Safari, Chrome or Firefox with JavaScript/cookies enabled.</div>
<ul id="footer_nav">
<li><a href="https://www.dlsite.com/eng/user/regulations">User Agreement</a></li>
<li><a href="https://www.dlsite.com/eng/guide/law">Legal Statement (ASCT)</a></li>
<li><a href="https://www.dlsite.com/eng/guide/settlement">Legal Statement (PSA)</a></li>
<li><a href="https://www.dlsite.com/eng/guide/privacy">Privacy Policy</a></li>
<li><a href="https://www.dlsite.com/eng/mosaic">Compliance Policy</a></li>
<li><a href="https://www.dlsite.com/eng/guide/copy">Copyright</a></li>
<li><a href="https://www.dlsite.com/eng/banners">Link to DLsite</a></li>
</ul>
<p>&copy; 1996 DLsite</p>
</div>
</div>
@@ -1628,8 +1732,8 @@ jQuery(function($){
<div data-vue-component="cookie-policy" data-async="true"></div>
<script type="text/javascript" src="/vue/js/pc/vendor.js?cdn_cache=1&v=0.1.2&_=1536029038"></script>
<script type="text/javascript" src="/vue/js/pc/app.js?cdn_cache=1&v=0.1.2&_=1559289223"></script>
<script type="text/javascript" src="/vue/js/pc/vendor.js?cdn_cache=1&v=0.1.2&_=1561425273"></script>
<script type="text/javascript" src="/vue/js/pc/app.js?cdn_cache=1&v=0.1.2&_=1578552328"></script>
<script type="text/javascript">
@@ -1663,15 +1767,10 @@ div.measure_tag {
}
</style>
<div class="measure_tag">
</div>
<div class="measure_tag"></div>
<!-- /script_footer -->
<script type="text/javascript">var contents = {"impression":[],"detail":[{"id":"RE228866","name":"With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]","category":"eng","brand":"RG14177","price":900,"regist_date":"2018\/10\/02","image_main":"\/\/img.dlsite.jp\/modpub\/images2\/work\/doujin\/RJ229000\/RJ228866_img_main.jpg","restore_price":null}],"time":0.0001552104949951172};</script>
<script type="text/javascript">var contents = {"impression":[],"detail":[{"id":"RE228866","name":"With Your First Girlfriend, at a Ghostly Night [Ear Cleaning] [Sleep Sharing]","category":"eng","brand":"RG14177","price":450,"regist_date":"2018\/10\/02","image_main":"\/\/img.dlsite.jp\/modpub\/images2\/work\/doujin\/RJ229000\/RJ228866_img_main.jpg","restore_price":"900"}],"time":0.00014901161193847656};</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More