228 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
cf5e269fd8 Merge pull request #233 from eai04191/feature/lint-tests
Lint tests
2019-07-02 12:51:40 +09:00
eai04191
4747f822ab Lint tests 2019-07-02 12:44:25 +09:00
shibafu
bcf78df2fc Merge pull request #228 from shikorism/develop
Release 20190629.1945
2019-06-29 19:50:51 +09:00
shibafu
fa171bc3d3 Merge pull request #224 from shikorism/feature/105-tag-suggest
チェックイン時にメタデータからタグをサジェストする
2019-06-29 19:17:07 +09:00
shibafu
b828a233bc Merge pull request #227 from eai04191/feature/icon-identity
Gravatarのデフォルトアイコンをretroにする
2019-06-29 17:24:17 +09:00
shibafu
bbfd6a9895 Merge pull request #226 from eai04191/feature/resolver-pixiv-fix
PixivResolverの取得画像をregularにする
2019-06-29 00:13:44 +09:00
shibafu
e1dd2b1c8f メタデータの読み込み中・読み込み失敗を表示するようにした 2019-06-28 00:27:31 +09:00
shibafu
ec4e0f3dda メタデータプレビュー専用のスタイルなので .link-card-mini はSFC内に封印 2019-06-27 23:55:36 +09:00
shibafu
0aed1d9ebe 画像を含まないメタデータの場合、サムネイルを非表示にする 2019-06-27 23:53:04 +09:00
shibafu
d8cdf218b7 Update resources/assets/sass/components/_link-card.scss
Co-Authored-By: Aoi Irie <eai@mizle.net>
2019-06-27 23:50:59 +09:00
eai04191
7c9eefe478 Gravatarのデフォルトをretroに指定 2019-06-27 13:32:20 +09:00
eai04191
810a1dbc59 PixivResolverの取得画像をregularにする 2019-06-27 00:34:53 +09:00
shibafu
a521a26aa5 Merge pull request #225 from eai04191/feature/test-komiflo
KomifloResolverのテストを追加
2019-06-27 00:21:24 +09:00
eai04191
e80acf79b7 KomifloResolverのテストを追加 2019-06-27 00:05:47 +09:00
shibafu
1b5fbfabc4 Merge pull request #223 from eai04191/feature/codecov
codecovを追加する
2019-06-26 23:32:11 +09:00
shibafu
0ad0b268bc 既に入力されているタグを二重追加しない 2019-06-26 23:23:06 +09:00
shibafu
da19806a3d ページロード時点でオカズリンクにURLが入力されている場合、すぐにメタデータを取得する 2019-06-26 23:20:53 +09:00
eai04191
4390af53f9 revert Dockerfile 2019-06-26 23:15:04 +09:00
shibafu
dd12582dba Merge pull request #189 from eai04191/feature/test-pixiv
PixivResolverのテストを追加
2019-06-26 22:32:15 +09:00
eai04191
6e81f091d1 必要のないパイプを削除 2019-06-26 15:46:35 +09:00
eai04191
c60d41427d Add codecov 2019-06-26 15:31:14 +09:00
shibafu
b8482e0e3c メタデータプレビューとタグ候補選択のコンポーネント 2019-06-26 01:44:18 +09:00
shibafu
a1cb313d4f Card APIでタグ情報も取れるようにする 2019-06-26 01:40:55 +09:00
eai04191
53f7a17b8e PixivResolverのテストを追加 2019-06-26 01:12:25 +09:00
shibafu
41039a6650 Merge pull request #214 from shikorism/feature/editorconfig
.editorconfigを追加
2019-06-24 22:01:46 +09:00
shibafu
6d4f6e47a3 Merge pull request #222 from shikorism/fix/218
ユーザー入力テキストをLinkifyする時に target="_blank" rel="noopener" を付与する
2019-06-24 22:01:13 +09:00
shibafu
47eec65101 ユーザー入力テキストをLinkifyする時に target="_blank" rel="noopener" を付与する 2019-06-24 20:19:58 +09:00
shibafu
c2da5eef9d Merge pull request #217 from eai04191/feature/update-readme
README更新
2019-06-24 20:11:16 +09:00
shibafu
94cabdc827 Merge pull request #204 from shikorism/feature/average-range-limit
平均記録を直近30チェックインから算出するように変更
2019-06-24 20:10:20 +09:00
shibafu
1e11bd3290 JSON fileのインデント幅を指定
ただしcomposer.jsonは4 spaceを使うので、据え置く
2019-06-24 20:05:30 +09:00
shibafu
f729fa7908 YAML fileのインデント幅を指定 2019-06-24 19:59:43 +09:00
eai04191
5172f1cb70 chownの変更先をstorageのみにする 2019-06-18 11:18:06 +09:00
eai04191
663888dcb1 prestissimoを使用してインストールを高速化 2019-06-18 11:16:51 +09:00
shibafu
8c5a7f4d09 .editorconfigを追加 2019-06-16 23:08:45 +09:00
shibafu
709f10f098 平均記録を直近30チェックインから算出するように変更 2019-06-12 00:03:18 +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
151 changed files with 62710 additions and 8406 deletions

View File

@@ -1,8 +1,9 @@
version: 2
jobs:
version: 2.1
executors:
build:
docker:
- image: circleci/php:7.1-node-browsers
- image: circleci/php:7.3-node-browsers
environment:
APP_DEBUG: true
APP_ENV: testing
@@ -16,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: |
@@ -67,7 +105,62 @@ jobs:
- run:
command: |
mkdir -p /tmp/phpunit
./vendor/bin/phpunit --log-junit /tmp/phpunit/phpunit.xml
./vendor/bin/phpunit --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
# Upload coverage
- 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

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
[*.json]
indent_size = 2
[composer.json]
indent_size = 4

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
## 開発環境の構築
@@ -50,7 +50,7 @@ docker-compose exec web php artisan migrate
6. ファイルに書き込めるように権限を設定します。
```
docker-compose exec web chown -R www-data /var/www/html
docker-compose exec web chown -R www-data /var/www/html/storage
```
7. アセットをビルドします。

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

@@ -51,6 +51,8 @@ class CardController
$metadata->tags()->sync($tagIds);
}
$metadata->load('tags');
$response = response($metadata);
if (!config('app.debug')) {
$response = $response->setCache(['public' => true, 'max_age' => 86400]);

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

@@ -35,9 +35,27 @@ class ProfileStatsComposer
}
// 概況欄のデータ取得
$average = DB::select(<<<'SQL'
SELECT
avg(span) AS average
FROM
(
SELECT
extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span
FROM
ejaculations
WHERE
user_id = :user_id
ORDER BY
ejaculated_date DESC
LIMIT
30
) AS temp
SQL
, ['user_id' => $user->id]);
$summary = DB::select(<<<'SQL'
SELECT
avg(span) AS average,
max(span) AS longest,
min(span) AS shortest,
sum(span) AS total_times,
@@ -56,6 +74,6 @@ FROM
SQL
, ['user_id' => $user->id]);
$view->with(compact('latestEjaculation', 'currentSession', 'summary'));
$view->with(compact('latestEjaculation', 'currentSession', 'average', 'summary'));
}
}

View File

@@ -11,7 +11,7 @@ class Metadata extends Model
protected $keyType = 'string';
protected $fillable = ['url', 'title', 'description', 'image', 'expires_at'];
protected $visible = ['url', 'title', 'description', 'image', 'expires_at'];
protected $visible = ['url', 'title', 'description', 'image', 'expires_at', 'tags'];
protected $dates = ['created_at', 'updated_at', 'expires_at'];

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,37 +38,36 @@ 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();
$metadata->title = $json['body']['illustTitle'] ?? '';
$metadata->description = '投稿者: ' . $json['body']['userName'] . PHP_EOL . strip_tags(str_replace('<br />', PHP_EOL, $json['body']['illustComment'] ?? ''));
$metadata->image = $this->proxize($json['body']['urls']['original'] ?? '');
$metadata->image = $this->proxize($json['body']['urls']['regular'] ?? '');
// ページ数の指定がある場合は画像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

@@ -11,6 +11,9 @@ class Tag extends Model
protected $fillable = [
'name'
];
protected $visible = [
'name'
];
public function ejaculations()
{

View File

@@ -41,7 +41,7 @@ class User extends Authenticatable
{
$hash = md5(strtolower(trim($this->email)));
return '//www.gravatar.com/avatar/' . $hash . '?s=' . $size;
return '//www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
}
/**
@@ -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

@@ -35,7 +35,7 @@ class Formatter
*/
public function linkify($text)
{
return $this->linkify->processUrls($text);
return $this->linkify->processUrls($text, ['attr' => ['target' => '_blank', 'rel' => 'noopener']]);
}
/**

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

@@ -1,9 +1,66 @@
import Vue from 'vue';
import TagInput from "./components/TagInput.vue";
import MetadataPreview from './components/MetadataPreview.vue';
export const bus = new Vue({name: "EventBus"});
export enum MetadataLoadState {
Inactive,
Loading,
Success,
Failed,
}
new Vue({
el: '#app',
data: {
metadata: null,
metadataLoadState: MetadataLoadState.Inactive,
},
components: {
TagInput
TagInput,
MetadataPreview
},
mounted() {
// オカズリンクにURLがセットされている場合は、すぐにメタデータを取得する
const linkInput = this.$el.querySelector<HTMLInputElement>("#link");
if (linkInput && /^https?:\/\//.test(linkInput.value)) {
this.fetchMetadata(linkInput.value);
}
},
methods: {
// オカズリンクの変更時
onChangeLink(event: Event) {
if (event.target instanceof HTMLInputElement) {
const url = event.target.value;
if (url.trim() === '' || !/^https?:\/\//.test(url)) {
this.metadata = null;
this.metadataLoadState = MetadataLoadState.Inactive;
return;
}
this.fetchMetadata(url);
}
},
// メタデータの取得
fetchMetadata(url: string) {
this.metadataLoadState = MetadataLoadState.Loading;
$.ajax({
url: '/api/checkin/card',
method: 'get',
type: 'json',
data: {
url
}
}).then(data => {
this.metadata = data;
this.metadataLoadState = MetadataLoadState.Success;
}).catch(e => {
this.metadata = null;
this.metadataLoadState = MetadataLoadState.Failed;
});
}
}
});

View File

@@ -0,0 +1,143 @@
<template>
<div class="form-row" v-if="state !== MetadataLoadState.Inactive">
<div class="form-group col-sm-12">
<div class="card link-card-mini mb-2 px-0">
<div v-if="state === MetadataLoadState.Loading" class="row no-gutters">
<div class="col-12">
<div class="card-body">
<h6 class="card-title text-center font-weight-bold text-info" style="font-size: small;"><span class="oi oi-loop-circular"></span> オカズの情報を読み込んでいます</h6>
</div>
</div>
</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="w-100 bg-secondary">
</div>
<div :class="descClasses">
<div class="card-body">
<h6 class="card-title font-weight-bold" style="font-size: small;">{{ metadata.title }}</h6>
<template v-if="suggestions.length > 0">
<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="tagClasses(tag)"
@click="addTag(tag.name)"><span class="oi oi-tag"></span> {{ tag.name }}</li>
</ul>
</template>
</div>
</div>
</div>
<div v-else class="row no-gutters">
<div class="col-12">
<div class="card-body">
<h6 class="card-title text-center font-weight-bold text-danger" style="font-size: small;"><span class="oi oi-circle-x"></span> オカズの情報を読み込めませんでした</h6>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import {Vue, Component, Prop} from "vue-property-decorator";
import {bus, MetadataLoadState} from "../checkin";
type Metadata = {
url: string,
title: string,
description: string,
image: string,
expires_at: string | null,
tags: {
name: string
}[],
};
type Suggestion = {
name: string,
used: boolean,
}
@Component
export default class MetadataPreview extends Vue {
@Prop() readonly state!: MetadataLoadState;
@Prop() readonly metadata!: Metadata | null;
// 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);
}
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 => {
return {
name: t.name,
used: this.tags.indexOf(t.name) !== -1
};
});
}
get hasImage() {
return this.metadata !== null && this.metadata.image !== ''
}
get descClasses() {
return {
"col-8": this.hasImage,
"col-12": !this.hasImage,
};
}
}
</script>
<style lang="scss" scoped>
.link-card-mini {
$height: 150px;
overflow: hidden;
.row > div:first-child {
display: flex;
&:not([display=none]) {
min-height: $height;
img {
position: absolute;
}
}
}
.card-text {
white-space: pre-line;
}
}
.metadata-tag-item {
cursor: pointer;
user-select: none;
}
</style>

View File

@@ -16,7 +16,8 @@
</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
export default class TagInput extends Vue {
@@ -28,6 +29,11 @@
tags: string[] = this.value.trim() !== "" ? this.value.trim().split(" ") : [];
buffer: string = "";
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) {
if (this.buffer.trim() !== "") {
switch (event.key) {
@@ -35,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") {
// 誤爆防止
@@ -51,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

@@ -8,7 +8,7 @@
@endif
<h6 class="font-weight-bold"><span class="oi oi-graph"></span> 概況</h6>
<p class="card-text mb-0">平均記録: {{ Formatter::formatInterval($summary[0]->average) }}</p>
<p class="card-text mb-0">平均記録: {{ Formatter::formatInterval($average[0]->average) }}</p>
<p class="card-text mb-0">最長記録: {{ Formatter::formatInterval($summary[0]->longest) }}</p>
<p class="card-text mb-0">最短記録: {{ Formatter::formatInterval($summary[0]->shortest) }}</p>
<p class="card-text mb-0">合計時間: {{ Formatter::formatInterval($summary[0]->total_times) }}</p>

View File

@@ -52,7 +52,9 @@
<div class="form-row">
<div class="form-group col-sm-12">
<label for="link"><span class="oi oi-link-intact"></span> オカズリンク</label>
<input id="link" name="link" type="text" autocomplete="off" class="form-control {{ $errors->has('link') ? ' is-invalid' : '' }}" placeholder="http://..." value="{{ old('link') ?? $defaults['link'] }}">
<input id="link" name="link" type="text" autocomplete="off" class="form-control {{ $errors->has('link') ? ' is-invalid' : '' }}"
placeholder="http://..." value="{{ old('link') ?? $defaults['link'] }}"
@change="onChangeLink">
<small class="form-text text-muted">
オカズのURLを貼り付けて登録することができます。
</small>
@@ -61,6 +63,7 @@
@endif
</div>
</div>
<metadata-preview :metadata="metadata" :state="metadataLoadState"></metadata-preview>
<div class="form-row">
<div class="form-group col-sm-12">
<label for="note"><span class="oi oi-comment-square"></span> ノート</label>
@@ -82,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

@@ -53,7 +53,9 @@
<div class="form-row">
<div class="form-group col-sm-12">
<label for="link"><span class="oi oi-link-intact"></span> オカズリンク</label>
<input id="link" name="link" type="text" autocomplete="off" class="form-control {{ $errors->has('link') ? ' is-invalid' : '' }}" placeholder="http://..." value="{{ old('link') ?? $ejaculation->link }}">
<input id="link" name="link" type="text" autocomplete="off" class="form-control {{ $errors->has('link') ? ' is-invalid' : '' }}"
placeholder="http://..." value="{{ old('link') ?? $ejaculation->link }}"
@change="onChangeLink">
<small class="form-text text-muted">
オカズのURLを貼り付けて登録することができます。
</small>
@@ -62,6 +64,7 @@
@endif
</div>
</div>
<metadata-preview :metadata="metadata" :state="metadataLoadState"></metadata-preview>
<div class="form-row">
<div class="form-group col-sm-12">
<label for="note"><span class="oi oi-comment-square"></span> ノート</label>
@@ -83,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;
@@ -49,6 +50,6 @@ trait CreateMockedResolver
protected function shouldUseMock(): bool
{
return (bool)env('TEST_USE_HTTP_MOCK', true);
return (bool) env('TEST_USE_HTTP_MOCK', true);
}
}

View File

@@ -20,7 +20,7 @@ class DLsiteResolverTest extends TestCase
public function testHome()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testHome.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -28,7 +28,7 @@ class DLsiteResolverTest extends TestCase
$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);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -36,7 +36,7 @@ class DLsiteResolverTest extends TestCase
public function testSoft()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testSoft.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testSoft.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -52,7 +52,7 @@ class DLsiteResolverTest extends TestCase
public function testComic()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testComic.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testComic.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -68,7 +68,7 @@ class DLsiteResolverTest extends TestCase
public function testManiax()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testManiax.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testManiax.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -84,7 +84,7 @@ class DLsiteResolverTest extends TestCase
public function testPro()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testPro.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testPro.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -100,15 +100,15 @@ class DLsiteResolverTest extends TestCase
public function testBooks()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testBooks.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testBooks.html');
$this->createResolver(DLsiteResolver::class, $responseText);
$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());
}
@@ -116,7 +116,7 @@ class DLsiteResolverTest extends TestCase
public function testGirls()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testGirls.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testGirls.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -132,7 +132,7 @@ class DLsiteResolverTest extends TestCase
public function testGirlsPro()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testGirlsPro.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testGirlsPro.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -148,7 +148,7 @@ class DLsiteResolverTest extends TestCase
public function testBL()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testBL.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testBL.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -164,7 +164,7 @@ class DLsiteResolverTest extends TestCase
public function testEng()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testEng.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testEng.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -180,7 +180,7 @@ class DLsiteResolverTest extends TestCase
public function testEcchiEng()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testEcchiEng.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testEcchiEng.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -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());
}
@@ -196,7 +196,7 @@ class DLsiteResolverTest extends TestCase
public function testSPLink()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testHome.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
// SP版touchのURLのテストだがリゾルバ側でURLから-touchを削除してPC版を取得するので、PC版の内容を使用する
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -205,7 +205,7 @@ class DLsiteResolverTest extends TestCase
$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);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri());
}
@@ -213,7 +213,7 @@ class DLsiteResolverTest extends TestCase
public function testShortLink()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testHome.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
$this->createResolver(DLsiteResolver::class, $responseText);
@@ -221,9 +221,99 @@ class DLsiteResolverTest extends TestCase
$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);
$this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags);
if ($this->shouldUseMock()) {
$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

@@ -20,7 +20,7 @@ class FC2ContentsResolverTest extends TestCase
public function testAdult()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/FC2Contents/adult.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/FC2Contents/adult.html');
$this->createResolver(FC2ContentsResolver::class, $responseText);
@@ -35,7 +35,7 @@ class FC2ContentsResolverTest extends TestCase
public function testGeneral()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/FC2Contents/general.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/FC2Contents/general.html');
$this->createResolver(FC2ContentsResolver::class, $responseText);

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,52 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\KomifloResolver;
use Tests\TestCase;
class KomifloResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testComic()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Komiflo/comic.json');
$this->createResolver(KomifloResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://komiflo.com/#!/comics/5490');
$this->assertEquals('魔法少女とえっち物語', $metadata->title);
$this->assertEquals('薙派 - メガストアα 19.07', $metadata->description);
$this->assertEquals('https://t.komiflo.com/564_mobile_large_3x/contents/23a4cd530060b8607aa434f4b299b249e71a4d5c.jpg', $metadata->image);
$this->assertEquals(['薙派', 'お姉さん', 'ショタ', 'ファンタジー', '巨乳', '野外・露出', '羞恥'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://api.komiflo.com/content/id/5490', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testComicWithNoParents()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Komiflo/comicWithNoParents.json');
$this->createResolver(KomifloResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://komiflo.com/#!/comics/3414');
$this->assertEquals('生まれなおしプログラム', $metadata->title);
$this->assertEquals('EROKI - ?', $metadata->description);
$this->assertEquals('https://t.komiflo.com/564_mobile_large_3x/contents/71cfb83640aead3cdd35e4329c4e2f427606a11d.jpg', $metadata->image);
$this->assertEquals(['EROKI', 'お姉さん', 'しつけ', 'オリジナル', 'ショートカット', '逆転', '巨乳'], $metadata->tags);
if ($this->shouldUseMock()) {
$this->assertSame('https://api.komiflo.com/content/id/3414', (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,12 +25,12 @@ 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());
$this->assertSame('https://nijie.info/view.php?id=66384', (string) $this->handler->getLastRequest()->getUri());
}
}
@@ -41,12 +41,12 @@ 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());
$this->assertSame('https://nijie.info/view.php?id=202707', (string) $this->handler->getLastRequest()->getUri());
}
}
@@ -57,11 +57,12 @@ 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());
$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());
$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());
$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

@@ -0,0 +1,100 @@
<?php
namespace Tests\Unit\MetadataResolver;
use App\MetadataResolver\PixivResolver;
use Tests\TestCase;
class PixivResolverTest extends TestCase
{
use CreateMockedResolver;
public function setUp()
{
parent::setUp();
if (!$this->shouldUseMock()) {
sleep(1);
}
}
public function testIllust()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Pixiv/illust.json');
$this->createResolver(PixivResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.pixiv.net/member_illust.php?mode=medium&illust_id=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 testIllustMultiPages()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Pixiv/illustMultiPages.json');
$this->createResolver(PixivResolver::class, $responseText);
$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/75899985', (string) $this->handler->getLastRequest()->getUri());
}
}
public function testManga()
{
$responseText = file_get_contents(__DIR__ . '/../../fixture/Pixiv/manga.json');
$this->createResolver(PixivResolver::class, $responseText);
$metadata = $this->resolver->resolve('https://www.pixiv.net/member_illust.php?mode=medium&illust_id=46713544');
$this->assertEquals('冬の日ラブラブ', $metadata->title);
$this->assertEquals('投稿者: Aza' . PHP_EOL . 'ラブラブエッチのらくがき' . PHP_EOL . PHP_EOL . '三万フォロワー感謝します~' . PHP_EOL . PHP_EOL . '最近忙しいので、自分の時間が少ない・・・', $metadata->description);
$this->assertEquals('https://i.pixiv.cat/img-master/img/2014/10/25/00/06/58/46713544_p0_master1200.jpg', $metadata->image);
$this->assertEquals(['落書き', 'おっぱい', 'オリジナル', 'パイズリ', '中出し', 'だいしゅきホールド', '愛のあるセックス', '黒髪ロング', 'オリジナル10000users入り'], $metadata->tags);
if ($this->shouldUseMock()) {
$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

@@ -20,7 +20,7 @@ class PlurkResolverTest extends TestCase
public function test()
{
$responseText = file_get_contents(__DIR__.'/../../fixture/Plurk/test.html');
$responseText = file_get_contents(__DIR__ . '/../../fixture/Plurk/test.html');
$this->createResolver(PlurkResolver::class, $responseText);

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');
}
}

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