From 7ca0acacb4754d0769937af6af55be6cdc59d909 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 15 Apr 2018 02:05:41 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=86=E3=83=B3?= =?UTF-8?q?=E3=83=84=E6=83=85=E5=A0=B1=E5=8F=96=E5=BE=97=E3=81=AE=E5=AE=9F?= =?UTF-8?q?=E8=A3=85=E3=82=92api.php=E3=81=8B=E3=82=89=E5=89=A5=E3=81=8C?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/MetadataResolver/Metadata.php | 10 +++ app/MetadataResolver/MetadataResolver.php | 24 +++++++ app/MetadataResolver/NicoSeigaResolver.php | 24 +++++++ app/MetadataResolver/NijieResolver.php | 33 +++++++++ app/MetadataResolver/OGPResolver.php | 52 ++++++++++++++ app/MetadataResolver/Resolver.php | 8 +++ app/Providers/AppServiceProvider.php | 5 +- routes/api.php | 72 ++----------------- .../Unit/MetadataResolver/OGPResolverTest.php | 28 ++++++++ 9 files changed, 190 insertions(+), 66 deletions(-) create mode 100644 app/MetadataResolver/Metadata.php create mode 100644 app/MetadataResolver/MetadataResolver.php create mode 100644 app/MetadataResolver/NicoSeigaResolver.php create mode 100644 app/MetadataResolver/NijieResolver.php create mode 100644 app/MetadataResolver/OGPResolver.php create mode 100644 app/MetadataResolver/Resolver.php create mode 100644 tests/Unit/MetadataResolver/OGPResolverTest.php diff --git a/app/MetadataResolver/Metadata.php b/app/MetadataResolver/Metadata.php new file mode 100644 index 0000000..ae2508b --- /dev/null +++ b/app/MetadataResolver/Metadata.php @@ -0,0 +1,10 @@ + NicoSeigaResolver::class, + '~nijie\.info/view\.php~' => NijieResolver::class, + '/.*/' => OGPResolver::class + ]; + + public function resolve(string $url): Metadata + { + foreach ($this->rules as $pattern => $class) { + if (preg_match($pattern, $url) === 1) { + $resolver = new $class; + return $resolver->resolve($url); + } + } + + throw new \UnexpectedValueException('URL not matched.'); + } +} \ No newline at end of file diff --git a/app/MetadataResolver/NicoSeigaResolver.php b/app/MetadataResolver/NicoSeigaResolver.php new file mode 100644 index 0000000..dbd81bc --- /dev/null +++ b/app/MetadataResolver/NicoSeigaResolver.php @@ -0,0 +1,24 @@ +get($url); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); + + // ページ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?"; + + return $metadata; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} \ No newline at end of file diff --git a/app/MetadataResolver/NijieResolver.php b/app/MetadataResolver/NijieResolver.php new file mode 100644 index 0000000..f4fdba1 --- /dev/null +++ b/app/MetadataResolver/NijieResolver.php @@ -0,0 +1,33 @@ +get($url); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); + + $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'])) { + $metadata->image = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']); + break; + } + } + + return $metadata; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} \ No newline at end of file diff --git a/app/MetadataResolver/OGPResolver.php b/app/MetadataResolver/OGPResolver.php new file mode 100644 index 0000000..0475d7d --- /dev/null +++ b/app/MetadataResolver/OGPResolver.php @@ -0,0 +1,52 @@ +get($url); + if ($res->getStatusCode() === 200) { + return $this->parse($res->getBody()); + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } + + public function parse(string $html): Metadata + { + $dom = new \DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $xpath = new \DOMXPath($dom); + + $metadata = new Metadata(); + + $titleNode = $xpath->query('//meta[@*="og:title"]'); + foreach ($titleNode as $node) { + if (!empty($node->getAttribute('content'))) { + $metadata->title = $node->getAttribute('content'); + break; + } + } + + $descriptionNode = $xpath->query('//meta[@*="og:description"]'); + foreach ($descriptionNode as $node) { + if (!empty($node->getAttribute('content'))) { + $metadata->description = $node->getAttribute('content'); + break; + } + } + + $imageNode = $xpath->query('//meta[@*="og:image"]'); + foreach ($imageNode as $node) { + if (!empty($node->getAttribute('content'))) { + $metadata->image = $node->getAttribute('content'); + break; + } + } + + return $metadata; + } +} \ No newline at end of file diff --git a/app/MetadataResolver/Resolver.php b/app/MetadataResolver/Resolver.php new file mode 100644 index 0000000..cc54d28 --- /dev/null +++ b/app/MetadataResolver/Resolver.php @@ -0,0 +1,8 @@ +app->singleton(MetadataResolver::class, function ($app) { + return new MetadataResolver(); + }); } } diff --git a/routes/api.php b/routes/api.php index 79ecd98..083a21b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@ get('/user', function (Request $request) { return $request->user(); }); -Route::get('/checkin/card', function (Request $request) { +Route::get('/checkin/card', function (Request $request, MetadataResolver $resolver) { $request->validate([ 'url:required|url' ]); $url = $request->input('url'); - $client = new GuzzleHttp\Client(); - $res = $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); - - $result = [ - 'title' => '', - 'description' => '', - 'image' => '' - ]; - - $titleNode = $xpath->query('//meta[@*="og:title"]'); - foreach ($titleNode as $node) { - if (!empty($node->getAttribute('content'))) { - $result['title'] = $node->getAttribute('content'); - break; - } - } - - $descriptionNode = $xpath->query('//meta[@*="og:description"]'); - foreach ($descriptionNode as $node) { - if (!empty($node->getAttribute('content'))) { - $result['description'] = $node->getAttribute('content'); - break; - } - } - - $imageNode = $xpath->query('//meta[@*="og:image"]'); - foreach ($imageNode as $node) { - if (!empty($node->getAttribute('content'))) { - $result['image'] = $node->getAttribute('content'); - break; - } - } - - // 一部サイトについては別のサムネイルの取得を試みる - if (mb_strpos($url, 'nico.ms/im') !== false || - mb_strpos($url, 'seiga.nicovideo.jp/seiga/im') !== false || - mb_strpos($url, 'sp.seiga.nicovideo.jp/seiga/#!/im') !== false) { - // ニコニコ静画用の処理 - preg_match('~http://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches); - $result['image'] = "http://lohas.nicoseiga.jp/thumb/${matches[1]}l?"; - } elseif (mb_strpos($url, 'nijie.info/view.php')) { - // ニジエ用の処理 - $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'])) { - $result['image'] = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']); - break; - } - } - } - - $response = response()->json($result); - if (!config('app.debug')) { - $response = $response->setCache(['public' => true, 'max_age' => 86400]); - } - return $response; - } else { - abort($res->getStatusCode()); + $metadata = $resolver->resolve($url); + $response = response()->json($metadata); + if (!config('app.debug')) { + $response = $response->setCache(['public' => true, 'max_age' => 86400]); } + return $response; }); \ No newline at end of file diff --git a/tests/Unit/MetadataResolver/OGPResolverTest.php b/tests/Unit/MetadataResolver/OGPResolverTest.php new file mode 100644 index 0000000..a8c0ab0 --- /dev/null +++ b/tests/Unit/MetadataResolver/OGPResolverTest.php @@ -0,0 +1,28 @@ +expectException(ClientException::class); + $resolver->resolve('http://example.com/404'); + } + + public function testResolve() + { + $resolver = new OGPResolver(); + + $metadata = $resolver->resolve('http://ogp.me'); + $this->assertEquals('Open Graph protocol', $metadata->title); + $this->assertEquals('The Open Graph protocol enables any web page to become a rich object in a social graph.', $metadata->description); + $this->assertEquals('http://ogp.me/logo.png', $metadata->image); + } +} From 3b2e81818b534306d5e1431872184f1d618c57d7 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 15 Apr 2018 21:46:49 +0900 Subject: [PATCH 2/4] =?UTF-8?q?sp.nijie.info=E3=81=AE=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=82=92=E6=AD=A3=E3=81=97=E3=81=8F=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #7 --- app/MetadataResolver/NijieResolver.php | 4 + .../MetadataResolver/NijieResolverTest.php | 105 ++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/Unit/MetadataResolver/NijieResolverTest.php diff --git a/app/MetadataResolver/NijieResolver.php b/app/MetadataResolver/NijieResolver.php index f4fdba1..7d3d1d6 100644 --- a/app/MetadataResolver/NijieResolver.php +++ b/app/MetadataResolver/NijieResolver.php @@ -6,6 +6,10 @@ class NijieResolver implements Resolver { public function resolve(string $url): Metadata { + if (mb_strpos($url, '//sp.nijie.info') !== false) { + $url = preg_replace('~//sp\.nijie\.info~', '//nijie.info', $url); + } + $client = new \GuzzleHttp\Client(); $res = $client->get($url); if ($res->getStatusCode() === 200) { diff --git a/tests/Unit/MetadataResolver/NijieResolverTest.php b/tests/Unit/MetadataResolver/NijieResolverTest.php new file mode 100644 index 0000000..0344415 --- /dev/null +++ b/tests/Unit/MetadataResolver/NijieResolverTest.php @@ -0,0 +1,105 @@ +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); + } + + public function testMultiplePicture() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $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); + } + + public function testAnimationGif() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://nijie.info/view.php?id=258078'); + $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title); + $this->assertEquals("以前pixivに投稿したgifアニメ。\r\n気の利いたタイトルが浮かばなかった。", $metadata->description); + $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testMp4Movie() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $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('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testStandardPictureSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $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); + } + + public function testMultiplePictureSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $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); + } + + public function testAnimationGifSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=258078'); + $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title); + $this->assertEquals("以前pixivに投稿したgifアニメ。\r\n気の利いたタイトルが浮かばなかった。", $metadata->description); + $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testMp4MovieSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $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('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } +} From d224e6bba44823da29ca018467ffacb4d357eb70 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 15 Apr 2018 22:27:38 +0900 Subject: [PATCH 3/4] =?UTF-8?q?gif=E3=81=A8mp4=E3=81=AE=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E3=82=B5=E3=83=A0=E3=83=8D=E3=82=A4=E3=83=AB=E3=82=92?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E3=81=95=E3=81=9B=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/MetadataResolver/NijieResolver.php | 2 +- tests/Unit/MetadataResolver/NijieResolverTest.php | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/MetadataResolver/NijieResolver.php b/app/MetadataResolver/NijieResolver.php index 7d3d1d6..9bfef14 100644 --- a/app/MetadataResolver/NijieResolver.php +++ b/app/MetadataResolver/NijieResolver.php @@ -23,7 +23,7 @@ class NijieResolver implements Resolver foreach ($dataNode as $node) { // 改行がそのまま入っていることがあるのでデコード前にエスケープが必要 $imageData = json_decode(preg_replace('/\r?\n/', '\n', $node->nodeValue), true); - if (isset($imageData['thumbnailUrl'])) { + 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; } diff --git a/tests/Unit/MetadataResolver/NijieResolverTest.php b/tests/Unit/MetadataResolver/NijieResolverTest.php index 0344415..5963418 100644 --- a/tests/Unit/MetadataResolver/NijieResolverTest.php +++ b/tests/Unit/MetadataResolver/NijieResolverTest.php @@ -39,8 +39,7 @@ class NijieResolverTest extends TestCase $metadata = $resolver->resolve('https://nijie.info/view.php?id=258078'); $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title); $this->assertEquals("以前pixivに投稿したgifアニメ。\r\n気の利いたタイトルが浮かばなかった。", $metadata->description); - $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); - $this->assertNotRegExp('~/diff/main/~', $metadata->image); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); } public function testMp4Movie() @@ -51,8 +50,7 @@ class NijieResolverTest extends TestCase $metadata = $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('/pic\d+\.nijie\.info/', $metadata->image); - $this->assertNotRegExp('~/diff/main/~', $metadata->image); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); } public function testStandardPictureSp() @@ -87,8 +85,7 @@ class NijieResolverTest extends TestCase $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=258078'); $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title); $this->assertEquals("以前pixivに投稿したgifアニメ。\r\n気の利いたタイトルが浮かばなかった。", $metadata->description); - $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); - $this->assertNotRegExp('~/diff/main/~', $metadata->image); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); } public function testMp4MovieSp() @@ -99,7 +96,6 @@ class NijieResolverTest extends TestCase $metadata = $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('/pic\d+\.nijie\.info/', $metadata->image); - $this->assertNotRegExp('~/diff/main/~', $metadata->image); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); } } From 503e8ba093ee1901ebb0ff639960d2c2cda749d1 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sat, 2 Jun 2018 23:33:16 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E3=80=8C=E5=90=8C=E3=81=98=E3=82=AA?= =?UTF-8?q?=E3=82=AB=E3=82=BA=E3=81=A7=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=80=8D=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Ejaculation.php | 5 +++++ resources/views/ejaculation/edit.blade.php | 2 +- resources/views/ejaculation/show.blade.php | 1 + resources/views/user/profile.blade.php | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Ejaculation.php b/app/Ejaculation.php index 399783a..48bdaf9 100644 --- a/app/Ejaculation.php +++ b/app/Ejaculation.php @@ -27,4 +27,9 @@ class Ejaculation extends Model { return $this->belongsToMany('App\Tag')->withTimestamps(); } + + public function textTags() + { + return implode(' ', $this->tags->map(function ($v) { return $v->name; })->all()); + } } diff --git a/resources/views/ejaculation/edit.blade.php b/resources/views/ejaculation/edit.blade.php index ed94a68..293802d 100644 --- a/resources/views/ejaculation/edit.blade.php +++ b/resources/views/ejaculation/edit.blade.php @@ -37,7 +37,7 @@
- +
    diff --git a/resources/views/ejaculation/show.blade.php b/resources/views/ejaculation/show.blade.php index 6afbfd8..c9006cc 100644 --- a/resources/views/ejaculation/show.blade.php +++ b/resources/views/ejaculation/show.blade.php @@ -28,6 +28,7 @@
    {{ $ejaculatedSpan ?? '精通' }} {{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}
    @if ($user->isMe())
    +
    diff --git a/resources/views/user/profile.blade.php b/resources/views/user/profile.blade.php index e3f3e50..8bc7c14 100644 --- a/resources/views/user/profile.blade.php +++ b/resources/views/user/profile.blade.php @@ -14,6 +14,7 @@
    {{ $ejaculation->ejaculated_span ?? '精通' }} {{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}
    @if ($user->isMe())
    +