commit
55cab01b42
18
app/DeactivatedUser.php
Normal file
18
app/DeactivatedUser.php
Normal 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'
|
||||||
|
];
|
||||||
|
}
|
@ -48,7 +48,7 @@ class RegisterController extends Controller
|
|||||||
protected function validator(array $data)
|
protected function validator(array $data)
|
||||||
{
|
{
|
||||||
$rules = [
|
$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',
|
'email' => 'required|string|email|max:255|unique:users',
|
||||||
'password' => 'required|string|min:6|confirmed'
|
'password' => 'required|string|min:6|confirmed'
|
||||||
];
|
];
|
||||||
|
@ -65,6 +65,10 @@ class EjaculationController extends Controller
|
|||||||
if (!empty($inputs['tags'])) {
|
if (!empty($inputs['tags'])) {
|
||||||
$tags = explode(' ', $inputs['tags']);
|
$tags = explode(' ', $inputs['tags']);
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
|
if ($tag === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$tag = Tag::firstOrCreate(['name' => $tag]);
|
$tag = Tag::firstOrCreate(['name' => $tag]);
|
||||||
$tagIds[] = $tag->id;
|
$tagIds[] = $tag->id;
|
||||||
}
|
}
|
||||||
@ -106,9 +110,7 @@ class EjaculationController extends Controller
|
|||||||
{
|
{
|
||||||
$ejaculation = Ejaculation::findOrFail($id);
|
$ejaculation = Ejaculation::findOrFail($id);
|
||||||
|
|
||||||
if (Auth::user()->cant('edit', $ejaculation)) {
|
$this->authorize('edit', $ejaculation);
|
||||||
abort(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('ejaculation.edit')->with(compact('ejaculation'));
|
return view('ejaculation.edit')->with(compact('ejaculation'));
|
||||||
}
|
}
|
||||||
@ -117,9 +119,7 @@ class EjaculationController extends Controller
|
|||||||
{
|
{
|
||||||
$ejaculation = Ejaculation::findOrFail($id);
|
$ejaculation = Ejaculation::findOrFail($id);
|
||||||
|
|
||||||
if (Auth::user()->cant('edit', $ejaculation)) {
|
$this->authorize('edit', $ejaculation);
|
||||||
abort(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$inputs = $request->all();
|
$inputs = $request->all();
|
||||||
|
|
||||||
@ -155,6 +155,10 @@ class EjaculationController extends Controller
|
|||||||
if (!empty($inputs['tags'])) {
|
if (!empty($inputs['tags'])) {
|
||||||
$tags = explode(' ', $inputs['tags']);
|
$tags = explode(' ', $inputs['tags']);
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
|
if ($tag === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$tag = Tag::firstOrCreate(['name' => $tag]);
|
$tag = Tag::firstOrCreate(['name' => $tag]);
|
||||||
$tagIds[] = $tag->id;
|
$tagIds[] = $tag->id;
|
||||||
}
|
}
|
||||||
@ -172,9 +176,7 @@ class EjaculationController extends Controller
|
|||||||
{
|
{
|
||||||
$ejaculation = Ejaculation::findOrFail($id);
|
$ejaculation = Ejaculation::findOrFail($id);
|
||||||
|
|
||||||
if (Auth::user()->cant('edit', $ejaculation)) {
|
$this->authorize('edit', $ejaculation);
|
||||||
abort(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = User::findOrFail($ejaculation->user_id);
|
$user = User::findOrFail($ejaculation->user_id);
|
||||||
$ejaculation->tags()->detach();
|
$ejaculation->tags()->detach();
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\DeactivatedUser;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class SettingController extends Controller
|
class SettingController extends Controller
|
||||||
{
|
{
|
||||||
@ -67,6 +71,51 @@ class SettingController extends Controller
|
|||||||
return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');
|
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()
|
// public function password()
|
||||||
// {
|
// {
|
||||||
|
37
app/Http/Controllers/TagController.php
Normal file
37
app/Http/Controllers/TagController.php
Normal 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'));
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,10 @@ class ActivityPubResolver implements Resolver, Parser
|
|||||||
$activityOrObject = json_decode($json, true);
|
$activityOrObject = json_decode($json, true);
|
||||||
$object = $activityOrObject['object'] ?? $activityOrObject;
|
$object = $activityOrObject['object'] ?? $activityOrObject;
|
||||||
|
|
||||||
|
if ($object['type'] !== 'Note') {
|
||||||
|
throw new UnsupportedContentException('Unsupported object type: ' . $object['type']);
|
||||||
|
}
|
||||||
|
|
||||||
$metadata = new Metadata();
|
$metadata = new Metadata();
|
||||||
|
|
||||||
$metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : '';
|
$metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : '';
|
||||||
|
@ -53,17 +53,19 @@ class DLsiteResolver implements Resolver
|
|||||||
public function resolve(string $url): Metadata
|
public function resolve(string $url): Metadata
|
||||||
{
|
{
|
||||||
//アフィリエイトの場合は普通のURLに変換
|
//アフィリエイトの場合は普通のURLに変換
|
||||||
if (strpos($url, '/dlaf/=/link/') !== false) {
|
// ID型
|
||||||
preg_match('~www\.dlsite\.com/(?P<genre>.+)/dlaf/=/link/work/aid/.+/id/(?P<titleId>..\d+)(\.html)?~', $url, $matches);
|
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 = "https://www.dlsite.com/{$matches['genre']}/work/=/product_id/{$matches['titleId']}.html";
|
||||||
}
|
}
|
||||||
|
// URL型
|
||||||
if (strpos($url, '/dlaf/=/aid/') !== false) {
|
if (strpos($url, '/dlaf/=/aid/') !== false) {
|
||||||
preg_match('~www\.dlsite\.com/.+/dlaf/=/aid/.+/url/(?P<url>.+)~', $url, $matches);
|
preg_match('~www\.dlsite\.com/.+/dlaf/=/aid/.+/url/(?P<url>.+)~', $url, $matches);
|
||||||
$affiliate_url = urldecode($matches['url']);
|
$affiliateUrl = urldecode($matches['url']);
|
||||||
if (preg_match('~www\.dlsite\.com/.+/(work|announce)/=/product_id/..\d+(\.html)?~', $affiliate_url, $matches)) {
|
if (preg_match('~www\.dlsite\.com/.+/(work|announce)/=/product_id/..\d+(\.html)?~', $affiliateUrl, $matches)) {
|
||||||
$url = $affiliate_url;
|
$url = $affiliateUrl;
|
||||||
} else {
|
} else {
|
||||||
throw new \RuntimeException("アフィリエイト先のリンクがDLsiteのタイトルではありません: $affiliate_url");
|
throw new \RuntimeException("アフィリエイト先のリンクがDLsiteのタイトルではありません: $affiliateUrl");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
app/MetadataResolver/HentaiFoundryResolver.php
Normal file
56
app/MetadataResolver/HentaiFoundryResolver.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
36
app/MetadataResolver/Kb10uyShortStoryServerResolver.php
Normal file
36
app/MetadataResolver/Kb10uyShortStoryServerResolver.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,11 @@ class MetadataResolver implements Resolver
|
|||||||
'~ec\.toranoana\.(jp|shop)/(tora|joshi)(_[rd]+)?/(ec|digi)/item/~' => ToranoanaResolver::class,
|
'~ec\.toranoana\.(jp|shop)/(tora|joshi)(_[rd]+)?/(ec|digi)/item/~' => ToranoanaResolver::class,
|
||||||
'~iwara\.tv/(videos|images)/.*~' => IwaraResolver::class,
|
'~iwara\.tv/(videos|images)/.*~' => IwaraResolver::class,
|
||||||
'~www\.dlsite\.com/.*/(work|announce)/=/product_id/..\d+(\.html)?~' => DLsiteResolver::class,
|
'~www\.dlsite\.com/.*/(work|announce)/=/product_id/..\d+(\.html)?~' => DLsiteResolver::class,
|
||||||
'~www\.dlsite\.com/.*/dlaf/=/link/(work|announce)/aid/.+/..\d+(\.html)?~' => DLsiteResolver::class,
|
'~www\.dlsite\.com/.*/dlaf/=(/.+/.+)?/link/work/aid/.+(/id)?/..\d+(\.html)?~' => DLsiteResolver::class,
|
||||||
'~www\.dlsite\.com/.*/dlaf/=/aid/.+/url/.+~' => DLsiteResolver::class,
|
'~www\.dlsite\.com/.*/dlaf/=/aid/.+/url/.+~' => DLsiteResolver::class,
|
||||||
'~dlsite\.jp/...tw/..\d+~' => DLsiteResolver::class,
|
'~dlsite\.jp/...tw/..\d+~' => DLsiteResolver::class,
|
||||||
'~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class,
|
'~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class,
|
||||||
'~www\.pixiv\.net/artworks/\d+~' => PixivResolver::class,
|
'~www\.pixiv\.net/(en/)?artworks/\d+~' => PixivResolver::class,
|
||||||
'~www\.pixiv\.net/user/\d+/series/\d+~' => PixivResolver::class,
|
'~www\.pixiv\.net/user/\d+/series/\d+~' => PixivResolver::class,
|
||||||
'~fantia\.jp/posts/\d+~' => FantiaResolver::class,
|
'~fantia\.jp/posts/\d+~' => FantiaResolver::class,
|
||||||
'~dmm\.co\.jp/~' => FanzaResolver::class,
|
'~dmm\.co\.jp/~' => FanzaResolver::class,
|
||||||
@ -32,6 +32,8 @@ class MetadataResolver implements Resolver
|
|||||||
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
|
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
|
||||||
'~store\.steampowered\.com/app/\d+~' => SteamResolver::class,
|
'~store\.steampowered\.com/app/\d+~' => SteamResolver::class,
|
||||||
'~www\.xtube\.com/video-watch/.*-\d+$~'=> XtubeResolver::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 = [
|
public $mimeTypes = [
|
||||||
@ -47,16 +49,19 @@ class MetadataResolver implements Resolver
|
|||||||
{
|
{
|
||||||
foreach ($this->rules as $pattern => $class) {
|
foreach ($this->rules as $pattern => $class) {
|
||||||
if (preg_match($pattern, $url) === 1) {
|
if (preg_match($pattern, $url) === 1) {
|
||||||
|
try {
|
||||||
/** @var Resolver $resolver */
|
/** @var Resolver $resolver */
|
||||||
$resolver = app($class);
|
$resolver = app($class);
|
||||||
|
|
||||||
return $resolver->resolve($url);
|
return $resolver->resolve($url);
|
||||||
|
} catch (UnsupportedContentException $e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->resolveWithAcceptHeader($url);
|
try {
|
||||||
if ($result !== null) {
|
return $this->resolveWithAcceptHeader($url);
|
||||||
return $result;
|
} catch (UnsupportedContentException $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->defaultResolver)) {
|
if (isset($this->defaultResolver)) {
|
||||||
@ -69,7 +74,7 @@ class MetadataResolver implements Resolver
|
|||||||
throw new \UnexpectedValueException('URL not matched.');
|
throw new \UnexpectedValueException('URL not matched.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolveWithAcceptHeader(string $url): ?Metadata
|
public function resolveWithAcceptHeader(string $url): Metadata
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Rails等はAcceptに */* が入っていると、ブラウザの適当なAcceptヘッダだと判断して全部無視してしまう。
|
// Rails等はAcceptに */* が入っていると、ブラウザの適当なAcceptヘッダだと判断して全部無視してしまう。
|
||||||
@ -114,6 +119,6 @@ class MetadataResolver implements Resolver
|
|||||||
// 5xx は変なAcceptが原因かもしれない(?)ので無視してフォールバック
|
// 5xx は変なAcceptが原因かもしれない(?)ので無視してフォールバック
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
throw new UnsupportedContentException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,16 @@ class NijieResolver implements Resolver
|
|||||||
$metadata = $this->ogpResolver->parse($html);
|
$metadata = $this->ogpResolver->parse($html);
|
||||||
$crawler = new Crawler($html);
|
$crawler = new Crawler($html);
|
||||||
|
|
||||||
// DomCrawler内でjson内の日本語がHTMLエンティティに変換されるのでhtml_entity_decode
|
$json = $crawler->filter('script[type="application/ld+json"]')->first()->text();
|
||||||
$json = html_entity_decode($crawler->filter('script[type="application/ld+json"]')->first()->text());
|
|
||||||
|
|
||||||
// 改行がそのまま入っていることがあるのでデコード前にエスケープが必要
|
// 改行がそのまま入っていることがあるのでデコード前にエスケープが必要
|
||||||
$data = json_decode(preg_replace('/\r?\n/', '\n', $json), true);
|
$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->title = $data['name'];
|
||||||
$metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description'];
|
$metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description'];
|
||||||
if (
|
if (
|
||||||
|
@ -45,8 +45,8 @@ class PixivResolver implements Resolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
$page = 0;
|
$page = 0;
|
||||||
if (preg_match('~www\.pixiv\.net/artworks/(\d+)~', $url, $matches)) {
|
if (preg_match('~www\.pixiv\.net/(en/)?artworks/(?P<illustId>\d+)~', $url, $matches)) {
|
||||||
$illustId = $matches[1];
|
$illustId = $matches['illustId'];
|
||||||
} else {
|
} else {
|
||||||
parse_str(parse_url($url, PHP_URL_QUERY), $params);
|
parse_str(parse_url($url, PHP_URL_QUERY), $params);
|
||||||
$illustId = $params['illust_id'];
|
$illustId = $params['illust_id'];
|
||||||
|
12
app/MetadataResolver/UnsupportedContentException.php
Normal file
12
app/MetadataResolver/UnsupportedContentException.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\MetadataResolver;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* このResolverやParserが対応していないサイトであったことを表わします。
|
||||||
|
*/
|
||||||
|
class UnsupportedContentException extends Exception
|
||||||
|
{
|
||||||
|
}
|
@ -11,10 +11,15 @@ class XtubeResolver implements Resolver
|
|||||||
* @var Client
|
* @var Client
|
||||||
*/
|
*/
|
||||||
private $client;
|
private $client;
|
||||||
|
/**
|
||||||
|
* @var OGPResolver
|
||||||
|
*/
|
||||||
|
private $ogpResolver;
|
||||||
|
|
||||||
public function __construct(Client $client)
|
public function __construct(Client $client, OGPResolver $ogpResolver)
|
||||||
{
|
{
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
|
$this->ogpResolver = $ogpResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolve(string $url): Metadata
|
public function resolve(string $url): Metadata
|
||||||
@ -25,17 +30,14 @@ class XtubeResolver implements Resolver
|
|||||||
|
|
||||||
$res = $this->client->get($url);
|
$res = $this->client->get($url);
|
||||||
$html = (string) $res->getBody();
|
$html = (string) $res->getBody();
|
||||||
$metadata = new Metadata();
|
$metadata = $this->ogpResolver->parse($html);
|
||||||
$crawler = new Crawler($html);
|
$crawler = new Crawler($html);
|
||||||
|
|
||||||
// poster URL抽出
|
|
||||||
$playerConfig = explode("\n", trim($crawler->filter('#playerWrapper script')->last()->text()));
|
|
||||||
preg_match('~https:\\\/\\\/cdn\d+-s-hw-e5\.xtube\.com\\\/m=(?P<size>.{8})\\\/videos\\\/\d{6}\\\/\d{2}\\\/.{5}-.{4}-\\\/original\\\/\d+\.jpg~', $playerConfig[0], $matches);
|
|
||||||
$metadata->image = str_replace('\/', '/', $matches[0]);
|
|
||||||
|
|
||||||
$metadata->title = trim($crawler->filter('.underPlayerRateForm h1')->text(''));
|
$metadata->title = trim($crawler->filter('.underPlayerRateForm h1')->text(''));
|
||||||
$metadata->description = trim($crawler->filter('.fullDescription ')->text(''));
|
$metadata->description = trim($crawler->filter('.fullDescription ')->text(''));
|
||||||
$metadata->tags = $crawler->filter('.tagsCategories a')->extract('_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;
|
return $metadata;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,11 @@ class User extends Authenticatable
|
|||||||
return Auth::check() && $this->id === Auth::user()->id;
|
return Auth::check() && $this->id === Auth::user()->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ejaculations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Ejaculation::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function likes()
|
public function likes()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Like::class);
|
return $this->hasMany(Like::class);
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"doctrine/dbal": "^2.9",
|
"doctrine/dbal": "^2.9",
|
||||||
"fideloper/proxy": "~3.3",
|
"fideloper/proxy": "~3.3",
|
||||||
"guzzlehttp/guzzle": "^6.3",
|
"guzzlehttp/guzzle": "^6.3",
|
||||||
|
"jakeasmith/http_build_url": "^1.0",
|
||||||
"laravel/framework": "5.5.*",
|
"laravel/framework": "5.5.*",
|
||||||
"laravel/tinker": "~1.0",
|
"laravel/tinker": "~1.0",
|
||||||
"misd/linkify": "^1.1",
|
"misd/linkify": "^1.1",
|
||||||
|
35
composer.lock
generated
35
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "28abd730d4572663d10ae815393c73cd",
|
"content-hash": "b6dfb80c350a7276bb2513a1aeb3d602",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anhskohbo/no-captcha",
|
"name": "anhskohbo/no-captcha",
|
||||||
@ -806,6 +806,39 @@
|
|||||||
],
|
],
|
||||||
"time": "2019-07-01T23:21:34+00:00"
|
"time": "2019-07-01T23:21:34+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jakeasmith/http_build_url",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jakeasmith/http_build_url.git",
|
||||||
|
"reference": "93c273e77cb1edead0cf8bcf8cd2003428e74e37"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/jakeasmith/http_build_url/zipball/93c273e77cb1edead0cf8bcf8cd2003428e74e37",
|
||||||
|
"reference": "93c273e77cb1edead0cf8bcf8cd2003428e74e37",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/http_build_url.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jake A. Smith",
|
||||||
|
"email": "theman@jakeasmith.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides functionality for http_build_url() to environments without pecl_http.",
|
||||||
|
"time": "2017-05-01T15:36:40+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "jakub-onderka/php-console-color",
|
"name": "jakub-onderka/php-console-color",
|
||||||
"version": "v0.2",
|
"version": "v0.2",
|
||||||
|
12
database/factories/EjaculationFactory.php
Normal file
12
database/factories/EjaculationFactory.php
Normal 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,
|
||||||
|
];
|
||||||
|
});
|
10
database/factories/LikeFactory.php
Normal file
10
database/factories/LikeFactory.php
Normal 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 [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
});
|
@ -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),
|
|
||||||
];
|
|
||||||
});
|
|
21
database/factories/UserFactory.php
Normal file
21
database/factories/UserFactory.php
Normal 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,
|
||||||
|
]);
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
3
resources/assets/js/app.js
vendored
3
resources/assets/js/app.js
vendored
@ -85,6 +85,9 @@ $(() => {
|
|||||||
if (xhr.status === 409) {
|
if (xhr.status === 409) {
|
||||||
callback(JSON.parse(xhr.responseText));
|
callback(JSON.parse(xhr.responseText));
|
||||||
return;
|
return;
|
||||||
|
} else if (xhr.status === 401) {
|
||||||
|
alert('いいねするためにはログインしてください。');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(xhr);
|
console.error(xhr);
|
||||||
|
5
resources/assets/js/setting/deactivate.js
vendored
Normal file
5
resources/assets/js/setting/deactivate.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
$('#deactivate-form').on('submit', function () {
|
||||||
|
if (!confirm('本当にアカウントを削除してもよろしいですか?')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
3
resources/assets/sass/app.scss
vendored
3
resources/assets/sass/app.scss
vendored
@ -13,3 +13,6 @@ $primary: #e53fb1;
|
|||||||
// Components
|
// Components
|
||||||
@import "components/ejaculation";
|
@import "components/ejaculation";
|
||||||
@import "components/link-card";
|
@import "components/link-card";
|
||||||
|
|
||||||
|
// Tag
|
||||||
|
@import "tag/index";
|
||||||
|
22
resources/assets/sass/tag/_index.scss
vendored
Normal file
22
resources/assets/sass/tag/_index.scss
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,9 @@
|
|||||||
<a href="{{ route('user.likes', ['name' => Auth::user()->name]) }}" class="dropdown-item">いいね</a>
|
<a href="{{ route('user.likes', ['name' => Auth::user()->name]) }}" class="dropdown-item">いいね</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a href="{{ route('setting') }}" class="dropdown-item">設定</a>
|
<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>
|
<a href="{{ route('logout') }}" class="dropdown-item" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">ログアウト</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -79,6 +82,9 @@
|
|||||||
<li class="nav-item {{ stripos(Route::currentRouteName(), 'user.okazu') === 0 ? 'active' : ''}}">
|
<li class="nav-item {{ stripos(Route::currentRouteName(), 'user.okazu') === 0 ? 'active' : ''}}">
|
||||||
<a class="nav-link" href="{{ route('user.okazu', ['name' => Auth::user()->name]) }}">オカズ</a>
|
<a class="nav-link" href="{{ route('user.okazu', ['name' => Auth::user()->name]) }}">オカズ</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item {{ stripos(Route::currentRouteName(), 'tag') === 0 ? 'active' : ''}}">
|
||||||
|
<a class="nav-link" href="{{ route('tag') }}">タグ一覧</a>
|
||||||
|
</li>
|
||||||
{{--<li class="nav-item">
|
{{--<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ route('ranking') }}">ランキング</a>
|
<a class="nav-link" href="{{ route('ranking') }}">ランキング</a>
|
||||||
</li>--}}
|
</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>
|
<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>
|
</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="row mt-2">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<a class="btn btn-outline-secondary" href="{{ route('ranking') }}">ランキング</a>
|
<a class="btn btn-outline-secondary" href="{{ route('ranking') }}">ランキング</a>
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a>
|
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' : '' }}"
|
<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>
|
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' : '' }}"
|
{{--<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>--}}
|
href="{{ route('setting.password') }}"><span class="oi oi-key mr-1"></span> パスワード</a>--}}
|
||||||
</div>
|
</div>
|
||||||
|
32
resources/views/setting/deactivate.blade.php
Normal file
32
resources/views/setting/deactivate.blade.php
Normal 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
|
16
resources/views/setting/deactivated.blade.php
Normal file
16
resources/views/setting/deactivated.blade.php
Normal 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
|
20
resources/views/tag/index.blade.php
Normal file
20
resources/views/tag/index.blade.php
Normal 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
|
@ -36,6 +36,8 @@ Route::middleware('auth')->group(function () {
|
|||||||
Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update');
|
Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update');
|
||||||
Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy');
|
Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy');
|
||||||
Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update');
|
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');
|
// 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/checkin', 'SearchController@index')->name('search');
|
||||||
Route::get('/search/related-tag', 'SearchController@relatedTag')->name('search.related-tag');
|
Route::get('/search/related-tag', 'SearchController@relatedTag')->name('search.related-tag');
|
||||||
|
|
||||||
|
Route::get('/tag', 'TagController@index')->name('tag');
|
||||||
|
|
||||||
Route::middleware('can:admin')
|
Route::middleware('can:admin')
|
||||||
->namespace('Admin')
|
->namespace('Admin')
|
||||||
->prefix('admin')
|
->prefix('admin')
|
||||||
|
64
tests/Feature/SettingTest.php
Normal file
64
tests/Feature/SettingTest.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $this->getCsrfToken($user, '/setting/deactivate');
|
||||||
|
$response = $this->actingAs($user)
|
||||||
|
->followingRedirects()
|
||||||
|
->post('/setting/deactivate', [
|
||||||
|
'_token' => $token,
|
||||||
|
'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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* テスト対象を呼び出す前にGETリクエストを行い、CSRFトークンを得る
|
||||||
|
* @param Authenticatable $user 認証情報
|
||||||
|
* @param string $uri リクエスト先
|
||||||
|
* @return string CSRFトークン
|
||||||
|
*/
|
||||||
|
private function getCsrfToken(Authenticatable $user, string $uri): string
|
||||||
|
{
|
||||||
|
$response = $this->actingAs($user)->get($uri);
|
||||||
|
$crawler = new Crawler($response->getContent());
|
||||||
|
|
||||||
|
return $crawler->filter('input[name=_token]')->attr('value');
|
||||||
|
}
|
||||||
|
}
|
@ -227,7 +227,7 @@ class DLsiteResolverTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAffiliateLink()
|
public function testOldAffiliateLink()
|
||||||
{
|
{
|
||||||
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
|
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
|
||||||
|
|
||||||
@ -243,6 +243,38 @@ class DLsiteResolverTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
public function testAffiliateUrl()
|
||||||
{
|
{
|
||||||
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
|
$responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html');
|
||||||
|
36
tests/Unit/MetadataResolver/HentaiFoundryResolverTest.php
Normal file
36
tests/Unit/MetadataResolver/HentaiFoundryResolverTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -129,4 +129,23 @@ class NijieResolverTest extends TestCase
|
|||||||
$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 testHasHtmlInAuthorProfile()
|
||||||
|
{
|
||||||
|
$responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testHasHtmlInAuthorProfileResponse.html');
|
||||||
|
|
||||||
|
$this->createResolver(NijieResolver::class, $responseText);
|
||||||
|
|
||||||
|
$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=285698', (string) $this->handler->getLastRequest()->getUri());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,4 +81,20 @@ class PixivResolverTest extends TestCase
|
|||||||
$this->assertSame('https://www.pixiv.net/ajax/illust/68188073', (string) $this->handler->getLastRequest()->getUri());
|
$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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
350
tests/fixture/HentaiFoundry/illust.html
vendored
Normal file
350
tests/fixture/HentaiFoundry/illust.html
vendored
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="//img.hentai-foundry.com/themes/Dark/css/default.css?ver=1497240029" title="Default CSS" />
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="//img.hentai-foundry.com/themes/Dark/favicon.ico" type="image/x-icon" />
|
||||||
|
<link rel="search" type="application/opensearchdescription+xml"
|
||||||
|
title="Search Hentai Foundry"
|
||||||
|
href="/search/OpenSearchDescription"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js">
|
||||||
|
</script>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<meta name="description" content="gift for Liru" /> <meta name="keywords" content="witch futa" />
|
||||||
|
<link title="Comments Feed" rel="alternate" type="application/atom+xml" href="/feed/PictureComments/id/723498" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="//img.hentai-foundry.com/themes/default/js/qtip2.2.1/jquery.qtip.min.css" />
|
||||||
|
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="//img.hentai-foundry.com/themes/default/js/qtip2.2.1/jquery.qtip.min.js" async="async"></script>
|
||||||
|
<title>Witchcraft by DevilHS - Hentai Foundry</title>
|
||||||
|
<script type="text/javascript" src="//img.hentai-foundry.com/themes/default/js/util.js?ver=20170507"></script>
|
||||||
|
<link rel="meta" href="//img.hentai-foundry.com/themes/Hentai/labels.rdf" type="application/rdf+xml" title="ICRA labels" />
|
||||||
|
<meta http-equiv="PICS-Label" content='(PICS-1.1 "http://www.icra.org/pics/vocabularyv03/" l gen true for "http://hentai-foundry.com" r (n 3 s 3 v 0 l 2 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 3) gen true for "http://www.hentai-foundry.com" r (n 3 s 3 v 0 l 2 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 3))' />
|
||||||
|
<meta http-equiv="PICS-Label" content='(PICS-1.1 "http://www.classify.org/safesurf/" L gen true for "http://www.hentai-foundry.com/" r (SS~~000 9 SS~~001 5 SS~~002 5 SS~~003 5 SS~~004 9 SS~~005 5 SS~~007 5 SS~~008 5 SS~~009 9 SS~~00A 5))' />
|
||||||
|
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="//img.hentai-foundry.com/themes/default/css/activebar.css" />
|
||||||
|
<script type="text/javascript" src="//img.hentai-foundry.com/themes/default/js/activebar.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var noadblocker=false;
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="//img.hentai-foundry.com/themes/default/js/ads.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
if(!noadblocker)
|
||||||
|
document.addEventListener("DOMContentLoaded", function() { setTimeout(adBar, 1000) } );
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body id="pictures_view">
|
||||||
|
<header>
|
||||||
|
<a href="/site/index">
|
||||||
|
<img src="//img.hentai-foundry.com/themes/Dark/images/logo.png" border="0" alt="Logo" title="Logo" id="logo"/>
|
||||||
|
</a>
|
||||||
|
<div id="searchBox">
|
||||||
|
<form action="/search/index" method="get">
|
||||||
|
<input type="text" name="query" />
|
||||||
|
<input type="submit" value="Search" />
|
||||||
|
<br />
|
||||||
|
<a class="navlink" href="/search/index">Advanced Search</a> </form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='headerLogin'>
|
||||||
|
<form action="/site/login" method="post">
|
||||||
|
<input type="hidden" value="SHpiT1dzOXpYRU50OVZzRXZOSWp-dzZnTTlmV3U1ck16J9k70kDwVW5YeySDxE7l5lbXi0wmIwy8jihTTFdr3w==" name="YII_CSRF_TOKEN" /> Username <input type="text" name="LoginForm[username]" />
|
||||||
|
Password <input type="password" name="LoginForm[password]" />
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
<br />
|
||||||
|
Remember <input type="checkbox" value="1" name="LoginForm[rememberMe]" style="background: #676573;" />
|
||||||
|
<a class='navlink' href="/users/create">Register</a>
|
||||||
|
| <a class='navlink' href="/users/LostPassword">Forgot your password?</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav id="mainmenu">
|
||||||
|
<input id="menuToggleCheckbox" type="checkbox" />
|
||||||
|
<label for="menuToggleCheckbox" id="menuToggle">≡<span>MENU</span></label>
|
||||||
|
<div id="filtersButton"><a onClick="ths = $(this); jQuery('#FilterBox').css({top: ths.offset().top, left: ths.offset().left + ths.outerWidth() + 10}); jQuery('#FilterBox').toggle(500); return false;" href="#"><i class="fa fa-list"></i> FILTERS</a></div><aside class="box lvl1" id="FilterBox">
|
||||||
|
<h2 class="titleSemantic">Filters</h2><div class="boxheader">
|
||||||
|
<div class="boxtitle">Filters <a style="float: right;" href="#" onClick="jQuery('#FilterBox').toggle(500); return false;">X</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<form action="/pictures/user/DevilHS/723498/Witchcraft" method="post">
|
||||||
|
<input type="hidden" value="SHpiT1dzOXpYRU50OVZzRXZOSWp-dzZnTTlmV3U1ck16J9k70kDwVW5YeySDxE7l5lbXi0wmIwy8jihTTFdr3w==" name="YII_CSRF_TOKEN" /><div class='filter_div rating_nudity'>
|
||||||
|
<label for='rating_nudity'><span style='font-size: 75%'>Show</span> Nudity <span class='rating lvl2' title='Nudity'>N</span></label><select class="ratingListBox" name="rating_nudity" id="rating_nudity">
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="1">Mild Nudity</option>
|
||||||
|
<option value="2">Moderate Nudity</option>
|
||||||
|
<option value="3" selected="selected">Explicit Nudity</option>
|
||||||
|
</select></div><div class='filter_div rating_violence'>
|
||||||
|
<label for='rating_violence'><span style='font-size: 75%'>Show</span> Violence <span class='rating lvl2' title='Violence'>V</span></label><select class="ratingListBox" name="rating_violence" id="rating_violence">
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="1">Comic or Mild Violence</option>
|
||||||
|
<option value="2">Moderate Violence</option>
|
||||||
|
<option value="3" selected="selected">Explicit or Graphic Violence</option>
|
||||||
|
</select></div><div class='filter_div rating_profanity'>
|
||||||
|
<label for='rating_profanity'><span style='font-size: 75%'>Show</span> Profanity <span class='rating lvl2' title='Profanity'>L</span></label><select class="ratingListBox" name="rating_profanity" id="rating_profanity">
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="1">Mild Profanity</option>
|
||||||
|
<option value="2">Moderate Profanity</option>
|
||||||
|
<option value="3" selected="selected">Proliferous or Severe Profanity</option>
|
||||||
|
</select></div><div class='filter_div rating_racism'>
|
||||||
|
<label for='rating_racism'><span style='font-size: 75%'>Show</span> Racism <span class='rating lvl2' title='Racism'>R</span></label><select class="ratingListBox" name="rating_racism" id="rating_racism">
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="1">Mild Racist themes or content</option>
|
||||||
|
<option value="2">Racist themes or content</option>
|
||||||
|
<option value="3" selected="selected">Strong racist themes or content</option>
|
||||||
|
</select></div><div class='filter_div rating_sex'>
|
||||||
|
<label for='rating_sex'><span style='font-size: 75%'>Show</span> Sexual content <span class='rating lvl2' title='Sexual content'>Sx</span></label><select class="ratingListBox" name="rating_sex" id="rating_sex">
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="1">Mild suggestive content</option>
|
||||||
|
<option value="2">Moderate suggestive or sexual content</option>
|
||||||
|
<option value="3" selected="selected">Explicit or adult sexual content</option>
|
||||||
|
</select></div><div class='filter_div rating_spoilers'>
|
||||||
|
<label for='rating_spoilers'><span style='font-size: 75%'>Show</span> Spoiler Warning <span class='rating lvl2' title='Spoiler Warning'>Sp</span></label><select class="ratingListBox" name="rating_spoilers" id="rating_spoilers">
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="1">Mild Spoiler Warning</option>
|
||||||
|
<option value="2">Moderate Spoiler Warning</option>
|
||||||
|
<option value="3" selected="selected">Major Spoiler Warning</option>
|
||||||
|
</select></div><div class='filter_div rating_yaoi'>
|
||||||
|
<label for='rating_yaoi'><span style='font-size: 75%'>Show</span> Yaoi <span class='rating lvl2' title='Shonen-ai (male homosexual) context'>♂♂</span></label><input type="hidden" value="0" name="rating_yaoi" /><input class="ratingCheckbox" checked="checked" type="checkbox" value="1" name="rating_yaoi" id="rating_yaoi" /></div><div class='filter_div rating_yuri'>
|
||||||
|
<label for='rating_yuri'><span style='font-size: 75%'>Show</span> Yuri <span class='rating lvl2' title='Shoujo-ai (female homosexual) context'>♀♀</span></label><input type="hidden" value="0" name="rating_yuri" /><input class="ratingCheckbox" checked="checked" type="checkbox" value="1" name="rating_yuri" id="rating_yuri" /></div><div class='filter_div rating_teen'>
|
||||||
|
<label for='rating_teen'><span style='font-size: 75%'>Show</span> Teen <span class='rating lvl2' title='Teen content'>T</span></label><input type="hidden" value="0" name="rating_teen" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_teen" id="rating_teen" /></div><div class='filter_div rating_guro'>
|
||||||
|
<label for='rating_guro'><span style='font-size: 75%'>Show</span> Guro <span class='rating lvl2' title='Gore, scat, similar macabre content'>G</span></label><input type="hidden" value="0" name="rating_guro" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_guro" id="rating_guro" /></div><div class='filter_div rating_furry'>
|
||||||
|
<label for='rating_furry'><span style='font-size: 75%'>Show</span> Furry <span class='rating lvl2' title='Anthropomorphic/furry content'>F</span></label><input type="hidden" value="0" name="rating_furry" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_furry" id="rating_furry" /></div><div class='filter_div rating_beast'>
|
||||||
|
<label for='rating_beast'><span style='font-size: 75%'>Show</span> Beast <span class='rating lvl2' title='Bestiality'>B</span></label><input type="hidden" value="0" name="rating_beast" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_beast" id="rating_beast" /></div><div class='filter_div rating_male'>
|
||||||
|
<label for='rating_male'><span style='font-size: 75%'>Show</span> Male <span class='rating lvl2' title='Contains male nudity'>♂</span></label><input type="hidden" value="0" name="rating_male" /><input class="ratingCheckbox" checked="checked" type="checkbox" value="1" name="rating_male" id="rating_male" /></div><div class='filter_div rating_female'>
|
||||||
|
<label for='rating_female'><span style='font-size: 75%'>Show</span> Female <span class='rating lvl2' title='Contains female nudity'>♀</span></label><input type="hidden" value="0" name="rating_female" /><input class="ratingCheckbox" checked="checked" type="checkbox" value="1" name="rating_female" id="rating_female" /></div><div class='filter_div rating_futa'>
|
||||||
|
<label for='rating_futa'><span style='font-size: 75%'>Show</span> Futa <span class='rating lvl2' title='Contains Futanari/Dickgirl/Transgender/Hermaphrodite subject'>Tg</span></label><input type="hidden" value="0" name="rating_futa" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_futa" id="rating_futa" /></div><div class='filter_div rating_other'>
|
||||||
|
<label for='rating_other'><span style='font-size: 75%'>Show</span> Other <span class='rating lvl2' title='Other offensive content'>!?</span></label><input type="hidden" value="0" name="rating_other" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_other" id="rating_other" /></div><div class='filter_div rating_scat'>
|
||||||
|
<label for='rating_scat'><span style='font-size: 75%'>Show</span> Scat <span class='rating lvl2' title='Scat/Coprophilia/Feces'>💩</span></label><input type="hidden" value="0" name="rating_scat" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_scat" id="rating_scat" /></div><div class='filter_div rating_incest'>
|
||||||
|
<label for='rating_incest'><span style='font-size: 75%'>Show</span> Incest <span class='rating lvl2' title='Incest'>I</span></label><input type="hidden" value="0" name="rating_incest" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_incest" id="rating_incest" /></div><div class='filter_div rating_rape'>
|
||||||
|
<label for='rating_rape'><span style='font-size: 75%'>Show</span> Rape <span class='rating lvl2' title='Non-consensual/Rape/Forced'>Nc</span></label><input type="hidden" value="0" name="rating_rape" /><input class="ratingCheckbox" type="checkbox" value="1" name="rating_rape" id="rating_rape" /></div><br /><div class='filter_div filter_media'><label for="filter_media">Limit Media to</label><select name="filter_media" id="filter_media">
|
||||||
|
<option value="A" selected="selected">All</option>
|
||||||
|
<optgroup label="Traditional media">
|
||||||
|
<optgroup label=".. Drawings">
|
||||||
|
<option value="1">Charcoal</option>
|
||||||
|
<option value="2">Colored Pencil / Crayon</option>
|
||||||
|
<option value="3">Ink or markers</option>
|
||||||
|
<option value="4">Oil pastels</option>
|
||||||
|
<option value="5">Graphite pencil</option>
|
||||||
|
<option value="6">Other drawing</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label=".. Paintings">
|
||||||
|
<option value="11">Airbrush</option>
|
||||||
|
<option value="12">Acrylics</option>
|
||||||
|
<option value="13">Oils</option>
|
||||||
|
<option value="14">Watercolor</option>
|
||||||
|
<option value="15">Other painting</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label=".. Crafts / Physical art">
|
||||||
|
<option value="21">Plushies</option>
|
||||||
|
<option value="22">Sculpture</option>
|
||||||
|
<option value="23">Other crafts</option>
|
||||||
|
</optgroup>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Digital media (CG)">
|
||||||
|
<option value="31">3D modelling</option>
|
||||||
|
<option value="33">Digital drawing or painting</option>
|
||||||
|
<option value="36">MS Paint</option>
|
||||||
|
<option value="32">Oekaki</option>
|
||||||
|
<option value="34">Pixel art</option>
|
||||||
|
<option value="35">Other digital art</option>
|
||||||
|
</optgroup>
|
||||||
|
<option value="0">Unspecified</option>
|
||||||
|
</select></div><div class='filter_div filter_order'><label for="filter_order">Sort By</label><select name="filter_order" id="filter_order">
|
||||||
|
<option value="date_new" selected="selected">Date Submitted (Newest)</option>
|
||||||
|
<option value="date_old">Date Submitted (Oldest)</option>
|
||||||
|
<option value="update_new">Date updated (Newest)</option>
|
||||||
|
<option value="update_old">Date updated (Oldest)</option>
|
||||||
|
<option value="a-z">Title A-z</option>
|
||||||
|
<option value="z-a">Title z-A</option>
|
||||||
|
<option value="views most">Views (most first)</option>
|
||||||
|
<option value="rating highest">rating (highest first)</option>
|
||||||
|
<option value="comments most">Comments (most first)</option>
|
||||||
|
<option value="faves most">Faves (most first)</option>
|
||||||
|
<option value="popularity most">Popularity (highest first)</option>
|
||||||
|
</select></div><div class='filter_div filter_type'><label for="filter_type">Limit Pictures to</label><select name="filter_type" id="filter_type">
|
||||||
|
<option value="0" selected="selected">All</option>
|
||||||
|
<option value="1">Regular Pictures</option>
|
||||||
|
<option value="2">Flash Submissions</option>
|
||||||
|
</select></div><input type="submit" name="yt1" value="Apply" id="yt1" /><input onClick="jQuery('#FilterBox').toggle(500);" name="yt2" type="button" value="Close" /></form></div>
|
||||||
|
</aside><ul id="yw12">
|
||||||
|
<li><a href="/site/about">About</a></li>
|
||||||
|
<li><a href="//forums.hentai-foundry.com/viewforum.php?f=13">FAQ</a></li>
|
||||||
|
<li><a href="//forums.hentai-foundry.com/">Forums</a></li>
|
||||||
|
<li><a href="https://discord.gg/t9ukHX2">Discord</a></li>
|
||||||
|
<li><a href="/category/browse">Browse Categories</a></li>
|
||||||
|
<li><a href="/users/byletter">Browse Users</a></li>
|
||||||
|
<li onclick=";" style="cursor: pointer"><a>Browse Submissions</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/pictures/featured">Featured Submissions</a></li>
|
||||||
|
<li><a href="/pictures/recent">Recent Submissions</a></li>
|
||||||
|
<li><a href="/pictures/popular">Popular Submissions</a></li>
|
||||||
|
<li><a href="/pictures/random">Random Submissions</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul></nav>
|
||||||
|
<main>
|
||||||
|
<h1 class="titleSemantic">Witchcraft</h1><center><p>
|
||||||
|
<!-- Slot number 4 --><a href='http://www.hentaiunited.com/'><img title="" src="//img.hentai-foundry.com/themes/Hentai/images/h-united/hu8.png" alt="" /></a></p></center>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container" id="page">
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="/">Home</a> » <a href="/user/DevilHS/profile">DevilHS</a> » <a href="/pictures/user/DevilHS">Pictures</a> » <span>Witchcraft</span></div><!-- breadcrumbs -->
|
||||||
|
|
||||||
|
|
||||||
|
<section class="box lvl1" id="picBox">
|
||||||
|
<h2 class="titleSemantic">Witchcraft</h2><div class="boxheader">
|
||||||
|
<div class="boxtitle"><span class="imageTitle">Witchcraft</span> <small>by</small> <a href="/user/DevilHS/profile">DevilHS</a> </div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<img width="929" height="1123" class="center" src="//pictures.hentai-foundry.com/d/DevilHS/723498/DevilHS-723498-Witchcraft.png" alt="Witchcraft by DevilHS" /></div>
|
||||||
|
<div class="boxfooter"> </div>
|
||||||
|
</section><section class="box lvl1" id="descriptionBox">
|
||||||
|
<h2 class="titleSemantic">Description</h2><div class="boxheader">
|
||||||
|
<div class="boxtitle">Description</div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<a href="/user/DevilHS/profile"><img style="float: left" title="DevilHS" src="//avatars.hentai-foundry.com/234691.jpg" alt="DevilHS" /></a><div class='picDescript'>gift for Liru</div></div>
|
||||||
|
</section><section class="box lvl1" id="yw0">
|
||||||
|
<h2 class="titleSemantic">General Info</h2><div class="boxheader">
|
||||||
|
<div class="boxtitle">General Info</div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div id="favoritesDialog">
|
||||||
|
</div><table>
|
||||||
|
<tr><td><b>Ratings</b></td> <td><div class='ratings_box'><span class='rating lvl3' title='Nudity'>N</span><span class='rating lvl3' title='Sexual content'>Sx</span><span class='rating lvl2' title='Contains female nudity'>♀</span><span class='rating lvl2' title='Contains Futanari/Dickgirl/Transgender/Hermaphrodite subject'>Tg</span></div></td> <td><b>Comments</b></td> <td>11</td></tr>
|
||||||
|
<tr><td><b>Category</b></td> <td><span class="categoryBreadcrumbs">
|
||||||
|
<a href="/categories/4/Original/pictures">Original</a> » <a href="/categories/13/Original/Futanari-Dickgirls/pictures">Futanari (Dickgirls)</a></span></td> <td><b>Media</b></td> <td>Digital drawing or painting</td></tr>
|
||||||
|
<tr><td><b>Date Submitted</b></td> <td><time datetime='2019-07-25T19:29:48-07:00'>July 25, 2019, 7:29:48 PM</time></td> <td><b>Time Taken</b></td> <td></td></tr>
|
||||||
|
<tr><td><b>Views</b></td> <td>20597</td> <td><b>Reference</b></td> <td></td></tr>
|
||||||
|
<tr><td><b><a href="#" id="yt0">Favorites...</a></b></td> <td>876</td> <td><b>Keywords</b></td> <td><a rel="tag" href="/search/index?query=witch&search_in=keywords">witch</a>, <a rel="tag" href="/search/index?query=futa&search_in=keywords">futa</a></td></tr>
|
||||||
|
<!-- <tr><td></td> <td></td> <td><b>Tags (Beta)</b></td> <td></td></tr> -->
|
||||||
|
<tr><td><b>Vote Score</b></td> <td>649</td> <td><b>License</b></td> <td><span id='license'>Berne Convention</span></td></tr>
|
||||||
|
</table></div>
|
||||||
|
</section><section class="box lvl1" id="comments_box">
|
||||||
|
<h2 class="titleSemantic">Comments</h2><div class="boxheader">
|
||||||
|
<div class="boxtitle">Comments (11) <div class="boxlinks"><a class="feedLink fa fa-rss" href="/feed/PictureComments/id/723498"></a></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<p>You are not authorized to comment here. Your must be registered and logged in to comment</p><div style="margin-left: 0px;" id="comment_3210484"><section class="box lvl2" id="yw2">
|
||||||
|
<h3 class="titleSemantic">Kessandra on August 15, 2019, 8:21:56 AM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/Kessandra/profile">Kessandra</a> <small>on</small> <time datetime="2019-08-15T08:21:56-07:00">August 15, 2019, 8:21:56 AM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/Kessandra/profile"><img title="Kessandra" src="//avatars.hentai-foundry.com/270836.jpg" alt="Kessandra" /></a>Looks like some beautiful magic is going on. </div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3199670"><section class="box lvl2" id="yw3">
|
||||||
|
<h3 class="titleSemantic">TrueInsanity on July 28, 2019, 4:34:52 AM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/TrueInsanity/profile">TrueInsanity</a> <small>on</small> <time datetime="2019-07-28T04:34:52-07:00">July 28, 2019, 4:34:52 AM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/TrueInsanity/profile"><img title="TrueInsanity" src="//avatars.hentai-foundry.com/336662.gif" alt="TrueInsanity" /></a>Oh hell yes</div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3199196"><section class="box lvl2" id="yw4">
|
||||||
|
<h3 class="titleSemantic">Kaze26 on July 27, 2019, 5:23:29 AM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/Kaze26/profile">Kaze26</a> <small>on</small> <time datetime="2019-07-27T05:23:29-07:00">July 27, 2019, 5:23:29 AM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/Kaze26/profile"><img title="Kaze26" src="//avatars.hentai-foundry.com/267944.jpg" alt="Kaze26" /></a><strong>Spectacular</strong></div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3198544"><section class="box lvl2" id="yw5">
|
||||||
|
<h3 class="titleSemantic">QuarterLife on July 25, 2019, 6:39:08 PM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/QuarterLife/profile">QuarterLife</a> <small>on</small> <time datetime="2019-07-25T18:39:08-07:00">July 25, 2019, 6:39:08 PM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/QuarterLife/profile"><img title="QuarterLife" src="//avatars.hentai-foundry.com/112455.gif" alt="QuarterLife" /></a>I'm curious as to what she's casting, is it to help her last longer or is it why she has a cock and balls?<br />
|
||||||
|
<br />
|
||||||
|
Either way it's a good picture, good job, nice tight.</div></div>
|
||||||
|
</section></div><div style="margin-left: 30px;" id="comment_3198632"><section class="box lvl2" id="yw6">
|
||||||
|
<h3 class="titleSemantic">DevilHS on July 25, 2019, 10:59:26 PM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/DevilHS/profile">DevilHS</a> <small>on</small> <time datetime="2019-07-25T22:59:26-07:00">July 25, 2019, 10:59:26 PM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/DevilHS/profile"><img title="DevilHS" src="//avatars.hentai-foundry.com/234691.jpg" alt="DevilHS" /></a>She casted herself a magic dick.</div></div>
|
||||||
|
</section></div><div style="margin-left: 60px;" id="comment_3198647"><section class="box lvl2" id="yw7">
|
||||||
|
<h3 class="titleSemantic">QuarterLife on July 26, 2019, 12:00:08 AM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/QuarterLife/profile">QuarterLife</a> <small>on</small> <time datetime="2019-07-26T00:00:08-07:00">July 26, 2019, 12:00:08 AM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/QuarterLife/profile"><img title="QuarterLife" src="//avatars.hentai-foundry.com/112455.gif" alt="QuarterLife" /></a>Ah alright, not just a regular dick, but a magic dick, looks like a good dick too. :D</div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3198616"><section class="box lvl2" id="yw8">
|
||||||
|
<h3 class="titleSemantic">XSUBREADERX on July 25, 2019, 9:59:58 PM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/XSUBREADERX/profile">XSUBREADERX</a> <small>on</small> <time datetime="2019-07-25T21:59:58-07:00">July 25, 2019, 9:59:58 PM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/XSUBREADERX/profile"><img title="XSUBREADERX" src="//avatars.hentai-foundry.com/376416.jpg" alt="XSUBREADERX" /></a>Very Nice!</div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3198548"><section class="box lvl2" id="yw9">
|
||||||
|
<h3 class="titleSemantic">AceSR on July 25, 2019, 6:41:45 PM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/AceSR/profile">AceSR</a> <small>on</small> <time datetime="2019-07-25T18:41:45-07:00">July 25, 2019, 6:41:45 PM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/AceSR/profile"><img title="AceSR" src="//avatars.hentai-foundry.com/360157.gif" alt="AceSR" /></a>Nice gift</div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3198498"><section class="box lvl2" id="yw10">
|
||||||
|
<h3 class="titleSemantic">FloridasSonShines on July 25, 2019, 2:47:00 PM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/FloridasSonShines/profile">FloridasSonShines</a> <small>on</small> <time datetime="2019-07-25T14:47:00-07:00">July 25, 2019, 2:47:00 PM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/FloridasSonShines/profile"><img title="FloridasSonShines" src="//avatars.hentai-foundry.com/310539.jpg" alt="FloridasSonShines" /></a>Awesome!</div></div>
|
||||||
|
</section></div><div style="margin-left: 0px;" id="comment_3198457"><section class="box lvl2" id="yw11">
|
||||||
|
<h3 class="titleSemantic">Kreegan on July 25, 2019, 12:39:04 PM</h3><div class="boxheader">
|
||||||
|
<div class="boxtitle"><a href="/user/Kreegan/profile">Kreegan</a> <small>on</small> <time datetime="2019-07-25T12:39:04-07:00">July 25, 2019, 12:39:04 PM</time><span class="commentButtons"></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="boxbody">
|
||||||
|
<div class="commentBody"><a class="avatar" href="/user/Kreegan/profile"><img title="Kreegan" src="//avatars.hentai-foundry.com/227005.jpg" alt="Kreegan" /></a>Great :D<br />
|
||||||
|
</div></div>
|
||||||
|
</section></div></div>
|
||||||
|
<div class="commentsFooter"></div>
|
||||||
|
</section>
|
||||||
|
</div><!-- page -->
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer id="footer">
|
||||||
|
<div id="footerText">Site Copyright © 2006-2019 All Rights Reserved<br />Site design by <a href="/user/Sticky/profile">Sticky</a><br />
|
||||||
|
<br />Art and stories Copyright their artists/writers<br />
|
||||||
|
Series & Characters Copyright their respective creators/studios<br />
|
||||||
|
<p>All characters depicted are 18 or older, even if otherwise specified. </p><!-- IPv6-test.com button BEGIN -->
|
||||||
|
<a href='http://ipv6-test.com/validate.php?url=referer'>
|
||||||
|
<img src='//img.hentai-foundry.com/themes/default/images/button-ipv6-80x15.png' alt='ipv6 ready' title='ipv6 ready' border='0' />
|
||||||
|
</a>
|
||||||
|
<!-- IPv6-test.com button END --><br /><img title="" src="//img.hentai-foundry.com/themes/Hentai/images/icra_sw.gif" alt="" /> <img title="" src="//img.hentai-foundry.com/themes/Hentai/images/88x31_RTA-5042-1996-1400-1577-RTA_c.gif" alt="" /></div>
|
||||||
|
<br />
|
||||||
|
<center>
|
||||||
|
|
||||||
|
|
||||||
|
</center>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-620339-3', {'cookieDomain': 'www.hentai-foundry.com'});
|
||||||
|
ga('set', 'anonymizeIp', true);
|
||||||
|
ga('send', 'pageview');
|
||||||
|
|
||||||
|
</script></footer>
|
||||||
|
<script type="text/javascript" src="//img.hentai-foundry.com/assets/3389c1c0/jui/js/jquery-ui.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
document.write('<img src="/site/setSize?size=' + detectResolution() + '" width=0 height=0 id=detectRes>');
|
||||||
|
jQuery(function($) {
|
||||||
|
jQuery('body').on('click','#yt0',function(){jQuery.ajax({'success':function(data) { $("#favoritesDialog").html(data).dialog("open"); return false; },'url':'\x2Fpictures\x2Ffans\x3Fpid\x3D723498','cache':false});return false;});
|
||||||
|
jQuery('#favoritesDialog').dialog({'title':'Fans\x20of\x20\x22Witchcraft\x22','autoOpen':false,'width':'50\x25','height':$(window).height() / 2,'resizable':false});
|
||||||
|
jQuery('body').on('click','#yt1',function(){jQuery.ajax({'success':function() {location.reload();},'type':'POST','url':'\x2Fsite\x2Ffilters','cache':false,'data':jQuery(this).parents("form").serialize()});return false;});
|
||||||
|
});
|
||||||
|
jQuery(window).on('load',function() {
|
||||||
|
jQuery("#license").qtip({'content':'Default\x20automatic\x20international\x20copyright.\x3Cbr\x20\x2F\x3E\x0A\x3Cbr\x20\x2F\x3E\x0AMore\x20Information\x3A\x20\x3Ca\x20href\x3D\x22http\x3A\x2F\x2Fen.wikipedia.org\x2Fwiki\x2FBerne_Convention_for_the_Protection_of_Literary_and_Artistic_Works\x22\x20rel\x3D\x22nofollow\x22\x3Ehttp\x3A\x2F\x2Fen.wikipedia.org\x2Fwiki\x2FBerne_Convention_for_the_Protection_of_Literary_and_Artistic_Works\x3C\x2Fa\x3E','show':{'delay':50},'position':{'my':'top\x20center','at':'bottom\x20center'},'hide':{'delay':250,'fixed':true},'style':{'classes':'qtip\x2Dlight','tip':'top\x20center'}});
|
||||||
|
});
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
205
tests/fixture/Kb10uyShortStoryServer/tomone.html
vendored
Normal file
205
tests/fixture/Kb10uyShortStoryServer/tomone.html
vendored
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ja">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="csrf-token" content="MVlIWAqKUOV500GwG5vpc4AR2tdJA0KMPQVkqBVd">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
|
||||||
|
integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="/styles/app.d45d104d9486642a2e8e.css">
|
||||||
|
<script defer src="/scripts/show-post.d45d104d9486642a2e8e.js"></script>
|
||||||
|
<link rel="stylesheet" href="/styles/show-post.d45d104d9486642a2e8e.css" media="all">
|
||||||
|
<title>朋音「は、はぁ?おむつ?」 - ShortStoryServer</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="navbar">
|
||||||
|
<a class="logo" href="/">
|
||||||
|
<img src="/images/newlogo.png" alt="kbS3">
|
||||||
|
<span class="title">ShortStoryServer</span>
|
||||||
|
</span>
|
||||||
|
<div class="menu">
|
||||||
|
<a class="item" data-dropdown="list-dropdown">一覧</a>
|
||||||
|
<div id="list-dropdown" class="dropdown" data-dropdown-merge>
|
||||||
|
<a class="item" href="https://ss.kb10uy.org/posts/latest">最近の作品</a>
|
||||||
|
<a class="item" href="https://ss.kb10uy.org/series/latest">最近のシリーズ</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="https://ss.kb10uy.org/search" class="item">検索</a>
|
||||||
|
|
||||||
|
<a class="item" data-dropdown="help-dropdown">ヘルプ</a>
|
||||||
|
<div id="help-dropdown" class="dropdown" data-dropdown-merge>
|
||||||
|
<a href="https://ss.kb10uy.org/help/playground" class="item">Playground</a>
|
||||||
|
<a href="https://ss.kb10uy.org/help/about" class="item">ShortStoryServer について</a>
|
||||||
|
<a href="https://ss.kb10uy.org/help/terms" class="item">規約</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user" data-dropdown="user-dropdown">
|
||||||
|
<span>ログインしていません</span>
|
||||||
|
<span class="dropdown-caret"></span>
|
||||||
|
|
||||||
|
<!-- メニュー -->
|
||||||
|
<div id="user-dropdown" class="dropdown">
|
||||||
|
<div class="info">
|
||||||
|
ログインして様々な機能を活用しましょう
|
||||||
|
</div>
|
||||||
|
<div class="separator"></div>
|
||||||
|
<a href="https://ss.kb10uy.org/login" class="item">ログイン</a>
|
||||||
|
<a href="https://ss.kb10uy.org/register" class="item">サインアップ</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="post-info" id="app">
|
||||||
|
<h1>朋音「は、はぁ?おむつ?」</h1>
|
||||||
|
<div class="user">
|
||||||
|
<img src="https://www.gravatar.com/avatar/4bcb8dbb4c04894bafdc4b54c557d9ef?s=256&d=retro" alt="kb10uy">
|
||||||
|
<div>
|
||||||
|
Author:<br>
|
||||||
|
<a href="https://ss.kb10uy.org/users/kb10uy">kb10uy <small>@kb10uy</small></a>
|
||||||
|
</div>
|
||||||
|
<details>
|
||||||
|
<summary>メニュー</summary>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
<p class="summary">
|
||||||
|
自炊したおかずってやつです。とりあえずこのSSの中ではkb10uyの彼女は朋音ってことにしといてください。そうじゃないと出す男が決定できないので。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul class="tags">
|
||||||
|
<li class="tag"><a href="https://ss.kb10uy.org/search?query=%E5%A6%84%E6%83%B3&type=tag">妄想</a></li>
|
||||||
|
<li class="tag"><a href="https://ss.kb10uy.org/search?query=R-18&type=tag">R-18</a></li>
|
||||||
|
<li class="tag"><a href="https://ss.kb10uy.org/search?query=kb10uy&type=tag">kb10uy</a></li>
|
||||||
|
<li class="tag"><a href="https://ss.kb10uy.org/search?query=%E5%B2%A9%E6%B0%B8%E6%9C%8B%E9%9F%B3&type=tag">岩永朋音</a></li>
|
||||||
|
<li class="tag"><a href="https://ss.kb10uy.org/search?query=%E3%81%8A%E3%82%80%E3%81%A4&type=tag">おむつ</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="social">
|
||||||
|
<a href="javascript:(()=>{window.open(`https://shikorism.net/checkin?link=${encodeURIComponent(location.href)}`)})()" class="tissue share-button">
|
||||||
|
<svg class="logo" version="1.1" viewBox="3 3 10.933333 10.933333">
|
||||||
|
<rect ry="0.61077178" y="8.3200169" x="3.8013506" height="3.7688808" width="9.5094738" style="fill:transparent;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path d="M 5.6372669,8.3056228 5.0631111,4.511235 C 5.33543,6.0470597 6.850946,6.7810892 8.7216603,5.6856858 9.0176595,6.3067847 10.083255,7.5490113 12.072359,6.2277467 l -0.472834,2.0778761 z" style="fill:#ffffff;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.77704424;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path d="M 3.7504177,10.87423 H 13.589607" style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
</svg>
|
||||||
|
<span>チェックイン</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://mastoshare.net/post.php?text=%E6%9C%8B%E9%9F%B3%E3%80%8C%E3%81%AF%E3%80%81%E3%81%AF%E3%81%81%EF%BC%9F%E3%81%8A%E3%82%80%E3%81%A4%EF%BC%9F%E3%80%8D+-+ShortStoryServer" class="mastodon share-button" onclick="window.open(this.href, '', 'width=500,height=400'); return false;">
|
||||||
|
<i class="fab fa-mastodon"></i>
|
||||||
|
<span>トゥート</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/intent/tweet?text=%E6%9C%8B%E9%9F%B3%E3%80%8C%E3%81%AF%E3%80%81%E3%81%AF%E3%81%81%EF%BC%9F%E3%81%8A%E3%82%80%E3%81%A4%EF%BC%9F%E3%80%8D+-+ShortStoryServer&url=https%3A%2F%2Fss.kb10uy.org%2Fposts%2F14" class="twitter share-button" onclick="window.open(this.href, '', 'width=500,height=400'); return false;">
|
||||||
|
<i class="fab fa-twitter"></i>
|
||||||
|
<span>ツイート</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<modal-dialog v-cloak button-type="ok-cancel" v-if="shown.series" @dialog-ok="addToSeries(14)" @dialog-closed="shown.series = false">
|
||||||
|
<template v-slot:label>シリーズに追加</template>
|
||||||
|
<p>
|
||||||
|
作品をシリーズに追加すると、シリーズのページからもこの作品にアクセスできるようになるほか、
|
||||||
|
登録されているシリーズが作品ページにも表示されます。
|
||||||
|
</p>
|
||||||
|
<form>
|
||||||
|
<div class="pair">
|
||||||
|
<label for="dialog-series">追加先</label>
|
||||||
|
<select id="dialog-series" name="series_target" v-model="selectedSeries">
|
||||||
|
<option v-for="seriesItem of series" :key="seriesItem.id" :value="seriesItem.id">{{ seriesItem.title }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</modal-dialog>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media (prefers-color-scheme: dark) {}</style>
|
||||||
|
<article class="post">
|
||||||
|
<p>
|
||||||
|
<span class="line male-1">kb10uy「うん、今日はこれ穿いて学校行って」</span>
|
||||||
|
<span class="line female-1">朋音「あの……アンタ本気で言ってるの?」</span>
|
||||||
|
<span class="line male-1">kb10uy「自分の好きでもない子にそんなおむつ穿いてとか言わないよ」</span>
|
||||||
|
<span class="line female-1">朋音「そういうことじゃないのよっ……」</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
kb10uy の要求はいたって単純である。
|
||||||
|
</p>
|
||||||
|
<ul class="">
|
||||||
|
<li class="">今日一日、おむつを穿いていること。 </li>
|
||||||
|
<li class="">必ずおむつの中に出すこと(大小問わず)。</li>
|
||||||
|
<li class="">必ず人のいる場所で出すこと。</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
単純か?
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="line female-1">朋音「いまアンタのカノジョになっちゃったことを心底後悔してるわ……」</span>
|
||||||
|
<span class="line male-1">kb10uy「でもやってくれるよね?」</span>
|
||||||
|
<span class="line female-1">朋音「うぅ……そうよ、やるわよ……正直、そ、その……やり、たい……し」</span>
|
||||||
|
<span class="line male-1">kb10uy「朋音ならそう言ってくれると思った!じゃあよろしくね」</span>
|
||||||
|
<span class="line female-1">朋音「何が宜しいのよ……まったく」</span>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
<span class="line female-1">朋音「え、ちょっと待って、ワタシこれ大きいほうもこれにするの!?」</span>
|
||||||
|
<span class="line male-1">kb10uy「うん、言ったじゃん」</span>
|
||||||
|
<span class="line female-1">朋音「はぁ……」</span>
|
||||||
|
<span class="line male-1">kb10uy「嫌だったら我慢して帰ってきてからしてもいいから」</span>
|
||||||
|
<span class="line female-1">朋音「言われなくてもそうするわよ!w」</span>
|
||||||
|
<span class="line male-1">kb10uy「おしっこは我慢できなさそう?」</span>
|
||||||
|
<span class="line female-1">朋音「ん、正直自信ない……」</span>
|
||||||
|
<span class="line male-1">kb10uy「まあ大丈夫だって、きっとバレないよ」</span>
|
||||||
|
<span class="line female-1">朋音「よくもそんなヘラヘラと……」</span>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
はぁ〜……。もうユウウツだわ……。
|
||||||
|
<span class="line female-2">夏稀「トモ、大丈夫?なんか妙に落ち込んでるように見えるけど……」</span>
|
||||||
|
<span class="line female-1">朋音「うーん……微妙」</span>
|
||||||
|
<span class="line female-2">夏稀「何かあった?」</span>
|
||||||
|
<span class="line female-1">朋音「あのさ……kb10uyいるじゃん」</span>
|
||||||
|
<span class="line female-2">夏稀「あぁ……1つ上の」</span>
|
||||||
|
<span class="line female-1">朋音「多分なつには言ってなかったと思うんだけどさ、ワタシちょっと前からあいつと付き合ってるんだわ」</span>
|
||||||
|
<span class="line female-2">夏稀「え、ほんとに!!良かったじゃん、おめでとう〜」</span>
|
||||||
|
<span class="line female-1">朋音「それがあんまりおめでたくなくてさ……」</span>
|
||||||
|
<span class="line female-2">夏稀「というと」</span>
|
||||||
|
<span class="line female-1">朋音「kb10uyって精力絶倫ってウワサじゃん」</span>
|
||||||
|
<span class="line female-2">夏稀「そうらしいね」</span>
|
||||||
|
<span class="line female-1">朋音「絶倫なだけならまだ良くてさ、ワタシもそういうことするんだろうなってのは覚悟してたし」</span>
|
||||||
|
<span class="line female-2">夏稀「したんだ?」</span>
|
||||||
|
<span class="line female-1">朋音「ん。プロポーズした日に……」</span>
|
||||||
|
<span class="line female-2">夏稀「マジで絶倫なんだ……」</span>
|
||||||
|
<span class="line female-1">朋音「いやこっからなのよ問題は。kb10uy、性癖もヤバヤバのヤバでさ」</span>
|
||||||
|
<span class="line female-2">夏稀「あー、はぁ……」</span>
|
||||||
|
<span class="line female-1">朋音「……誰にも言わない?」</span>
|
||||||
|
<span class="line female-2">夏稀「……うん。」</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="line female-1">朋音「ワタシ今おむつ穿かされてるの」</span>
|
||||||
|
<span class="line female-2">夏稀「<i class="">えっっ!!</i>」</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="line female-1">朋音「っ………///」</span>
|
||||||
|
<span class="line female-2">夏稀「なるほど……」</span>
|
||||||
|
<span class="line female-2">夏稀「でもトモも穿いてるってことはやっぱりムッツリだよね」</span>
|
||||||
|
<span class="line female-1">朋音「返す言葉もないわ……」</span>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
実際に中に放尿するシーンはみなさんのご想像におまかせします
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
僕は廊下で人とぶつかってその衝撃でジョロロロって感じのシチュエーションで抜きました
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="/scripts/vendor.d45d104d9486642a2e8e.js"></script>
|
||||||
|
<script src="/scripts/app.d45d104d9486642a2e8e.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
114
tests/fixture/Nijie/testHasHtmlInAuthorProfileResponse.html
vendored
Normal file
114
tests/fixture/Nijie/testHasHtmlInAuthorProfileResponse.html
vendored
Normal file
File diff suppressed because one or more lines are too long
1031
tests/fixture/Xtube/video.html
vendored
1031
tests/fixture/Xtube/video.html
vendored
File diff suppressed because one or more lines are too long
1
webpack.mix.js
vendored
1
webpack.mix.js
vendored
@ -16,6 +16,7 @@ mix.js('resources/assets/js/app.js', 'public/js')
|
|||||||
.js('resources/assets/js/home.js', 'public/js')
|
.js('resources/assets/js/home.js', 'public/js')
|
||||||
.js('resources/assets/js/user/stats.js', 'public/js/user')
|
.js('resources/assets/js/user/stats.js', 'public/js/user')
|
||||||
.js('resources/assets/js/setting/privacy.js', 'public/js/setting')
|
.js('resources/assets/js/setting/privacy.js', 'public/js/setting')
|
||||||
|
.js('resources/assets/js/setting/deactivate.js', 'public/js/setting')
|
||||||
.ts('resources/assets/js/checkin.ts', 'public/js')
|
.ts('resources/assets/js/checkin.ts', 'public/js')
|
||||||
.sass('resources/assets/sass/app.scss', 'public/css')
|
.sass('resources/assets/sass/app.scss', 'public/css')
|
||||||
.autoload({
|
.autoload({
|
||||||
|
Loading…
Reference in New Issue
Block a user