Update v.0.9.0 3e109602
3
.github/action-run-tests/entrypoint.sh
vendored
@ -2,6 +2,9 @@
|
||||
|
||||
echo "🎌 Running LRR Test Suite 🎌"
|
||||
|
||||
# Install cpan deps in case some are missing
|
||||
perl ./tools/install.pl install-back
|
||||
|
||||
# Run the perl tests on the repo
|
||||
prove -r -l -v tests/
|
||||
|
||||
|
15
.github/workflows/push-brewtest.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
on: push
|
||||
name: "Test Homebrew"
|
||||
jobs:
|
||||
testBrew:
|
||||
name: Test Homebrew version
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Build and test bundled homebrew formula
|
||||
run: |
|
||||
cd tools/build/homebrew
|
||||
echo "Replacing commit hash in formula with current hash $(git rev-parse --verify HEAD)"
|
||||
sed -i.bck "s/COMMIT_HASH/$(git rev-parse --verify HEAD)/" Lanraragi.rb
|
||||
brew install --force --verbose --build-from-source Lanraragi.rb
|
||||
brew test --verbose Lanraragi.rb
|
15
.github/workflows/push-brewtest.yml.bak
vendored
@ -1,15 +0,0 @@
|
||||
on: push
|
||||
name: "测试软件在macos下的运行"
|
||||
jobs:
|
||||
testBrew:
|
||||
name: 测试dev版本
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: 构建和测试包的homebrew套件
|
||||
run: |
|
||||
cd tools/build/homebrew
|
||||
echo "将套件中的Hash替换为当前Hash $(git rev-parse --verify HEAD)"
|
||||
sed -i.bck "s/COMMIT_HASH/$(git rev-parse --verify HEAD)/" Lanraragi.rb
|
||||
brew install --force --verbose --build-from-source Lanraragi.rb
|
||||
brew test --verbose Lanraragi.rb
|
18
.github/workflows/push-continous-delivery.yml
vendored
@ -10,12 +10,12 @@ jobs:
|
||||
name: 构建并提交Docker镜像
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: 设置 QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: 设置 Docker Buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
- uses: actions/cache@main
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/buildxcache
|
||||
key: ${{ runner.os }}-docker-buildx-${{ github.sha }}
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
name: 为 Windows 构建构建并导出 Docker rootfs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: Docker 构建和导出
|
||||
run: |
|
||||
git submodule init
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
docker create --name rootfs windycloud/lanraragi_cn:dev
|
||||
docker export --output=package.tar rootfs
|
||||
- name: 上传 rootfs
|
||||
uses: actions/upload-artifact@main
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: package
|
||||
path: package.tar
|
||||
@ -62,9 +62,9 @@ jobs:
|
||||
needs: exportDockerRootFS
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: 下载包
|
||||
uses: actions/download-artifact@main
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: package
|
||||
- name: 构建MSI安装程序
|
||||
@ -81,7 +81,7 @@ jobs:
|
||||
choco install nuget.commandline
|
||||
./tools/build/windows/build.ps1
|
||||
- name: 上传安装程序
|
||||
uses: actions/upload-artifact@main
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: LANraragi.msi
|
||||
path: ./tools/build/windows/Karen/Setup/bin/LANraragi.msi
|
||||
|
@ -5,7 +5,7 @@ jobs:
|
||||
name: 运行测试套件和 Perl Critic
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: 测试 Docker 构建
|
||||
run: |
|
||||
git submodule init
|
||||
|
75
.github/workflows/release-delivery.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
name: 构建和导出Windows构建的Docker rootfs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: Docker 构建和导出
|
||||
run: |
|
||||
git submodule init
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
docker create --name rootfs windycloud/lanraragi_cn
|
||||
docker export --output=package.tar rootfs
|
||||
- name: 上传 rootfs
|
||||
uses: actions/upload-artifact@main
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: package
|
||||
path: package.tar
|
||||
@ -27,9 +27,9 @@ jobs:
|
||||
needs: exportDockerRootFS
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: 下载包
|
||||
uses: actions/download-artifact@main
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: package
|
||||
- name: 构建MSI安装程序
|
||||
@ -46,18 +46,17 @@ jobs:
|
||||
choco install nuget.commandline
|
||||
./tools/build/windows/build.ps1
|
||||
- name: 上传安装程序
|
||||
uses: actions/upload-artifact@main
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: wsl
|
||||
path: ./tools/build/windows/Karen/Setup/bin/LANraragi.msi
|
||||
|
||||
uploadWSLRelease:
|
||||
name: 添加Windows安装程序以发布
|
||||
needs: buildReleaseWSL
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 下载包
|
||||
uses: actions/download-artifact@main
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: wsl
|
||||
- name: 上传安装程序以发布
|
||||
@ -66,22 +65,22 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: wsl/LANraragi.msi application/octet-stream
|
||||
# - uses: Ilshidur/action-discord@master
|
||||
# env:
|
||||
# DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
# with:
|
||||
# args: 'Windows Installer built and available on the Release page! <:logo:821516019179978772>🪟'
|
||||
- uses: Ilshidur/action-discord@master
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
with:
|
||||
args: 'Windows Installer built and available on the Release page! <:logo:821516019179978772>🪟'
|
||||
|
||||
buildLatestDocker:
|
||||
name: 构建最新的Docker图像
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@master
|
||||
- name: 设置 QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: 设置 Docker Buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
- uses: actions/cache@main
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/buildxcache
|
||||
key: ${{ runner.os }}-docker-buildx-${{ github.sha }}
|
||||
@ -106,26 +105,26 @@ jobs:
|
||||
--cache-from "type=local,src=/tmp/buildxcache" \
|
||||
--cache-to "type=local,dest=/tmp/buildxcache" \
|
||||
--file ./tools/build/docker/Dockerfile .
|
||||
# - uses: Ilshidur/action-discord@master
|
||||
# env:
|
||||
# DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
# with:
|
||||
# args: 'Docker image built and available on Docker Hub! 🐳'
|
||||
- uses: Ilshidur/action-discord@master
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
with:
|
||||
args: 'Docker image built and available on Docker Hub! 🐳'
|
||||
|
||||
# discordNotifications:
|
||||
# name: 发布一些通知
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
# steps:
|
||||
# - name: 从 GITHUB_REF 中提取版本号
|
||||
# run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
# - uses: Ilshidur/action-discord@master
|
||||
# with:
|
||||
# args: 'LANraragi {{ RELEASE_VERSION }} is now available! @everyone'
|
||||
# - uses: Ilshidur/action-discord@master
|
||||
# with:
|
||||
# args: 'https://github.com/WindyCloudCute/LANraragi_Chinese/releases/tag/{{ RELEASE_VERSION }}'
|
||||
# - uses: Ilshidur/action-discord@master
|
||||
# with:
|
||||
# args: 'Docker image and Windows installer are building... Please wait warmly. ☕'
|
||||
discordNotifications:
|
||||
name: 发布一些通知
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
steps:
|
||||
- name: 从 GITHUB_REF 中提取版本号
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
- uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: 'LANraragi {{ RELEASE_VERSION }} is now available! @everyone'
|
||||
- uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: 'https://github.com/WindyCloudCute/LANraragi_Chinese/releases/tag/{{ RELEASE_VERSION }}'
|
||||
- uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: 'Docker image and Windows installer are building... Please wait warmly. ☕'
|
||||
|
3
.gitignore
vendored
@ -3,7 +3,6 @@ content
|
||||
.minion.db*
|
||||
npm-debug.log
|
||||
dump.rdb
|
||||
package-lock.json
|
||||
.vstags
|
||||
.gitbook
|
||||
*qemu-*-static*
|
||||
@ -13,3 +12,5 @@ config.log
|
||||
autobackup.json
|
||||
lib/LANraragi/Plugin/Scripts/ETagConverter.pm
|
||||
lib/LANraragi/Plugin/Metadata/ETagCN.pm
|
||||
Makefile
|
||||
oshino
|
@ -2,6 +2,7 @@ package LANraragi;
|
||||
|
||||
use local::lib;
|
||||
|
||||
use open ':std', ':encoding(UTF-8)';
|
||||
|
||||
use utf8;
|
||||
use Mojo::Base 'Mojolicious';
|
||||
@ -11,9 +12,9 @@ use Storable;
|
||||
use Sys::Hostname;
|
||||
use Config;
|
||||
|
||||
use LANraragi::Utils::Generic qw(start_shinobu start_minion);
|
||||
use LANraragi::Utils::Logging qw(get_logger get_logdir);
|
||||
use LANraragi::Utils::Plugins qw(get_plugins);
|
||||
use LANraragi::Utils::Generic qw(start_shinobu start_minion);
|
||||
use LANraragi::Utils::Logging qw(get_logger get_logdir);
|
||||
use LANraragi::Utils::Plugins qw(get_plugins);
|
||||
use LANraragi::Utils::TempFolder qw(get_temp);
|
||||
use LANraragi::Utils::Routing;
|
||||
use LANraragi::Utils::Minion;
|
||||
@ -36,8 +37,19 @@ sub startup {
|
||||
my $vername = $packagejson->{version_name};
|
||||
my $descstr = $packagejson->{description};
|
||||
|
||||
# Use the hostname and osname for a sorta-unique set of secrets.
|
||||
$self->secrets( [ hostname(), $Config{"osname"}, 'oshino' ] );
|
||||
my $secret = "";
|
||||
my $secretfile_path = $ENV{LRR_DATA_DIRECTORY} ? $ENV{LRR_DATA_DIRECTORY} . "/oshino" : "oshino";
|
||||
if ( -e $secretfile_path ) {
|
||||
$secret = Mojo::File->new($secretfile_path)->slurp;
|
||||
} else {
|
||||
|
||||
# Generate a random string as the secret and store it in a file
|
||||
$secret .= sprintf( "%x", rand 16 ) for 1 .. 8;
|
||||
Mojo::File->new($secretfile_path)->spew($secret);
|
||||
}
|
||||
|
||||
# Use the hostname alongside the random secret
|
||||
$self->secrets( [ $secret . hostname() ] );
|
||||
$self->plugin('RenderFile');
|
||||
|
||||
# Set Template::Toolkit as default renderer so we can use the LRR templates
|
||||
@ -137,10 +149,15 @@ sub startup {
|
||||
# Enable Minion capabilities in the app
|
||||
shutdown_from_pid( get_temp . "/minion.pid" );
|
||||
|
||||
my $miniondb = $self->LRR_CONF->get_redisad . "/" . $self->LRR_CONF->get_miniondb;
|
||||
$self->LRR_LOGGER->info("Minion将使用位于 $miniondb 的Redis数据库");
|
||||
$self->plugin( 'Minion' => { Redis => "redis://$miniondb" } );
|
||||
$self->LRR_LOGGER->info("成功连接到Minion数据库。");
|
||||
my $miniondb = $self->LRR_CONF->get_redisad . "/" . $self->LRR_CONF->get_miniondb;
|
||||
my $redispassword = $self->LRR_CONF->get_redispassword;
|
||||
|
||||
# If the password is non-empty, add the required delimiters
|
||||
if ($redispassword) { $redispassword = "x:" . $redispassword . "@"; }
|
||||
|
||||
say "Minion will use the Redis database at $miniondb";
|
||||
$self->plugin( 'Minion' => { Redis => "redis://$redispassword$miniondb" } );
|
||||
$self->LRR_LOGGER->info("Successfully connected to Minion database.");
|
||||
$self->minion->missing_after(5); # Clean up older workers after 5 seconds of unavailability
|
||||
|
||||
LANraragi::Utils::Minion::add_tasks( $self->minion );
|
||||
@ -193,7 +210,7 @@ sub add_sigint_handler {
|
||||
shutdown_from_pid( get_temp . "/minion.pid" );
|
||||
|
||||
\&$old_int; # Calling the old handler to cleanly exit the server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub migrate_old_settings {
|
||||
|
@ -4,11 +4,11 @@ use Mojo::Base 'Mojolicious::Controller';
|
||||
use Redis;
|
||||
use Encode;
|
||||
use Storable;
|
||||
use Mojo::JSON qw(decode_json);
|
||||
use Mojo::JSON qw(decode_json);
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
use LANraragi::Utils::Database qw(redis_decode);
|
||||
|
||||
use LANraragi::Utils::Generic qw(render_api_response);
|
||||
use LANraragi::Utils::Generic qw(render_api_response);
|
||||
use LANraragi::Utils::Database qw(get_archive_json set_isnew);
|
||||
|
||||
use LANraragi::Model::Archive;
|
||||
@ -65,7 +65,7 @@ sub serve_metadata {
|
||||
sub get_categories {
|
||||
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "find_arc_categories" ) || return;
|
||||
my $id = check_id_parameter( $self, "find_arc_categories" ) || return;
|
||||
|
||||
my @categories = LANraragi::Model::Category::get_categories_containing_archive($id);
|
||||
|
||||
@ -80,13 +80,13 @@ sub get_categories {
|
||||
|
||||
sub serve_thumbnail {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "serve_thumbnail" ) || return;
|
||||
my $id = check_id_parameter( $self, "serve_thumbnail" ) || return;
|
||||
LANraragi::Model::Archive::serve_thumbnail( $self, $id );
|
||||
}
|
||||
|
||||
sub update_thumbnail {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "update_thumbnail" ) || return;
|
||||
my $id = check_id_parameter( $self, "update_thumbnail" ) || return;
|
||||
LANraragi::Model::Archive::update_thumbnail( $self, $id );
|
||||
}
|
||||
|
||||
@ -106,14 +106,14 @@ sub serve_file {
|
||||
sub serve_page {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "serve_page" ) || return;
|
||||
my $path = $self->req->param('path') || "404.xyz";
|
||||
my $path = $self->req->param('path') || "404.xyz";
|
||||
|
||||
LANraragi::Model::Archive::serve_page( $self, $id, $path );
|
||||
}
|
||||
|
||||
sub get_file_list {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "get_file_list" ) || return;
|
||||
my $id = check_id_parameter( $self, "get_file_list" ) || return;
|
||||
|
||||
my $force = $self->req->param('force') eq "true" || "0";
|
||||
my $reader_json;
|
||||
@ -130,7 +130,7 @@ sub get_file_list {
|
||||
|
||||
sub clear_new {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "clear_new" ) || return;
|
||||
my $id = check_id_parameter( $self, "clear_new" ) || return;
|
||||
|
||||
set_isnew( $id, "false" );
|
||||
|
||||
@ -145,7 +145,7 @@ sub clear_new {
|
||||
|
||||
sub delete_archive {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "delete_archive" ) || return;
|
||||
my $id = check_id_parameter( $self, "delete_archive" ) || return;
|
||||
|
||||
my $delStatus = LANraragi::Utils::Database::delete_archive($id);
|
||||
|
||||
@ -161,7 +161,7 @@ sub delete_archive {
|
||||
|
||||
sub update_metadata {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "update_metadata" ) || return;
|
||||
my $id = check_id_parameter( $self, "update_metadata" ) || return;
|
||||
|
||||
my $title = $self->req->param('title');
|
||||
my $tags = $self->req->param('tags');
|
||||
@ -177,9 +177,10 @@ sub update_metadata {
|
||||
|
||||
sub update_progress {
|
||||
my $self = shift;
|
||||
my $id = check_id_parameter( $self, "update_progress" ) || return;
|
||||
my $id = check_id_parameter( $self, "update_progress" ) || return;
|
||||
|
||||
my $page = $self->stash('page') || 0;
|
||||
my $time = time();
|
||||
|
||||
# Undocumented parameter to force progress update
|
||||
my $force = $self->req->param('force') || 0;
|
||||
@ -206,8 +207,8 @@ sub update_progress {
|
||||
}
|
||||
|
||||
# Just set the progress value.
|
||||
$redis->hset( $id, "progress", $page );
|
||||
$redis->hset( $id, "lastreadtime", time());
|
||||
$redis->hset( $id, "progress", $page );
|
||||
$redis->hset( $id, "lastreadtime", $time );
|
||||
|
||||
# Update total pages read statistic
|
||||
$redis_cfg->incr("LRR_TOTALPAGESTAT");
|
||||
@ -217,10 +218,11 @@ sub update_progress {
|
||||
|
||||
$self->render(
|
||||
json => {
|
||||
operation => "update_progress",
|
||||
id => $id,
|
||||
page => $page,
|
||||
success => 1
|
||||
operation => "update_progress",
|
||||
id => $id,
|
||||
page => $page,
|
||||
lastreadtime => $time,
|
||||
success => 1
|
||||
}
|
||||
);
|
||||
|
||||
|
163
lib/LANraragi/Controller/Api/Tankoubon.pm
Normal file
@ -0,0 +1,163 @@
|
||||
package LANraragi::Controller::Api::Tankoubon;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use Redis;
|
||||
use Encode;
|
||||
|
||||
use LANraragi::Model::Tankoubon;
|
||||
use LANraragi::Utils::Generic qw(render_api_response);
|
||||
|
||||
sub get_tankoubon_list {
|
||||
|
||||
my $self = shift;
|
||||
my $req = $self->req;
|
||||
|
||||
my $page = $req->param('page');
|
||||
|
||||
my ( $total, $filtered, @rgs ) = LANraragi::Model::Tankoubon::get_tankoubon_list($page);
|
||||
$self->render( json => {result => \@rgs, total => $total, filtered => $filtered} );
|
||||
|
||||
}
|
||||
|
||||
sub get_tankoubon {
|
||||
|
||||
my $self = shift;
|
||||
my $tank_id = $self->stash('id');
|
||||
my $req = $self->req;
|
||||
|
||||
my $fulldata = $req->param('include_full_data');
|
||||
my $page = $req->param('page');
|
||||
|
||||
my ( $total, $filtered, %tankoubon ) = LANraragi::Model::Tankoubon::get_tankoubon($tank_id, $fulldata, $page);
|
||||
|
||||
unless (%tankoubon) {
|
||||
render_api_response( $self, "get_tankoubon", "The given tankoubon does not exist." );
|
||||
return;
|
||||
}
|
||||
|
||||
$self->render( json => {result => \%tankoubon, total => $total, filtered => $filtered} );
|
||||
}
|
||||
|
||||
sub create_tankoubon {
|
||||
|
||||
my $self = shift;
|
||||
my $name = $self->req->param('name') || "";
|
||||
my $tankid = $self->req->param('tankid') || "";
|
||||
|
||||
if ( $name eq "" ) {
|
||||
render_api_response( $self, "create_tankoubon", "Tankoubon name not specified." );
|
||||
return;
|
||||
}
|
||||
|
||||
my $created_id = LANraragi::Model::Tankoubon::create_tankoubon( $name, $tankid );
|
||||
$self->render(
|
||||
json => {
|
||||
operation => "create_tankoubon",
|
||||
tankoubon_id => $created_id,
|
||||
success => 1
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
sub delete_tankoubon {
|
||||
|
||||
my $self = shift;
|
||||
my $tankid = $self->stash('id');
|
||||
|
||||
my $result = LANraragi::Model::Tankoubon::delete_tankoubon($tankid);
|
||||
|
||||
if ($result) {
|
||||
render_api_response( $self, "delete_tankoubon" );
|
||||
} else {
|
||||
render_api_response( $self, "delete_tankoubon", "The given tankoubon does not exist." );
|
||||
}
|
||||
}
|
||||
|
||||
sub update_archive_list {
|
||||
|
||||
my $self = shift;
|
||||
my $tankid = $self->stash('id');
|
||||
my $data = $self->req->json;
|
||||
|
||||
my ( $result, $err ) = LANraragi::Model::Tankoubon::update_archive_list( $tankid, $data );
|
||||
|
||||
if ($result) {
|
||||
my %tankoubon = LANraragi::Model::Tankoubon::get_tankoubon($tankid);
|
||||
my $successMessage = "Updated archives of tankoubon \"$tankoubon{name}\"!";
|
||||
|
||||
render_api_response( $self, "update_archive_list", undef, $successMessage );
|
||||
} else {
|
||||
render_api_response( $self, "update_archive_list", $err );
|
||||
}
|
||||
}
|
||||
|
||||
sub add_to_tankoubon {
|
||||
|
||||
my $self = shift;
|
||||
my $tankid = $self->stash('id');
|
||||
my $arcid = $self->stash('archive');
|
||||
|
||||
my ( $result, $err ) = LANraragi::Model::Tankoubon::add_to_tankoubon( $tankid, $arcid );
|
||||
|
||||
if ($result) {
|
||||
my $successMessage = "Added $arcid to tankoubon $tankid!";
|
||||
my %tankoubon = LANraragi::Model::Tankoubon::get_tankoubon($tankid);
|
||||
my $title = LANraragi::Model::Archive::get_title($arcid);
|
||||
|
||||
if ( %tankoubon && defined($title) ) {
|
||||
$successMessage = "Added \"$title\" to tankoubon \"$tankoubon{name}\"!";
|
||||
}
|
||||
|
||||
render_api_response( $self, "add_to_tankoubon", undef, $successMessage );
|
||||
} else {
|
||||
render_api_response( $self, "add_to_tankoubon", $err );
|
||||
}
|
||||
}
|
||||
|
||||
sub remove_from_tankoubon {
|
||||
|
||||
my $self = shift;
|
||||
my $tankid = $self->stash('id');
|
||||
my $arcid = $self->stash('archive');
|
||||
|
||||
my ( $result, $err ) = LANraragi::Model::Tankoubon::remove_from_tankoubon( $tankid, $arcid );
|
||||
|
||||
if ($result) {
|
||||
my $successMessage = "Removed $arcid from tankoubon $tankid!";
|
||||
my %tankoubon = LANraragi::Model::Tankoubon::get_tankoubon($tankid);
|
||||
my $title = LANraragi::Model::Archive::get_title($arcid);
|
||||
|
||||
if ( %tankoubon && defined($title) ) {
|
||||
$successMessage = "Removed \"$title\" from tankoubon \"$tankoubon{name}\"!";
|
||||
}
|
||||
|
||||
render_api_response( $self, "remove_from_tankoubon", undef, $successMessage );
|
||||
} else {
|
||||
render_api_response( $self, "remove_from_tankoubon", $err );
|
||||
}
|
||||
}
|
||||
|
||||
sub get_tankoubons_file {
|
||||
|
||||
my $self = shift;
|
||||
my $arcid = $self->stash('id');
|
||||
|
||||
if ( $arcid eq "" ) {
|
||||
render_api_response( $self, "get_tankoubons_file", "Archive not specified." );
|
||||
return;
|
||||
}
|
||||
|
||||
my @tanks = LANraragi::Model::Tankoubon::get_tankoubons_file( $arcid );
|
||||
|
||||
$self->render(
|
||||
json => {
|
||||
operation => "find_arc_tankoubons",
|
||||
tankoubons => \@tanks,
|
||||
success => 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -5,11 +5,11 @@ use Redis;
|
||||
use Encode;
|
||||
use Mojo::JSON qw(decode_json);
|
||||
|
||||
use LANraragi::Utils::Generic qw(generate_themes_header);
|
||||
use LANraragi::Utils::Tags qw(rewrite_tags split_tags_to_array restore_CRLF);
|
||||
use LANraragi::Utils::Database qw(get_computed_tagrules set_tags set_title set_isnew invalidate_cache);
|
||||
use LANraragi::Utils::Plugins qw(get_plugins get_plugin get_plugin_parameters);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Generic qw(generate_themes_header);
|
||||
use LANraragi::Utils::Tags qw(rewrite_tags split_tags_to_array restore_CRLF);
|
||||
use LANraragi::Utils::Database qw(redis_decode get_computed_tagrules set_tags set_title set_isnew invalidate_cache);
|
||||
use LANraragi::Utils::Plugins qw(get_plugins get_plugin get_plugin_parameters);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
# This action will render a template
|
||||
sub index {
|
||||
@ -52,6 +52,12 @@ sub socket {
|
||||
message => sub {
|
||||
my ( $self, $msg ) = @_;
|
||||
|
||||
$logger->debug("Received WS message $msg");
|
||||
|
||||
# encode message before json-decoding it in case it has UTF8 characters in the argument overrides
|
||||
$msg = encode( 'UTF-8', $msg );
|
||||
$logger->trace("Encoded message $msg");
|
||||
|
||||
# JSON-decode message and perform the requested action
|
||||
my $command = decode_json($msg);
|
||||
my $operation = $command->{'operation'};
|
||||
@ -62,7 +68,6 @@ sub socket {
|
||||
$client->finish( 1001 => '没有提供档案.' );
|
||||
return;
|
||||
}
|
||||
$logger->debug("运行 $id");
|
||||
|
||||
if ( $operation eq "plugin" ) {
|
||||
|
||||
@ -80,6 +85,10 @@ sub socket {
|
||||
|
||||
# Try getting the saved defaults
|
||||
@args = get_plugin_parameters($pluginname);
|
||||
} else {
|
||||
|
||||
# Decode user overrides
|
||||
@args = map { redis_decode($_) } @args;
|
||||
}
|
||||
|
||||
# Send reply message for completed archive
|
||||
@ -154,7 +163,7 @@ sub socket {
|
||||
id => $id,
|
||||
filename => $delStatus,
|
||||
message => $delStatus ? "Archive deleted." : "Archive not found.",
|
||||
success => $delStatus ? 1 : 0
|
||||
success => $delStatus ? 1 : 0
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package LANraragi::Controller::Category;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
|
||||
use utf8;
|
||||
use URI::Escape;
|
||||
use Redis;
|
||||
use Encode;
|
||||
@ -21,25 +21,18 @@ sub index {
|
||||
|
||||
$redis->quit();
|
||||
|
||||
#Then complete it with the rest from the database.
|
||||
#40-character long keys only => Archive IDs
|
||||
my @keys = $redis->keys('????????????????????????????????????????');
|
||||
|
||||
my @idlist = LANraragi::Model::Archive::generate_archive_list;
|
||||
#Parse the archive list and build <li> elements accordingly.
|
||||
my $arclist = "";
|
||||
|
||||
#Only show IDs that still have their files present.
|
||||
foreach my $id (@keys) {
|
||||
my $zipfile = $redis->hget( $id, "file" );
|
||||
my $title = $redis->hget( $id, "title" );
|
||||
$title = redis_decode($title);
|
||||
$title = xml_escape($title);
|
||||
foreach my $arc (@idlist) {
|
||||
my $title = xml_escape($arc->{title});
|
||||
my $id = xml_escape($arc->{arcid});
|
||||
|
||||
if ( -e $zipfile ) {
|
||||
$arclist .=
|
||||
"<li><input type='checkbox' name='archive' id='$id' class='archive' onchange='Category.updateArchiveInCategory(this.id, this.checked)'>";
|
||||
$arclist .= "<label for='$id'> $title</label></li>";
|
||||
}
|
||||
$arclist .=
|
||||
"<li><input type='checkbox' name='archive' id='$id' class='archive' onchange='Category.updateArchiveInCategory(this.id, this.checked)'>";
|
||||
$arclist .= "<label for='$id'> $title</label></li>";
|
||||
}
|
||||
|
||||
$redis->quit();
|
||||
|
@ -1,11 +1,12 @@
|
||||
package LANraragi::Controller::Config;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use LANraragi::Utils::Generic qw(generate_themes_header remove_spaces remove_newlines);
|
||||
use LANraragi::Utils::Database qw(redis_encode save_computed_tagrules);
|
||||
use LANraragi::Utils::Generic qw(generate_themes_header);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF);
|
||||
use LANraragi::Utils::Database qw(redis_encode save_computed_tagrules);
|
||||
use LANraragi::Utils::TempFolder qw(get_tempsize);
|
||||
use LANraragi::Utils::Tags qw(tags_rules_to_array replace_CRLF restore_CRLF);
|
||||
use Mojo::JSON qw(encode_json);
|
||||
use LANraragi::Utils::Tags qw(tags_rules_to_array replace_CRLF restore_CRLF);
|
||||
use Mojo::JSON qw(encode_json);
|
||||
|
||||
use Authen::Passphrase::BlowfishCrypt;
|
||||
|
||||
@ -22,7 +23,7 @@ sub index {
|
||||
motd => $self->LRR_CONF->get_motd,
|
||||
dirname => $self->LRR_CONF->get_userdir,
|
||||
thumbdir => $self->LRR_CONF->get_thumbdir,
|
||||
forceddirname => ( defined $ENV{LRR_DATA_DIRECTORY} ? 1 : 0 ),
|
||||
forceddirname => ( defined $ENV{LRR_DATA_DIRECTORY} ? 1 : 0 ),
|
||||
forcedthumbdir => ( defined $ENV{LRR_THUMB_DIRECTORY} ? 1 : 0 ),
|
||||
pagesize => $self->LRR_CONF->get_pagesize,
|
||||
enablepass => $self->LRR_CONF->enable_pass,
|
||||
@ -125,20 +126,29 @@ sub save_config {
|
||||
#Did all the checks pass ?
|
||||
if ($success) {
|
||||
|
||||
# Clean up the user's inputs for non-toggle options and encode for redis insertion
|
||||
$redis->watch("LRR_CONFIG");
|
||||
$redis->multi;
|
||||
|
||||
foreach my $key ( keys %confhash ) {
|
||||
remove_spaces( $confhash{$key} );
|
||||
remove_newlines( $confhash{$key} );
|
||||
$confhash{$key} = redis_encode( $confhash{$key} );
|
||||
$self->LRR_LOGGER->debug( "Saving $key with value " . $confhash{$key} );
|
||||
my $value = $confhash{$key};
|
||||
|
||||
if ( $value ne '0' && $value ne '1' ) {
|
||||
|
||||
# Clean up the user's inputs for non-toggle options and encode for redis insertion
|
||||
$value = trim($value);
|
||||
$value = trim_CRLF($value);
|
||||
$value = redis_encode($value);
|
||||
}
|
||||
|
||||
# For all keys of the hash, add them to the redis config hash with the matching keys.
|
||||
$self->LRR_LOGGER->debug( "Saving $key with value " . $value );
|
||||
$redis->hset( "LRR_CONFIG", $key, $value );
|
||||
}
|
||||
|
||||
#for all keys of the hash, add them to the redis config hash with the matching keys.
|
||||
$redis->hset( "LRR_CONFIG", $_, $confhash{$_}, sub { } ) for keys %confhash;
|
||||
$redis->wait_all_responses;
|
||||
$redis->exec;
|
||||
}
|
||||
|
||||
$redis->quit();
|
||||
$redis->quit;
|
||||
|
||||
my @computed_tagrules = tags_rules_to_array( $self->req->param('tagrules') );
|
||||
$self->LRR_LOGGER->debug( "Saving computed tag rules : " . encode_json( \@computed_tagrules ) );
|
||||
|
@ -1,7 +1,7 @@
|
||||
package LANraragi::Controller::Index;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
|
||||
use utf8;
|
||||
use URI::Escape;
|
||||
use Redis;
|
||||
use Encode;
|
||||
|
@ -23,15 +23,16 @@ sub index {
|
||||
my @downloadplugins = get_plugins("download");
|
||||
|
||||
$self->render(
|
||||
template => "plugins",
|
||||
title => $self->LRR_CONF->get_htmltitle,
|
||||
descstr => $self->LRR_DESC,
|
||||
metadata => craft_plugin_array(@metaplugins),
|
||||
downloaders => craft_plugin_array(@downloadplugins),
|
||||
logins => craft_plugin_array(@loginplugins),
|
||||
scripts => craft_plugin_array(@scriptplugins),
|
||||
csshead => generate_themes_header($self),
|
||||
version => $self->LRR_VERSION
|
||||
template => "plugins",
|
||||
title => $self->LRR_CONF->get_htmltitle,
|
||||
descstr => $self->LRR_DESC,
|
||||
replacetitles => $self->LRR_CONF->can_replacetitles,
|
||||
metadata => craft_plugin_array(@metaplugins),
|
||||
downloaders => craft_plugin_array(@downloadplugins),
|
||||
logins => craft_plugin_array(@loginplugins),
|
||||
scripts => craft_plugin_array(@scriptplugins),
|
||||
csshead => generate_themes_header($self),
|
||||
version => $self->LRR_VERSION
|
||||
);
|
||||
|
||||
}
|
||||
@ -81,6 +82,12 @@ sub save_config {
|
||||
my $errormess = "";
|
||||
|
||||
eval {
|
||||
|
||||
# Save title preference first
|
||||
my $replacetitles = ( scalar $self->req->param('replacetitles') ? '1' : '0' );
|
||||
$redis->hset( "LRR_CONFIG", "replacetitles", $replacetitles );
|
||||
|
||||
# Save each plugin's settings
|
||||
foreach my $pluginfo (@plugins) {
|
||||
|
||||
my $namespace = $pluginfo->{namespace};
|
||||
@ -172,7 +179,7 @@ sub process_upload {
|
||||
return;
|
||||
}
|
||||
|
||||
my $dir = getcwd() . ("/lib/LANraragi/Plugin/$plugintype/");
|
||||
my $dir = getcwd() . ("/lib/LANraragi/Plugin/$plugintype/");
|
||||
my $output_file = $dir . $filename;
|
||||
|
||||
$logger->info("将新插件 $filename 上传到 $output_file ...");
|
||||
|
33
lib/LANraragi/Controller/Tankoubon.pm
Normal file
@ -0,0 +1,33 @@
|
||||
package LANraragi::Controller::Tankoubon;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use utf8;
|
||||
use URI::Escape;
|
||||
use Redis;
|
||||
use Encode;
|
||||
use Mojo::Util qw(xml_escape);
|
||||
|
||||
use LANraragi::Utils::Generic qw(generate_themes_header);
|
||||
use LANraragi::Utils::Database qw(redis_decode);
|
||||
|
||||
# Go through the archives in the content directory and build the template at the end.
|
||||
sub index {
|
||||
|
||||
my $self = shift;
|
||||
my $redis = $self->LRR_CONF->get_redis;
|
||||
my $force = 0;
|
||||
|
||||
my $userlogged = $self->LRR_CONF->enable_pass == 0 || $self->session('is_logged');
|
||||
|
||||
$redis->quit();
|
||||
|
||||
$self->render(
|
||||
template => "tankoubon",
|
||||
title => $self->LRR_CONF->get_htmltitle,
|
||||
descstr => $self->LRR_DESC,
|
||||
csshead => generate_themes_header($self),
|
||||
version => $self->LRR_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
@ -14,7 +14,8 @@ use File::Basename;
|
||||
use File::Copy "cp";
|
||||
use File::Path qw(make_path);
|
||||
|
||||
use LANraragi::Utils::Generic qw(remove_spaces remove_newlines render_api_response);
|
||||
use LANraragi::Utils::Generic qw(render_api_response);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF);
|
||||
use LANraragi::Utils::TempFolder qw(get_temp);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Archive qw(extract_single_file extract_thumbnail);
|
||||
@ -27,14 +28,14 @@ use LANraragi::Utils::Database
|
||||
sub get_title($id) {
|
||||
|
||||
my $logger = get_logger( "Archives", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
|
||||
if ( $id eq "" ) {
|
||||
$logger->debug("No archive ID provided.");
|
||||
return ();
|
||||
}
|
||||
|
||||
return redis_decode($redis->hget( $id, "title" ));
|
||||
return redis_decode( $redis->hget( $id, "title" ) );
|
||||
}
|
||||
|
||||
# Functions used when dealing with archives.
|
||||
@ -57,8 +58,8 @@ sub update_thumbnail {
|
||||
$page = 1 unless $page;
|
||||
|
||||
my $thumbdir = LANraragi::Model::Config->get_thumbdir;
|
||||
my $use_jxl = LANraragi::Model::Config->get_jxlthumbpages;
|
||||
my $format = $use_jxl ? 'jxl' : 'jpg';
|
||||
my $use_jxl = LANraragi::Model::Config->get_jxlthumbpages;
|
||||
my $format = $use_jxl ? 'jxl' : 'jpg';
|
||||
|
||||
# Thumbnails are stored in the content directory, thumb subfolder.
|
||||
# Another subfolder with the first two characters of the id is used for FS optimization.
|
||||
@ -100,9 +101,9 @@ sub serve_thumbnail {
|
||||
my $no_fallback = $self->req->param('no_fallback');
|
||||
$no_fallback = ( $no_fallback && $no_fallback eq "true" ) || "0"; # Prevent undef warnings by checking the variable first
|
||||
|
||||
my $thumbdir = LANraragi::Model::Config->get_thumbdir;
|
||||
my $use_jxl = LANraragi::Model::Config->get_jxlthumbpages;
|
||||
my $format = $use_jxl ? 'jxl' : 'jpg';
|
||||
my $thumbdir = LANraragi::Model::Config->get_thumbdir;
|
||||
my $use_jxl = LANraragi::Model::Config->get_jxlthumbpages;
|
||||
my $format = $use_jxl ? 'jxl' : 'jpg';
|
||||
my $fallback_format = $format eq 'jxl' ? 'jpg' : 'jxl';
|
||||
|
||||
# Thumbnails are stored in the content directory, thumb subfolder.
|
||||
@ -110,8 +111,8 @@ sub serve_thumbnail {
|
||||
my $subfolder = substr( $id, 0, 2 );
|
||||
|
||||
# Check for the page and set the appropriate thumbnail name and fallback thumbnail name
|
||||
my $thumbbase = ( $page - 1 > 0 ) ? "$thumbdir/$subfolder/$id/$page" : "$thumbdir/$subfolder/$id";
|
||||
my $thumbname = "$thumbbase.$format";
|
||||
my $thumbbase = ( $page - 1 > 0 ) ? "$thumbdir/$subfolder/$id/$page" : "$thumbdir/$subfolder/$id";
|
||||
my $thumbname = "$thumbbase.$format";
|
||||
my $fallback_thumbname = "$thumbbase.$fallback_format";
|
||||
|
||||
# Check if the preferred format thumbnail exists, if not, try the alternate format
|
||||
@ -123,7 +124,7 @@ sub serve_thumbnail {
|
||||
unless ( -e $thumbname ) {
|
||||
my $job_id = $self->minion->enqueue( thumbnail_task => [ $thumbdir, $id, $page ] => { priority => 0, attempts => 3 } );
|
||||
|
||||
if ( $no_fallback ) {
|
||||
if ($no_fallback) {
|
||||
$self->render(
|
||||
json => {
|
||||
operation => "serve_thumbnail",
|
||||
@ -133,11 +134,13 @@ sub serve_thumbnail {
|
||||
status => 202 # 202 Accepted
|
||||
);
|
||||
} else {
|
||||
|
||||
# If the thumbnail doesn't exist, serve the default thumbnail.
|
||||
$self->render_file( filepath => "./public/img/noThumb.png" );
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
|
||||
# Simply serve the thumbnail.
|
||||
$self->render_file( filepath => $thumbname );
|
||||
}
|
||||
@ -176,7 +179,7 @@ sub serve_page {
|
||||
|
||||
# Extract the file from the parent archive if it doesn't exist
|
||||
$logger->debug("Extracting missing file");
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $archive = $redis->hget( $id, "file" );
|
||||
$redis->quit();
|
||||
|
||||
@ -249,8 +252,8 @@ sub update_metadata {
|
||||
}
|
||||
|
||||
# Clean up the user's inputs and encode them.
|
||||
( remove_spaces($_) ) for ( $title, $tags );
|
||||
( remove_newlines($_) ) for ( $title, $tags );
|
||||
( $_ = trim($_) ) for ( $title, $tags );
|
||||
( $_ = trim_CRLF($_) ) for ( $title, $tags );
|
||||
|
||||
if ( defined $title ) {
|
||||
set_title( $id, $title );
|
||||
|
@ -2,21 +2,21 @@ package LANraragi::Model::Backup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Redis;
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
|
||||
use LANraragi::Model::Category;
|
||||
use LANraragi::Utils::Database;
|
||||
use LANraragi::Utils::Generic qw(remove_newlines);
|
||||
use LANraragi::Utils::String qw(trim_CRLF);
|
||||
use LANraragi::Utils::Database qw(redis_encode redis_decode invalidate_cache set_title set_tags);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
#build_backup_JSON()
|
||||
#Goes through the Redis archive IDs and builds a JSON string containing their metadata.
|
||||
sub build_backup_JSON {
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $logger = get_logger( "Backup/Restore", "lanraragi" );
|
||||
|
||||
# Basic structure of the backup object
|
||||
@ -65,7 +65,7 @@ sub build_backup_JSON {
|
||||
my ( $name, $title, $tags, $thumbhash ) = @hash{qw(name title tags thumbhash)};
|
||||
|
||||
( $_ = redis_decode($_) ) for ( $name, $title, $tags );
|
||||
( remove_newlines($_) ) for ( $name, $title, $tags );
|
||||
( $_ = trim_CRLF($_) ) for ( $name, $title, $tags );
|
||||
|
||||
# Backup all user-generated metadata, alongside the unique ID.
|
||||
my %arc = (
|
||||
|
@ -2,7 +2,7 @@ package LANraragi::Model::Category;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Redis;
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
|
@ -16,7 +16,7 @@ my $home = Mojo::Home->new;
|
||||
$home->detect;
|
||||
|
||||
my $config = Mojolicious::Plugin::Config->register( Mojolicious->new, { file => $home . '/lrr.conf' } );
|
||||
if ($ENV{LRR_REDIS_ADDRESS}) {
|
||||
if ( $ENV{LRR_REDIS_ADDRESS} ) {
|
||||
$config->{redis_address} = $ENV{LRR_REDIS_ADDRESS};
|
||||
}
|
||||
|
||||
@ -45,8 +45,8 @@ sub get_minion {
|
||||
my $miniondb = get_redisad . "/" . get_miniondb;
|
||||
my $password = get_redispassword;
|
||||
|
||||
# If the password is non-empty, add the required @
|
||||
if ($password) { $password = $password . "@"; }
|
||||
# If the password is non-empty, add the required delimiters
|
||||
if ($password) { $password = "x:" . $password . "@"; }
|
||||
|
||||
return Minion->new( Redis => "redis://$password$miniondb" );
|
||||
}
|
||||
@ -168,7 +168,7 @@ sub get_tagrules {
|
||||
}
|
||||
|
||||
sub get_htmltitle { return &get_redis_conf( "htmltitle", "LANraragi" ) }
|
||||
sub get_motd { return &get_redis_conf( "motd", "Welcome to this Library running LANraragi!" ) }
|
||||
sub get_motd { return &get_redis_conf( "motd", "欢迎来到 LANraragi!" ) }
|
||||
sub get_tempmaxsize { return &get_redis_conf( "tempmaxsize", "500" ) }
|
||||
sub get_pagesize { return &get_redis_conf( "pagesize", "100" ) }
|
||||
sub enable_pass { return &get_redis_conf( "enablepass", "1" ) }
|
||||
@ -187,5 +187,6 @@ sub enable_cryptofs { return &get_redis_conf( "enablecryptofs", "0" ) }
|
||||
sub get_hqthumbpages { return &get_redis_conf( "hqthumbpages", "0" ) }
|
||||
sub get_jxlthumbpages { return &get_redis_conf( "jxlthumbpages", "0" ) }
|
||||
sub get_replacedupe { return &get_redis_conf( "replacedupe", "0" ) }
|
||||
sub can_replacetitles { return &get_redis_conf( "replacetitles", "1" ) }
|
||||
|
||||
1;
|
||||
|
@ -2,7 +2,7 @@ package LANraragi::Model::Opds;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
|
||||
use Redis;
|
||||
use POSIX qw(strftime);
|
||||
@ -49,17 +49,31 @@ sub generate_opds_catalog {
|
||||
@list = sort { lc( $a->{title} ) cmp lc( $b->{title} ) } @list;
|
||||
@cats = sort { lc( $a->{name} ) cmp lc( $b->{name} ) } @cats;
|
||||
|
||||
return $mojo->render_to_string(
|
||||
if($mojo->LRR_CONF->get_motd == "欢迎来到 LANraragi!"){
|
||||
return $mojo->render_to_string(
|
||||
template => "opds",
|
||||
arclist => \@list,
|
||||
catlist => \@cats,
|
||||
nocat => $cat_id eq "",
|
||||
title => $mojo->LRR_CONF->get_htmltitle,
|
||||
motd => $mojo->LRR_CONF->get_motd,
|
||||
motd => "Welcome to this Library running LANraragi!",
|
||||
version => $mojo->LRR_VERSION,
|
||||
api_key_query => $api_key ? "?key=" . $api_key : "",
|
||||
api_key_and => $api_key ? "&key=" . $api_key : ""
|
||||
);
|
||||
}else{
|
||||
return $mojo->render_to_string(
|
||||
template => "opds",
|
||||
arclist => \@list,
|
||||
catlist => \@cats,
|
||||
nocat => $cat_id eq "",
|
||||
title => $mojo->LRR_CONF->get_htmltitle,
|
||||
motd => $mojo->LRR_CONF->get_motd,
|
||||
version => $mojo->LRR_VERSION,
|
||||
api_key_query => $api_key ? "?key=" . $api_key : "",
|
||||
api_key_and => $api_key ? "&key=" . $api_key : ""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sub generate_opds_item {
|
||||
|
@ -11,16 +11,16 @@ use Mojo::JSON qw(decode_json encode_json);
|
||||
use Mojo::UserAgent;
|
||||
use Data::Dumper;
|
||||
|
||||
use LANraragi::Utils::Generic qw(remove_spaces remove_newlines);
|
||||
use LANraragi::Utils::String qw(trim);
|
||||
use LANraragi::Utils::Database qw(set_tags set_title);
|
||||
use LANraragi::Utils::Archive qw(extract_thumbnail);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Tags qw(rewrite_tags split_tags_to_array);
|
||||
use LANraragi::Utils::Archive qw(extract_thumbnail);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Tags qw(rewrite_tags split_tags_to_array);
|
||||
|
||||
# Sub used by Auto-Plugin.
|
||||
sub exec_enabled_plugins_on_file {
|
||||
|
||||
my $id = shift;
|
||||
my $id = shift;
|
||||
my $logger = get_logger( "Auto-Plugin", "lanraragi" );
|
||||
|
||||
$logger->info("Executing enabled metadata plugins on archive with id $id.");
|
||||
@ -33,6 +33,7 @@ sub exec_enabled_plugins_on_file {
|
||||
my @plugins = LANraragi::Utils::Plugins::get_enabled_plugins("metadata");
|
||||
|
||||
# If the regex plugin is in the list, make sure it's ran first.
|
||||
# TODO: Make plugin exec order configurable
|
||||
foreach my $plugin (@plugins) {
|
||||
if ( $plugin->{namespace} eq "regexplugin" ) {
|
||||
my $regex_plugin = $plugin;
|
||||
@ -80,8 +81,9 @@ sub exec_enabled_plugins_on_file {
|
||||
set_title( $id, $plugin_result{title} );
|
||||
|
||||
$newtitle = $plugin_result{title};
|
||||
$logger->debug("Changing title to $newtitle.");
|
||||
$logger->debug("Changing title to $newtitle. (Will do nothing if title is blank)");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,10 +272,10 @@ sub exec_metadata_plugin {
|
||||
my %returnhash = ( new_tags => $newtags );
|
||||
|
||||
# Indicate a title change, if the plugin reports one
|
||||
if ( exists $newmetadata{title} ) {
|
||||
if ( exists $newmetadata{title} && LANraragi::Model::Config->can_replacetitles ) {
|
||||
|
||||
my $newtitle = $newmetadata{title};
|
||||
remove_spaces($newtitle);
|
||||
$newtitle = trim($newtitle);
|
||||
$returnhash{title} = $newtitle;
|
||||
}
|
||||
return %returnhash;
|
||||
|
@ -9,7 +9,8 @@ use Redis;
|
||||
use Storable qw/ nfreeze thaw /;
|
||||
use Sort::Naturally;
|
||||
|
||||
use LANraragi::Utils::Generic qw(split_workload_by_cpu remove_spaces);
|
||||
use LANraragi::Utils::Generic qw(split_workload_by_cpu);
|
||||
use LANraragi::Utils::String qw(trim);
|
||||
use LANraragi::Utils::Database qw(redis_decode redis_encode);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
@ -19,10 +20,10 @@ use LANraragi::Model::Category;
|
||||
# do_search (filter, category_id, page, key, order, newonly, untaggedonly)
|
||||
# Performs a search on the database.
|
||||
sub do_search {
|
||||
|
||||
|
||||
my ( $filter, $category_id, $start, $sortkey, $sortorder, $newonly, $untaggedonly ) = @_;
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
my $logger = get_logger( "Search Engine", "lanraragi" );
|
||||
|
||||
unless ( $redis->exists("LAST_JOB_TIME") ) {
|
||||
@ -36,7 +37,6 @@ sub do_search {
|
||||
# Look in searchcache first
|
||||
my $sortorder_inv = $sortorder ? 0 : 1;
|
||||
my $cachekey = redis_encode("$category_id-$filter-$sortkey-$sortorder-$newonly-$untaggedonly");
|
||||
#print $cachekey . "\n";
|
||||
my $cachekey_inv = redis_encode("$category_id-$filter-$sortkey-$sortorder_inv-$newonly-$untaggedonly");
|
||||
my ( $cachehit, @filtered ) = check_cache( $cachekey, $cachekey_inv );
|
||||
|
||||
@ -65,7 +65,7 @@ sub do_search {
|
||||
sub check_cache {
|
||||
|
||||
my ( $cachekey, $cachekey_inv ) = @_;
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
my $logger = get_logger( "Search Cache", "lanraragi" );
|
||||
|
||||
my @filtered = ();
|
||||
@ -191,7 +191,7 @@ sub search_uncached {
|
||||
# If the tag has a namespace, We don't add a wildcard at the start of the tag to keep it intact.
|
||||
# Otherwise, we add a wildcard at the start to match all namespaces.
|
||||
my $indexkey = $tag =~ /:/ ? "INDEX_$tag*" : "INDEX_*$tag*";
|
||||
my @keys = $redis->keys($indexkey);
|
||||
my @keys = $redis->keys($indexkey);
|
||||
|
||||
# Get the list of IDs for each key
|
||||
foreach my $key (@keys) {
|
||||
@ -203,7 +203,7 @@ sub search_uncached {
|
||||
|
||||
# Append fuzzy title search
|
||||
my $namesearch = $isexact ? "$tag\x00*" : "*$tag*";
|
||||
my $scan = -1;
|
||||
my $scan = -1;
|
||||
while ( $scan != 0 ) {
|
||||
|
||||
# First iteration
|
||||
@ -226,14 +226,14 @@ sub search_uncached {
|
||||
|
||||
if ( scalar @ids == 0 && !$isneg ) {
|
||||
|
||||
# 没有更多的结果,我们可以在这里结束搜索
|
||||
# No more results, we can end search here
|
||||
$logger->trace("该标记没有结果,正在停止搜索。");
|
||||
@filtered = ();
|
||||
last;
|
||||
} else {
|
||||
$logger->trace( "找到此标记的 " . scalar @ids . " 个结果." );
|
||||
|
||||
# 将新列表与以前的列表相交
|
||||
# Intersect the new list with the previous ones
|
||||
@filtered = intersect_arrays( \@ids, \@filtered, $isneg );
|
||||
}
|
||||
}
|
||||
@ -374,7 +374,7 @@ sub compute_search_filter {
|
||||
# Escape already present regex characters
|
||||
$logger->debug("预转义标签: $tag");
|
||||
|
||||
remove_spaces($tag);
|
||||
$tag = trim($tag);
|
||||
|
||||
# Escape characters according to redis zscan rules
|
||||
$tag =~ s/([\[\]\^\\])/\\$1/g;
|
||||
@ -406,10 +406,10 @@ sub sort_results {
|
||||
# (If no tag, defaults to "zzzz")
|
||||
my %tmpfilter = map { $_ => ( $redis->hget( $_, "tags" ) =~ m/.*${re}:(.*)(\,.*|$)/ ) ? $1 : "zzzz" } @filtered;
|
||||
|
||||
my @sorted = map { $_->[0] } # Map back to only having the ID
|
||||
sort { ncmp( $a->[1], $b->[1] ) } # Sort by the tag
|
||||
map { [ $_, lc( $tmpfilter{$_} ) ] } # Map to an array containing the ID and the lowercased tag
|
||||
keys %tmpfilter; # List of IDs
|
||||
my @sorted = map { $_->[0] } # Map back to only having the ID
|
||||
sort { ncmp( $a->[1], $b->[1] ) } # Sort by the tag
|
||||
map { [ $_, lc( $tmpfilter{$_} ) ] } # Map to an array containing the ID and the lowercased tag
|
||||
keys %tmpfilter; # List of IDs
|
||||
|
||||
if ($sortorder) {
|
||||
@sorted = reverse @sorted;
|
||||
|
@ -8,31 +8,14 @@ use Redis;
|
||||
use File::Find;
|
||||
use Mojo::JSON qw(encode_json);
|
||||
|
||||
use LANraragi::Utils::Generic qw(remove_spaces remove_newlines is_archive trim_url);
|
||||
use LANraragi::Utils::Generic qw(is_archive);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF trim_url);
|
||||
use LANraragi::Utils::Database qw(redis_decode redis_encode);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
sub get_archive_count {
|
||||
|
||||
#We can't trust the DB to contain the exact amount of files,
|
||||
#As deleted files are still kept in store.
|
||||
my $dirname = LANraragi::Model::Config->get_userdir;
|
||||
my $count = 0;
|
||||
|
||||
#Count files the old-fashioned way instead
|
||||
find(
|
||||
{ wanted => sub {
|
||||
return if -d $_; #Directories are excluded on the spot
|
||||
if ( is_archive($_) ) {
|
||||
$count++;
|
||||
}
|
||||
},
|
||||
no_chdir => 1,
|
||||
follow_fast => 1
|
||||
},
|
||||
$dirname
|
||||
);
|
||||
return $count;
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
return $redis->zcard("LRR_TITLES") + 0; # Total number of archives (as int)
|
||||
}
|
||||
|
||||
sub get_page_stat {
|
||||
@ -74,26 +57,28 @@ sub build_stat_hashes {
|
||||
|
||||
# Iterate on hashes to get their tags
|
||||
$logger->info("Building stat indexes... ($archive_count archives)");
|
||||
|
||||
# TODO go through tanks first, and remove their IDs from @keys
|
||||
|
||||
foreach my $id (@keys) {
|
||||
if ( $redis->hexists( $id, "tags" ) ) {
|
||||
|
||||
my $rawtags = $redis->hget( $id, "tags" );
|
||||
|
||||
# Split tags by comma
|
||||
my @tags = split( /,\s?/, redis_decode($rawtags) );
|
||||
my @tags = split( /,\s?/, redis_decode($rawtags) );
|
||||
my $has_tags = 0;
|
||||
|
||||
foreach my $t (@tags) {
|
||||
remove_spaces($t);
|
||||
remove_newlines($t);
|
||||
$t = trim($t);
|
||||
$t = trim_CRLF($t);
|
||||
|
||||
# The following are basic and therefore don't count as "tagged"
|
||||
$has_tags = 1 unless $t =~ /(artist|parody|series|language|event|group|date_added|timestamp):.*/;
|
||||
|
||||
# If the tag is a source: tag, add it to the URL index
|
||||
if ( $t =~ /source:(.*)/i ) {
|
||||
my $url = $1;
|
||||
trim_url($url);
|
||||
my $url = trim_url($1);
|
||||
$logger->trace("Adding $url as an URL for $id");
|
||||
$redistx->hset( "LRR_URLMAP", $url, $id ); # No need to encode the value, as URLs are already encoded by design
|
||||
}
|
||||
@ -120,8 +105,8 @@ sub build_stat_hashes {
|
||||
|
||||
# Decode and lowercase the title
|
||||
$title = lc( redis_decode($title) );
|
||||
remove_spaces($title);
|
||||
remove_newlines($title);
|
||||
$title = trim($title);
|
||||
$title = trim_CRLF($title);
|
||||
$title = redis_encode($title);
|
||||
|
||||
# The LRR_TITLES lexicographically sorted set contains both the title and the id under the form $title\x00$id.
|
||||
@ -153,7 +138,7 @@ sub is_url_recorded {
|
||||
$logger->debug("Checking if url $url is in the url map.");
|
||||
|
||||
# Trim last slash from url if it's present
|
||||
trim_url($url);
|
||||
$url = trim_url($url);
|
||||
|
||||
if ( $redis->hexists( "LRR_URLMAP", $url ) ) {
|
||||
$id = $redis->hget( "LRR_URLMAP", $url );
|
||||
@ -166,11 +151,11 @@ sub is_url_recorded {
|
||||
sub build_tag_stats {
|
||||
|
||||
my $minscore = shift;
|
||||
my $logger = get_logger( "Tag Stats", "lanraragi" );
|
||||
my $logger = get_logger( "Tag Stats", "lanraragi" );
|
||||
$logger->debug("Serving tag statistics with a minimum weight of $minscore");
|
||||
|
||||
# Login to Redis and grab the stats sorted set
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
my %tagcloud = $redis->zrangebyscore( "LRR_STATS", $minscore, "+inf", "WITHSCORES" );
|
||||
$redis->quit();
|
||||
|
||||
@ -196,12 +181,23 @@ sub build_tag_stats {
|
||||
}
|
||||
|
||||
sub compute_content_size {
|
||||
my $redis_db = LANraragi::Model::Config->get_redis;
|
||||
|
||||
#Get size of archive folder
|
||||
my $dirname = LANraragi::Model::Config->get_userdir;
|
||||
my $size = 0;
|
||||
my @keys = $redis_db->keys('????????????????????????????????????????');
|
||||
|
||||
find( sub { $size += -s if -f }, $dirname );
|
||||
$redis_db->multi;
|
||||
foreach my $id (@keys) {
|
||||
LANraragi::Utils::Database::get_arcsize($redis_db, $id);
|
||||
}
|
||||
my @result = $redis_db->exec;
|
||||
$redis_db->quit;
|
||||
|
||||
my $size = 0;
|
||||
foreach my $row (@result) {
|
||||
if (defined($row)) {
|
||||
$size = $size + $row;
|
||||
}
|
||||
}
|
||||
|
||||
return int( $size / 1073741824 * 100 ) / 100;
|
||||
}
|
||||
|
342
lib/LANraragi/Model/Tankoubon.pm
Normal file
@ -0,0 +1,342 @@
|
||||
package LANraragi::Model::Tankoubon;
|
||||
|
||||
use feature qw(signatures);
|
||||
no warnings 'experimental::signatures';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Redis;
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
use List::Util qw(min);
|
||||
|
||||
use LANraragi::Utils::Database qw(redis_encode redis_decode invalidate_cache get_archive_json_multi get_tankoubons_by_file);
|
||||
use LANraragi::Utils::Generic qw(array_difference);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
# get_tankoubon_list(page)
|
||||
# Returns a list of all the Tankoubon objects.
|
||||
sub get_tankoubon_list ( $page = 0 ) {
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
|
||||
$page //= 0;
|
||||
|
||||
# Tankoubons are represented by TANK_[timestamp] in DB. Can't wait for 2038!
|
||||
my @tanks = $redis->keys('TANK_??????????');
|
||||
|
||||
# Jam tanks into an array of hashes
|
||||
my @result;
|
||||
foreach my $key ( sort @tanks ) {
|
||||
my %data = get_tankoubon($key);
|
||||
push( @result, \%data );
|
||||
}
|
||||
|
||||
# # Only get the first X keys
|
||||
my $keysperpage = LANraragi::Model::Config->get_pagesize;
|
||||
|
||||
# Return total keys and the filtered ones
|
||||
my $total = $#tanks + 1;
|
||||
my $start = $page * $keysperpage;
|
||||
my $end = min( $start + $keysperpage - 1, $#result );
|
||||
|
||||
if ( $page < 0 ) {
|
||||
return ( $total, $total, @result );
|
||||
} else {
|
||||
return ( $total, $#result + 1, @result[ $start .. $end ] );
|
||||
}
|
||||
|
||||
#return @result;
|
||||
}
|
||||
|
||||
# create_tankoubon(name, existing_id)
|
||||
# Create a Tankoubon.
|
||||
# If an existing Tankoubon ID is supplied, said Tankoubon will be updated with the given parameters.
|
||||
# Returns the ID of the created/updated Tankoubon.
|
||||
sub create_tankoubon ( $name, $tank_id ) {
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
|
||||
# Set all fields of the group object
|
||||
unless ( length($tank_id) ) {
|
||||
$tank_id = "TANK_" . time();
|
||||
|
||||
my $isnewkey = 0;
|
||||
until ($isnewkey) {
|
||||
|
||||
# Check if the group ID exists, move timestamp further if it does
|
||||
if ( $redis->exists($tank_id) ) {
|
||||
$tank_id = "TANK_" . ( time() + 1 );
|
||||
} else {
|
||||
$isnewkey = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
# Get name
|
||||
my @old_name = $redis->zrangebyscore( $tank_id, 0, 0, qw{LIMIT 0 1} );
|
||||
my $n = redis_decode( $old_name[0] );
|
||||
|
||||
$redis->zrem( $tank_id, $n );
|
||||
}
|
||||
|
||||
# Default values for new group
|
||||
# Score 0 will be reserved for the name of the tank
|
||||
$redis->zadd( $tank_id, 0, redis_encode($name) );
|
||||
|
||||
$redis->quit;
|
||||
|
||||
return $tank_id;
|
||||
}
|
||||
|
||||
# get_tankoubon(tankoubonid, fulldata, page)
|
||||
# Returns the Tankoubon matching the given id.
|
||||
# Returns undef if the id doesn't exist.
|
||||
sub get_tankoubon ( $tank_id, $fulldata = 0, $page = 0 ) {
|
||||
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $keysperpage = LANraragi::Model::Config->get_pagesize;
|
||||
|
||||
$page //= 0;
|
||||
|
||||
if ( $tank_id eq "" ) {
|
||||
$logger->debug("No Tankoubon ID provided.");
|
||||
return ();
|
||||
}
|
||||
|
||||
unless ( length($tank_id) == 15 && $redis->exists($tank_id) ) {
|
||||
$logger->warn("$tank_id doesn't exist in the database!");
|
||||
return ();
|
||||
}
|
||||
|
||||
# Declare some needed variables
|
||||
my %tank;
|
||||
my @archives;
|
||||
my @limit = split( ' ', "LIMIT " . ( $keysperpage * $page ) . " $keysperpage" );
|
||||
|
||||
# Get name
|
||||
my @name = $redis->zrangebyscore( $tank_id, 0, 0, qw{LIMIT 0 1} );
|
||||
$tank{name} = redis_decode( $name[0] );
|
||||
|
||||
my %tankoubon;
|
||||
|
||||
# Grab page
|
||||
if ( $page < 0 ) {
|
||||
%tankoubon = $redis->zrangebyscore( $tank_id, 1, "+inf", "WITHSCORES" );
|
||||
} else {
|
||||
%tankoubon = $redis->zrangebyscore( $tank_id, 1, "+inf", "WITHSCORES", @limit );
|
||||
}
|
||||
|
||||
# Sort and add IDs to archives array
|
||||
foreach my $i ( sort { $tankoubon{$a} <=> $tankoubon{$b} } keys %tankoubon ) {
|
||||
push( @archives, $i );
|
||||
}
|
||||
|
||||
# Verify if we require fulldata files or just IDs
|
||||
if ($fulldata) {
|
||||
my @data = get_archive_json_multi(@archives);
|
||||
eval { $tank{archives} = \@archives };
|
||||
eval { $tank{full_data} = \@data }
|
||||
} else {
|
||||
eval { $tank{archives} = \@archives };
|
||||
}
|
||||
|
||||
if ($@) {
|
||||
$logger->error("Couldn't deserialize contents of Tankoubon $tank_id! $@");
|
||||
}
|
||||
|
||||
# Add the key as well
|
||||
$tank{id} = $tank_id;
|
||||
|
||||
my $total = $redis->zcard($tank_id) - 1;
|
||||
|
||||
return ( $total, $#archives + 1, %tank );
|
||||
}
|
||||
|
||||
# delete_tankoubon(tankoubonid)
|
||||
# Deletes the Tankoubon with the given ID.
|
||||
# Returns 0 if the given ID isn't a Tankoubon ID, 1 otherwise
|
||||
sub delete_tankoubon ($tank_id) {
|
||||
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
|
||||
if ( length($tank_id) != 15 ) {
|
||||
|
||||
# Probably not a Tankoubon ID
|
||||
$logger->error("$tank_id is not a Tankoubon ID, doing nothing.");
|
||||
$redis->quit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( $redis->exists($tank_id) ) {
|
||||
$redis->del($tank_id);
|
||||
$redis->quit;
|
||||
return 1;
|
||||
} else {
|
||||
$logger->warn("$tank_id doesn't exist in the database!");
|
||||
$redis->quit;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
# update_archive_list(tankoubonid, arcid)
|
||||
# Updates the archives list in a Tankoubon.
|
||||
# Returns 1 on success, 0 on failure alongside an error message.
|
||||
sub update_archive_list ( $tank_id, $data ) {
|
||||
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $err = "";
|
||||
my @tank_archives = @{ $data->{"archives"} };
|
||||
|
||||
if ( $redis->exists($tank_id) ) {
|
||||
|
||||
foreach my $key (@tank_archives) {
|
||||
unless ( $redis->exists($key) ) {
|
||||
$err = "$key does not exist in the database.";
|
||||
$logger->error($err);
|
||||
$redis->quit;
|
||||
return ( 0, $err );
|
||||
}
|
||||
}
|
||||
|
||||
my @origs = $redis->zrangebyscore( $tank_id, 1, "+inf" );
|
||||
my @diff = array_difference( \@tank_archives, \@origs );
|
||||
my @update;
|
||||
|
||||
# Remove the ones not in the order
|
||||
if (@diff) {
|
||||
$redis->zrem( $tank_id, @diff );
|
||||
}
|
||||
|
||||
# Prepare zadd array
|
||||
my $len = @tank_archives;
|
||||
|
||||
for ( my $i = 0; $i < $len; $i = $i + 1 ) {
|
||||
push @update, $i + 1;
|
||||
push @update, $tank_archives[$i];
|
||||
}
|
||||
|
||||
# Update
|
||||
$redis->zadd( $tank_id, @update );
|
||||
|
||||
$redis->quit;
|
||||
return ( 1, $err );
|
||||
}
|
||||
|
||||
$err = "$tank_id doesn't exist in the database!";
|
||||
$logger->warn($err);
|
||||
$redis->quit;
|
||||
return ( 0, $err );
|
||||
}
|
||||
|
||||
# add_to_tankoubon(tankoubonid, arcid)
|
||||
# Adds the given archive ID to the given Tankoubon.
|
||||
# Returns 1 on success, 0 on failure alongside an error message.
|
||||
sub add_to_tankoubon ( $tank_id, $arc_id ) {
|
||||
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $err = "";
|
||||
|
||||
if ( $redis->exists($tank_id) ) {
|
||||
|
||||
unless ( $redis->exists($arc_id) ) {
|
||||
$err = "$arc_id does not exist in the database.";
|
||||
$logger->error($err);
|
||||
$redis->quit;
|
||||
return ( 0, $err );
|
||||
}
|
||||
|
||||
if ( $redis->zscore( $tank_id, $arc_id ) ) {
|
||||
$err = "$arc_id already present in category $tank_id, doing nothing.";
|
||||
$logger->warn($err);
|
||||
$redis->quit;
|
||||
return ( 1, $err );
|
||||
}
|
||||
|
||||
my $score = $redis->zcard($tank_id);
|
||||
|
||||
$redis->zadd( $tank_id, $score, $arc_id );
|
||||
|
||||
$redis->quit;
|
||||
return ( 1, $err );
|
||||
}
|
||||
|
||||
$err = "$tank_id doesn't exist in the database!";
|
||||
$logger->warn($err);
|
||||
$redis->quit;
|
||||
return ( 0, $err );
|
||||
}
|
||||
|
||||
# remove_from_tankoubon(tankoubonid, arcid)
|
||||
# Removes the given archive ID from the given Tankoubon.
|
||||
# Returns 1 on success, 0 on failure alongside an error message.
|
||||
sub remove_from_tankoubon ( $tank_id, $arcid ) {
|
||||
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $err = "";
|
||||
|
||||
if ( $redis->exists($tank_id) ) {
|
||||
|
||||
unless ( $redis->exists($arcid) ) {
|
||||
$err = "$arcid does not exist in the database.";
|
||||
$logger->error($err);
|
||||
$redis->quit;
|
||||
return ( 0, $err );
|
||||
}
|
||||
|
||||
# Get the score for reference
|
||||
my $score = $redis->zscore( $tank_id, $arcid );
|
||||
|
||||
unless ($score) {
|
||||
$err = "$arcid not in tankoubon $tank_id, doing nothing.";
|
||||
$logger->warn($err);
|
||||
$redis->quit;
|
||||
return ( 1, $err );
|
||||
}
|
||||
|
||||
# Get all the elements after the one to remove to update the score
|
||||
my %toupdate = $redis->zrangebyscore( $tank_id, $score + 1, "+inf", "WITHSCORES" );
|
||||
|
||||
my @update;
|
||||
|
||||
# Build new scores
|
||||
foreach my $i ( keys %toupdate ) {
|
||||
push @update, $toupdate{$i} - 1;
|
||||
push @update, $i;
|
||||
}
|
||||
|
||||
# Remove element
|
||||
$redis->zrem( $tank_id, $arcid );
|
||||
|
||||
# Update scores
|
||||
if ( scalar @update ) {
|
||||
$redis->zadd( $tank_id, @update );
|
||||
}
|
||||
|
||||
$redis->quit;
|
||||
return ( 1, $err );
|
||||
}
|
||||
|
||||
$err = "$tank_id doesn't exist in the database!";
|
||||
$logger->warn($err);
|
||||
$redis->quit;
|
||||
return ( 0, $err );
|
||||
}
|
||||
|
||||
# get_tankoubons_file(arcid)
|
||||
# Gets a list of Tankoubons where archive ID is contained.
|
||||
# Returns an array of tank IDs.
|
||||
sub get_tankoubons_file ($arcid) {
|
||||
|
||||
return get_tankoubons_by_file($arcid);
|
||||
|
||||
}
|
||||
|
||||
1;
|
@ -13,7 +13,8 @@ use File::Copy qw(move);
|
||||
use LANraragi::Utils::Database qw(invalidate_cache compute_id);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Database qw(redis_encode);
|
||||
use LANraragi::Utils::Generic qw(is_archive remove_spaces remove_newlines trim_url get_bytelength);
|
||||
use LANraragi::Utils::Generic qw(is_archive get_bytelength);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF trim_url);
|
||||
|
||||
use LANraragi::Model::Config;
|
||||
use LANraragi::Model::Plugins;
|
||||
@ -31,8 +32,8 @@ use LANraragi::Model::Category;
|
||||
# Returns a status value, the ID and title of the file, and a status message.
|
||||
sub handle_incoming_file {
|
||||
|
||||
my ( $tempfile, $catid, $tags ) = @_;
|
||||
my ( $filename, $dirs, $suffix ) = fileparse( $tempfile, qr/\.[^.]*/ );
|
||||
my ( $tempfile, $catid, $tags ) = @_;
|
||||
my ( $filename, $dirs, $suffix ) = fileparse( $tempfile, qr/\.[^.]*/ );
|
||||
$filename = $filename . $suffix;
|
||||
my $logger = get_logger( "文件上传/下载", "lanraragi" );
|
||||
|
||||
@ -57,7 +58,7 @@ sub handle_incoming_file {
|
||||
my $isdupe = $redis->exists($id) && -e $redis->hget( $id, "file" );
|
||||
|
||||
# Stop here if file is a dupe and replacement is turned off.
|
||||
if ((-e $output_file || $isdupe) && !$replace_dupe) {
|
||||
if ( ( -e $output_file || $isdupe ) && !$replace_dupe ) {
|
||||
|
||||
# Trash temporary file
|
||||
unlink $tempfile;
|
||||
@ -75,7 +76,7 @@ sub handle_incoming_file {
|
||||
# If we are replacing an existing one, just remove the old one first.
|
||||
if ($replace_dupe) {
|
||||
$logger->debug("更换之前删除存档 $id。");
|
||||
LANraragi::Utils::Database::delete_archive( $id );
|
||||
LANraragi::Utils::Database::delete_archive($id);
|
||||
}
|
||||
|
||||
# Add the file to the database ourselves so Shinobu doesn't do it
|
||||
@ -91,8 +92,8 @@ sub handle_incoming_file {
|
||||
my @tags = split( /,\s?/, $tags );
|
||||
|
||||
foreach my $t (@tags) {
|
||||
remove_spaces($t);
|
||||
remove_newlines($t);
|
||||
$t = trim($t);
|
||||
$t = trim_CRLF($t);
|
||||
|
||||
# If the tag is a source: tag, add it to the URL index
|
||||
if ( $t =~ /source:(.*)/i ) {
|
||||
@ -122,6 +123,7 @@ sub handle_incoming_file {
|
||||
# (The file being physically present is necessary in case last modified time is used)
|
||||
LANraragi::Utils::Database::add_timestamp_tag( $redis, $id );
|
||||
LANraragi::Utils::Database::add_pagecount( $redis, $id );
|
||||
LANraragi::Utils::Database::add_arcsize( $redis, $id );
|
||||
$redis->quit();
|
||||
$redis_search->quit();
|
||||
|
||||
|
@ -1,97 +0,0 @@
|
||||
package LANraragi::Plugin::Download::Koushoku;
|
||||
use utf8;
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'uninitialized';
|
||||
|
||||
use URI;
|
||||
use Mojo::UserAgent;
|
||||
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
# Meta-information about your plugin.
|
||||
sub plugin_info {
|
||||
|
||||
return (
|
||||
# Standard metadata
|
||||
name => "Koushoku Downloader",
|
||||
type => "download",
|
||||
namespace => "kskdl",
|
||||
|
||||
#login_from => "ehlogin",
|
||||
author => "Difegue",
|
||||
version => "1.0",
|
||||
description => "Downloads the given KSK URL and adds it to LANraragi.",
|
||||
|
||||
# Downloader-specific metadata
|
||||
# https://ksk.moe/view/____/_________
|
||||
url_regex => "https?:\/\/ksk.moe\/view\/.*\/.*"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
# Mandatory function to be implemented by your downloader
|
||||
sub provide_url {
|
||||
shift;
|
||||
my $lrr_info = shift;
|
||||
my $logger = get_logger( "KSK Downloader", "plugins" );
|
||||
|
||||
# Get the URL to download
|
||||
# We don't really download anything here, we just use the E-H URL to get an archiver URL that can be downloaded normally.
|
||||
my $url = $lrr_info->{url};
|
||||
my $ua = $lrr_info->{user_agent};
|
||||
my $gID = "";
|
||||
my $gToken = "";
|
||||
|
||||
$logger->debug($url);
|
||||
if ( $url =~ /.*\/view\/([0-9]*)\/([0-z]*)\/*.*/ ) {
|
||||
$gID = $1;
|
||||
$gToken = $2;
|
||||
} else {
|
||||
return ( error => "Not a valid Koushoku URL!" );
|
||||
}
|
||||
|
||||
$logger->debug("gID: $gID, gToken: $gToken");
|
||||
|
||||
# Sadly, we need to look at the original page to get the hash key to get a download URL. (and get cookies in case those are needed?)
|
||||
# It's DOM parsing time again!
|
||||
my $response = $ua->max_redirects(5)->get($url)->result;
|
||||
my $content = $response->body;
|
||||
my $dom = Mojo::DOM->new($content);
|
||||
my $hash = "";
|
||||
|
||||
eval {
|
||||
# Hash is stuck in the value of the "Original" DL button.
|
||||
$hash = $dom->at(".original")->attr('value');
|
||||
};
|
||||
|
||||
if ( $hash eq "" ) {
|
||||
return ( error => "Couldn't retrieve download hash from URL $gID/$gToken" );
|
||||
}
|
||||
|
||||
# POST to the download endpoint to get the download URL
|
||||
# https://ksk.moe/download/11537/d951ca197324
|
||||
my $downloadURL = "https:\/\/ksk.moe\/download\/$gID\/$gToken";
|
||||
$logger->info("下载表单 URL: $downloadURL, hash: $hash");
|
||||
|
||||
# First redirect should be our download URL.
|
||||
my $finalURL = URI->new();
|
||||
|
||||
eval {
|
||||
$response = $ua->max_redirects(0)->post( $downloadURL => form => { hash => $hash } )->result;
|
||||
$content = $response->body;
|
||||
if ( $response->code == 302 ) {
|
||||
$logger->debug( "重定向/位置 header: " . $response->headers->location );
|
||||
$finalURL = URI->new( $response->headers->location );
|
||||
}
|
||||
};
|
||||
|
||||
if ( $@ || $finalURL eq "" ) {
|
||||
return ( error => "Couldn't proceed with an original size download: <pre>$content</pre>" );
|
||||
}
|
||||
|
||||
# All done!
|
||||
return ( download_url => $finalURL->as_string );
|
||||
}
|
||||
|
||||
1;
|
@ -19,15 +19,20 @@ sub plugin_info {
|
||||
type => "metadata",
|
||||
namespace => "trabant",
|
||||
author => "Difegue",
|
||||
version => "2.3",
|
||||
description => "在 chaika.moe 中搜索与您的档案匹配的标签。 这将首先尝试使用缩略图,然后回退到默认文本搜索.",
|
||||
version => "2.3.1",
|
||||
description =>
|
||||
"在 chaika.moe 中搜索与您的档案匹配的标签。 这将首先尝试使用缩略图,然后回退到默认文本搜索.",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFQocjU4r+QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAAEZElEQVQ4y42T3WtTdxzGn/M7J+fk5SRpTk7TxMZkXU84tTbVNrUT3YxO7HA4pdtQZDe7cgx2\ns8vBRvEPsOwFYTDYGJUpbDI2wV04cGXCGFLonIu1L2ptmtrmxeb1JDkvv121ZKVze66f74eH7/f5\nMmjRwMCAwrt4/9KDpflMJpPHvyiR2DPcJklJ3TRDDa0xk36cvrm8vDwHAAwAqKrqjjwXecPG205w\nHBuqa9rk77/d/qJYLD7cCht5deQIIczbgiAEKLVAKXWUiqVV06Tf35q8dYVJJBJem2A7Kwi2nQzD\nZig1CG93+PO5/KN6tf5NKpVqbsBUVVVFUUxwHJc1TXNBoxojS7IbhrnLMMx9pVJlBqFQKBKPxwcB\nkJYgjKIo3QCE1nSKoghbfJuKRqN2RVXexMaQzWaLezyeEUEQDjscjk78PxFFUYRkMsltJgGA3t7e\nyMLCwie6rr8iCILVbDbvMgwzYRjGxe0o4XC4s1AoHPP5fMP5/NNOyzLKAO6Ew+HrDADBbre/Ryk9\nnzx81FXJNlEpVpF+OqtpWu2MpmnXWmH9/f2umZmZi4cOHXnLbILLzOchhz1YerJAs9m1GwRAg2GY\nh7GYah488BJYzYW+2BD61AFBlmX/1nSNRqN9//792ujoaIPVRMjOKHoie3DytVGmp2fXCAEAjuMm\nu7u7Umosho6gjL/u/QHeEgvJZHJ2K/D+/fuL4+PjXyvPd5ldkShy1UXcmb4DnjgQj/fd5gDA6/XS\nYCAwTwh9oT3QzrS1+VDVi+vd3Tsy26yQVoFF3dAXJVmK96p9EJ0iLNOwKKU3CQCk0+lSOpP5WLDz\nF9Q9kZqyO0SloOs6gMfbHSU5NLRiUOuax2/HyZPHEOsLw2SbP83eu/fLxrkNp9P554XxCzVa16MC\n7+BPnTk9cfmH74KJE8nmga7Xy5JkZ8VKifGIHpoBb1VX8hNTd3/t/7lQ3OeXfFPvf/jBRw8ezD/a\n7M/aWq91cGgnJaZ2VcgSdnV1XRNNd3vAoBVVYusmnEQS65hfgSG6c+zy3Kre7nF/KrukcMW0Zg8O\nD08DoJutDxxOEb5IPUymwrq8ft1gLKfkFojkkRxemERCAQUACPFWRazYLJcrFGwQhyufbQQ7rFpy\nLMkCwGZC34qPIuwp+XPOjBFwazQ/txrdFS2GGS/Xuj+pUKLGk1Kjvlded3s72lyGW+PLbGVcmrAA\ngN0wTk1NWYODg9XOKltGtpazi5GigzroUnHN5nUHG1ylRsG7rDXHmnEpu4CeEtEKkqNc6QqlLc/M\n8uT5lLH5eq0aGxsju1O7GQB498a5s/0x9dRALPaQEDZnYwnhWJtMCCNrjeb0UP34Z6e/PW22zjPP\n+vwXBwfPvbw38XnXjk7GsiwKAIQQhjAMMrlsam45d+zLH6/8o6vkWcBcrXbVKQhf6bpucCwLjmUB\nSmmhXC419eblrbD/TAgAkUjE987xE0c7ZDmk66ajUCnq+cL63fErl25s5/8baQPaWLhx6goAAAAA\nSUVORK5CYII=",
|
||||
parameters => [
|
||||
{ type => "bool", desc => "保存存档标题" },
|
||||
parameters => [
|
||||
{ type => "bool", desc => "添加以下标签(如果可用):下载URL,Gallery ID,类别,时间戳" },
|
||||
{ type => "bool", desc => "将没有名称空间的标签添加到“另一个:”名称空间,反映了E-H的命名空间的行为" },
|
||||
{ type => "string", desc => "将自定义的“source:”标签添加到您的存档中。示例:chaika。如果空白,不会添加标签" }
|
||||
{ type => "bool",
|
||||
desc =>
|
||||
"将没有名称空间的标签添加到“另一个:”名称空间,反映了E-H的命名空间的行为"
|
||||
},
|
||||
{ type => "string",
|
||||
desc => "将自定义的“source:”标签添加到您的存档中。示例:chaika。如果空白,不会添加标签"
|
||||
}
|
||||
],
|
||||
oneshot_arg => "Chaika Gallery或存档URL(将在您的档案中附加匹配标签)"
|
||||
);
|
||||
@ -38,8 +43,8 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ( $savetitle, $addextra, $addother, $addsource ) = @_; # Plugin parameters
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ( $addextra, $addother, $addsource ) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
my $newtags = "";
|
||||
@ -58,7 +63,8 @@ sub get_tags {
|
||||
# Try text search if it fails
|
||||
if ( $newtags eq "" ) {
|
||||
$logger->info("没有结果,回到文本搜索。");
|
||||
( $newtags, $newtitle ) = search_for_archive( $lrr_info->{archive_title}, $lrr_info->{existing_tags}, $addextra, $addother, $addsource );
|
||||
( $newtags, $newtitle ) =
|
||||
search_for_archive( $lrr_info->{archive_title}, $lrr_info->{existing_tags}, $addextra, $addother, $addsource );
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,9 +73,9 @@ sub get_tags {
|
||||
return ( error => "未找到匹配的 Chaika 档案!" );
|
||||
} else {
|
||||
$logger->info("将以下标签发送到 LRR:$newtags");
|
||||
|
||||
#Return a hash containing the new metadata
|
||||
if ( $savetitle && $newtags ne "" ) { return ( tags => $newtags, title => $newtitle ); }
|
||||
else { return ( tags => $newtags ); }
|
||||
return ( tags => $newtags, title => $newtitle );
|
||||
}
|
||||
|
||||
}
|
||||
@ -145,7 +151,7 @@ sub get_json_from_chaika {
|
||||
my $ua = Mojo::UserAgent->new;
|
||||
my $res = $ua->get($URL)->result;
|
||||
|
||||
if ($res->is_error) {
|
||||
if ( $res->is_error ) {
|
||||
return;
|
||||
}
|
||||
my $textrep = $res->body;
|
||||
@ -161,38 +167,40 @@ sub parse_chaika_json {
|
||||
|
||||
my $tags = $json->{"tags"} || ();
|
||||
foreach my $tag (@$tags) {
|
||||
|
||||
#Replace underscores with spaces
|
||||
$tag =~ s/_/ /g;
|
||||
|
||||
|
||||
#Add 'other' namespace if none
|
||||
if ($addother && index($tag, ":") == -1) {
|
||||
if ( $addother && index( $tag, ":" ) == -1 ) {
|
||||
$tag = "other:" . $tag;
|
||||
}
|
||||
}
|
||||
|
||||
my $category = lc $json->{"category"};
|
||||
my $download = $json->{"download"} ? $json->{"download"} : $json->{"archives"}->[0]->{"link"};
|
||||
my $gallery = $json->{"gallery"} ? $json->{"gallery"} : $json->{"id"};
|
||||
my $category = lc $json->{"category"};
|
||||
my $download = $json->{"download"} ? $json->{"download"} : $json->{"archives"}->[0]->{"link"};
|
||||
my $gallery = $json->{"gallery"} ? $json->{"gallery"} : $json->{"id"};
|
||||
my $timestamp = $json->{"posted"};
|
||||
if ($tags && $addextra) {
|
||||
if ($category ne "") {
|
||||
push(@$tags, "category:" . $category);
|
||||
if ( $tags && $addextra ) {
|
||||
if ( $category ne "" ) {
|
||||
push( @$tags, "category:" . $category );
|
||||
}
|
||||
if ($download ne "") {
|
||||
push(@$tags, "download:" . $download);
|
||||
if ( $download ne "" ) {
|
||||
push( @$tags, "download:" . $download );
|
||||
}
|
||||
if ($gallery ne "") {
|
||||
push(@$tags, "gallery:" . $gallery);
|
||||
if ( $gallery ne "" ) {
|
||||
push( @$tags, "gallery:" . $gallery );
|
||||
}
|
||||
if ($timestamp ne "") {
|
||||
push(@$tags, "timestamp:" . $timestamp);
|
||||
if ( $timestamp ne "" ) {
|
||||
push( @$tags, "timestamp:" . $timestamp );
|
||||
}
|
||||
}
|
||||
|
||||
if ($gallery && $gallery ne "") {
|
||||
if ( $gallery && $gallery ne "" ) {
|
||||
|
||||
# add custom source, but only if having found gallery
|
||||
if ($addsource && $addsource ne "") {
|
||||
push(@$tags, "source:" . $addsource);
|
||||
if ( $addsource && $addsource ne "" ) {
|
||||
push( @$tags, "source:" . $addsource );
|
||||
}
|
||||
return ( join( ', ', @$tags ), $json->{"title"} );
|
||||
} else {
|
||||
|
@ -16,15 +16,19 @@ sub plugin_info {
|
||||
type => "metadata",
|
||||
namespace => "chaikafileplugin",
|
||||
author => "Difegue & Plebs",
|
||||
version => "0.1",
|
||||
version => "0.2",
|
||||
description => "获取chaika的api.json文件的内容转换到元数据中",
|
||||
icon =>
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFQocjU4r+QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAAEZElEQVQ4y42T3WtTdxzGn/M7J+fk5SRpTk7TxMZkXU84tTbVNrUT3YxO7HA4pdtQZDe7cgx2\ns8vBRvEPsOwFYTDYGJUpbDI2wV04cGXCGFLonIu1L2ptmtrmxeb1JDkvv121ZKVze66f74eH7/f5\nMmjRwMCAwrt4/9KDpflMJpPHvyiR2DPcJklJ3TRDDa0xk36cvrm8vDwHAAwAqKrqjjwXecPG205w\nHBuqa9rk77/d/qJYLD7cCht5deQIIczbgiAEKLVAKXWUiqVV06Tf35q8dYVJJBJem2A7Kwi2nQzD\nZig1CG93+PO5/KN6tf5NKpVqbsBUVVVFUUxwHJc1TXNBoxojS7IbhrnLMMx9pVJlBqFQKBKPxwcB\nkJYgjKIo3QCE1nSKoghbfJuKRqN2RVXexMaQzWaLezyeEUEQDjscjk78PxFFUYRkMsltJgGA3t7e\nyMLCwie6rr8iCILVbDbvMgwzYRjGxe0o4XC4s1AoHPP5fMP5/NNOyzLKAO6Ew+HrDADBbre/Ryk9\nnzx81FXJNlEpVpF+OqtpWu2MpmnXWmH9/f2umZmZi4cOHXnLbILLzOchhz1YerJAs9m1GwRAg2GY\nh7GYah488BJYzYW+2BD61AFBlmX/1nSNRqN9//792ujoaIPVRMjOKHoie3DytVGmp2fXCAEAjuMm\nu7u7Umosho6gjL/u/QHeEgvJZHJ2K/D+/fuL4+PjXyvPd5ldkShy1UXcmb4DnjgQj/fd5gDA6/XS\nYCAwTwh9oT3QzrS1+VDVi+vd3Tsy26yQVoFF3dAXJVmK96p9EJ0iLNOwKKU3CQCk0+lSOpP5WLDz\nF9Q9kZqyO0SloOs6gMfbHSU5NLRiUOuax2/HyZPHEOsLw2SbP83eu/fLxrkNp9P554XxCzVa16MC\n7+BPnTk9cfmH74KJE8nmga7Xy5JkZ8VKifGIHpoBb1VX8hNTd3/t/7lQ3OeXfFPvf/jBRw8ezD/a\n7M/aWq91cGgnJaZ2VcgSdnV1XRNNd3vAoBVVYusmnEQS65hfgSG6c+zy3Kre7nF/KrukcMW0Zg8O\nD08DoJutDxxOEb5IPUymwrq8ft1gLKfkFojkkRxemERCAQUACPFWRazYLJcrFGwQhyufbQQ7rFpy\nLMkCwGZC34qPIuwp+XPOjBFwazQ/txrdFS2GGS/Xuj+pUKLGk1Kjvlded3s72lyGW+PLbGVcmrAA\ngN0wTk1NWYODg9XOKltGtpazi5GigzroUnHN5nUHG1ylRsG7rDXHmnEpu4CeEtEKkqNc6QqlLc/M\n8uT5lLH5eq0aGxsju1O7GQB498a5s/0x9dRALPaQEDZnYwnhWJtMCCNrjeb0UP34Z6e/PW22zjPP\n+vwXBwfPvbw38XnXjk7GsiwKAIQQhjAMMrlsam45d+zLH6/8o6vkWcBcrXbVKQhf6bpucCwLjmUB\nSmmhXC419eblrbD/TAgAkUjE987xE0c7ZDmk66ajUCnq+cL63fErl25s5/8baQPaWLhx6goAAAAA\nSUVORK5CYII=",
|
||||
parameters => [
|
||||
{ type => "bool", desc => "保存存档标题" },
|
||||
parameters => [
|
||||
{ type => "bool", desc => "添加以下标签(如果存在): download URL, gallery ID, category, timestamp" },
|
||||
{ type => "bool", desc => "将没有命名空间的标签添加到“other:”命名空间,镜像 E-H 为所有内容命名空间的行为" },
|
||||
{ type => "string", desc => "将自定义“source:”标签添加到您的存档中。 例如:chaika。 如果为空则不会添加标签" }
|
||||
{ type => "bool",
|
||||
desc =>
|
||||
"将没有命名空间的标签添加到“other:”命名空间,镜像 E-H 为所有内容命名空间的行为"
|
||||
},
|
||||
{ type => "string",
|
||||
desc => "将自定义“source:”标签添加到您的存档中。 例如:chaika。 如果为空则不会添加标签"
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -33,8 +37,8 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($savetitle, $addextra, $addother, $addsource) = @_; # Plugin parameters
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ( $addextra, $addother, $addsource ) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
my $newtags = "";
|
||||
@ -43,6 +47,7 @@ sub get_tags {
|
||||
# Try reading any embedded api.json file
|
||||
my $path_in_archive = is_file_in_archive( $lrr_info->{file_path}, "api.json" );
|
||||
if ($path_in_archive) {
|
||||
|
||||
#Extract api.json
|
||||
my $filepath = extract_file_from_archive( $lrr_info->{file_path}, $path_in_archive );
|
||||
( $newtags, $newtitle ) = tags_from_file( $filepath, $addextra, $addother, $addsource );
|
||||
@ -54,9 +59,9 @@ sub get_tags {
|
||||
} else {
|
||||
|
||||
$logger->info("Sending the following tags to LRR: $newtags");
|
||||
|
||||
#Return a hash containing the new metadata
|
||||
if ( $savetitle && $newtags ne "" ) { return ( tags => $newtags, title => $newtitle ); }
|
||||
else { return ( tags => $newtags ); }
|
||||
return ( tags => $newtags, title => $newtitle );
|
||||
}
|
||||
|
||||
}
|
||||
@ -68,7 +73,7 @@ sub get_tags {
|
||||
# tags_from_file
|
||||
sub tags_from_file {
|
||||
|
||||
my ( $filepath, $addextra, $addother, $addsource ) = @_;
|
||||
my ( $filepath, $addextra, $addother, $addsource ) = @_;
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
@ -76,16 +81,16 @@ sub tags_from_file {
|
||||
my $stringjson = "";
|
||||
|
||||
open( my $fh, '<:encoding(UTF-8)', $filepath )
|
||||
or return ( error => "Could not open $filepath!" );
|
||||
or return ( error => "Could not open $filepath!" );
|
||||
|
||||
while ( my $row = <$fh> ) {
|
||||
chomp $row;
|
||||
$stringjson .= $row;
|
||||
}
|
||||
unlink( $fh );
|
||||
unlink($fh);
|
||||
|
||||
#Use Mojo::JSON to decode the string into a hash
|
||||
my $hashjson = from_json( $stringjson );
|
||||
my $hashjson = from_json($stringjson);
|
||||
|
||||
return parse_chaika_json( $hashjson, $addextra, $addother, $addsource );
|
||||
}
|
||||
@ -99,38 +104,40 @@ sub parse_chaika_json {
|
||||
|
||||
my $tags = $json->{"tags"} || ();
|
||||
foreach my $tag (@$tags) {
|
||||
|
||||
#Replace underscores with spaces
|
||||
$tag =~ s/_/ /g;
|
||||
|
||||
#Add 'other' namespace if none
|
||||
if ($addother && index($tag, ":") == -1) {
|
||||
if ( $addother && index( $tag, ":" ) == -1 ) {
|
||||
$tag = "other:" . $tag;
|
||||
}
|
||||
}
|
||||
|
||||
my $category = lc $json->{"category"};
|
||||
my $download = $json->{"download"} ? $json->{"download"} : $json->{"archives"}->[0]->{"link"};
|
||||
my $gallery = $json->{"gallery"} ? $json->{"gallery"} : $json->{"id"};
|
||||
my $category = lc $json->{"category"};
|
||||
my $download = $json->{"download"} ? $json->{"download"} : $json->{"archives"}->[0]->{"link"};
|
||||
my $gallery = $json->{"gallery"} ? $json->{"gallery"} : $json->{"id"};
|
||||
my $timestamp = $json->{"posted"};
|
||||
if ($tags && $addextra) {
|
||||
if ($category ne "") {
|
||||
push(@$tags, "category:" . $category);
|
||||
if ( $tags && $addextra ) {
|
||||
if ( $category ne "" ) {
|
||||
push( @$tags, "category:" . $category );
|
||||
}
|
||||
if ($download ne "") {
|
||||
push(@$tags, "download:" . $download);
|
||||
if ( $download ne "" ) {
|
||||
push( @$tags, "download:" . $download );
|
||||
}
|
||||
if ($gallery ne "") {
|
||||
push(@$tags, "gallery:" . $gallery);
|
||||
if ( $gallery ne "" ) {
|
||||
push( @$tags, "gallery:" . $gallery );
|
||||
}
|
||||
if ($timestamp ne "") {
|
||||
push(@$tags, "timestamp:" . $timestamp);
|
||||
if ( $timestamp ne "" ) {
|
||||
push( @$tags, "timestamp:" . $timestamp );
|
||||
}
|
||||
}
|
||||
|
||||
if ($gallery && $gallery ne "") {
|
||||
if ( $gallery && $gallery ne "" ) {
|
||||
|
||||
# add custom source, but only if having found gallery
|
||||
if ($addsource && $addsource ne "") {
|
||||
push(@$tags, "source:" . $addsource);
|
||||
if ( $addsource && $addsource ne "" ) {
|
||||
push( @$tags, "source:" . $addsource );
|
||||
}
|
||||
return ( join( ', ', @$tags ), $json->{"title"} );
|
||||
} else {
|
||||
@ -138,5 +145,4 @@ sub parse_chaika_json {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
@ -17,7 +17,7 @@ sub plugin_info {
|
||||
author => "Difegue",
|
||||
version => "2.1",
|
||||
description => "应用自定义标签修改.",
|
||||
icon =>
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFQ05iQtpeQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAAD8ElEQVQ4y4WUW2yURRTHfzPfty2UWrZbqdrdhktowQAJiQ/iDUwkaFJ5IAQfGjWBByMhwYAm\nBH3xqT5ArARS9cUgJC1qjCDxCY1JEy6lLVJrW9lKq6Wlgcqy2W7b/S4zx4fdbstFneRMZjLJb/7n\nf86MOn78+MlEIvE6gIhgRRARECGTmaS0tJT6+vrvo7HKxserq6f4v9Hc3PyxiBh5yBgYGJBUKiW/\n9vZKV/eVb7s6OyN79uxhbGwMKVx8f2gRUUEQEAQhfhDg+wGeHxAaA8DZs2e50tVNZ+flJ3v7B8ra\n29uJx+M0NTXhed6DCg8dPtzs+77xfV883xfP8yVXCM/zxA9CCcNQzv3402+bX96yuK2tjd27dxOP\nx2loaCCdTj+gkIJlICAFLz0vx/RMjmx2kunpaXK5nF5au3RJb29fzdq162ouX+p4ZHh4mO3bt98j\n0DU2n5oUZqUUvudz8cIFEvEEoQkRIBarXLFr584frLVmdHRUj4yNXu3r69tbVVV1u7W1lcbGxjxQ\njMzjKxAw1rJoURlr1q7B9wMEQaFKBerDwMfzPKKLo6t+uXp12Z07d16pq6tL79ixg0gkgjamkCv5\nvItKUfkrFCilmN0orZmayrJgQSlDQ0NPHz3WskhrTXd3NwBaxBR8m007v7aza+a8RQTXcaipTTAx\nMYHn+2SnJ4nFYgwODuaBRmxB3FylEEGsLRKliBZcHSFVkSFVOUWiOk5uxiObzZJKpfJAa8wciPvA\nzIflzxSKW+EtDgx8wKUbXVSYcuM4TtEWba1VSkGkJILruvmIuFibh2gNjtI4SuNqB6XBVQ7jFTdo\nK/+GiWemNmmt59omFouNfXf6dLsIalZZGJrI6lV1zwIcOP8hQ871YsE0mpRJUVFWTnZ5WjpSF09t\n+WhbJdf4DMDdv2/foYMHDx46c+YMW7dupba2lutDw1UrV674G+B3/xr9VT1gVLFMWjk4SoOg/ope\nZyo9/elzq1/QQIs7v8u11oyMjFBTU6OszKas0I4D6t4nG1qDAqwWJnWG0IbrAdz737ZSCmsNUvAw\nmAnxJsxcHyEoV+FUgDiWhXfL2RS89FXdtSfeYss8oDEGz/Ow1mKMQReq9vXmE4SSVyPAAl3KiT9b\n2X/7XZZMPcbz9sWTl94/9+bn63vUXvaKCxCGIclkkmQyCcCxlhZJ/jHI3XR60hqjpdinloiUSJ/0\nlZQuLC/Z0L9B4n2V73zR08ORXUcEyFd2fHycTCZT7KWLHR3q5s3xyslMxlhrlYjklVvLuv6V/tE3\nTr29vGzZxlffe+q1jV82hObRwFZXVxONRvnXn/e/ounCJzU/z5yPPOzsH4cGnEj6mhLzAAAAAElF\nTkSuQmCC",
|
||||
parameters => [ { type => "string", desc => "要复制的标签,以逗号分隔." } ]
|
||||
);
|
||||
@ -28,8 +28,8 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($tagstocopy) = @_; # Plugin parameters
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($tagstocopy) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_logger( "Tag Copy", "plugins" );
|
||||
|
||||
|
@ -20,12 +20,12 @@ sub plugin_info {
|
||||
|
||||
return (
|
||||
#Standard metadata
|
||||
name => "E-Hentai",
|
||||
type => "metadata",
|
||||
namespace => "ehplugin",
|
||||
login_from => "ehlogin",
|
||||
author => "Difegue and others",
|
||||
version => "2.5.1",
|
||||
name => "E-Hentai",
|
||||
type => "metadata",
|
||||
namespace => "ehplugin",
|
||||
login_from => "ehlogin",
|
||||
author => "Difegue and others",
|
||||
version => "2.5.2",
|
||||
description =>
|
||||
"搜索 g.e-hentai 以查找与您的存档匹配的标签. <br/><i class='fa fa-exclamation-circle'></i> 此插件将使用存档的 source: tag (如果存在)",
|
||||
icon =>
|
||||
@ -35,7 +35,6 @@ sub plugin_info {
|
||||
{ type => "bool", desc => "保存档案名称" },
|
||||
{ type => "bool", desc => "首先使用缩略图获取(否则使用标题)" },
|
||||
{ type => "bool", desc => "使用标题的GID搜索(返回标题)" },
|
||||
{ type => "bool", desc => "使用 ExHentai(可以在没有星形 cookie 的情况下搜索fjorded内容)" },
|
||||
{ type => "bool",
|
||||
desc => "如果可用,请保存原始标题,而不是英文或罗马拼音标题"
|
||||
},
|
||||
@ -44,7 +43,7 @@ sub plugin_info {
|
||||
|
||||
],
|
||||
oneshot_arg => "E-H Gallery URL (Will attach tags matching this exact gallery to your archive)",
|
||||
cooldown => 15
|
||||
cooldown => 4
|
||||
);
|
||||
|
||||
}
|
||||
@ -53,9 +52,9 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my $ua = $lrr_info->{user_agent};
|
||||
my ( $lang, $savetitle, $usethumbs, $search_gid, $enablepanda, $jpntitle, $additionaltags, $expunged ) = @_; # Plugin parameters
|
||||
my ( $lang, $usethumbs, $search_gid, $enablepanda, $jpntitle, $additionaltags, $expunged ) = @_; # Plugin parameters
|
||||
|
||||
# Use the logger to output status - they'll be passed to a specialized logfile and written to STDOUT.
|
||||
my $logger = get_plugin_logger();
|
||||
@ -110,7 +109,7 @@ sub get_tags {
|
||||
if ( $hashdata{tags} ne "" ) {
|
||||
|
||||
if ( !$hasSrc ) { $hashdata{tags} .= ", source:" . ( split( '://', $domain ) )[1] . "/g/$gID/$gToken"; }
|
||||
if ($savetitle) { $hashdata{title} = $ehtitle; }
|
||||
$hashdata{title} = $ehtitle;
|
||||
}
|
||||
|
||||
#Return a hash containing the new metadata - it will be integrated in LRR.
|
||||
|
@ -13,7 +13,7 @@ use utf8;
|
||||
use LANraragi::Model::Plugins;
|
||||
use LANraragi::Utils::Database;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
use LANraragi::Utils::Generic qw(remove_spaces);
|
||||
use LANraragi::Utils::String qw(trim);
|
||||
use LANraragi::Utils::Archive qw(is_file_in_archive extract_file_from_archive);
|
||||
|
||||
#Meta-information about your plugin.
|
||||
@ -21,18 +21,17 @@ sub plugin_info {
|
||||
|
||||
return (
|
||||
#Standard metadata
|
||||
name => "eze",
|
||||
type => "metadata",
|
||||
namespace => "ezeplugin",
|
||||
author => "Difegue",
|
||||
version => "2.3",
|
||||
name => "eze",
|
||||
type => "metadata",
|
||||
namespace => "ezeplugin",
|
||||
author => "Difegue",
|
||||
version => "2.3.1",
|
||||
description =>
|
||||
"从 eze 样式的 info.json 文件 ({'gallery_info': {xxx} } syntax)中收集元数据, 这些文件嵌入在您的存档中或具有相同名称的同一文件夹中. ({archive_name}.json)",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFDYBnHlU6AAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAAETUlEQVQ4y22UTWhTWRTHf/d9JHmNJLFpShMcKoRIqxXE4sKpjgthYLCLggU/wI1CUWRUxlmU\nWblw20WZMlJc1yKKKCjCdDdYuqgRiygq2mL8aJpmQot5uabv3XdnUftG0bu593AOv3M45/yvGBgY\n4OrVqwRBgG3bGIaBbduhDSClxPM8tNZMTEwwMTGB53lYloXWmkgkwqdPnygUCljZbJbW1lYqlQqG\nYYRBjuNw9+5dHj16RD6fJ51O09bWxt69e5mammJ5eZm1tTXi8Tiu6xKNRrlx4wZWNBqlXq8Tj8cx\nTRMhBJZlMT4+zuXLlxFCEIvFqFarBEFAKpXCcRzq9TrpdJparcbIyAiHDh1icXERyzAMhBB4nofv\n+5imiWmavHr1inQ6jeM4ZLNZDMMglUqxuLiIlBLXdfn48SNKKXp6eqhUKiQSCaxkMsna2hqe52Hb\nNsMdec3n8+Pn2+vpETt37qSlpYVyucz8/DzT09Ns3bqVYrEIgOM4RCIRrI1MiUQCz/P43vE8jxcv\nXqCUwvM8Zmdn2bJlC6lUitHRUdrb2zFNE9/3sd6/f4/jOLiuSzKZDCH1wV/EzMwM3d3dNN69o729\nnXK5jFKKPXv2sLS0RF9fHydOnMD3fZRSaK0xtNYEQYBpmtTr9RC4b98+LMsCwLZtHj9+TCwWI5/P\nI6Xk5MmTXLhwAaUUG3MA4M6dOzQaDd68eYOUkqHIZj0U2ay11mzfvp1du3YhhGBgYIDjx4/T3d1N\nvV4nCAKklCilcF2XZrOJlBIBcOnSJc6ePYsQgj9yBf1l//7OJcXPH1Y1wK/Ff8SfvT995R9d/SA8\nzyMaja5Xq7Xm1q1bLCwssLS09M1Atm3bFr67urq+8W8oRUqJlBJLCMHNmze5d+8e2Ww2DPyrsSxq\ntRqZTAattZibm6PZbHJFVoUQgtOxtAbwfR8A13WJxWIYANVqFd/36e/v/ypzIpEgCAKEEMzNzYXN\n34CN/FsSvu+jtSaTyeC67jrw4cOHdHZ2kslkQmCz2SQSiYT269evMU0zhF2RVaH1ejt932dlZYXh\n4eF14MLCArZtI6UMAb+1/qBPx9L6jNOmAY4dO/b/agBnnDb9e1un3vhQzp8/z/Xr19eBQgjevn3L\n1NTUd5WilKJQKGAYxje+lpYWrl27xuTk5PqKARSLRfr6+hgaGiKbzfLy5UvGx8dRSqGUwnEcDMNA\nKYUQIlRGNBplZmaGw4cPE4/HOXDgAMbs7Cy9vb1cvHiR+fl5Hjx4QC6XwzAMYrEYz549Y3p6mufP\nn4d6NU0Tx3GYnJzk6NGjNJtNduzYQUdHB+LL8mu1Gv39/WitGRsb4/79+3R1dbF7925yuVw4/Uaj\nwalTpzhy5AhjY2P4vs/BgwdJp9OYG7ByuUwmk6FUKgFw7tw5SqUSlUqFp0+fkkgk2LRpEysrKzx5\n8oTBwUG01ty+fZv9+/eTz+dZXV3lP31rAEu+yXjEAAAAAElFTkSuQmCC",
|
||||
parameters => [
|
||||
{ type => "bool", desc => "保存档案名称" },
|
||||
{ type => "bool",
|
||||
{ type => "bool",
|
||||
desc => "如果可用,请保存原始标题,而不是英文或罗马拼音标题"
|
||||
},
|
||||
{ type => "bool", desc => "获取额外的时间戳(发布时间)和上传者元数据" },
|
||||
@ -45,25 +44,25 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($save_title, $origin_title, $additional_tags) = @_; # Plugin parameters
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ( $origin_title, $additional_tags ) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
my $path_in_archive = is_file_in_archive( $lrr_info->{file_path}, "info.json" );
|
||||
|
||||
my ($name, $path, $suffix) = fileparse($lrr_info->{file_path}, qr/\.[^.]*/);
|
||||
my ( $name, $path, $suffix ) = fileparse( $lrr_info->{file_path}, qr/\.[^.]*/ );
|
||||
my $path_nearby_json = $path . $name . '.json';
|
||||
|
||||
my $filepath;
|
||||
my $delete_after_parse;
|
||||
|
||||
|
||||
#Extract info.json
|
||||
if($path_in_archive) {
|
||||
if ($path_in_archive) {
|
||||
$filepath = extract_file_from_archive( $lrr_info->{file_path}, $path_in_archive );
|
||||
$logger->debug("Found file in archive at $filepath");
|
||||
$delete_after_parse = 1;
|
||||
} elsif (-e $path_nearby_json) {
|
||||
} elsif ( -e $path_nearby_json ) {
|
||||
$filepath = $path_nearby_json;
|
||||
$logger->debug("Found file nearby at $filepath");
|
||||
$delete_after_parse = 0;
|
||||
@ -75,7 +74,7 @@ sub get_tags {
|
||||
my $stringjson = "";
|
||||
|
||||
open( my $fh, '<:encoding(UTF-8)', $filepath )
|
||||
or return ( error => "Could not open $filepath!" );
|
||||
or return ( error => "Could not open $filepath!" );
|
||||
|
||||
while ( my $row = <$fh> ) {
|
||||
chomp $row;
|
||||
@ -88,9 +87,10 @@ sub get_tags {
|
||||
$logger->debug("Loaded the following JSON: $stringjson");
|
||||
|
||||
#Parse it
|
||||
my ( $tags, $title ) = tags_from_eze_json($origin_title, $additional_tags, $hashjson);
|
||||
my ( $tags, $title ) = tags_from_eze_json( $origin_title, $additional_tags, $hashjson );
|
||||
|
||||
if ($delete_after_parse) {
|
||||
|
||||
if ($delete_after_parse){
|
||||
#Delete it
|
||||
unlink $filepath;
|
||||
}
|
||||
@ -98,7 +98,7 @@ sub get_tags {
|
||||
#Return tags
|
||||
$logger->info("Sending the following tags to LRR: $tags");
|
||||
|
||||
if ( $save_title && $title ) {
|
||||
if ($title) {
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tags, title => $title );
|
||||
} else {
|
||||
@ -111,7 +111,7 @@ sub get_tags {
|
||||
#Goes through the JSON hash obtained from an info.json file and return the contained tags.
|
||||
sub tags_from_eze_json {
|
||||
|
||||
my ($origin_title, $additional_tags, $hash) = @_;
|
||||
my ( $origin_title, $additional_tags, $hash ) = @_;
|
||||
my $return = "";
|
||||
|
||||
#Tags are in gallery_info -> tags -> one array per namespace
|
||||
@ -120,11 +120,11 @@ sub tags_from_eze_json {
|
||||
# Titles returned by eze are in complete E-H notation.
|
||||
my $title = $hash->{"gallery_info"}->{"title"};
|
||||
|
||||
if ($origin_title && $hash->{"gallery_info"}->{"title_original"} ) {
|
||||
if ( $origin_title && $hash->{"gallery_info"}->{"title_original"} ) {
|
||||
$title = $hash->{"gallery_info"}->{"title_original"};
|
||||
}
|
||||
|
||||
remove_spaces($title);
|
||||
$title = trim($title);
|
||||
|
||||
foreach my $namespace ( sort keys %$tags ) {
|
||||
|
||||
@ -139,23 +139,25 @@ sub tags_from_eze_json {
|
||||
}
|
||||
|
||||
# Add source tag if possible
|
||||
my $site = $hash->{"gallery_info"}->{"source"}->{"site"};
|
||||
my $gid = $hash->{"gallery_info"}->{"source"}->{"gid"};
|
||||
my $gtoken = $hash->{"gallery_info"}->{"source"}->{"token"};
|
||||
my $category = $hash->{"gallery_info"}->{"category"};
|
||||
my $uploader = $hash->{"gallery_info_full"}->{"uploader"};
|
||||
my $site = $hash->{"gallery_info"}->{"source"}->{"site"};
|
||||
my $gid = $hash->{"gallery_info"}->{"source"}->{"gid"};
|
||||
my $gtoken = $hash->{"gallery_info"}->{"source"}->{"token"};
|
||||
my $category = $hash->{"gallery_info"}->{"category"};
|
||||
my $uploader = $hash->{"gallery_info_full"}->{"uploader"};
|
||||
my $timestamp = $hash->{"gallery_info_full"}->{"date_uploaded"};
|
||||
|
||||
if ( $timestamp ) {
|
||||
if ($timestamp) {
|
||||
|
||||
# convert microsecond to second
|
||||
$timestamp = $timestamp / 1000;
|
||||
} else {
|
||||
my $upload_date = $hash->{"gallery_info"}->{"upload_date"};
|
||||
my $time = timegm_modern($$upload_date[5],$$upload_date[4],$$upload_date[3],$$upload_date[2],$$upload_date[1]-1,$$upload_date[0]);
|
||||
my $time = timegm_modern( $$upload_date[5], $$upload_date[4], $$upload_date[3], $$upload_date[2], $$upload_date[1] - 1,
|
||||
$$upload_date[0] );
|
||||
$timestamp = $time;
|
||||
}
|
||||
|
||||
if ( $category ) {
|
||||
if ($category) {
|
||||
$return .= ", category:$category";
|
||||
}
|
||||
|
||||
|
@ -13,25 +13,25 @@ use utf8;
|
||||
#You can also use the LRR Internal API when fitting.
|
||||
use LANraragi::Model::Plugins;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
use LANraragi::Utils::Generic qw(remove_spaces remove_newlines);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF);
|
||||
|
||||
#Meta-information about your plugin.
|
||||
sub plugin_info {
|
||||
|
||||
return (
|
||||
#Standard metadata
|
||||
name => "FAKKU",
|
||||
type => "metadata",
|
||||
namespace => "fakkumetadata",
|
||||
login_from => "fakkulogin",
|
||||
author => "Difegue, Nodja",
|
||||
version => "0.8",
|
||||
name => "FAKKU",
|
||||
type => "metadata",
|
||||
namespace => "fakkumetadata",
|
||||
login_from => "fakkulogin",
|
||||
author => "Difegue, Nodja",
|
||||
version => "0.9",
|
||||
description =>
|
||||
"在 FAKKU 中搜索与您的档案匹配的标签。 如果您有帐户,请不要忘记在登录插件中输入匹配的 cookie 才能访问有争议的内容。 <br/><br/>
|
||||
<i class='fa fa-exclamation-circle'></i> <b>此插件可以并且将根据您搜索的内容返回无效结果!!</b> <br/>FAKKU 搜索 API 不是很精确,我建议您尽可能使用 Chaika.moe 插件.",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAACZSURBVDhPlY+xDYQwDEWvZgRGYA22Y4frqJDSZhFugiuuo4cqPGT0iTjAYL3C+fGzktc3hEcsQvJq6HtjE2Jdv4viH4a4pWnL8q4A6g+ET9P8YhS2/kqwIZXWnwqChDxPfCFfD76wOzJ2IOR/0DSwnuRKYAKUW3gq2OsJTYM0jr7QVRVwlabJEaw3ARYBcmFXeomxphIeEMIMmh3lOLQR+QQAAAAASUVORK5CYII=",
|
||||
parameters => [ { type => "bool", desc => "保存档案名称" } ],
|
||||
parameters => [],
|
||||
oneshot_arg => "FAKKU 图库 URL(将与此确切图库匹配的标签附加到您的档案中)"
|
||||
);
|
||||
|
||||
@ -44,8 +44,6 @@ sub get_tags {
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my $ua = $lrr_info->{user_agent};
|
||||
|
||||
my ($savetitle) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
# Work your magic here - You can create subs below to organize the code better
|
||||
@ -79,8 +77,7 @@ sub get_tags {
|
||||
$logger->info("Sending the following tags to LRR: $newtags");
|
||||
|
||||
#Return a hash containing the new metadata - it will be integrated in LRR.
|
||||
if ( $savetitle && $newtags ne "" ) { return ( tags => $newtags, title => $newtitle ); }
|
||||
else { return ( tags => $newtags ); }
|
||||
return ( tags => $newtags, title => $newtitle );
|
||||
}
|
||||
|
||||
######
|
||||
@ -179,7 +176,7 @@ sub get_tags_from_fakku {
|
||||
my $metadata_parent = $tags_parent->parent->parent;
|
||||
|
||||
my $title = $metadata_parent->at('h1')->text;
|
||||
remove_spaces($title);
|
||||
$title = trim($title);
|
||||
$logger->debug("Parsed title: $title");
|
||||
|
||||
my @tags = ();
|
||||
@ -201,8 +198,8 @@ sub get_tags_from_fakku {
|
||||
? $row[1]->at('a')->text
|
||||
: $row[1]->text;
|
||||
|
||||
remove_spaces($value);
|
||||
remove_newlines($value);
|
||||
$value = trim($value);
|
||||
$value = trim_CRLF($value);
|
||||
|
||||
$logger->debug("Parsed row: $namespace");
|
||||
$logger->debug("Matching tag: $value");
|
||||
@ -223,8 +220,8 @@ sub get_tags_from_fakku {
|
||||
foreach my $link (@tag_links) {
|
||||
my $tag = $link->text;
|
||||
|
||||
remove_spaces($tag);
|
||||
remove_newlines($tag);
|
||||
$tag = trim($tag);
|
||||
$tag = trim_CRLF($tag);
|
||||
unless ( $tag eq "+" || $tag eq "" ) {
|
||||
push( @tags, lc $tag );
|
||||
}
|
||||
|
@ -20,11 +20,11 @@ sub plugin_info {
|
||||
type => "metadata",
|
||||
namespace => "hentagplugin",
|
||||
author => "siliconfeces",
|
||||
version => "0.1",
|
||||
version => "0.2",
|
||||
description => "解析嵌入在档案中的 Hentag info.json 文件。 Achtung,没有 API 调用!",
|
||||
parameters => [{ type => "bool", desc => "Save archive title" }],
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAPoAAAD6AG1e1JrAAAEzklEQVR4nO2Xa0xbZRjH6xe/0NKW0p7TQgul9GJpuQ0YbcFgXFx0M+NaoF0gY4mbkwLLxjYoMoiZMVGnDJZt2TT7plnGxmVCgXEdi859MGabH0xqNHFeBsxsETSGnL95314sWyuXrMYYP/zznHN6zvv83uft87zvw4tnzRYho/HGMmrEMmpOxGg5MWuAiNFBzOofE30uN0Ao0yJWkuKXBsL41LWIo5KkQijVeiVKo4XHlyV5GcUmqBKLllWJRYiXmyFg1AgHEbgXxKVAqjJDaS6A0mRDQtpmsMZsyNNyIDeuLoUpd1mqNiM2LsXLy9Xsw+H875eP5N9Fi+Vn7rWcL7hk5RYIZCshAjMXiNVItW7FKxcG0OCZgmtoAq6hcTj7PkRp77sou/QeynojiqMi71w8vmzYvg28g5u/4Tpsi2izLnBuyxz3ZiFQajqPGFki4thnHlsCgUSN8nd60HLjSzSNXcP+8es4MPEZXFfHYb/SA/uVbr+NKM4+2M1VjZxC8Udvczy/Y7gt81yr5R46bEtwZvQhlkmJsP56OM+cx8GpG2gcnfFBjM2iYXQS9oEeVAycWIu4iv4uEBCe2zJPZk8AQAA6bb/BkXE5PACrg0imQ9XJc2ie/RxNnhk0jc5i/9h1NIz8BWAf7A46C173d6Gin1jfNXlefvl9rAtALNdBJNbBuetjNM/cROP0FJqmZ9A0fQ0N0xOo9pwOOg2I3FcOnUT16GmQsFN5TsFx9Qzy6uvAa7P6AIhdFYDVQ8ikotY8gnbHPA7Vf43D+7xorf8OrrqbKDzUAPtQyDL0d1HHzx07AP22l5BWVgxjyQ5qtS9spUvK2xBA1jA6836HO2sBbVn30ZGzCFfqbRhfLEalp8cX5oETNMTOqXPIrnPg6acYCMQp4Mcmgy9IgkBIsky/QYBMDzoKFtFquwe3bQ5Hn30IV8YtpJdUrATo64Jz8ixy9tZSx5JEI+IUBiqS0hEBnBRAQ+vAoxIxWgrQaVui75MMOlrwEC7zLZh3lIcH2FODmBiVz/EjE+KFA6jO6EWMTAkho6EgJBo+q4FAloyazKHoAXTYlugM5Yo8JCRY/bIErSIhH7uzJ0GKVyutH08QwB1MyTlSniPoLv09kLpPFKDNPyixr1t/iajQ96IUgXk6cGTNRzsC99FufRBR5PeoRqDF8iMa8+5E0FdosfwUnQi0+tNwZ+Yg3Yql8nTEU5mDVsKasCtrzJ8FUaoDjoxLNN9J0SEi1S9wTWpCTeZwdAuRI1iKQz/wXf8jldCxhr3gf4DO/8wSuNdzJPv3AAz/PcBAGAB+xCVYWPOxPJiGWR502H4NArQXPIDLfBvpJXbYh7tXHskmziJ3b+3KCDB6zjeWnltnY2IAX6ZCVfoFHCvkghvTG4V/YI/5U2iKttBTb+UnvgaFQOyc/gDpjgrw+UkBAOKcEzFkPB23vtaMNUAgS4JW9TIac+/QPeFI/g9ozv8WWSm7ESNVwtb8Ko1A6cXj9FT8/FvNiE8yQSTTUuciRkedi1jdMrFhmlPTKs0pgUim+wR5n4hRZEPAJNOmRShNBWsINKqb6DdCKXUeOt6ymNFBxOq9vA2156wBQrov0G/oHzO0bNNWnLTg8WQPCY1gIPw6cu8Vs3rLnwIWEm0oy+KXAAAAAElFTkSuQmCC",
|
||||
parameters => [],
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAPoAAAD6AG1e1JrAAAEzklEQVR4nO2Xa0xbZRjH6xe/0NKW0p7TQgul9GJpuQ0YbcFgXFx0M+NaoF0gY4mbkwLLxjYoMoiZMVGnDJZt2TT7plnGxmVCgXEdi859MGabH0xqNHFeBsxsETSGnL95314sWyuXrMYYP/zznHN6zvv83uft87zvw4tnzRYho/HGMmrEMmpOxGg5MWuAiNFBzOofE30uN0Ao0yJWkuKXBsL41LWIo5KkQijVeiVKo4XHlyV5GcUmqBKLllWJRYiXmyFg1AgHEbgXxKVAqjJDaS6A0mRDQtpmsMZsyNNyIDeuLoUpd1mqNiM2LsXLy9Xsw+H875eP5N9Fi+Vn7rWcL7hk5RYIZCshAjMXiNVItW7FKxcG0OCZgmtoAq6hcTj7PkRp77sou/QeynojiqMi71w8vmzYvg28g5u/4Tpsi2izLnBuyxz3ZiFQajqPGFki4thnHlsCgUSN8nd60HLjSzSNXcP+8es4MPEZXFfHYb/SA/uVbr+NKM4+2M1VjZxC8Udvczy/Y7gt81yr5R46bEtwZvQhlkmJsP56OM+cx8GpG2gcnfFBjM2iYXQS9oEeVAycWIu4iv4uEBCe2zJPZk8AQAA6bb/BkXE5PACrg0imQ9XJc2ie/RxNnhk0jc5i/9h1NIz8BWAf7A46C173d6Gin1jfNXlefvl9rAtALNdBJNbBuetjNM/cROP0FJqmZ9A0fQ0N0xOo9pwOOg2I3FcOnUT16GmQsFN5TsFx9Qzy6uvAa7P6AIhdFYDVQ8ikotY8gnbHPA7Vf43D+7xorf8OrrqbKDzUAPtQyDL0d1HHzx07AP22l5BWVgxjyQ5qtS9spUvK2xBA1jA6836HO2sBbVn30ZGzCFfqbRhfLEalp8cX5oETNMTOqXPIrnPg6acYCMQp4Mcmgy9IgkBIsky/QYBMDzoKFtFquwe3bQ5Hn30IV8YtpJdUrATo64Jz8ixy9tZSx5JEI+IUBiqS0hEBnBRAQ+vAoxIxWgrQaVui75MMOlrwEC7zLZh3lIcH2FODmBiVz/EjE+KFA6jO6EWMTAkho6EgJBo+q4FAloyazKHoAXTYlugM5Yo8JCRY/bIErSIhH7uzJ0GKVyutH08QwB1MyTlSniPoLv09kLpPFKDNPyixr1t/iajQ96IUgXk6cGTNRzsC99FufRBR5PeoRqDF8iMa8+5E0FdosfwUnQi0+tNwZ+Yg3Yql8nTEU5mDVsKasCtrzJ8FUaoDjoxLNN9J0SEi1S9wTWpCTeZwdAuRI1iKQz/wXf8jldCxhr3gf4DO/8wSuNdzJPv3AAz/PcBAGAB+xCVYWPOxPJiGWR502H4NArQXPIDLfBvpJXbYh7tXHskmziJ3b+3KCDB6zjeWnltnY2IAX6ZCVfoFHCvkghvTG4V/YI/5U2iKttBTb+UnvgaFQOyc/gDpjgrw+UkBAOKcEzFkPB23vtaMNUAgS4JW9TIac+/QPeFI/g9ozv8WWSm7ESNVwtb8Ko1A6cXj9FT8/FvNiE8yQSTTUuciRkedi1jdMrFhmlPTKs0pgUim+wR5n4hRZEPAJNOmRShNBWsINKqb6DdCKXUeOt6ymNFBxOq9vA2156wBQrov0G/oHzO0bNNWnLTg8WQPCY1gIPw6cu8Vs3rLnwIWEm0oy+KXAAAAAElFTkSuQmCC",
|
||||
);
|
||||
|
||||
}
|
||||
@ -33,8 +33,7 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($save_title) = @_; # Plugin parameter
|
||||
my $lrr_info = shift; # Global info hash
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
my $file = $lrr_info->{file_path};
|
||||
@ -42,6 +41,7 @@ sub get_tags {
|
||||
my $path_in_archive = is_file_in_archive( $file, "info.json" );
|
||||
|
||||
if ($path_in_archive) {
|
||||
|
||||
#Extract info.json
|
||||
my $filepath = extract_file_from_archive( $file, $path_in_archive );
|
||||
|
||||
@ -69,10 +69,10 @@ sub get_tags {
|
||||
|
||||
#Return tags
|
||||
$logger->info("Sending the following tags to LRR: $tags");
|
||||
if ( $save_title && $title ) {
|
||||
if ($title) {
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tags, title => $title );
|
||||
} elsif ($tags ne "") {
|
||||
} elsif ( $tags ne "" ) {
|
||||
return ( tags => $tags );
|
||||
}
|
||||
}
|
||||
@ -96,18 +96,19 @@ sub tags_from_hentag_json {
|
||||
my $otherTags = $hash->{"otherTags"};
|
||||
my $language = language_from_hentag_json($hash);
|
||||
my $urls = $hash->{"locations"};
|
||||
|
||||
# not handled yet: category, createdAt
|
||||
|
||||
# tons of different shit creates different kinds of info.json file, so validate the shit out of the data
|
||||
@found_tags = try_add_tags(\@found_tags, "series:", $parodies);
|
||||
@found_tags = try_add_tags(\@found_tags, "group:", $groups);
|
||||
@found_tags = try_add_tags(\@found_tags, "artist:", $artists);
|
||||
@found_tags = try_add_tags(\@found_tags, "character:", $characters);
|
||||
@found_tags = try_add_tags(\@found_tags, "male:", $maleTags);
|
||||
@found_tags = try_add_tags(\@found_tags, "female:", $femaleTags);
|
||||
@found_tags = try_add_tags(\@found_tags, "other:", $otherTags);
|
||||
@found_tags = try_add_tags( \@found_tags, "series:", $parodies );
|
||||
@found_tags = try_add_tags( \@found_tags, "group:", $groups );
|
||||
@found_tags = try_add_tags( \@found_tags, "artist:", $artists );
|
||||
@found_tags = try_add_tags( \@found_tags, "character:", $characters );
|
||||
@found_tags = try_add_tags( \@found_tags, "male:", $maleTags );
|
||||
@found_tags = try_add_tags( \@found_tags, "female:", $femaleTags );
|
||||
@found_tags = try_add_tags( \@found_tags, "other:", $otherTags );
|
||||
push( @found_tags, "language:" . $language ) unless !defined $language;
|
||||
@found_tags = try_add_tags(\@found_tags, "source:", $urls);
|
||||
@found_tags = try_add_tags( \@found_tags, "source:", $urls );
|
||||
|
||||
#Done-o
|
||||
my $concat_tags = join( ", ", @found_tags );
|
||||
@ -118,19 +119,20 @@ sub tags_from_hentag_json {
|
||||
sub language_from_hentag_json {
|
||||
my ($hash) = @_;
|
||||
|
||||
my $language = $hash->{"language"};
|
||||
my $language = $hash->{"language"};
|
||||
return $language;
|
||||
}
|
||||
|
||||
sub try_add_tags {
|
||||
my @found_tags = @{$_[0]};
|
||||
my $prefix = $_[1];
|
||||
my $tags = $_[2];
|
||||
my @found_tags = @{ $_[0] };
|
||||
my $prefix = $_[1];
|
||||
my $tags = $_[2];
|
||||
my @potential_tags;
|
||||
|
||||
if (ref($tags) eq 'ARRAY') {
|
||||
if ( ref($tags) eq 'ARRAY' ) {
|
||||
foreach my $tag (@$tags) {
|
||||
if (ref($tag) eq 'HASH' || ref($tag) eq 'ARRAY') {
|
||||
if ( ref($tag) eq 'HASH' || ref($tag) eq 'ARRAY' ) {
|
||||
|
||||
# Weird stuff in here, don't continue parsing to avoid garbage data
|
||||
return @found_tags;
|
||||
}
|
||||
@ -138,7 +140,7 @@ sub try_add_tags {
|
||||
}
|
||||
}
|
||||
|
||||
push(@found_tags, @potential_tags);
|
||||
push( @found_tags, @potential_tags );
|
||||
return @found_tags;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,9 @@ use utf8;
|
||||
use LANraragi::Model::Plugins;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
|
||||
use LANraragi::Utils::String;
|
||||
use String::Similarity;
|
||||
|
||||
# Most parsing is reused between the two plugins
|
||||
require LANraragi::Plugin::Metadata::Hentag;
|
||||
|
||||
@ -24,17 +27,16 @@ sub plugin_info {
|
||||
type => "metadata",
|
||||
namespace => "hentagonlineplugin",
|
||||
author => "siliconfeces",
|
||||
version => "0.1",
|
||||
version => "0.2",
|
||||
description => "在 hentag.com 中搜索与您的档案相匹配的tag",
|
||||
parameters => [
|
||||
{ type => "bool", desc => "保存存档标题" },
|
||||
{ type => "string",
|
||||
desc =>
|
||||
"要考虑的以逗号分隔的语言列表。 第一语言 = 最优先。 默认为“英语、日语”"
|
||||
}
|
||||
],
|
||||
oneshot_arg => "Hentag.com vault URL (Will attach matching tags to your archive)",
|
||||
icon =>
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAPoAAAD6AG1e1JrAAAEzklEQVR4nO2Xa0xbZRjH6xe/0NKW0p7TQgul9GJpuQ0YbcFgXFx0M+NaoF0gY4mbkwLLxjYoMoiZMVGnDJZt2TT7plnGxmVCgXEdi859MGabH0xqNHFeBsxsETSGnL95314sWyuXrMYYP/zznHN6zvv83uft87zvw4tnzRYho/HGMmrEMmpOxGg5MWuAiNFBzOofE30uN0Ao0yJWkuKXBsL41LWIo5KkQijVeiVKo4XHlyV5GcUmqBKLllWJRYiXmyFg1AgHEbgXxKVAqjJDaS6A0mRDQtpmsMZsyNNyIDeuLoUpd1mqNiM2LsXLy9Xsw+H875eP5N9Fi+Vn7rWcL7hk5RYIZCshAjMXiNVItW7FKxcG0OCZgmtoAq6hcTj7PkRp77sou/QeynojiqMi71w8vmzYvg28g5u/4Tpsi2izLnBuyxz3ZiFQajqPGFki4thnHlsCgUSN8nd60HLjSzSNXcP+8es4MPEZXFfHYb/SA/uVbr+NKM4+2M1VjZxC8Udvczy/Y7gt81yr5R46bEtwZvQhlkmJsP56OM+cx8GpG2gcnfFBjM2iYXQS9oEeVAycWIu4iv4uEBCe2zJPZk8AQAA6bb/BkXE5PACrg0imQ9XJc2ie/RxNnhk0jc5i/9h1NIz8BWAf7A46C173d6Gin1jfNXlefvl9rAtALNdBJNbBuetjNM/cROP0FJqmZ9A0fQ0N0xOo9pwOOg2I3FcOnUT16GmQsFN5TsFx9Qzy6uvAa7P6AIhdFYDVQ8ikotY8gnbHPA7Vf43D+7xorf8OrrqbKDzUAPtQyDL0d1HHzx07AP22l5BWVgxjyQ5qtS9spUvK2xBA1jA6836HO2sBbVn30ZGzCFfqbRhfLEalp8cX5oETNMTOqXPIrnPg6acYCMQp4Mcmgy9IgkBIsky/QYBMDzoKFtFquwe3bQ5Hn30IV8YtpJdUrATo64Jz8ixy9tZSx5JEI+IUBiqS0hEBnBRAQ+vAoxIxWgrQaVui75MMOlrwEC7zLZh3lIcH2FODmBiVz/EjE+KFA6jO6EWMTAkho6EgJBo+q4FAloyazKHoAXTYlugM5Yo8JCRY/bIErSIhH7uzJ0GKVyutH08QwB1MyTlSniPoLv09kLpPFKDNPyixr1t/iajQ96IUgXk6cGTNRzsC99FufRBR5PeoRqDF8iMa8+5E0FdosfwUnQi0+tNwZ+Yg3Yql8nTEU5mDVsKasCtrzJ8FUaoDjoxLNN9J0SEi1S9wTWpCTeZwdAuRI1iKQz/wXf8jldCxhr3gf4DO/8wSuNdzJPv3AAz/PcBAGAB+xCVYWPOxPJiGWR502H4NArQXPIDLfBvpJXbYh7tXHskmziJ3b+3KCDB6zjeWnltnY2IAX6ZCVfoFHCvkghvTG4V/YI/5U2iKttBTb+UnvgaFQOyc/gDpjgrw+UkBAOKcEzFkPB23vtaMNUAgS4JW9TIac+/QPeFI/g9ozv8WWSm7ESNVwtb8Ko1A6cXj9FT8/FvNiE8yQSTTUuciRkedi1jdMrFhmlPTKs0pgUim+wR5n4hRZEPAJNOmRShNBWsINKqb6DdCKXUeOt6ymNFBxOq9vA2156wBQrov0G/oHzO0bNNWnLTg8WQPCY1gIPw6cu8Vs3rLnwIWEm0oy+KXAAAAAElFTkSuQmCC",
|
||||
);
|
||||
|
||||
@ -45,7 +47,8 @@ sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift;
|
||||
my ( $save_title, $allowed_languages ) = @_;
|
||||
my ($allowed_languages) = @_;
|
||||
|
||||
my $ua = $lrr_info->{user_agent};
|
||||
my $oneshot_param = $lrr_info->{oneshot_param};
|
||||
my $logger = get_plugin_logger();
|
||||
@ -63,7 +66,8 @@ sub get_tags {
|
||||
}
|
||||
|
||||
my $vault_id;
|
||||
my @source_urls = undef;
|
||||
my @source_urls = undef;
|
||||
my $archive_title = undef;
|
||||
|
||||
# First, try running based on a vault ID from a hentag URL
|
||||
if ( defined($oneshot_param) && $oneshot_param ne '' ) {
|
||||
@ -105,7 +109,7 @@ sub get_tags {
|
||||
|
||||
# Title lookup
|
||||
$logger->info('Title lookup');
|
||||
my $archive_title = $lrr_info->{archive_title};
|
||||
$archive_title = $lrr_info->{archive_title};
|
||||
|
||||
if ( $string_json eq '' ) {
|
||||
$string_json = get_json_by_title( $ua, $archive_title, $logger );
|
||||
@ -117,11 +121,11 @@ sub get_tags {
|
||||
my $json = from_json($string_json);
|
||||
|
||||
#Parse it
|
||||
my ( $tags, $title ) = tags_from_hentag_api_json( $json, $allowed_languages );
|
||||
my ( $tags, $title ) = tags_from_hentag_api_json( $json, $allowed_languages, $archive_title );
|
||||
|
||||
#Return tags IFF data is found
|
||||
$logger->info("Sending the following tags to LRR: $tags");
|
||||
if ( $save_title && $title ) {
|
||||
if ($title) {
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tags, title => $title );
|
||||
} elsif ( $tags ne "" ) {
|
||||
@ -133,7 +137,7 @@ sub get_tags {
|
||||
}
|
||||
|
||||
# Returns the ID from a hentag URL, or undef if invalid.
|
||||
sub parse_vault_url($url) {
|
||||
sub parse_vault_url ($url) {
|
||||
if ( !defined $url ) {
|
||||
return;
|
||||
}
|
||||
@ -191,7 +195,8 @@ sub get_json_by_urls ( $ua, $logger, @urls ) {
|
||||
}
|
||||
|
||||
# Fetches tags and title, restricted to a language
|
||||
sub tags_in_language_from_hentag_api_json ( $json, $language ) {
|
||||
# If $title_hint is set, it attempts to pick the "best" result if multiple hits were returned from Hentag
|
||||
sub tags_in_language_from_hentag_api_json ( $json, $language, $title_hint = undef ) {
|
||||
$language =~ s/^\s+|\s+$//g;
|
||||
$language = lc($language);
|
||||
|
||||
@ -201,17 +206,18 @@ sub tags_in_language_from_hentag_api_json ( $json, $language ) {
|
||||
if (@lang_json_pairs) {
|
||||
|
||||
# Possible improvement: Look for hits with "better" metadata (more tags, more tags in namespaces, etc).
|
||||
my ( $tags, $title ) = LANraragi::Plugin::Metadata::Hentag::tags_from_hentag_json( $lang_json_pairs[0] );
|
||||
my ( $tags, $title ) =
|
||||
LANraragi::Plugin::Metadata::Hentag::tags_from_hentag_json( pick_best_hit( $title_hint, @lang_json_pairs ) );
|
||||
return ( $tags, $title );
|
||||
}
|
||||
return ( '', '' );
|
||||
}
|
||||
|
||||
# Returns (string_with_tags, string_with_title) on success, (empty_string, empty_string) on failure
|
||||
sub tags_from_hentag_api_json ( $json, $string_prefered_languages ) {
|
||||
sub tags_from_hentag_api_json ( $json, $string_prefered_languages, $title_hint = undef ) {
|
||||
my @prefered_languages = split( ",", $string_prefered_languages );
|
||||
foreach my $language (@prefered_languages) {
|
||||
my ( $tags, $title ) = tags_in_language_from_hentag_api_json( $json, $language );
|
||||
my ( $tags, $title ) = tags_in_language_from_hentag_api_json( $json, $language, $title_hint );
|
||||
if ( $tags ne '' ) {
|
||||
return ( $tags, $title );
|
||||
}
|
||||
@ -219,7 +225,7 @@ sub tags_from_hentag_api_json ( $json, $string_prefered_languages ) {
|
||||
return ( '', '' );
|
||||
}
|
||||
|
||||
sub get_existing_hentag_source_url(@tags) {
|
||||
sub get_existing_hentag_source_url (@tags) {
|
||||
foreach my $tag ( get_source_tags(@tags) ) {
|
||||
if ( parse_vault_url($tag) ) {
|
||||
return $tag;
|
||||
@ -228,7 +234,7 @@ sub get_existing_hentag_source_url(@tags) {
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_source_tags(@tags) {
|
||||
sub get_source_tags (@tags) {
|
||||
my @found_tags;
|
||||
foreach my $tag (@tags) {
|
||||
if ( $tag =~ /.*source:(.*)/ ) {
|
||||
@ -238,4 +244,18 @@ sub get_source_tags(@tags) {
|
||||
return @found_tags;
|
||||
}
|
||||
|
||||
sub pick_best_hit ( $title_hint, @hits ) {
|
||||
if ( !defined($title_hint) ) {
|
||||
return $hits[0];
|
||||
}
|
||||
$title_hint = lc( LANraragi::Utils::String::clean_title($title_hint) );
|
||||
|
||||
my @titles;
|
||||
while ( my ( $index, $elem ) = each @hits ) {
|
||||
my ( $tags, $title ) = LANraragi::Plugin::Metadata::Hentag::tags_from_hentag_json($elem);
|
||||
$titles[$index] = lc( LANraragi::Utils::String::clean_title($title) );
|
||||
}
|
||||
return $hits[ LANraragi::Utils::String::most_similar( $title_hint, @titles ) ];
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -21,11 +21,11 @@ sub plugin_info {
|
||||
type => "metadata",
|
||||
namespace => "koromoplugin",
|
||||
author => "CirnoT, Difegue",
|
||||
version => "2.0",
|
||||
version => "2.1",
|
||||
description => " 在Koromo 风格的 Info.json 文件收集作为嵌入到档案中的元数据. ( {'Tags': [xxx] } syntax)",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TpVoqDmYQcchQnSyIijhKFYtgobQVWnUweekfNDEkKS6OgmvBwZ/FqoOLs64OroIg+APi4uqk6CIl3pcUWsR44fE+zrvn8N59gNCoMs3qGgc03TbTibiUy69IoVeE0QsRAYgys4xkZiEL3/q6pz6quxjP8u/7s/rUgsWAgEQ8ywzTJl4nnt60Dc77xCIryyrxOfGYSRckfuS64vEb55LLAs8UzWx6jlgklkodrHQwK5sa8RRxVNV0yhdyHquctzhr1Rpr3ZO/MFLQlzNcpzWMBBaRRAoSFNRQQRU2YrTrpFhI03ncxz/k+lPkUshVASPHPDagQXb94H/we7ZWcXLCS4rEge4Xx/kYAUK7QLPuON/HjtM8AYLPwJXe9m80gJlP0uttLXoE9G8DF9dtTdkDLneAwSdDNmVXCtISikXg/Yy+KQ8M3ALhVW9urXOcPgBZmtXSDXBwCIyWKHvN5909nXP7t6c1vx8dzXKFeWpUawAAAAZiS0dEAOwAEABqpSa6lwAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+MKCRQBJSKMeg0AAAGVSURBVDjLpZMxa9tQFIXPeaiyhxiZzKFjBme1JFfYgYAe9Bd0yA8JIaQhkJLBP6T/wh3qpZYzm2I8dyilJJMTW7yTIVGRFasE8uAt93K+d+5991IS8Ybj1SVIer24ty8Jk2wyl5S/GkDSi+O4s9PaOYOQh91wSHK2DeLViVut1pmkTwAQtAPUQcz/xCRBEpKOg3ZwEnbDDklvK6AQ+77fds4tSJbBcM7Nm83GbhXiVcXj8fiHpO/WWgfgHAAkXYxGoy8k/UG/nzxDnsqRxF7cO0iS5AhAQxKLm6bpVZqmn8sxAI3kQ2KjKDqQ9GRFEqDNfpQcukrMkDRF3ADAJJvM1+v1n0G/n5D0AcBaew3gFMCFtfbyuVT/cHCYrFarX1mWLQCgsAWSXtgNO81mY/ed7380xpyUn3XOXefr/Ntyufw9vZn+LL7zn21J+fRmOru/f/hrjNmThFLOGWPeV8UvBklSTnIWdsNh0A4g6RiAI/n17vZuWBVvncQNSBAYEK5OvNGDbSMdRdE+AJdl2aJumfjWdX4EIwDvDt7UjSEAAAAASUVORK5CYII=",
|
||||
parameters => [ { type => "bool", desc => "保存档案名称" } ]
|
||||
parameters => []
|
||||
);
|
||||
|
||||
}
|
||||
@ -34,8 +34,7 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($save_title) = @_; # Plugin parameter
|
||||
my $lrr_info = shift; # Global info hash
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
my $file = $lrr_info->{file_path};
|
||||
@ -77,7 +76,7 @@ sub get_tags {
|
||||
|
||||
#Return tags
|
||||
$logger->info("Sending the following tags to LRR: $tags");
|
||||
if ( $save_title && $title ) {
|
||||
if ($title) {
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tags, title => $title );
|
||||
} else {
|
||||
@ -133,7 +132,6 @@ sub tags_from_koromo_json {
|
||||
|
||||
push( @found_tags, "series:" . $parody ) unless !$parody;
|
||||
|
||||
|
||||
# Don't add bogus artist:ARRAYblabla if artist is an array
|
||||
if ($artist) {
|
||||
if ( ref $artist eq 'ARRAY' ) {
|
||||
|
@ -1,188 +0,0 @@
|
||||
package LANraragi::Plugin::Metadata::Koushoku;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
#Plugins can freely use all Perl packages already installed on the system
|
||||
#Try however to restrain yourself to the ones already installed for LRR (see tools/cpanfile) to avoid extra installations by the end-user.
|
||||
use URI::Escape;
|
||||
use Mojo::JSON qw(decode_json);
|
||||
use Mojo::UserAgent;
|
||||
use Mojo::DOM;
|
||||
|
||||
#You can also use the LRR Internal API when fitting.
|
||||
use LANraragi::Model::Plugins;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
use LANraragi::Utils::Generic qw(remove_spaces);
|
||||
|
||||
#Meta-information about your plugin.
|
||||
sub plugin_info {
|
||||
|
||||
return (
|
||||
#Standard metadata
|
||||
name => "Koushoku",
|
||||
type => "metadata",
|
||||
namespace => "kskmetadata",
|
||||
author => "Difegue",
|
||||
version => "1.0",
|
||||
description =>
|
||||
"在 KSK 中搜索与您的存档匹配的标签。 <br/><i class='fa fa-exclamation-circle'></i> 此插件将使用存档的 source: 标记(如果存在)。",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAANkSURBVDhPJZJpU5NXGIbf/9Ev0g+MM7Udp9WWDsVOsRYQKEVZQ4BsZnt9sy9shgShBbTYaTVCKY1B1pBEQggGFOogKEvYdOoXfszVQ/rhmTkz59zXc9/PeaRO12163DZCbgc+8y06HTJ+h5UOp4xLvoXdoOFBf5Auu4LS3obc0oJDp8VhNtLlcyN1uRWcZj13vS5cBi1+mwWPYiLY6cYjG+lxKoR8LgHpw9BQz+OBAbS1tch6DR1uO1Kox4dWVcfdDg9uswGnVSc66wn47QJmwtreTEPFVZxCoKosJ3hbRmlpRt8kNEIrdfscNN+o4tfeHhz6VhHBgqG1nsHeDpxGDV6zDkWjIvxLH25tK2+WUkzcG8JrNdJ/x4803NuJrr4G7Y/X8+UWIl1TDUGfgsfUjl2nwm/WMjrUh72tEXXFNYoKP+b74ks4FQOStuEnVNVlWBtv8kBYcmhVBJwWLOo6vKY2fvbaSD0ZxdnWxKWCj1CVXiEyPIBVuAz6bUiySc0dj0zAbsZtaM1fRH4fwm/RMDYYYCP2lNnfBsn89ZghxcIjMfmxng5GQ92ExIwkj6Kn5UYF6uofhMUG2mvLycYi7GaTnKwvk0vH+XctzXE6weupCFvRCP9MjLMx+Tfdulak4s8KqSr5kppvLmNT3WRQWN5Oz7ObibObnmMnMSXECxwtxdidi7L+Z5jlP0bYnJnEKX5PUpeVshqdINzl475dZnN+kqPsIocrApCa5fVchP3kDAeLc3nQ1vQTNqcjbCZncbQ3It1XZLLhR7wUtTMZZWd2Ugj+f3yYjpFLzbC/OM1BZoHcygJ7KeFEuHu7lsJmViN5G+o4jsd5+fAhKyMjecDJUoK9xDTH4uG753E+bCxxtJpkX5xzmQS5FyniU2MYNCKCsbo8b/84GWf7aZSt2Wi+81kdPU+wPj1OOOAhIHbi3Yu0GGqS07evqCv7llCXA+n6VxcpKTzHwsgwH1bTvBf0g7NOwu7J6jPGQn4iQ4H8XPZErNPNdYIWPZfPn6OvUwDUlVe59vknfHe+gLGAn9PtNQ7XnpHLJjgUdQZ6vy4iCMDxaiq/D8WFBXx9oZCA+DFJI3agougiVV9cyEOqij6l32UkFr6Xz7yfibG3PM/eSoLs1Di2+loaS0uovFIkFlDhPxYUixj0Cgg3AAAAAElFTkSuQmCC",
|
||||
parameters => [ { type => "bool", desc => "保存存档标题" } ],
|
||||
oneshot_arg => "Koushoku Gallery URL (Will attach tags matching this exact gallery to your archive)"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#Mandatory function to be implemented by your plugin
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my $ua = $lrr_info->{user_agent};
|
||||
|
||||
my ($savetitle) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
# Work your magic here - You can create subs below to organize the code better
|
||||
my $ksk_URL = "";
|
||||
|
||||
# If the user specified a oneshot argument, use it as-is.
|
||||
# We could stand to pre-check it to see if it really is a FAKKU URL but meh
|
||||
if ( $lrr_info->{oneshot_param} ) {
|
||||
$ksk_URL = $lrr_info->{oneshot_param};
|
||||
} elsif ( $lrr_info->{existing_tags} =~ /.*source:\s*ksk\.moe\/view\/([0-9]*)\/([0-z]*)\/*.*/gi ) {
|
||||
my $gID = $1;
|
||||
my $gToken = $2;
|
||||
$ksk_URL = "https://ksk.moe/view/$gID/$gToken";
|
||||
$logger->debug("Skipping search and using $gID / $gToken from source tag");
|
||||
} else {
|
||||
|
||||
# Search for a KSK URL if the user didn't specify one
|
||||
$ksk_URL = search_for_ksk_url( $lrr_info->{archive_title}, $ua );
|
||||
}
|
||||
|
||||
# Do we have a URL to grab data from?
|
||||
if ( $ksk_URL ne "" ) {
|
||||
$logger->debug("Detected Koushoku URL: $ksk_URL");
|
||||
} else {
|
||||
$logger->info("No matching Koushoku Gallery Found!");
|
||||
return ( error => "No matching Koushoku Gallery Found!" );
|
||||
}
|
||||
|
||||
my ( $newtags, $newtitle );
|
||||
eval { ( $newtags, $newtitle ) = get_tags_from_ksk( $ksk_URL, $ua ); };
|
||||
|
||||
if ($@) {
|
||||
return ( error => $@ );
|
||||
}
|
||||
|
||||
$logger->info("Sending the following tags to LRR: $newtags");
|
||||
|
||||
#Return a hash containing the new metadata - it will be integrated in LRR.
|
||||
if ( $savetitle && $newtags ne "" ) { return ( tags => $newtags, title => $newtitle ); }
|
||||
else { return ( tags => $newtags ); }
|
||||
}
|
||||
|
||||
######
|
||||
## KSK-Specific Methods
|
||||
######
|
||||
|
||||
# search_for_ksk_url(title, useragent)
|
||||
# Uses the website's search to find a gallery and returns its gallery ID.
|
||||
sub search_for_ksk_url {
|
||||
|
||||
my ( $title, $ua ) = @_;
|
||||
my $dom = get_search_result_dom( $title, $ua );
|
||||
|
||||
# Get the first link on the page that has rel="bookmark"
|
||||
my $path = $dom->at('a[rel="bookmark"]')->attr('href');
|
||||
|
||||
if ( $path ne "" ) {
|
||||
return "https://ksk.moe" . $path;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub get_search_result_dom {
|
||||
|
||||
my ( $title, $ua ) = @_;
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
# Use the regular search page.
|
||||
my $URL = "https://ksk.moe/browse?s=" . uri_escape_utf8($title);
|
||||
|
||||
$logger->debug("Using URL $URL to search.");
|
||||
|
||||
my $res = $ua->max_redirects(5)->get($URL)->result;
|
||||
$logger->debug( "Got this HTML: " . $res->body );
|
||||
|
||||
return $res->dom;
|
||||
}
|
||||
|
||||
# get_tags_from_ksk(fURL, useragent)
|
||||
# Parses a KSK URL for tags.
|
||||
sub get_tags_from_ksk {
|
||||
|
||||
my ( $url, $ua ) = @_;
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
my $dom = get_dom_from_ksk( $url, $ua );
|
||||
|
||||
# Title is the first h1 block
|
||||
my $title = $dom->at('h1')->text;
|
||||
remove_spaces($title);
|
||||
$logger->debug("Parsed title: $title");
|
||||
|
||||
# Get all the links with rel="tag"
|
||||
my @tags = ();
|
||||
my @tags_dom = $dom->find('a[rel="tag"]')->each;
|
||||
|
||||
# Use the href to get the tag name and namespace.
|
||||
@tags_dom = map { $_->attr('href') } @tags_dom;
|
||||
|
||||
foreach my $href (@tags_dom) {
|
||||
|
||||
# "/tags/blahblah" => "blahblah", "/artists/blah%20blah" => "artist:blah blah"
|
||||
if ( $href =~ /\/(.*)\/(.*)/ ) {
|
||||
$logger->debug("Matching tag: $1 / $2");
|
||||
|
||||
# url-decode it before pushing
|
||||
my $tag = uri_unescape($2);
|
||||
remove_spaces($tag);
|
||||
|
||||
if ( $1 eq "artists" ) {
|
||||
$tag = "artist:" . $tag;
|
||||
}
|
||||
|
||||
if ( $1 eq "parodies" ) {
|
||||
$tag = "parody:" . $tag;
|
||||
}
|
||||
|
||||
if ( $1 eq "magazines" ) {
|
||||
$tag = "magazine:" . $tag;
|
||||
}
|
||||
|
||||
push( @tags, lc $tag );
|
||||
}
|
||||
}
|
||||
|
||||
return ( join( ', ', @tags ), $title );
|
||||
|
||||
}
|
||||
|
||||
sub get_dom_from_ksk {
|
||||
|
||||
my ( $url, $ua ) = @_;
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
my $res = $ua->max_redirects(5)->get($url)->result;
|
||||
$logger->trace( "Got this HTML: " . $res->body );
|
||||
my $dom = $res->dom;
|
||||
}
|
||||
|
||||
1;
|
100
lib/LANraragi/Plugin/Metadata/Ksk.pm
Normal file
@ -0,0 +1,100 @@
|
||||
package LANraragi::Plugin::Metadata::Ksk;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use LANraragi::Model::Plugins;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
use LANraragi::Utils::Archive qw(is_file_in_archive extract_file_from_archive);
|
||||
|
||||
use YAML::Syck qw(LoadFile);
|
||||
|
||||
sub plugin_info {
|
||||
|
||||
return (
|
||||
name => "Koushoku.yaml",
|
||||
type => "metadata",
|
||||
namespace => "kskyamlmeta",
|
||||
author => "siliconfeces",
|
||||
version => "0.002",
|
||||
description => "将档案中的 koushoku.yaml/info.yaml 文件解析为元数据添加到档案中.",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAANkSURBVDhPJZJpU5NXGIbf/9Ev0g+MM7Udp9WWDsVOsRYQKEVZQ4BsZnt9sy9shgShBbTYaTVCKY1B1pBEQggGFOogKEvYdOoXfszVQ/rhmTkz59zXc9/PeaRO12163DZCbgc+8y06HTJ+h5UOp4xLvoXdoOFBf5Auu4LS3obc0oJDp8VhNtLlcyN1uRWcZj13vS5cBi1+mwWPYiLY6cYjG+lxKoR8LgHpw9BQz+OBAbS1tch6DR1uO1Kox4dWVcfdDg9uswGnVSc66wn47QJmwtreTEPFVZxCoKosJ3hbRmlpRt8kNEIrdfscNN+o4tfeHhz6VhHBgqG1nsHeDpxGDV6zDkWjIvxLH25tK2+WUkzcG8JrNdJ/x4803NuJrr4G7Y/X8+UWIl1TDUGfgsfUjl2nwm/WMjrUh72tEXXFNYoKP+b74ks4FQOStuEnVNVlWBtv8kBYcmhVBJwWLOo6vKY2fvbaSD0ZxdnWxKWCj1CVXiEyPIBVuAz6bUiySc0dj0zAbsZtaM1fRH4fwm/RMDYYYCP2lNnfBsn89ZghxcIjMfmxng5GQ92ExIwkj6Kn5UYF6uofhMUG2mvLycYi7GaTnKwvk0vH+XctzXE6weupCFvRCP9MjLMx+Tfdulak4s8KqSr5kppvLmNT3WRQWN5Oz7ObibObnmMnMSXECxwtxdidi7L+Z5jlP0bYnJnEKX5PUpeVshqdINzl475dZnN+kqPsIocrApCa5fVchP3kDAeLc3nQ1vQTNqcjbCZncbQ3It1XZLLhR7wUtTMZZWd2Ugj+f3yYjpFLzbC/OM1BZoHcygJ7KeFEuHu7lsJmViN5G+o4jsd5+fAhKyMjecDJUoK9xDTH4uG753E+bCxxtJpkX5xzmQS5FyniU2MYNCKCsbo8b/84GWf7aZSt2Wi+81kdPU+wPj1OOOAhIHbi3Yu0GGqS07evqCv7llCXA+n6VxcpKTzHwsgwH1bTvBf0g7NOwu7J6jPGQn4iQ4H8XPZErNPNdYIWPZfPn6OvUwDUlVe59vknfHe+gLGAn9PtNQ7XnpHLJjgUdQZ6vy4iCMDxaiq/D8WFBXx9oZCA+DFJI3agougiVV9cyEOqij6l32UkFr6Xz7yfibG3PM/eSoLs1Di2+loaS0uovFIkFlDhPxYUixj0Cgg3AAAAAElFTkSuQmCC",
|
||||
parameters => [ { type => "bool", desc => "Assume english" } ],
|
||||
);
|
||||
}
|
||||
|
||||
sub get_tags {
|
||||
shift;
|
||||
my $lrr_info = shift;
|
||||
my ($assume_english) = @_;
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
my $file = $lrr_info->{file_path};
|
||||
|
||||
my $path_in_archive = is_file_in_archive( $file, "koushoku.yaml" );
|
||||
|
||||
if ( !$path_in_archive ) {
|
||||
$path_in_archive = is_file_in_archive( $file, "info.yaml" );
|
||||
}
|
||||
|
||||
if ( !$path_in_archive ) {
|
||||
return ( error => "No KSK metadata file found in archive" );
|
||||
}
|
||||
|
||||
my $filepath = extract_file_from_archive( $file, $path_in_archive );
|
||||
|
||||
my $parsed_data = LoadFile($filepath);
|
||||
|
||||
my ( $tags, $title ) = tags_from_ksk_yaml( $parsed_data, $assume_english );
|
||||
|
||||
unlink $filepath;
|
||||
|
||||
#Return tags
|
||||
$logger->info("Sending the following tags to LRR: $tags");
|
||||
if ($title) {
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tags, title => $title );
|
||||
} else {
|
||||
return ( tags => $tags );
|
||||
}
|
||||
}
|
||||
|
||||
sub tags_from_ksk_yaml {
|
||||
my $hash = $_[0];
|
||||
my $assume_english = $_[1];
|
||||
my @found_tags;
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
my $title = $hash->{"Title"};
|
||||
my $tags = $hash->{"Tags"};
|
||||
my $parody = $hash->{"Parody"};
|
||||
my $artists = $hash->{"Artist"};
|
||||
my $magazine = $hash->{"Magazine"};
|
||||
my $url = $hash->{"URL"};
|
||||
|
||||
foreach my $tag (@$tags) {
|
||||
push( @found_tags, $tag );
|
||||
}
|
||||
foreach my $tag (@$artists) {
|
||||
push( @found_tags, "artist:" . $tag );
|
||||
}
|
||||
foreach my $tag (@$parody) {
|
||||
push( @found_tags, "series:" . $tag );
|
||||
}
|
||||
foreach my $tag (@$magazine) {
|
||||
push( @found_tags, "magazine:" . $tag );
|
||||
}
|
||||
if ($assume_english) {
|
||||
push( @found_tags, "language:english" );
|
||||
}
|
||||
|
||||
push( @found_tags, "source:" . $url ) unless !$url;
|
||||
|
||||
#Done-o
|
||||
my $concat_tags = join( ", ", @found_tags );
|
||||
return ( $concat_tags, $title );
|
||||
|
||||
}
|
||||
|
||||
1;
|
@ -12,24 +12,23 @@ use Scalar::Util qw(looks_like_number);
|
||||
#You can also use the LRR Internal API when fitting.
|
||||
use LANraragi::Model::Plugins;
|
||||
use LANraragi::Utils::Database qw(redis_encode redis_decode);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Generic qw(remove_spaces);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
#Meta-information about your plugin.
|
||||
sub plugin_info {
|
||||
|
||||
return (
|
||||
#Standard metadata
|
||||
name => "Filename Parsing",
|
||||
type => "metadata",
|
||||
namespace => "regexplugin",
|
||||
author => "Difegue",
|
||||
version => "1.0",
|
||||
name => "Filename Parsing",
|
||||
type => "metadata",
|
||||
namespace => "regexplugin",
|
||||
author => "Difegue",
|
||||
version => "1.0",
|
||||
description =>
|
||||
"从给定档案的文件名派生标签. <br>遵循同人志命名标准(发布)[艺术家]TITLE(系列)[语言].",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAL1JREFUOI1jZMABpNbH/sclx8DAwPAscDEjNnEMQUIGETIYhUOqYdgMhTPINQzdUEZqGIZsKBM1DEIGTOiuexqwCKdidDl0vtT62P9kuZCJEWuKYWBgYGBgRHbh04BFDNIb4jAUbbSrZTARUkURg6lD10OUC/0PNaMYgs1Skgwk1jCSDCQWoBg46dYmhite0+D8pwGLCMY6uotRDOy8toZBkI2HIhcO/pxCm8KBUkOxFl/kGoq3gCXFYFxVAACeoU/8xSNybwAAAABJRU5ErkJggg==",
|
||||
parameters => [ { type => "bool", desc => "保存档案名称", default_value => "1" } ]
|
||||
parameters => [ ]
|
||||
);
|
||||
|
||||
}
|
||||
@ -39,10 +38,9 @@ sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($savetitle) = @_; # Plugin parameters
|
||||
|
||||
my $logger = get_logger( "regexparse", "plugins" );
|
||||
my $file = $lrr_info->{file_path};
|
||||
my $file = $lrr_info->{file_path};
|
||||
|
||||
# lrr_info's file_path is taken straight from the filesystem, which might not be proper UTF-8.
|
||||
# Run a decode to make sure we can derive tags with the proper encoding.
|
||||
@ -99,12 +97,8 @@ sub get_tags {
|
||||
|
||||
$logger->info("Sending the following tags to LRR: $tagstring");
|
||||
|
||||
if ($savetitle) {
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tagstring, title => $title );
|
||||
} else {
|
||||
return ( tags => $tagstring );
|
||||
}
|
||||
$logger->info("Parsed title is $title");
|
||||
return ( tags => $tagstring, title => $title );
|
||||
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,13 @@ sub plugin_info {
|
||||
namespace => "nhplugin",
|
||||
login_from => "nhentaicfbypass",
|
||||
author => "Difegue and others",
|
||||
version => "1.7.2",
|
||||
version => "1.7.3",
|
||||
description => "在 nHentai 中搜索与您的档案匹配的标签.
|
||||
<br>支持从格式为以下的文件中读取 ID \"{Id} Title\" 如果没有,尝试搜索匹配的图库.
|
||||
<br><i class='fa fa-exclamation-circle'></i> 此插件将使用 source: tag 的标签(如果存在).",
|
||||
icon =>
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFA8s1yKFJwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAACL0lEQVQ4y6XTz0tUURQH8O+59773nLFcaGWTk4UUVCBFiJs27VxEQRH0AyRo4x8Q/Qtt2rhr\nU6soaCG0KYKSwIhMa9Ah+yEhZM/5oZMG88N59717T4sxM8eZCM/ycD6Xwznn0pWhG34mh/+PA8mk\n8jO5heziP0sFYwfgMDFQJg4IUjmquSFGG+OIlb1G9li5kykgTgvzSoUCaIYlo8/Igcjpj5wOkARp\n8AupP0uzJLijCY4zzoXOxdBLshAgABr8VOp7bpAXDEI7IBrhdksnjNr3WzI4LaIRV9fk2iAaYV/y\nA1dPiYjBAALgpQxnhV2XzTCAGWGeq7ACBvCdzKQyTH+voAm2hGlpcmQt2Bc2K+ymAhWPxTzPDQLt\nOKo1FiNBQaArq9WNRQwEgKl7XQ1duzSRSn/88vX0qf7DPQddx1nI5UfHxt+m0sLYPiP3shRAG8MD\nok1XEEXR/EI2ly94nrNYWG6Nx0/2Hp2b94dv34mlZge1e4hVCJ4jc6tl9ZP803n3/i4lpdyzq2N0\n7M3DkSeF5ZVYS8v1qxcGz5+5eey4nPDbmGdE9FpGeWErVNe2tTabX3r0+Nk3PwOgXFkdfz99+exA\nMtFZITEt9F23mpLG0hYTVQCKpfKPlZ/rqWKpYoAPcTmpginW76QBbb0OBaBaDdjaDbNlJmQE3/d0\nMYoaybU9126oPkrEhpr+U2wjtoVVGBowkslEsVSupRKdu0Mduq7q7kqExjSS3V2dvwDLavx0eczM\neAAAAABJRU5ErkJggg==",
|
||||
parameters => [ { type => "bool", desc => "保存档案名称" } ],
|
||||
parameters => [],
|
||||
oneshot_arg => "nHentai Gallery URL (Will attach tags matching this exact gallery to your archive)"
|
||||
);
|
||||
|
||||
@ -39,10 +39,9 @@ sub plugin_info {
|
||||
sub get_tags {
|
||||
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my ($savetitle) = @_; # Plugin parameters
|
||||
my $ua = $lrr_info->{user_agent}; # UserAgent from login plugin
|
||||
|
||||
my $lrr_info = shift; # Global info hash
|
||||
my $ua = $lrr_info->{user_agent}; # UserAgent from login plugin
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
# Work your magic here - You can create subs below to organize the code better
|
||||
@ -78,7 +77,7 @@ sub get_tags {
|
||||
return ( error => "No matching nHentai Gallery Found!" );
|
||||
}
|
||||
|
||||
my %hashdata = get_tags_from_NH( $galleryID, $savetitle, $ua );
|
||||
my %hashdata = get_tags_from_NH( $galleryID, $ua );
|
||||
|
||||
$logger->info( "Sending the following tags to LRR: " . $hashdata{tags} );
|
||||
|
||||
@ -93,7 +92,7 @@ sub get_tags {
|
||||
#Uses the website's search to find a gallery and returns its content.
|
||||
sub get_gallery_dom_by_title {
|
||||
|
||||
my ($title, $ua) = @_;
|
||||
my ( $title, $ua ) = @_;
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
@ -116,7 +115,7 @@ sub get_gallery_dom_by_title {
|
||||
|
||||
sub get_gallery_id_from_title {
|
||||
|
||||
my ($title, $ua) = @_;
|
||||
my ( $title, $ua ) = @_;
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
|
||||
@ -125,7 +124,7 @@ sub get_gallery_id_from_title {
|
||||
return $1;
|
||||
}
|
||||
|
||||
my $dom = get_gallery_dom_by_title($title, $ua);
|
||||
my $dom = get_gallery_dom_by_title( $title, $ua );
|
||||
|
||||
if ($dom) {
|
||||
|
||||
@ -147,14 +146,15 @@ sub get_gallery_id_from_title {
|
||||
# retrieves html page from NH
|
||||
sub get_html_from_NH {
|
||||
|
||||
my ($gID, $ua) = @_;
|
||||
|
||||
my ( $gID, $ua ) = @_;
|
||||
|
||||
my $URL = "https://nhentai.net/g/$gID/";
|
||||
|
||||
my $res = $ua->get($URL)->result;
|
||||
|
||||
if ( $res->is_error ) {
|
||||
return;
|
||||
my $code = $res->code;
|
||||
return "error ($code)";
|
||||
}
|
||||
|
||||
return $res->body;
|
||||
@ -213,11 +213,17 @@ sub get_title_from_json {
|
||||
|
||||
sub get_tags_from_NH {
|
||||
|
||||
my ( $gID, $savetitle, $ua ) = @_;
|
||||
my ( $gID, $ua ) = @_;
|
||||
|
||||
my %hashdata = ( tags => "" );
|
||||
|
||||
my $html = get_html_from_NH($gID, $ua);
|
||||
my $html = get_html_from_NH( $gID, $ua );
|
||||
|
||||
# If the string starts with "error", we couldn't retrieve data from NH.
|
||||
if ( $html =~ /^error/ ) {
|
||||
return ( error => "Error retrieving gallery from nHentai! ($html)" );
|
||||
}
|
||||
|
||||
my $json = get_json_from_html($html);
|
||||
|
||||
if ($json) {
|
||||
@ -225,8 +231,8 @@ sub get_tags_from_NH {
|
||||
push( @tags, "source:nhentai.net/g/$gID" ) if ( @tags > 0 );
|
||||
|
||||
# Use NH's "pretty" names (romaji titles without extraneous data we already have like (Event)[Artist], etc)
|
||||
$hashdata{tags} = join( ', ', @tags );
|
||||
$hashdata{title} = get_title_from_json($json) if ($savetitle);
|
||||
$hashdata{tags} = join( ', ', @tags );
|
||||
$hashdata{title} = get_title_from_json($json);
|
||||
}
|
||||
|
||||
return %hashdata;
|
||||
|
@ -1,74 +0,0 @@
|
||||
package LANraragi::Plugin::Scripts::BlacklistMigrate;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'uninitialized';
|
||||
use utf8;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
use LANraragi::Utils::Database qw(save_computed_tagrules);
|
||||
use LANraragi::Utils::Tags qw(tags_rules_to_array restore_CRLF);
|
||||
use LANraragi::Utils::Generic qw(remove_spaces);
|
||||
use Mojo::JSON qw(encode_json);
|
||||
use LANraragi::Model::Config;
|
||||
|
||||
#Meta-information about your plugin.
|
||||
sub plugin_info {
|
||||
|
||||
return (
|
||||
#Standard metadata
|
||||
name => "Blacklist Migration",
|
||||
type => "script",
|
||||
namespace => "blist2rule",
|
||||
author => "Difegue",
|
||||
version => "1.0",
|
||||
description => "将您的黑名单从 LANraragi < 0.8.0 的数据库迁移到新的标签规则系统."
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
# Mandatory function to be implemented by your script
|
||||
sub run_script {
|
||||
shift;
|
||||
my $lrr_info = shift; # Global info hash
|
||||
|
||||
my $logger = get_plugin_logger();
|
||||
my $redis = LANraragi::Model::Config->get_redis_config;
|
||||
|
||||
my $blist = LANraragi::Model::Config::get_redis_conf( "blacklist", undef );
|
||||
my $rules = LANraragi::Model::Config::get_redis_conf( "tagrules",
|
||||
"-already uploaded;-forbidden content;-incomplete;-ongoing;-complete;-various;-digital;-translated;-russian;-chinese;-portuguese;-french;-spanish;-italian;-vietnamese;-german;-indonesian"
|
||||
);
|
||||
|
||||
unless ($blist) {
|
||||
$logger->info("No blacklist in config, nothing to migrate!");
|
||||
return ( status => "Nothing to migrate" );
|
||||
}
|
||||
|
||||
$logger->debug("Blacklist is $blist");
|
||||
$logger->debug("Rules are $rules");
|
||||
my @blacklist = split( ',', $blist ); # array-ize the blacklist string
|
||||
my $migrated = 0;
|
||||
|
||||
# Parse the blacklist and add matching tag rules.
|
||||
foreach my $tag (@blacklist) {
|
||||
|
||||
remove_spaces($tag);
|
||||
if ( index( uc($rules), uc($tag) ) == -1 ) {
|
||||
$logger->debug("Adding rule -$tag");
|
||||
$rules = $rules . ";-$tag";
|
||||
$migrated++;
|
||||
}
|
||||
}
|
||||
|
||||
# Save rules and recompute them
|
||||
$redis->hset( "LRR_CONFIG", "tagrules", $rules );
|
||||
$redis->hdel( "LRR_CONFIG", "blacklist" );
|
||||
|
||||
my @computed_tagrules = tags_rules_to_array( restore_CRLF($rules) );
|
||||
$logger->debug( "Saving computed tag rules : " . encode_json( \@computed_tagrules ) );
|
||||
save_computed_tagrules( \@computed_tagrules );
|
||||
|
||||
return ( migrated_tags => $migrated );
|
||||
}
|
||||
|
||||
1;
|
@ -7,7 +7,7 @@ use utf8;
|
||||
use Mojo::UserAgent;
|
||||
use LANraragi::Utils::Logging qw(get_plugin_logger);
|
||||
use LANraragi::Model::Stats;
|
||||
use LANraragi::Utils::Generic qw(trim_url);
|
||||
use LANraragi::Utils::String qw(trim_url);
|
||||
|
||||
#Meta-information about your plugin.
|
||||
sub plugin_info {
|
||||
|
@ -357,10 +357,8 @@ sub extract_file_from_archive ( $archive, $filename ) {
|
||||
my $path = get_temp . "/plugin";
|
||||
mkdir $path;
|
||||
|
||||
my $tmp = File::Temp->new( DIR => $path );
|
||||
$tmp->unlink_on_destroy(0);
|
||||
|
||||
return extract_single_file( $archive, $filename, $tmp->filename );
|
||||
my $tmp = tempdir( DIR => $path, CLEANUP => 1 );
|
||||
return extract_single_file( $archive, $filename, $tmp );
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -3,27 +3,30 @@ package LANraragi::Utils::Database;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use feature qw(signatures);
|
||||
no warnings 'experimental::signatures';
|
||||
|
||||
use Digest::SHA qw(sha256_hex);
|
||||
use Mojo::JSON qw(decode_json);
|
||||
use Mojo::JSON qw(decode_json);
|
||||
use Encode;
|
||||
use File::Basename;
|
||||
use File::Path qw(remove_tree);
|
||||
use Redis;
|
||||
use Cwd;
|
||||
use Unicode::Normalize;
|
||||
use List::MoreUtils qw(uniq);
|
||||
|
||||
use LANraragi::Utils::Generic qw(flat remove_spaces remove_newlines trim_url);
|
||||
use LANraragi::Utils::Tags qw(unflat_tagrules tags_rules_to_array restore_CRLF);
|
||||
use LANraragi::Utils::Generic qw(flat);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF trim_url);
|
||||
use LANraragi::Utils::Tags qw(unflat_tagrules tags_rules_to_array restore_CRLF join_tags_to_string split_tags_to_array );
|
||||
use LANraragi::Utils::Archive qw(get_filelist);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
# Functions for interacting with the DB Model.
|
||||
use Exporter 'import';
|
||||
our @EXPORT_OK =
|
||||
qw(redis_encode redis_decode invalidate_cache compute_id set_tags set_title set_isnew get_computed_tagrules save_computed_tagrules get_archive_json get_archive_json_multi);
|
||||
qw(redis_encode redis_decode invalidate_cache compute_id set_tags set_title set_isnew get_computed_tagrules save_computed_tagrules get_archive_json get_archive_json_multi get_tankoubons_by_file);
|
||||
|
||||
# Creates a DB entry for a file path with the given ID.
|
||||
# This function doesn't actually require the file to exist at its given location.
|
||||
@ -39,6 +42,9 @@ sub add_archive_to_redis ( $id, $file, $redis ) {
|
||||
|
||||
$redis->hset( $id, "name", redis_encode($name) );
|
||||
$redis->hset( $id, "tags", "" );
|
||||
if ( defined($file) && -e $file ) {
|
||||
set_arcsize( $redis, $id, -s $file );
|
||||
}
|
||||
|
||||
# Don't encode filenames.
|
||||
$redis->hset( $id, "file", $file );
|
||||
@ -58,13 +64,16 @@ sub add_archive_to_redis ( $id, $file, $redis ) {
|
||||
sub change_archive_id ( $old_id, $new_id ) {
|
||||
|
||||
my $logger = get_logger( "Archive", "lanraragi" );
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
|
||||
$logger->debug("Changing ID $old_id to $new_id");
|
||||
|
||||
if ( $redis->exists($old_id) ) {
|
||||
$redis->rename( $old_id, $new_id );
|
||||
}
|
||||
|
||||
my $file = $redis->hget( $new_id, "file" );
|
||||
set_arcsize( $redis, $new_id, -s $file );
|
||||
$redis->quit;
|
||||
|
||||
# We also need to update categories that contain the ID.
|
||||
@ -132,7 +141,7 @@ sub get_archive_json ( $redis, $id ) {
|
||||
}
|
||||
|
||||
# Uses Redis' MULTI to get an archive JSON for each ID.
|
||||
sub get_archive_json_multi(@ids) {
|
||||
sub get_archive_json_multi (@ids) {
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
|
||||
@ -171,7 +180,8 @@ sub build_json ( $id, %hash ) {
|
||||
|
||||
# It's not a new archive, but it might have never been clicked on yet,
|
||||
# so grab the value for $isnew stored in redis.
|
||||
my ( $name, $title, $tags, $file, $isnew, $progress, $pagecount, $lastreadtime) = @hash{qw(name title tags file isnew progress pagecount lastreadtime)};
|
||||
my ( $name, $title, $tags, $file, $isnew, $progress, $pagecount, $lastreadtime ) =
|
||||
@hash{qw(name title tags file isnew progress pagecount lastreadtime)};
|
||||
|
||||
# Return undef if the file doesn't exist.
|
||||
return unless ( defined($file) && -e $file );
|
||||
@ -185,13 +195,13 @@ sub build_json ( $id, %hash ) {
|
||||
}
|
||||
|
||||
my $arcdata = {
|
||||
arcid => $id,
|
||||
title => $title,
|
||||
tags => $tags,
|
||||
isnew => $isnew ? $isnew : "false",
|
||||
extension => lc( ( split( /\./, $file ) )[-1] ),
|
||||
progress => $progress ? int($progress) : 0,
|
||||
pagecount => $pagecount ? int($pagecount) : 0,
|
||||
arcid => $id,
|
||||
title => $title,
|
||||
tags => $tags,
|
||||
isnew => $isnew ? $isnew : "false",
|
||||
extension => lc( ( split( /\./, $file ) )[-1] ),
|
||||
progress => $progress ? int($progress) : 0,
|
||||
pagecount => $pagecount ? int($pagecount) : 0,
|
||||
lastreadtime => $lastreadtime ? int($lastreadtime) : 0
|
||||
};
|
||||
|
||||
@ -199,7 +209,7 @@ sub build_json ( $id, %hash ) {
|
||||
}
|
||||
|
||||
# Deletes the archive with the given id from redis, and the matching archive file/thumbnail.
|
||||
sub delete_archive($id) {
|
||||
sub delete_archive ($id) {
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my $filename = $redis->hget( $id, "file" );
|
||||
@ -207,8 +217,8 @@ sub delete_archive($id) {
|
||||
$oldtags = redis_decode($oldtags);
|
||||
|
||||
my $oldtitle = lc( redis_decode( $redis->hget( $id, "title" ) ) );
|
||||
remove_spaces($oldtitle);
|
||||
remove_newlines($oldtitle);
|
||||
$oldtitle = trim($oldtitle);
|
||||
$oldtitle = trim_CRLF($oldtitle);
|
||||
$oldtitle = redis_encode($oldtitle);
|
||||
|
||||
$redis->del($id);
|
||||
@ -277,7 +287,7 @@ sub clean_database {
|
||||
|
||||
# Get the filemap for ID checks later down the line
|
||||
my @filemapids = $redis_config->exists("LRR_FILEMAP") ? $redis_config->hvals("LRR_FILEMAP") : ();
|
||||
my %filemap = map { $_ => 1 } @filemapids;
|
||||
my %filemap = map { $_ => 1 } @filemapids;
|
||||
|
||||
#40-character long keys only => Archive IDs
|
||||
my @keys = $redis->keys('????????????????????????????????????????');
|
||||
@ -345,8 +355,8 @@ sub set_title ( $id, $newtitle ) {
|
||||
# Remove old title from search set
|
||||
if ( $redis->hexists( $id, "title" ) ) {
|
||||
my $oldtitle = lc( redis_decode( $redis->hget( $id, "title" ) ) );
|
||||
remove_spaces($oldtitle);
|
||||
remove_newlines($oldtitle);
|
||||
$oldtitle = trim($oldtitle);
|
||||
$oldtitle = trim_CRLF($oldtitle);
|
||||
$oldtitle = redis_encode($oldtitle);
|
||||
$redis_search->zrem( "LRR_TITLES", "$oldtitle\0$id" );
|
||||
}
|
||||
@ -356,8 +366,8 @@ sub set_title ( $id, $newtitle ) {
|
||||
|
||||
# Set title/ID key in search set
|
||||
$newtitle = lc($newtitle);
|
||||
remove_spaces($newtitle);
|
||||
remove_newlines($newtitle);
|
||||
$newtitle = trim($newtitle);
|
||||
$newtitle = trim_CRLF($newtitle);
|
||||
$newtitle = redis_encode($newtitle);
|
||||
$redis_search->zadd( "LRR_TITLES", 0, "$newtitle\0$id" );
|
||||
}
|
||||
@ -379,7 +389,7 @@ sub set_tags ( $id, $newtags, $append = 0 ) {
|
||||
unless ( length $newtags ) { return; }
|
||||
|
||||
if ($oldtags) {
|
||||
remove_spaces($oldtags);
|
||||
$oldtags = trim($oldtags);
|
||||
|
||||
if ( $oldtags ne "" ) {
|
||||
$newtags = $oldtags . "," . $newtags;
|
||||
@ -387,6 +397,8 @@ sub set_tags ( $id, $newtags, $append = 0 ) {
|
||||
}
|
||||
}
|
||||
|
||||
$newtags = join_tags_to_string( uniq( split_tags_to_array($newtags) ) );
|
||||
|
||||
# Update sets depending on the added/removed tags
|
||||
update_indexes( $id, $oldtags, $newtags );
|
||||
|
||||
@ -415,6 +427,8 @@ sub set_isnew ( $id, $isnew ) {
|
||||
|
||||
$redis_search->quit;
|
||||
$redis->quit;
|
||||
|
||||
invalidate_cache();
|
||||
}
|
||||
|
||||
# Splits both old and new tags, and:
|
||||
@ -425,15 +439,14 @@ sub update_indexes ( $id, $oldtags, $newtags ) {
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
$redis->multi;
|
||||
|
||||
my @oldtags = split( /,\s?/, $oldtags // "" );
|
||||
my @newtags = split( /,\s?/, $newtags // "" );
|
||||
my @oldtags = split( /,\s?/, $oldtags // "" );
|
||||
my @newtags = split( /,\s?/, $newtags // "" );
|
||||
my $has_tags = 0;
|
||||
|
||||
foreach my $tag (@oldtags) {
|
||||
|
||||
if ( $tag =~ /source:(.*)/i ) {
|
||||
my $url = $1;
|
||||
trim_url($url);
|
||||
my $url = trim_url($1);
|
||||
$redis->hdel( "LRR_URLMAP", $url );
|
||||
}
|
||||
|
||||
@ -448,8 +461,7 @@ sub update_indexes ( $id, $oldtags, $newtags ) {
|
||||
|
||||
# If the tag is a source: tag, add it to the URL index
|
||||
if ( $tag =~ /source:(.*)/i ) {
|
||||
my $url = $1;
|
||||
trim_url($url);
|
||||
my $url = trim_url($1);
|
||||
$redis->hset( "LRR_URLMAP", $url, $id );
|
||||
}
|
||||
|
||||
@ -469,7 +481,7 @@ sub update_indexes ( $id, $oldtags, $newtags ) {
|
||||
|
||||
# This function is used for all ID computation in LRR.
|
||||
# Takes the path to the file as an argument.
|
||||
sub compute_id($file) {
|
||||
sub compute_id ($file) {
|
||||
|
||||
#Read the first 512 KBs only (allows for faster disk speeds )
|
||||
open( my $handle, '<', $file ) or die "Couldn't open $file :" . $!;
|
||||
@ -491,7 +503,7 @@ sub compute_id($file) {
|
||||
}
|
||||
|
||||
# Normalize the string to Unicode NFC, then layer on redis_encode for Redis-safe serialization.
|
||||
sub redis_encode($data) {
|
||||
sub redis_encode ($data) {
|
||||
|
||||
my $NFC_data = NFC($data);
|
||||
return encode_utf8($NFC_data);
|
||||
@ -499,7 +511,7 @@ sub redis_encode($data) {
|
||||
|
||||
# Final Solution to the Unicode glitches -- Eval'd double-decode for data obtained from Redis.
|
||||
# This should be a one size fits-all function.
|
||||
sub redis_decode($data) {
|
||||
sub redis_decode ($data) {
|
||||
|
||||
# Setting FB_CROAK tells encode to die instantly if it encounters any errors.
|
||||
# Without this setting, it typically tries to replace characters... which might already be valid UTF8!
|
||||
@ -513,7 +525,7 @@ sub redis_decode($data) {
|
||||
|
||||
# Bust the current search cache key in Redis.
|
||||
# Add "1" as a parameter to rebuild stat hashes as well. (Use with caution!)
|
||||
sub invalidate_cache($rebuild_indexes = 0) {
|
||||
sub invalidate_cache ( $rebuild_indexes = 0 ) {
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis_search;
|
||||
$redis->del("LRR_SEARCHCACHE");
|
||||
@ -526,13 +538,13 @@ sub invalidate_cache($rebuild_indexes = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
sub save_computed_tagrules($tagrules) {
|
||||
sub save_computed_tagrules ($tagrules) {
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis_config;
|
||||
$redis->del("LRR_TAGRULES");
|
||||
|
||||
if (@$tagrules) {
|
||||
my @flat = reverse flat(@$tagrules);
|
||||
my @flat = reverse flat(@$tagrules);
|
||||
my @encoded_flat = map { redis_encode($_) } @flat;
|
||||
$redis->lpush( "LRR_TAGRULES", @encoded_flat );
|
||||
}
|
||||
@ -548,7 +560,7 @@ sub get_computed_tagrules {
|
||||
|
||||
if ( $redis->exists("LRR_TAGRULES") ) {
|
||||
my @flattened_rules = $redis->lrange( "LRR_TAGRULES", 0, -1 );
|
||||
my @decoded_rules = map { redis_decode($_) } @flattened_rules;
|
||||
my @decoded_rules = map { redis_decode($_) } @flattened_rules;
|
||||
@tagrules = unflat_tagrules( \@decoded_rules );
|
||||
} else {
|
||||
@tagrules = tags_rules_to_array( restore_CRLF( LANraragi::Model::Config->get_tagrules ) );
|
||||
@ -559,4 +571,45 @@ sub get_computed_tagrules {
|
||||
return @tagrules;
|
||||
}
|
||||
|
||||
sub get_tankoubons_by_file ($arcid) {
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
my @tankoubons;
|
||||
|
||||
my $logger = get_logger( "Tankoubon", "lanraragi" );
|
||||
my $err = "";
|
||||
|
||||
unless ( $redis->exists($arcid) ) {
|
||||
$err = "$arcid does not exist in the database.";
|
||||
$logger->error($err);
|
||||
$redis->quit;
|
||||
return ();
|
||||
}
|
||||
|
||||
my @tanks = $redis->keys('TANK_??????????');
|
||||
|
||||
foreach my $key ( sort @tanks ) {
|
||||
|
||||
if ( $redis->zscore( $key, $arcid ) ) {
|
||||
push( @tankoubons, $key );
|
||||
}
|
||||
}
|
||||
|
||||
$redis->quit;
|
||||
return @tankoubons;
|
||||
}
|
||||
|
||||
sub add_arcsize ( $redis, $id ) {
|
||||
my $file = $redis->hget( $id, "file" );
|
||||
my $arcsize = -s $file;
|
||||
set_arcsize( $redis, $id, $arcsize );
|
||||
}
|
||||
|
||||
sub set_arcsize ( $redis, $id, $arcsize ) {
|
||||
$redis->hset( $id, "arcsize", $arcsize );
|
||||
}
|
||||
|
||||
sub get_arcsize ( $redis, $id ) {
|
||||
return $redis->hget( $id, "arcsize" );
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -2,10 +2,10 @@ package LANraragi::Utils::Generic;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use feature "switch";
|
||||
no warnings 'experimental';
|
||||
use utf8;
|
||||
|
||||
use Storable qw(store);
|
||||
use Digest::SHA qw(sha256_hex);
|
||||
use Mojo::Log;
|
||||
@ -15,43 +15,13 @@ use Proc::Simple;
|
||||
use Sys::CpuAffinity;
|
||||
|
||||
use LANraragi::Utils::TempFolder qw(get_temp);
|
||||
use LANraragi::Utils::String qw(trim);
|
||||
use LANraragi::Utils::Logging qw(get_logger);
|
||||
|
||||
# Generic Utility Functions.
|
||||
use Exporter 'import';
|
||||
our @EXPORT_OK =
|
||||
qw(remove_spaces remove_newlines trim_url is_image is_archive render_api_response get_tag_with_namespace shasum start_shinobu
|
||||
split_workload_by_cpu start_minion get_css_list generate_themes_header flat get_bytelength);
|
||||
|
||||
# Remove spaces before and after a word
|
||||
sub remove_spaces {
|
||||
if ( $_[0] ) {
|
||||
$_[0] =~ s/^\s+|\s+$//g;
|
||||
}
|
||||
}
|
||||
|
||||
# Remove all newlines in a string
|
||||
sub remove_newlines {
|
||||
if ( $_[0] ) {
|
||||
$_[0] =~ s/\R//g;
|
||||
}
|
||||
}
|
||||
|
||||
# Fixes up a URL string for use in the DL system.
|
||||
sub trim_url {
|
||||
|
||||
remove_spaces( $_[0] );
|
||||
|
||||
# Remove scheme, www. and query parameters if present. Other subdomains are not removed
|
||||
if ( $_[0] =~ /https?:\/\/(www\.)?([^\?]*)\??.*/gm ) {
|
||||
$_[0] = $2;
|
||||
}
|
||||
|
||||
my $char = chop $_[0];
|
||||
if ( $char ne "/" ) {
|
||||
$_[0] .= $char;
|
||||
}
|
||||
}
|
||||
our @EXPORT_OK = qw(is_image is_archive render_api_response get_tag_with_namespace shasum start_shinobu
|
||||
split_workload_by_cpu start_minion get_css_list generate_themes_header flat get_bytelength array_difference);
|
||||
|
||||
# Checks if the provided file is an image.
|
||||
# Uses non-capturing groups (?:) to avoid modifying the incoming argument.
|
||||
@ -72,10 +42,10 @@ sub render_api_response {
|
||||
|
||||
$mojo->render(
|
||||
json => {
|
||||
operation => $operation,
|
||||
error => $failed ? $errormessage : "",
|
||||
success => $failed ? 0 : 1,
|
||||
successMessage => $failed ? "" : $successMessage,
|
||||
operation => $operation,
|
||||
error => $failed ? $errormessage : "",
|
||||
success => $failed ? 0 : 1,
|
||||
successMessage => $failed ? "" : $successMessage,
|
||||
},
|
||||
status => $failed ? 400 : 200
|
||||
);
|
||||
@ -88,8 +58,8 @@ sub get_tag_with_namespace {
|
||||
|
||||
foreach my $tag (@values) {
|
||||
my ( $namecheck, $value ) = split( ':', $tag );
|
||||
remove_spaces($namecheck);
|
||||
remove_spaces($value);
|
||||
$namecheck = trim($namecheck);
|
||||
$value = trim($value);
|
||||
|
||||
if ( $namecheck eq $namespace ) {
|
||||
return $value;
|
||||
@ -119,7 +89,7 @@ sub split_workload_by_cpu {
|
||||
|
||||
# Start a Minion worker if there aren't any available.
|
||||
sub start_minion {
|
||||
my $mojo = shift;
|
||||
my $mojo = shift;
|
||||
my $logger = get_logger( "Minion", "minion" );
|
||||
|
||||
my $numcpus = Sys::CpuAffinity::getNumCpus();
|
||||
@ -150,8 +120,8 @@ sub start_minion {
|
||||
}
|
||||
|
||||
sub _spawn {
|
||||
my ( $job, $pid ) = @_;
|
||||
my ( $id, $task ) = ( $job->id, $job->task );
|
||||
my ( $job, $pid ) = @_;
|
||||
my ( $id, $task ) = ( $job->id, $job->task );
|
||||
my $logger = get_logger( "Minion Worker", "minion" );
|
||||
$job->app->log->debug(qq{进程 $pid 正在执行作业 "$id" 和任务 "$task"});
|
||||
}
|
||||
@ -266,4 +236,22 @@ sub get_bytelength {
|
||||
return length shift;
|
||||
}
|
||||
|
||||
# Gets right difference between 2 arrays.
|
||||
sub array_difference {
|
||||
my ( $array1, $array2 ) = @_;
|
||||
|
||||
my %seen;
|
||||
my @difference;
|
||||
|
||||
# Add all elements from array1 to the hash
|
||||
$seen{$_} = 1 for @$array1;
|
||||
|
||||
# Check elements in array2 and add the ones not seen in array1 to the difference array
|
||||
foreach my $element (@$array2) {
|
||||
push @difference, $element unless $seen{$element};
|
||||
}
|
||||
|
||||
return @difference;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -2,7 +2,6 @@ package LANraragi::Utils::Logging;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use open ':std', ':encoding(UTF-8)';
|
||||
use feature 'say';
|
||||
|
@ -11,7 +11,8 @@ use LANraragi::Utils::Logging qw(get_logger);
|
||||
use LANraragi::Utils::Database qw(redis_decode);
|
||||
use LANraragi::Utils::Archive qw(extract_thumbnail extract_archive);
|
||||
use LANraragi::Utils::Plugins qw(get_downloader_for_url get_plugin get_plugin_parameters use_plugin);
|
||||
use LANraragi::Utils::Generic qw(trim_url split_workload_by_cpu);
|
||||
use LANraragi::Utils::Generic qw(split_workload_by_cpu);
|
||||
use LANraragi::Utils::String qw(trim_url);
|
||||
use LANraragi::Utils::TempFolder qw(get_temp);
|
||||
|
||||
use LANraragi::Model::Upload;
|
||||
@ -30,7 +31,7 @@ sub add_tasks {
|
||||
my $logger = get_logger( "Minion", "minion" );
|
||||
|
||||
# Non-cover thumbnails are rendered in low quality by default.
|
||||
my $use_hq = $page eq 0 || LANraragi::Model::Config->get_hqthumbpages;
|
||||
my $use_hq = $page eq 0 || LANraragi::Model::Config->get_hqthumbpages;
|
||||
my $thumbname = "";
|
||||
|
||||
eval { $thumbname = extract_thumbnail( $thumbdir, $id, $page, $use_hq ); };
|
||||
@ -72,8 +73,8 @@ sub add_tasks {
|
||||
sub {
|
||||
foreach my $id (@$_) {
|
||||
|
||||
my $use_jxl = LANraragi::Model::Config->get_jxlthumbpages;
|
||||
my $format = $use_jxl ? 'jxl' : 'jpg';
|
||||
my $use_jxl = LANraragi::Model::Config->get_jxlthumbpages;
|
||||
my $format = $use_jxl ? 'jxl' : 'jpg';
|
||||
my $subfolder = substr( $id, 0, 2 );
|
||||
my $thumbname = "$thumbdir/$subfolder/$id.$format";
|
||||
|
||||
@ -150,7 +151,7 @@ sub add_tasks {
|
||||
my ( $job, @args ) = @_;
|
||||
my ( $url, $catid ) = @args;
|
||||
|
||||
my $ua = Mojo::UserAgent->new;
|
||||
my $ua = Mojo::UserAgent->new;
|
||||
my $logger = get_logger( "Minion", "minion" );
|
||||
$logger->info("正在下载 $url...");
|
||||
|
||||
|
@ -86,6 +86,8 @@ sub apply_routes {
|
||||
$logged_in->get('/logs/mojo')->to('logging#print_mojo');
|
||||
$logged_in->get('/logs/redis')->to('logging#print_redis');
|
||||
|
||||
$logged_in->get('/tankoubons')->to('tankoubon#index');
|
||||
|
||||
# OPDS API
|
||||
$public_api->get('/api/opds')->to('api-other#serve_opds_catalog');
|
||||
$public_api->get('/api/opds/:id')->to('api-other#serve_opds_item');
|
||||
@ -112,6 +114,7 @@ sub apply_routes {
|
||||
$public_api->delete('/api/archives/:id/isnew')->to('api-archive#clear_new');
|
||||
$public_api->get('/api/archives/:id')->to('api-archive#serve_metadata');
|
||||
$public_api->get('/api/archives/:id/categories')->to('api-archive#get_categories');
|
||||
$public_api->get('/api/archives/:id/tankoubons')->to('api-tankoubon#get_tankoubons_file');
|
||||
$public_api->get('/api/archives/:id/metadata')->to('api-archive#serve_metadata');
|
||||
$logged_in_api->put('/api/archives/:id/thumbnail')->to('api-archive#update_thumbnail');
|
||||
$logged_in_api->put('/api/archives/:id/metadata')->to('api-archive#update_metadata');
|
||||
@ -150,6 +153,15 @@ sub apply_routes {
|
||||
$logged_in_api->put('/api/categories/:id/:archive')->to('api-category#add_to_category');
|
||||
$logged_in_api->delete('/api/categories/:id/:archive')->to('api-category#remove_from_category');
|
||||
|
||||
# Tankoubon API
|
||||
$public_api->get('/api/tankoubons')->to('api-tankoubon#get_tankoubon_list');
|
||||
$public_api->get('/api/tankoubons/:id')->to('api-tankoubon#get_tankoubon');
|
||||
$logged_in_api->put('/api/tankoubons')->to('api-tankoubon#create_tankoubon');
|
||||
$logged_in_api->delete('/api/tankoubons/:id')->to('api-tankoubon#delete_tankoubon');
|
||||
$logged_in_api->put('/api/tankoubons/:id/archive')->to('api-tankoubon#update_archive_list');
|
||||
$logged_in_api->put('/api/tankoubons/:id/:archive')->to('api-tankoubon#add_to_tankoubon');
|
||||
$logged_in_api->delete('/api/tankoubons/:id/:archive')->to('api-tankoubon#remove_from_tankoubon');
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
80
lib/LANraragi/Utils/String.pm
Normal file
@ -0,0 +1,80 @@
|
||||
package LANraragi::Utils::String;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use feature "switch";
|
||||
no warnings 'experimental';
|
||||
use feature qw(signatures);
|
||||
|
||||
use String::Similarity;
|
||||
|
||||
use Exporter 'import';
|
||||
our @EXPORT_OK = qw(clean_title trim trim_CRLF trim_url most_similar);
|
||||
|
||||
# Remove "junk" from titles, turning something like "(c12) [poop (butt)] hardcore handholding [monogolian] [recensored]" into "hardcore handholding"
|
||||
sub clean_title ($title) {
|
||||
$title = trim($title);
|
||||
|
||||
# Remove leading "(c12)"
|
||||
$title =~ s/^\([^)]*\)?\s?//g;
|
||||
|
||||
# Remove leading "[poop (butt)]"
|
||||
$title =~ s/^\[[^]]*\]?\s?//g;
|
||||
|
||||
# Remove trailing [mongolian] [recensored]"
|
||||
$title =~ s/\s?\[[^]]*\]$//g;
|
||||
$title =~ s/\s?\[[^]]*\]$//g;
|
||||
return $title;
|
||||
}
|
||||
|
||||
# Remove spaces before and after a word
|
||||
sub trim ($s) {
|
||||
$s =~ s/^\s+|\s+$//g;
|
||||
return $s;
|
||||
}
|
||||
|
||||
# Remove all newlines in a string
|
||||
sub trim_CRLF ($s) {
|
||||
$s =~ s/\R//g;
|
||||
return $s;
|
||||
}
|
||||
|
||||
# Fixes up a URL string for use in the DL system.
|
||||
sub trim_url ($url) {
|
||||
|
||||
$url = trim($url);
|
||||
|
||||
# Remove scheme, www. and query parameters if present. Other subdomains are not removed
|
||||
if ( $url =~ /https?:\/\/(www\.)?([^\?]*)\??.*/gm ) {
|
||||
$url = $2;
|
||||
}
|
||||
|
||||
my $char = chop $url;
|
||||
if ( $char ne "/" ) {
|
||||
$url .= $char;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
# Finds the index of the string in @values that is most similar to $tested_string. Returns undef if @values is empty.
|
||||
# If multiple rows score "first place", the first one is returned
|
||||
sub most_similar ( $tested_string, @values ) {
|
||||
if ( !@values ) {
|
||||
return;
|
||||
}
|
||||
|
||||
my $best_similarity = 0.0;
|
||||
my $best_index = undef;
|
||||
|
||||
while ( my ( $index, $elem ) = each @values ) {
|
||||
my $similarity = similarity( $tested_string, $elem );
|
||||
if ( !defined($best_index) || $similarity > $best_similarity ) {
|
||||
$best_similarity = $similarity;
|
||||
$best_index = $index;
|
||||
}
|
||||
}
|
||||
return $best_index;
|
||||
}
|
||||
|
||||
1;
|
@ -6,12 +6,11 @@ use warnings;
|
||||
use feature "switch";
|
||||
no warnings 'experimental';
|
||||
|
||||
use LANraragi::Utils::Generic qw(remove_spaces remove_newlines);
|
||||
use LANraragi::Utils::String qw(trim trim_CRLF);
|
||||
|
||||
# Functions related to the Tag system.
|
||||
use Exporter 'import';
|
||||
our @EXPORT_OK =
|
||||
qw( unflat_tagrules replace_CRLF restore_CRLF tags_rules_to_array rewrite_tags split_tags_to_array );
|
||||
our @EXPORT_OK = qw( unflat_tagrules replace_CRLF restore_CRLF tags_rules_to_array rewrite_tags split_tags_to_array join_tags_to_string );
|
||||
|
||||
sub is_null_or_empty {
|
||||
return !length(shift);
|
||||
@ -19,63 +18,67 @@ sub is_null_or_empty {
|
||||
|
||||
sub replace_CRLF {
|
||||
my ($val) = @_;
|
||||
$val =~ s/\x{d}\x{a}/;/g if ( $val );
|
||||
$val =~ s/\x{d}\x{a}/;/g if ($val);
|
||||
return $val;
|
||||
}
|
||||
|
||||
sub restore_CRLF {
|
||||
my ($val) = @_;
|
||||
$val =~ s/;/\x{d}\x{a}/g if ( $val );
|
||||
$val =~ s/;/\x{d}\x{a}/g if ($val);
|
||||
return $val;
|
||||
}
|
||||
|
||||
sub unflat_tagrules {
|
||||
my ( $flattened_rules ) = @_;
|
||||
my ($flattened_rules) = @_;
|
||||
my @tagrules = ();
|
||||
while (@{$flattened_rules || []}) {
|
||||
push(@tagrules, [ splice(@$flattened_rules, 0, 3) ]);
|
||||
while ( @{ $flattened_rules || [] } ) {
|
||||
push( @tagrules, [ splice( @$flattened_rules, 0, 3 ) ] );
|
||||
}
|
||||
return @tagrules;
|
||||
}
|
||||
|
||||
sub split_tags_to_array {
|
||||
my ( $tags_string ) = @_;
|
||||
my @tags = split( ',', $tags_string );
|
||||
my ($tags_string) = @_;
|
||||
my @tags = split( ',', $tags_string );
|
||||
foreach my $tags (@tags) {
|
||||
remove_spaces($tags);
|
||||
remove_newlines($tags);
|
||||
$tags = trim($tags);
|
||||
$tags = trim_CRLF($tags);
|
||||
}
|
||||
return @tags;
|
||||
}
|
||||
|
||||
sub join_tags_to_string {
|
||||
return join( ',', @_ );
|
||||
}
|
||||
|
||||
sub tags_rules_to_array {
|
||||
my ( $text_rules ) = @_;
|
||||
my ($text_rules) = @_;
|
||||
my @rules;
|
||||
my @lines = split( '\n', $text_rules );
|
||||
foreach my $line ( @lines ) {
|
||||
foreach my $line (@lines) {
|
||||
my ( $match, $value ) = split( '->', $line );
|
||||
remove_spaces($match);
|
||||
remove_spaces($value);
|
||||
if (!is_null_or_empty($match)) {
|
||||
$match = trim($match);
|
||||
$value = trim($value);
|
||||
if ( !is_null_or_empty($match) ) {
|
||||
|
||||
my $rule_type;
|
||||
if ( !$value && $match =~ m/^-.*:\*$/ ) {
|
||||
$rule_type = 'remove_ns';
|
||||
$match = substr ($match, 1, length($match)-3);
|
||||
$match = substr( $match, 1, length($match) - 3 );
|
||||
} elsif ( !$value && $match =~ m/^-/ ) {
|
||||
$rule_type = 'remove';
|
||||
$match = substr ($match, 1);
|
||||
$match = substr( $match, 1 );
|
||||
} elsif ( !$value && $match =~ m/^~/ ) {
|
||||
$rule_type = 'strip_ns';
|
||||
$match = substr ($match, 1);
|
||||
$match = substr( $match, 1 );
|
||||
} elsif ( $match =~ m/:\*$/ && $value =~ m/:\*$/ ) {
|
||||
$rule_type = 'replace_ns';
|
||||
$match = substr ($match, 0, length($match)-2);
|
||||
$value = substr ($value, 0, length($value)-2);
|
||||
$match = substr( $match, 0, length($match) - 2 );
|
||||
$value = substr( $value, 0, length($value) - 2 );
|
||||
} elsif ( !$value ) {
|
||||
$rule_type = 'remove'; # blacklist mode
|
||||
$rule_type = 'remove'; # blacklist mode
|
||||
} else {
|
||||
$rule_type = 'replace'
|
||||
$rule_type = 'replace';
|
||||
}
|
||||
|
||||
push( @rules, [ $rule_type, lc $match, $value || '' ] ) if ($rule_type);
|
||||
@ -89,9 +92,9 @@ sub rewrite_tags {
|
||||
return @$tags if ( !@$rules );
|
||||
|
||||
my @parsed_tags;
|
||||
foreach my $tag ( @$tags ) {
|
||||
my $new_tag = apply_rules($tag, $rules);
|
||||
push(@parsed_tags, $new_tag) if ($new_tag);
|
||||
foreach my $tag (@$tags) {
|
||||
my $new_tag = apply_rules( $tag, $rules );
|
||||
push( @parsed_tags, $new_tag ) if ($new_tag);
|
||||
}
|
||||
return @parsed_tags;
|
||||
}
|
||||
@ -99,10 +102,10 @@ sub rewrite_tags {
|
||||
sub apply_rules {
|
||||
my ( $tag, $rules ) = @_;
|
||||
|
||||
foreach my $rule ( @$rules ) {
|
||||
foreach my $rule (@$rules) {
|
||||
my $match = $rule->[1];
|
||||
my $value = $rule->[2];
|
||||
given($rule->[0]) {
|
||||
given ( $rule->[0] ) {
|
||||
when ('remove') { return if ( lc $tag eq $match ); }
|
||||
when ('remove_ns') { return if ( $tag =~ m/^$match:/i ); }
|
||||
when ('replace_ns') { $tag =~ s/^\Q$match:/$value\:/i; }
|
||||
|
@ -111,6 +111,11 @@ sub clean_temp_partial {
|
||||
}
|
||||
closedir $dir_fh;
|
||||
|
||||
if ( scalar @folder_list <= 1 ) {
|
||||
$logger->info("Only one folder left in /temp, aborting clean.");
|
||||
return;
|
||||
}
|
||||
|
||||
@folder_list = sort {
|
||||
my $a_stat = stat($a);
|
||||
my $b_stat = stat($b);
|
||||
|
@ -7,6 +7,7 @@ package Shinobu;
|
||||
# Tracking all files in the content folder and making sure they're sync'ed with the database
|
||||
# Automatically cleaning the temporary folder when it reaches a certain size
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
@ -260,6 +261,11 @@ sub add_to_filemap ( $redis_cfg, $file ) {
|
||||
invalidate_cache();
|
||||
}
|
||||
|
||||
unless ( LANraragi::Utils::Database::get_arcsize( $redis_arc, $id ) ) {
|
||||
$logger->debug("arcsize is not set for $id, storing now!");
|
||||
LANraragi::Utils::Database::add_arcsize( $redis_arc, $id );
|
||||
}
|
||||
|
||||
# Set pagecount in case it's not already there
|
||||
unless ( $redis_arc->hget( $id, "pagecount" ) ) {
|
||||
$logger->debug("未计算 $id 的页数,立即执行!");
|
||||
|
2372
package-lock.json
generated
Normal file
@ -18,7 +18,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/WindyCloudCute/LANraragi_Chinese"
|
||||
"url": "git+https://github.com/WindyCloudCute/LANraragi_Chinese.git"
|
||||
},
|
||||
"author": "dfug",
|
||||
"license": "MIT",
|
||||
@ -44,7 +44,7 @@
|
||||
"preact": "^10.7.1",
|
||||
"react-toastify": "^9.0.0-rc-2",
|
||||
"roboto-fontface": "^0.8.0",
|
||||
"sweetalert2": "^11.4.10",
|
||||
"sweetalert2": "^11.6.14",
|
||||
"swiper": "^8.4.5",
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
@ -53,4 +53,4 @@
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.26.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,6 +308,7 @@ LRR.getImgSize = function (target) {
|
||||
$.ajax({
|
||||
async: false,
|
||||
url: target,
|
||||
cache: true,
|
||||
type: "HEAD",
|
||||
success: (data, textStatus, request) => {
|
||||
imgSize = parseInt(request.getResponseHeader("Content-Length") / 1024, 10);
|
||||
|
@ -106,7 +106,7 @@ Edit.saveMetadata = function () {
|
||||
formData.append("title", $("#title").val());
|
||||
|
||||
return fetch(`api/archives/${id}/metadata`, { method: "PUT", body: formData })
|
||||
.then((response) => (response.ok ? response.json() : { success: 0, error: "反应不好" }))
|
||||
.then((response) => (response.ok ? response.json() : { success: 0, error: "响应不正确" }))
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
LRR.toast({
|
||||
|
@ -50,7 +50,10 @@ IndexTable.initializeAll = function () {
|
||||
IndexTable.dataTable = $(".datatables").DataTable({
|
||||
serverSide: true,
|
||||
processing: true,
|
||||
ajax: "search",
|
||||
ajax: {
|
||||
url: "search",
|
||||
cache: true,
|
||||
},
|
||||
deferRender: true,
|
||||
lengthChange: false,
|
||||
pageLength: Index.pageSize,
|
||||
|
@ -52,7 +52,6 @@ Reader.initializeAll = function () {
|
||||
focusConfirm: false,
|
||||
confirmButtonText: "是的我要删除!",
|
||||
cancelButtonText: "取消",
|
||||
cancelButtonText: "取消",
|
||||
reverseButtons: true,
|
||||
confirmButtonColor: "#d33",
|
||||
}).then((result) => {
|
||||
@ -275,7 +274,6 @@ Reader.initInfiniteScrollView = function () {
|
||||
Reader.pages.slice(1).forEach((source) => {
|
||||
const img = new Image();
|
||||
img.id = `page-${Reader.pages.indexOf(source)}`;
|
||||
img.loading = "lazy";
|
||||
img.height = 800;
|
||||
img.width = 600;
|
||||
img.src = source;
|
||||
|
@ -42,6 +42,33 @@ Server.callAPI = function (endpoint, method, successMessage, errorMessage, succe
|
||||
.catch((error) => LRR.showErrorToast(errorMessage, error));
|
||||
};
|
||||
|
||||
Server.callAPIBody = function (endpoint, method, body, successMessage, errorMessage, successCallback) {
|
||||
return fetch(endpoint, { method, body })
|
||||
.then((response) => (response.ok ? response.json() : { success: 0, error: "响应不正确" }))
|
||||
.then((data) => {
|
||||
if (Object.prototype.hasOwnProperty.call(data, "success") && !data.success) {
|
||||
throw new Error(data.error);
|
||||
} else {
|
||||
let message = successMessage;
|
||||
if ("successMessage" in data && data.successMessage !== null) {
|
||||
message = data.successMessage;
|
||||
}
|
||||
if (message !== null) {
|
||||
LRR.toast({
|
||||
heading: message,
|
||||
icon: "success",
|
||||
hideAfter: 7000,
|
||||
});
|
||||
}
|
||||
|
||||
if (successCallback !== null) return successCallback(data);
|
||||
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.catch((error) => LRR.showErrorToast(errorMessage, error));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the status of a Minion job until it's completed.
|
||||
* @param {*} jobId Job ID to check
|
||||
|
@ -21,7 +21,7 @@
|
||||
<script src="/js/vendor/preact.umd.js" type="text/JAVASCRIPT"></script>
|
||||
<script src="/js/vendor/hooks.umd.js" type="text/JAVASCRIPT"></script>
|
||||
<script src="/js/vendor/compat.umd.js" type="text/JAVASCRIPT"></script>
|
||||
<script>window.React=window.preactCompat;window.react=window.preactCompat;</script>
|
||||
<script>window.React = window.preactCompat; window.react = window.preactCompat;</script>
|
||||
<script src="/js/vendor/clsx.min.js" type="text/JAVASCRIPT"></script>
|
||||
<script src="/js/vendor/react-toastify.umd.js" type="text/JAVASCRIPT"></script>
|
||||
<script src="/js/vendor/jquery.ui.widget.js" type="text/JAVASCRIPT"></script>
|
||||
@ -83,6 +83,17 @@
|
||||
<i class="fa fa-digital-tachograph" aria-hidden="true"></i> 元数据插件
|
||||
</div>
|
||||
<div class="collapsible-body">
|
||||
|
||||
<h1 class="ih" style="display:inline">允许插件替换档案标题: </h1>
|
||||
[% IF replacetitles %]
|
||||
<input id="replacetitles" name="replacetitles" class="fa" type="checkbox" checked> [% ELSE
|
||||
%]
|
||||
<input id="replacetitles" name="replacetitles" class="fa" type="checkbox"> [% END %]
|
||||
<label for="replacetitles">
|
||||
<br> 如果启用,则元数据插件将可以更改档案的标题。
|
||||
</label>
|
||||
<br />
|
||||
<br />
|
||||
[% INCLUDE pluginlist plugins = metadata %]
|
||||
</div>
|
||||
</li>
|
||||
|
@ -20,7 +20,7 @@
|
||||
<input id="enablecryptofs" name="enablecryptofs" class="fa" type="checkbox" checked> [% ELSE %]
|
||||
<input id="enablecryptofs" name="enablecryptofs" class="fa" type="checkbox"> [% END %]
|
||||
<label for="enablecryptofs">
|
||||
<br>如果启用,LANraragi 会将存档文件名截断为 143 字节,这是 eCryptFS 接受的最大值.
|
||||
<br>如果启用,LANraragi 会将存档文件名截断至 143 字节,这是 eCryptFS 接受的最大值.
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -65,7 +65,7 @@
|
||||
<td class="config-td">
|
||||
<input id="readerquality" class="stdinput" type="number" min="0" max="100" style="width:100%" maxlength="255"
|
||||
size="20" value="[% readerquality %]" name="readerquality">
|
||||
<br> 调整大小后的图像的质量。 低质量 = 小容量图片。 (0-100)
|
||||
<br> 调整大小后的图像的质量。 低质量 = 图片占用空间少。 (0-100)
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
use Mojo::File;
|
||||
use utf8;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
# LANraragi::Plugin::Metadata::ChaikaFile
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
use File::Temp qw(tempfile);
|
||||
use File::Copy "cp";
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use utf8;
|
||||
|
||||
use Test::Trap;
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
@ -18,21 +18,23 @@ require "$cwd/tests/mocks.pl";
|
||||
|
||||
use_ok('LANraragi::Plugin::Metadata::ChaikaFile');
|
||||
|
||||
my @tags_list= (
|
||||
'full censorship', 'female:sole female', 'male:sole male', 'artist:kemuri haku', 'female:tall girl',
|
||||
'female:cunnilingus', 'male:shotacon', 'female:defloration', 'female:nakadashi', 'female:x-ray',
|
||||
'female:big breasts', 'language:translated', 'language:english'
|
||||
my @tags_list = (
|
||||
'full censorship', 'female:sole female', 'male:sole male', 'artist:kemuri haku',
|
||||
'female:tall girl', 'female:cunnilingus', 'male:shotacon', 'female:defloration',
|
||||
'female:nakadashi', 'female:x-ray', 'female:big breasts', 'language:translated',
|
||||
'language:english'
|
||||
);
|
||||
my @tags_list_extra= (
|
||||
'other:full censorship', 'female:sole female', 'male:sole male', 'artist:kemuri haku', 'female:tall girl',
|
||||
'female:cunnilingus', 'male:shotacon', 'female:defloration', 'female:nakadashi', 'female:x-ray',
|
||||
'female:big breasts', 'language:translated', 'language:english', 'category:manga', 'download:/archive/27240/download/',
|
||||
'gallery:23532', 'timestamp:1521357552', 'source:chaika'
|
||||
my @tags_list_extra = (
|
||||
'other:full censorship', 'female:sole female', 'male:sole male', 'artist:kemuri haku',
|
||||
'female:tall girl', 'female:cunnilingus', 'male:shotacon', 'female:defloration',
|
||||
'female:nakadashi', 'female:x-ray', 'female:big breasts', 'language:translated',
|
||||
'language:english', 'category:manga', 'download:/archive/27240/download/', 'gallery:23532',
|
||||
'timestamp:1521357552', 'source:chaika'
|
||||
);
|
||||
|
||||
use_ok('LANraragi::Plugin::Metadata::ChaikaFile');
|
||||
|
||||
note ( 'testing reading file without extra data...' );
|
||||
note('testing reading file without extra data...');
|
||||
{
|
||||
# Copy the sample json to a temporary directory as it's deleted once parsed
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
@ -46,16 +48,19 @@ note ( 'testing reading file without extra data...' );
|
||||
my %dummyhash = ( file_path => "test" );
|
||||
|
||||
my $saveTitle = 0;
|
||||
my $addextra = 0;
|
||||
my $addother = 0;
|
||||
my $addextra = 0;
|
||||
my $addother = 0;
|
||||
my $addsource = '';
|
||||
my %ko_tags = trap { LANraragi::Plugin::Metadata::ChaikaFile::get_tags( "", \%dummyhash, $saveTitle, $addextra, $addother, $addsource ); };
|
||||
my %ko_tags = trap { LANraragi::Plugin::Metadata::ChaikaFile::get_tags( "", \%dummyhash, $addextra, $addother, $addsource ); };
|
||||
|
||||
is( $ko_tags{title}, undef, 'gallery title');
|
||||
is( $ko_tags{tags}, join(", ", @tags_list), 'gallery tag list');
|
||||
is( $ko_tags{title},
|
||||
"[Kemuri Haku] Zettai Seikou Keikaku | Absolute Intercourse Plan (COMIC Shitsurakuten 2016-03) [English] [Redlantern]",
|
||||
'gallery title'
|
||||
);
|
||||
is( $ko_tags{tags}, join( ", ", @tags_list ), 'gallery tag list' );
|
||||
}
|
||||
|
||||
note ( 'testing reading file with extra data...' );
|
||||
note('testing reading file with extra data...');
|
||||
{
|
||||
# Copy the sample json to a temporary directory as it's deleted once parsed
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
@ -68,15 +73,17 @@ note ( 'testing reading file with extra data...' );
|
||||
|
||||
my %dummyhash = ( file_path => "test" );
|
||||
|
||||
my $saveTitle = 1;
|
||||
my $addextra = 1;
|
||||
my $addother = 1;
|
||||
my $addextra = 1;
|
||||
my $addother = 1;
|
||||
my $addsource = 'chaika';
|
||||
|
||||
my %ko_tags = trap { LANraragi::Plugin::Metadata::ChaikaFile::get_tags( "", \%dummyhash, $saveTitle, $addextra, $addother, $addsource ); };
|
||||
my %ko_tags = trap { LANraragi::Plugin::Metadata::ChaikaFile::get_tags( "", \%dummyhash, $addextra, $addother, $addsource ); };
|
||||
|
||||
is( $ko_tags{title}, "[Kemuri Haku] Zettai Seikou Keikaku | Absolute Intercourse Plan (COMIC Shitsurakuten 2016-03) [English] [Redlantern]", 'gallery title');
|
||||
is( $ko_tags{tags}, join(", ", @tags_list_extra), 'gallery tag list');
|
||||
is( $ko_tags{title},
|
||||
"[Kemuri Haku] Zettai Seikou Keikaku | Absolute Intercourse Plan (COMIC Shitsurakuten 2016-03) [English] [Redlantern]",
|
||||
'gallery title'
|
||||
);
|
||||
is( $ko_tags{tags}, join( ", ", @tags_list_extra ), 'gallery tag list' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
@ -18,7 +18,7 @@ require "$cwd/tests/mocks.pl";
|
||||
|
||||
use_ok('LANraragi::Plugin::Metadata::ComicInfo');
|
||||
|
||||
#note("00 - [れむ] 夜伽妻 [DL版]");
|
||||
note("00 - [れむ] 夜伽妻 [DL版]");
|
||||
{
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . "/comicinfo/00_sample.xml", $fh );
|
||||
@ -44,7 +44,7 @@ use_ok('LANraragi::Plugin::Metadata::ComicInfo');
|
||||
is( $returned_tags, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
#note("01 - 夜伽妻");
|
||||
note("01 - 夜伽妻");
|
||||
{
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . "/comicinfo/01_sample.xml", $fh );
|
||||
@ -70,7 +70,7 @@ use_ok('LANraragi::Plugin::Metadata::ComicInfo');
|
||||
is( $returned_tags, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
#note("02 - よつばと! 第01巻");
|
||||
note("02 - よつばと! 第01巻");
|
||||
{
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . "/comicinfo/02_sample.xml", $fh );
|
||||
|
@ -1,13 +1,13 @@
|
||||
# LANraragi::Plugin::Metadata::EHentai
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
use Mojo::File;
|
||||
use utf8;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
# LANraragi::Plugin::Metadata::Eze
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
use File::Temp qw(tempfile);
|
||||
use File::Copy "cp";
|
||||
use utf8;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
|
||||
use Test::Trap;
|
||||
@ -19,12 +19,12 @@ require "$cwd/tests/mocks.pl";
|
||||
use_ok('LANraragi::Plugin::Metadata::Eze');
|
||||
|
||||
sub eve_test {
|
||||
|
||||
my ($jsonpath, $save_title, $origin_title, $additional_tags) = @_;
|
||||
|
||||
my ( $jsonpath, $save_title, $origin_title, $additional_tags ) = @_;
|
||||
|
||||
# Copy the eze sample json to a temporary directory as it's deleted once parsed
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . $jsonpath , $fh );
|
||||
cp( $SAMPLES . $jsonpath, $fh );
|
||||
|
||||
# Mock LANraragi::Utils::Archive's subs to return the temporary sample JSON
|
||||
# Since we're using exports, the methods are under the plugin's namespace.
|
||||
@ -37,19 +37,19 @@ sub eve_test {
|
||||
|
||||
# Since this is calling the sub directly and not in an object context,
|
||||
# we pass a dummy string as first parameter to replace the object.
|
||||
my %ezetags = trap { LANraragi::Plugin::Metadata::Eze::get_tags( "", \%dummyhash, $save_title, $origin_title, $additional_tags ); };
|
||||
my %ezetags =
|
||||
trap { LANraragi::Plugin::Metadata::Eze::get_tags( "", \%dummyhash, $save_title, $origin_title, $additional_tags ); };
|
||||
|
||||
return %ezetags;
|
||||
|
||||
}
|
||||
|
||||
note("eze-lite Tests, save_title on, origin_title off, additional_tags off");
|
||||
note("eze-lite Tests, origin_title off, additional_tags off");
|
||||
{
|
||||
my $save_title = 1;
|
||||
my $origin_title = 0;
|
||||
my $origin_title = 0;
|
||||
my $additional_tags = 0;
|
||||
|
||||
my %ezetags = eve_test("/eze/eze_lite_sample.json", $save_title, $origin_title, $additional_tags);
|
||||
|
||||
my %ezetags = eve_test( "/eze/eze_lite_sample.json", $origin_title, $additional_tags );
|
||||
|
||||
is( $ezetags{title},
|
||||
"(C72) [Mitarashi Club (Mitarashi Kousei)] Akiko-san to Issho (Kanon) [English] [Belldandy100] [Decensored]",
|
||||
@ -61,14 +61,12 @@ note("eze-lite Tests, save_title on, origin_title off, additional_tags off");
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
note("eze-lite Tests, save_title on, origin_title on, additional_tags on");
|
||||
note("eze-lite Tests, origin_title on, additional_tags on");
|
||||
{
|
||||
my $save_title = 1;
|
||||
my $origin_title = 1;
|
||||
my $origin_title = 1;
|
||||
my $additional_tags = 1;
|
||||
|
||||
my %ezetags = eve_test("/eze/eze_lite_sample.json", $save_title, $origin_title, $additional_tags);
|
||||
|
||||
my %ezetags = eve_test( "/eze/eze_lite_sample.json", $origin_title, $additional_tags );
|
||||
|
||||
is( $ezetags{title},
|
||||
"(C72) [Mitarashi Club (Mitarashi Kousei)] Akiko-san to Issho (Kanon) [English] [Belldandy100] [Decensored]",
|
||||
@ -80,58 +78,32 @@ note("eze-lite Tests, save_title on, origin_title on, additional_tags on");
|
||||
);
|
||||
}
|
||||
|
||||
note("eze-full Tests, save_title off, origin_title off, additional_tags off");
|
||||
note("eze-full Tests, origin_title off, additional_tags on");
|
||||
{
|
||||
my $save_title = 0;
|
||||
my $origin_title = 0;
|
||||
my $additional_tags = 0;
|
||||
|
||||
my %ezetags = eve_test("/eze/eze_full_sample.json", $save_title, $origin_title, $additional_tags);
|
||||
my $origin_title = 0;
|
||||
my $additional_tags = 1;
|
||||
|
||||
is( $ezetags{title},
|
||||
undef,
|
||||
"title parsing test 1/2"
|
||||
my %ezetags = eve_test( "/eze/eze_full_sample.json", $origin_title, $additional_tags );
|
||||
|
||||
is( $ezetags{title}, "(C91) [HitenKei (Hiten)] R.E.I.N.A [Chinese] [無邪気漢化組]", "title parsing test 1/2" );
|
||||
is( $ezetags{tags},
|
||||
"artist:hiten, female:defloration, female:pantyhose, female:sole female, group:hitenkei, language:chinese, language:translated, male:sole male, parody:original, category:doujinshi, uploader:cocy, timestamp:1484412360, source:exhentai.org/g/1017975/49b3c275a1",
|
||||
"tags parsing test 2/2"
|
||||
);
|
||||
}
|
||||
|
||||
note("eze-full Tests, origin_title on, additional_tags off");
|
||||
{
|
||||
my $origin_title = 1;
|
||||
my $additional_tags = 0;
|
||||
|
||||
my %ezetags = eve_test( "/eze/eze_full_sample.json", $origin_title, $additional_tags );
|
||||
|
||||
is( $ezetags{title}, "(C91) [HitenKei (Hiten)] R.E.I.N.A [中国翻訳]", "title parsing test 1/2" );
|
||||
is( $ezetags{tags},
|
||||
"artist:hiten, female:defloration, female:pantyhose, female:sole female, group:hitenkei, language:chinese, language:translated, male:sole male, parody:original, category:doujinshi, source:exhentai.org/g/1017975/49b3c275a1",
|
||||
"tags parsing test 2/2"
|
||||
);
|
||||
}
|
||||
|
||||
note("eze-full Tests, save_title on, origin_title off, additional_tags on");
|
||||
{
|
||||
my $save_title = 1;
|
||||
my $origin_title = 0;
|
||||
my $additional_tags = 1;
|
||||
|
||||
my %ezetags = eve_test("/eze/eze_full_sample.json", $save_title, $origin_title, $additional_tags);
|
||||
|
||||
is( $ezetags{title},
|
||||
"(C91) [HitenKei (Hiten)] R.E.I.N.A [Chinese] [無邪気漢化組]",
|
||||
"title parsing test 1/2"
|
||||
);
|
||||
is( $ezetags{tags},
|
||||
"artist:hiten, female:defloration, female:pantyhose, female:sole female, group:hitenkei, language:chinese, language:translated, male:sole male, parody:original, category:doujinshi, uploader:cocy, timestamp:1484412360, source:exhentai.org/g/1017975/49b3c275a1",
|
||||
"tags parsing test 2/2"
|
||||
);
|
||||
}
|
||||
|
||||
note("eze-full Tests, save_title on, origin_title on, additional_tags on");
|
||||
{
|
||||
my $save_title = 1;
|
||||
my $origin_title = 1;
|
||||
my $additional_tags = 1;
|
||||
|
||||
my %ezetags = eve_test("/eze/eze_full_sample.json", $save_title, $origin_title, $additional_tags);
|
||||
|
||||
is( $ezetags{title},
|
||||
"(C91) [HitenKei (Hiten)] R.E.I.N.A [中国翻訳]",
|
||||
"title parsing test 1/2"
|
||||
);
|
||||
is( $ezetags{tags},
|
||||
"artist:hiten, female:defloration, female:pantyhose, female:sole female, group:hitenkei, language:chinese, language:translated, male:sole male, parody:original, category:doujinshi, uploader:cocy, timestamp:1484412360, source:exhentai.org/g/1017975/49b3c275a1",
|
||||
"tags parsing test 2/2"
|
||||
);
|
||||
}
|
||||
|
||||
done_testing();
|
@ -1,12 +1,12 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
use Mojo::File;
|
||||
use utf8;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Module::Pluggable require => 1, search_path => ['LANraragi::Plugin::Metadata'];
|
||||
use utf8;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
# LANraragi::Plugin::Metadata::Hentag
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
use File::Temp qw(tempfile);
|
||||
use File::Copy "cp";
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use utf8;
|
||||
|
||||
use Test::Trap;
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
@ -1,16 +1,17 @@
|
||||
# LANraragi::Plugin::Metadata::HentagOnline
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use feature qw(signatures);
|
||||
no warnings 'experimental::signatures';
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
|
||||
use Test::Trap;
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
use utf8;
|
||||
my $cwd = getcwd();
|
||||
|
||||
my $cwd = getcwd();
|
||||
my $SAMPLES = "$cwd/tests/samples";
|
||||
require "$cwd/tests/mocks.pl";
|
||||
|
||||
@ -30,27 +31,27 @@ note("00 - api response");
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title);
|
||||
my %get_tags_params = ( archive_title => $archive_title );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, 1 );
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "[Doi Sakazaki] Boin Tantei vs Kaitou Sanmensou [ENG]";
|
||||
my $expected_tags =
|
||||
"artist:doi sakazaki, female:big breasts, female:maid, female:paizuri, language:english, source:https://hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
is( $received_title, $archive_title, "sent correct title to get_json_from_api");
|
||||
is( $received_title, $archive_title, "sent correct title to get_json_from_api" );
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
note("01 - vault url parsing");
|
||||
{
|
||||
my $url_nowww = "https://hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $url_www = "https://www.hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $url_nowww = "https://hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $url_www = "https://www.hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $expected_id = "QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
|
||||
is(LANraragi::Plugin::Metadata::HentagOnline::parse_vault_url($url_nowww), $expected_id, "got correct id, without www");
|
||||
is(LANraragi::Plugin::Metadata::HentagOnline::parse_vault_url($url_www), $expected_id, "got correct id, with www");
|
||||
is(LANraragi::Plugin::Metadata::HentagOnline::parse_vault_url(''), undef, "empty URL returns nothing");
|
||||
is( LANraragi::Plugin::Metadata::HentagOnline::parse_vault_url($url_nowww), $expected_id, "got correct id, without www" );
|
||||
is( LANraragi::Plugin::Metadata::HentagOnline::parse_vault_url($url_www), $expected_id, "got correct id, with www" );
|
||||
is( LANraragi::Plugin::Metadata::HentagOnline::parse_vault_url(''), undef, "empty URL returns nothing" );
|
||||
}
|
||||
|
||||
note("02 - multilaguage hit");
|
||||
@ -67,39 +68,39 @@ note("02 - multilaguage hit");
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title);
|
||||
my %get_tags_params = ( archive_title => $archive_title );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, 1 );
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "Do match this title";
|
||||
my $expected_tags =
|
||||
"artist:the artist, female:penis, language:english, source:https://hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
is( $received_title, $archive_title, "sent correct title to get_json_from_api");
|
||||
"artist:the artist, female:penis, language:english, source:https://hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
is( $received_title, $archive_title, "sent correct title to get_json_from_api" );
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
note("03 - source tag parsing");
|
||||
{
|
||||
my $expected_source = "https://hentag.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my @split_tags = split(",", "artist:the artist, female:penis, language:english, source:$expected_source");
|
||||
my $received_source = LANraragi::Plugin::Metadata::HentagOnline::get_existing_hentag_source_url( @split_tags );
|
||||
is( $received_source, $expected_source, "got correct url");
|
||||
my @split_tags = split( ",", "artist:the artist, female:penis, language:english, source:$expected_source" );
|
||||
my $received_source = LANraragi::Plugin::Metadata::HentagOnline::get_existing_hentag_source_url(@split_tags);
|
||||
is( $received_source, $expected_source, "got correct url" );
|
||||
}
|
||||
|
||||
note("04 - other source tag parsing");
|
||||
{
|
||||
my $wrong = "https://example.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my @split_tags = split(",", "artist:the artist, female:penis, language:english, source:$wrong");
|
||||
my $received_source = LANraragi::Plugin::Metadata::HentagOnline::get_existing_hentag_source_url( @split_tags );
|
||||
is( $received_source, undef, "don't detect false positive");
|
||||
my $wrong = "https://example.com/vault/QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my @split_tags = split( ",", "artist:the artist, female:penis, language:english, source:$wrong" );
|
||||
my $received_source = LANraragi::Plugin::Metadata::HentagOnline::get_existing_hentag_source_url(@split_tags);
|
||||
is( $received_source, undef, "don't detect false positive" );
|
||||
}
|
||||
|
||||
note("05 - hentag source tag usage");
|
||||
{
|
||||
my $archive_title = "The adventures of irrelevant title";
|
||||
my $expected_id = "QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $mock_json = Mojo::File->new("$SAMPLES/hentag/02_search_response.json")->slurp;
|
||||
my $expected_id = "QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $mock_json = Mojo::File->new("$SAMPLES/hentag/02_search_response.json")->slurp;
|
||||
my $received_id;
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
@ -108,30 +109,31 @@ note("05 - hentag source tag usage");
|
||||
fail("get_json_by_title should not have been called");
|
||||
return;
|
||||
};
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_json_by_vault_id = sub($ua, $vault_id, $logger) {
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_json_by_vault_id = sub ( $ua, $vault_id, $logger ) {
|
||||
$received_id = $vault_id;
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title, existing_tags => "sometag, source:https://hentag.com/vault/$expected_id");
|
||||
my %get_tags_params =
|
||||
( archive_title => $archive_title, existing_tags => "sometag, source:https://hentag.com/vault/$expected_id" );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, 1 );
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "[Doi Sakazaki] Boin Tantei vs Kaitou Sanmensou [ENG]";
|
||||
my $expected_tags =
|
||||
"artist:doi sakazaki, female:big breasts, female:maid, female:paizuri, language:english, source:https://hentag.com/vault/$expected_id";
|
||||
is( $received_id, $expected_id, "sent correct id to get_json_by_vault_id");
|
||||
"artist:doi sakazaki, female:big breasts, female:maid, female:paizuri, language:english, source:https://hentag.com/vault/$expected_id";
|
||||
is( $received_id, $expected_id, "sent correct id to get_json_by_vault_id" );
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
note("06 - source url lookups");
|
||||
{
|
||||
my $expected_id = "QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $expected_id = "QNWPNY5lxYtqOxDN7OgqsyqW0pZDNwf3REXoLyb4iWpkR8n5qrfm3Bw";
|
||||
my $archive_title = "The adventures of irrelevant title";
|
||||
my $url1 = "https://example.com/big-tiddy-anime-waifus";
|
||||
my $url2 = "https://example.com/big-tiddy-anime-waifus-vol2";
|
||||
my $mock_json = Mojo::File->new("$SAMPLES/hentag/02_search_response.json")->slurp;
|
||||
my $url1 = "https://example.com/big-tiddy-anime-waifus";
|
||||
my $url2 = "https://example.com/big-tiddy-anime-waifus-vol2";
|
||||
my $mock_json = Mojo::File->new("$SAMPLES/hentag/02_search_response.json")->slurp;
|
||||
my @received_urls;
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
@ -144,20 +146,20 @@ note("06 - source url lookups");
|
||||
fail("get_json_by_vault_id should not have been called");
|
||||
return;
|
||||
};
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_json_by_urls = sub($ua, $logger, @urls) {
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_json_by_urls = sub ( $ua, $logger, @urls ) {
|
||||
@received_urls = @urls;
|
||||
return $mock_json;
|
||||
};
|
||||
my %get_tags_params = ( archive_title => $archive_title, existing_tags => "sometag, source:$url1, source:$url2");
|
||||
my %get_tags_params = ( archive_title => $archive_title, existing_tags => "sometag, source:$url1, source:$url2" );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, 1 );
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "[Doi Sakazaki] Boin Tantei vs Kaitou Sanmensou [ENG]";
|
||||
my $expected_tags =
|
||||
"artist:doi sakazaki, female:big breasts, female:maid, female:paizuri, language:english, source:https://hentag.com/vault/$expected_id";
|
||||
eq_array( \@received_urls, \($url1, $url2), "sent correct urls to get_json_by_urls");
|
||||
"artist:doi sakazaki, female:big breasts, female:maid, female:paizuri, language:english, source:https://hentag.com/vault/$expected_id";
|
||||
eq_array( \@received_urls, \( $url1, $url2 ), "sent correct urls to get_json_by_urls" );
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
note("07 - no allowed language");
|
||||
@ -174,10 +176,10 @@ note("07 - no allowed language");
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title);
|
||||
my %get_tags_params = ( archive_title => $archive_title );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, 1, "florp, flarp" );
|
||||
ok( exists($response{error}), "got an error");
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, "florp, flarp" );
|
||||
ok( exists( $response{error} ), "got an error" );
|
||||
}
|
||||
|
||||
note("08 - multiple hits in same language");
|
||||
@ -194,15 +196,58 @@ note("08 - multiple hits in same language");
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title);
|
||||
my %get_tags_params = ( archive_title => $archive_title );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params, 1 );
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "First hit";
|
||||
my $expected_tags =
|
||||
"artist:plop, female:bilbul, language:english, source:whatever";
|
||||
my $expected_tags = "artist:plop, female:bilbul, language:english, source:whatever";
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
note("08 - multiple hits, pick the best hit");
|
||||
{
|
||||
my $archive_title = "some series 1";
|
||||
my $received_title;
|
||||
my $mock_json = Mojo::File->new("$SAMPLES/hentag/05_search_response_multiple_similar_titles.json")->slurp;
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_json_by_title = sub {
|
||||
my ( $ua, $our_archive_title ) = @_;
|
||||
$received_title = $our_archive_title;
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "(c20) [auth (circ)] some series 1 [peepee] [poopoo]";
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
}
|
||||
|
||||
note("09 - multiple hits, pick the best hit, by similarity");
|
||||
{
|
||||
my $archive_title = "some seris 1";
|
||||
my $received_title;
|
||||
my $mock_json = Mojo::File->new("$SAMPLES/hentag/05_search_response_multiple_similar_titles.json")->slurp;
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
local *LANraragi::Plugin::Metadata::HentagOnline::get_json_by_title = sub {
|
||||
my ( $ua, $our_archive_title ) = @_;
|
||||
$received_title = $our_archive_title;
|
||||
return $mock_json;
|
||||
};
|
||||
|
||||
my %get_tags_params = ( archive_title => $archive_title );
|
||||
|
||||
my %response = LANraragi::Plugin::Metadata::HentagOnline::get_tags( "", \%get_tags_params );
|
||||
|
||||
my $expected_title = "(c20) [auth (circ)] some series 1 [peepee] [poopoo]";
|
||||
is( $response{title}, $expected_title, "correct title" );
|
||||
is( $response{tags}, $expected_tags, "correct tags" );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
@ -1,6 +1,6 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
@ -9,7 +9,7 @@ use Mojo::File;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
use utf8;
|
||||
|
||||
my $cwd = getcwd();
|
||||
my $SAMPLES = "$cwd/tests/samples";
|
||||
require "$cwd/tests/mocks.pl";
|
||||
|
@ -1,13 +1,13 @@
|
||||
# LANraragi::Plugin::Metadata::Koromo
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
use File::Temp qw(tempfile);
|
||||
use File::Copy "cp";
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use utf8;
|
||||
|
||||
use Test::Trap;
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
@ -1,47 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Data::Dumper;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
use Mojo::File;
|
||||
use utf8;
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
my $cwd = getcwd();
|
||||
my $SAMPLES = "$cwd/tests/samples";
|
||||
require "$cwd/tests/mocks.pl";
|
||||
|
||||
my @tags_list_from_gallery =
|
||||
( 'comic kairakuten 2018-06', 'original work', 'range murata', 'twintails', 'color', 'illustration', 'non-h', 'unlimited' );
|
||||
|
||||
use_ok('LANraragi::Plugin::Metadata::Koushoku');
|
||||
|
||||
note("testing searching URL by title ...");
|
||||
|
||||
{
|
||||
my $html = ( Mojo::File->new("$SAMPLES/koushoku/001_search_response.html")->slurp );
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::Koushoku::get_search_result_dom = sub { return Mojo::DOM->new($html); };
|
||||
local *LANraragi::Plugin::Metadata::Koushoku::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
|
||||
my $url = LANraragi::Plugin::Metadata::Koushoku::search_for_ksk_url("my wonderful manga");
|
||||
is( $url, "https://ksk.moehttps://ksk.moe/view/3077/f8d48ef8c7be", "url check" );
|
||||
}
|
||||
|
||||
note("testing parsing gallery front page ...");
|
||||
|
||||
{
|
||||
my $html = ( Mojo::File->new("$SAMPLES/koushoku/002_gallery_front.html")->slurp );
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::Koushoku::get_dom_from_ksk = sub { return Mojo::DOM->new($html); };
|
||||
local *LANraragi::Plugin::Metadata::Koushoku::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
|
||||
my ( $tags, $title ) = LANraragi::Plugin::Metadata::Koushoku::get_tags_from_ksk("https://url/to/my/page.html");
|
||||
cmp_bag( [ split( ', ', $tags ) ], \@tags_list_from_gallery, "tag check" );
|
||||
is( $title, 'futuregraph #175', "title check" );
|
||||
}
|
||||
|
||||
done_testing();
|
75
tests/LANraragi/Plugin/Metadata/Ksk.t
Normal file
@ -0,0 +1,75 @@
|
||||
# LANraragi::Plugin::Metadata::Ksk
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
use File::Temp qw(tempfile);
|
||||
use File::Copy "cp";
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
|
||||
use Test::Trap;
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
my $cwd = getcwd();
|
||||
my $SAMPLES = "$cwd/tests/samples";
|
||||
require "$cwd/tests/mocks.pl";
|
||||
|
||||
use_ok('LANraragi::Plugin::Metadata::Ksk');
|
||||
|
||||
note("test not assuming language");
|
||||
{
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . "/ksk/fake.yaml", $fh );
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::Ksk::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
local *LANraragi::Plugin::Metadata::Ksk::extract_file_from_archive = sub { $filename };
|
||||
local *LANraragi::Plugin::Metadata::Ksk::is_file_in_archive = sub { 1 };
|
||||
|
||||
my %dummyhash = ( file_path => "test" );
|
||||
|
||||
my %ko_tags = LANraragi::Plugin::Metadata::Ksk::get_tags( "", \%dummyhash, 0 );
|
||||
my $expected_tags =
|
||||
"Harry Potter, Ebony Dark'ness Dementia Raven Way, Draco Malfoy, artist:xXMidnightEssenceXx, artist:bloodytearz666, series:Harry Potter, magazine:My Immortal - Genesis, source:https://www.fanfiction.net/s/6829556/1/My-Immortal";
|
||||
is( $ko_tags{title}, "My Immortal", "Title is overwritten" );
|
||||
is( $ko_tags{tags}, $expected_tags, "Language is missing" );
|
||||
}
|
||||
|
||||
note("test assuming language");
|
||||
{
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . "/ksk/fake.yaml", $fh );
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::Ksk::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
local *LANraragi::Plugin::Metadata::Ksk::extract_file_from_archive = sub { $filename };
|
||||
local *LANraragi::Plugin::Metadata::Ksk::is_file_in_archive = sub { 1 };
|
||||
|
||||
my %dummyhash = ( file_path => "test" );
|
||||
|
||||
my %ko_tags = LANraragi::Plugin::Metadata::Ksk::get_tags( "", \%dummyhash, 1 );
|
||||
my $expected_tags =
|
||||
"Harry Potter, Ebony Dark'ness Dementia Raven Way, Draco Malfoy, artist:xXMidnightEssenceXx, artist:bloodytearz666, series:Harry Potter, magazine:My Immortal - Genesis, language:english, source:https://www.fanfiction.net/s/6829556/1/My-Immortal";
|
||||
is( $ko_tags{title}, "My Immortal", "Title is overwritten" );
|
||||
is( $ko_tags{tags}, $expected_tags, "Language is present" );
|
||||
}
|
||||
|
||||
note("test support for info.yaml");
|
||||
{
|
||||
my ( $fh, $filename ) = tempfile();
|
||||
cp( $SAMPLES . "/ksk/fake.yaml", $fh );
|
||||
|
||||
no warnings 'once', 'redefine';
|
||||
local *LANraragi::Plugin::Metadata::Ksk::get_plugin_logger = sub { return get_logger_mock(); };
|
||||
local *LANraragi::Plugin::Metadata::Ksk::extract_file_from_archive = sub { $filename };
|
||||
local *LANraragi::Plugin::Metadata::Ksk::is_file_in_archive = sub { my $fn = $_[1]; return $fn eq "info.yaml"; };
|
||||
|
||||
my %dummyhash = ( file_path => "test" );
|
||||
|
||||
my %ko_tags = LANraragi::Plugin::Metadata::Ksk::get_tags( "", \%dummyhash, 1 );
|
||||
is( $ko_tags{title}, "My Immortal", "Loads data from info.yaml" );
|
||||
}
|
||||
|
||||
done_testing();
|
@ -1,12 +1,12 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Cwd qw( getcwd );
|
||||
use Mojo::JSON qw(decode_json encode_json);
|
||||
use Mojo::File;
|
||||
use utf8;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Test::More;
|
||||
|
53
tests/LANraragi/Utils/String.t
Normal file
@ -0,0 +1,53 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use Test::More;
|
||||
use Test::Deep;
|
||||
|
||||
BEGIN { use_ok('LANraragi::Utils::String'); }
|
||||
|
||||
note('testing trim...');
|
||||
{
|
||||
my $input = "";
|
||||
my $expected = "";
|
||||
my $result = LANraragi::Utils::String::trim($input);
|
||||
|
||||
is( $result, $expected, "Empty string should result in empty string" );
|
||||
}
|
||||
|
||||
{
|
||||
my $input = "already trimmed";
|
||||
my $expected = "already trimmed";
|
||||
my $result = LANraragi::Utils::String::trim($input);
|
||||
|
||||
is( $result, $expected, "Pre-trimmed should do nothing" );
|
||||
}
|
||||
|
||||
{
|
||||
my $input = " trim everything ";
|
||||
my $expected = "trim everything";
|
||||
my $result = LANraragi::Utils::String::trim($input);
|
||||
|
||||
is( $result, $expected, "Trim should trim" );
|
||||
is( $input, " trim everything ", "Trim doesn't modify the input variable" );
|
||||
}
|
||||
|
||||
note('testing title cleanup...');
|
||||
{
|
||||
my $input = "(C83) [Tetsubou Shounen (Natsushi)] So hold my hand one more time [English] [Yuri-ism Project]";
|
||||
my $expected = "So hold my hand one more time";
|
||||
my $result = LANraragi::Utils::String::clean_title($input);
|
||||
|
||||
is( $result, $expected, "Remove leading/trailing junk" );
|
||||
}
|
||||
|
||||
note('testing string similarity detection...');
|
||||
{
|
||||
is( LANraragi::Utils::String::most_similar( "orange", ( "door hinge", "sporange" ) ), 1, "Simple case" );
|
||||
is( LANraragi::Utils::String::most_similar( "orange", () ), undef, "Empty set" );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
@ -1,6 +1,6 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use Data::Dumper;
|
||||
|
||||
use LANraragi::Utils::Generic qw(flat);
|
||||
|
@ -1,8 +1,8 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Cwd;
|
||||
use utf8;
|
||||
use Cwd;
|
||||
|
||||
use Mojo::Base 'Mojolicious';
|
||||
|
||||
use Test::More tests => 1;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Cwd;
|
||||
use utf8;
|
||||
use Cwd;
|
||||
|
||||
use Data::Dumper;
|
||||
use Test::MockObject;
|
||||
use Mojo::JSON qw (decode_json);
|
||||
@ -87,7 +87,16 @@ sub setup_redis_mock {
|
||||
"title": "Fate GO MEMO",
|
||||
"file": "package.json",
|
||||
"lastreadtime": 0
|
||||
}
|
||||
},
|
||||
"TANK_1589141306": [
|
||||
"Hello",
|
||||
"2810d5e0a8d027ecefebca6237031a0fa7b91eb3",
|
||||
"28697b96f0ac5858be2614ed10ca47742c9522fd"
|
||||
],
|
||||
"TANK_1589138380":[
|
||||
"World",
|
||||
"28697b96f0ac5858be2614ed10ca47742c9522fd"
|
||||
]
|
||||
})
|
||||
};
|
||||
|
||||
@ -113,6 +122,7 @@ sub setup_redis_mock {
|
||||
$redis->mock( 'select', sub { 1 } );
|
||||
$redis->mock( 'flushdb', sub { 1 } );
|
||||
$redis->mock( 'zincrby', sub { 1 } );
|
||||
$redis->mock( 'zrem', sub { 1 } );
|
||||
$redis->mock( 'watch', sub { 1 } );
|
||||
$redis->mock( 'hlen', sub { 1337 } );
|
||||
$redis->mock( 'dbsize', sub { 1337 } );
|
||||
@ -190,6 +200,26 @@ sub setup_redis_mock {
|
||||
}
|
||||
);
|
||||
|
||||
# $redis->mock(
|
||||
# 'zscore', # $redis->zscore => array position in list named by key in datamodel
|
||||
# sub {
|
||||
# my $self = shift;
|
||||
# my ($key, $value) = @_;
|
||||
|
||||
# if ( !exists $datamodel{$key} ) {
|
||||
# $datamodel{$key} = [];
|
||||
# }
|
||||
|
||||
# for my $i ( 0 .. $#results ) {
|
||||
# if ($element == $value) {
|
||||
# return
|
||||
# }
|
||||
# }
|
||||
|
||||
# return scalar @{ $datamodel{$key} };
|
||||
# }
|
||||
# );
|
||||
|
||||
$redis->mock(
|
||||
'zrangebylex', # $redis->zrangebylex => get all values of key in datamodel
|
||||
sub {
|
||||
@ -205,6 +235,21 @@ sub setup_redis_mock {
|
||||
}
|
||||
);
|
||||
|
||||
$redis->mock(
|
||||
'zrangebyscore', # $redis->zrangebyscore => get all values of key in datamodel
|
||||
sub {
|
||||
my $self = shift;
|
||||
my ( $key, $start, $end ) = @_;
|
||||
|
||||
if ( !exists $datamodel{$key} ) {
|
||||
$datamodel{$key} = [];
|
||||
}
|
||||
|
||||
# Return array, ordered alphabetically
|
||||
return @{ $datamodel{$key} };
|
||||
}
|
||||
);
|
||||
|
||||
$redis->mock(
|
||||
'zscan', # $redis->zscan => get all values of key in datamodel
|
||||
sub {
|
||||
|
@ -11,39 +11,38 @@ require $cwd . "/tests/mocks.pl";
|
||||
setup_redis_mock();
|
||||
|
||||
my @modules = (
|
||||
"Shinobu", "LANraragi",
|
||||
"LANraragi::Utils::Archive", "LANraragi::Utils::Database",
|
||||
"LANraragi::Utils::Generic", "LANraragi::Utils::Plugins",
|
||||
"LANraragi::Utils::Routing", "LANraragi::Utils::TempFolder",
|
||||
"LANraragi::Utils::Logging", "LANraragi::Utils::Minion",
|
||||
"LANraragi::Utils::Tags", "LANraragi::Controller::Api::Archive",
|
||||
"LANraragi::Controller::Api::Search", "LANraragi::Controller::Api::Category",
|
||||
"LANraragi::Controller::Api::Database", "LANraragi::Controller::Api::Shinobu",
|
||||
"LANraragi::Controller::Api::Minion", "LANraragi::Controller::Api::Other",
|
||||
"LANraragi::Controller::Backup", "LANraragi::Controller::Batch",
|
||||
"LANraragi::Controller::Config", "LANraragi::Controller::Edit",
|
||||
"LANraragi::Controller::Index", "LANraragi::Controller::Logging",
|
||||
"LANraragi::Controller::Login", "LANraragi::Controller::Plugins",
|
||||
"LANraragi::Controller::Reader", "LANraragi::Controller::Stats",
|
||||
"LANraragi::Controller::Upload", "LANraragi::Controller::Category",
|
||||
"LANraragi::Model::Archive", "LANraragi::Model::Backup",
|
||||
"LANraragi::Model::Config", "LANraragi::Model::Plugins",
|
||||
"LANraragi::Model::Reader", "LANraragi::Model::Search",
|
||||
"LANraragi::Model::Stats", "LANraragi::Model::Category",
|
||||
"LANraragi::Model::Upload", "LANraragi::Model::Opds",
|
||||
"LANraragi::Plugin::Metadata::Chaika", "LANraragi::Plugin::Metadata::CopyTags",
|
||||
"LANraragi::Plugin::Metadata::DateAdded", "LANraragi::Plugin::Metadata::EHentai",
|
||||
"LANraragi::Plugin::Metadata::Eze", "LANraragi::Plugin::Metadata::Hdoujin",
|
||||
"LANraragi::Plugin::Metadata::Koromo", "LANraragi::Plugin::Metadata::MEMS",
|
||||
"LANraragi::Plugin::Metadata::nHentai", "LANraragi::Plugin::Metadata::RegexParse",
|
||||
"LANraragi::Plugin::Metadata::Fakku", "LANraragi::Plugin::Metadata::Koushoku",
|
||||
"LANraragi::Plugin::Login::EHentai", "LANraragi::Plugin::Login::Fakku",
|
||||
"LANraragi::Plugin::Scripts::SourceFinder", "LANraragi::Plugin::Scripts::FolderToCat",
|
||||
"LANraragi::Plugin::Download::EHentai", "LANraragi::Plugin::Download::Chaika",
|
||||
"LANraragi::Plugin::Download::Koushoku", "LANraragi::Plugin::Scripts::nHentaiSourceConverter",
|
||||
"LANraragi::Plugin::Scripts::BlacklistMigrate", "LANraragi::Plugin::Metadata::Hitomi",
|
||||
"LANraragi::Plugin::Metadata::Hentag", "LANraragi::Plugin::Metadata::HentagOnline",
|
||||
"LANraragi::Plugin::Metadata::ComicInfo", "LANraragi::Plugin::Metadata::ChaikaFile"
|
||||
"Shinobu", "LANraragi",
|
||||
"LANraragi::Utils::Archive", "LANraragi::Utils::Database",
|
||||
"LANraragi::Utils::Generic", "LANraragi::Utils::Plugins",
|
||||
"LANraragi::Utils::Routing", "LANraragi::Utils::TempFolder",
|
||||
"LANraragi::Utils::Logging", "LANraragi::Utils::Minion",
|
||||
"LANraragi::Utils::Tags", "LANraragi::Controller::Api::Archive",
|
||||
"LANraragi::Controller::Api::Search", "LANraragi::Controller::Api::Category",
|
||||
"LANraragi::Controller::Api::Database", "LANraragi::Controller::Api::Shinobu",
|
||||
"LANraragi::Controller::Api::Minion", "LANraragi::Controller::Api::Other",
|
||||
"LANraragi::Controller::Backup", "LANraragi::Controller::Batch",
|
||||
"LANraragi::Controller::Config", "LANraragi::Controller::Edit",
|
||||
"LANraragi::Controller::Index", "LANraragi::Controller::Logging",
|
||||
"LANraragi::Controller::Login", "LANraragi::Controller::Plugins",
|
||||
"LANraragi::Controller::Reader", "LANraragi::Controller::Stats",
|
||||
"LANraragi::Controller::Upload", "LANraragi::Controller::Category",
|
||||
"LANraragi::Model::Archive", "LANraragi::Model::Backup",
|
||||
"LANraragi::Model::Config", "LANraragi::Model::Plugins",
|
||||
"LANraragi::Model::Reader", "LANraragi::Model::Search",
|
||||
"LANraragi::Model::Stats", "LANraragi::Model::Category",
|
||||
"LANraragi::Model::Upload", "LANraragi::Model::Opds",
|
||||
"LANraragi::Plugin::Metadata::Chaika", "LANraragi::Plugin::Metadata::CopyTags",
|
||||
"LANraragi::Plugin::Metadata::DateAdded", "LANraragi::Plugin::Metadata::EHentai",
|
||||
"LANraragi::Plugin::Metadata::Eze", "LANraragi::Plugin::Metadata::Hdoujin",
|
||||
"LANraragi::Plugin::Metadata::Koromo", "LANraragi::Plugin::Metadata::MEMS",
|
||||
"LANraragi::Plugin::Metadata::nHentai", "LANraragi::Plugin::Metadata::RegexParse",
|
||||
"LANraragi::Plugin::Metadata::Fakku", "LANraragi::Plugin::Login::EHentai",
|
||||
"LANraragi::Plugin::Login::Fakku", "LANraragi::Plugin::Scripts::SourceFinder",
|
||||
"LANraragi::Plugin::Scripts::FolderToCat", "LANraragi::Plugin::Download::EHentai",
|
||||
"LANraragi::Plugin::Download::Chaika", "LANraragi::Plugin::Scripts::nHentaiSourceConverter",
|
||||
"LANraragi::Plugin::Metadata::Hitomi", "LANraragi::Plugin::Metadata::Hentag",
|
||||
"LANraragi::Plugin::Metadata::HentagOnline", "LANraragi::Plugin::Metadata::ComicInfo",
|
||||
"LANraragi::Plugin::Metadata::ChaikaFile", "LANraragi::Plugin::Metadata::Ksk",
|
||||
);
|
||||
|
||||
# Test all modules load properly
|
||||
|
@ -19,7 +19,6 @@ use LANraragi::Plugin::Metadata::Chaika;
|
||||
use LANraragi::Plugin::Metadata::Eze;
|
||||
use LANraragi::Plugin::Metadata::Fakku;
|
||||
use LANraragi::Plugin::Metadata::Hitomi;
|
||||
use LANraragi::Plugin::Metadata::Koushoku;
|
||||
|
||||
# Mock Redis
|
||||
my $cwd = getcwd;
|
||||
@ -95,27 +94,10 @@ note("FAKKU Tests : Disabled due to cloudflare being used");
|
||||
# is( $f_result_title, $f_title, 'FAKKU title parsing test' );
|
||||
# }
|
||||
|
||||
note("Koushoku Tests : Disabled due to cloudflare being used");
|
||||
|
||||
# {
|
||||
# my $ua = Mojo::UserAgent->new;
|
||||
# my $k_title = "futuregraph #175";
|
||||
# my $k_url = "https://ksk.moe/view/3077/f8d48ef8c7be";
|
||||
# my $k_tags =
|
||||
# "artist:range murata, parody:original work, magazine:comic kairakuten 2018-06, color, illustration, non-h, twintails, unlimited";
|
||||
|
||||
# is( LANraragi::Plugin::Metadata::Koushoku::search_for_ksk_url( "title:futuregraph magazine:comic kairakuten 2018-06", $ua ),
|
||||
# $k_url, 'Koushoku search test' );
|
||||
|
||||
# my ( $k_result_tags, $k_result_title ) = LANraragi::Plugin::Metadata::Koushoku::get_tags_from_ksk( $k_url, $ua );
|
||||
# is( $k_result_tags, $k_tags, 'Koushoku tags parsing test' );
|
||||
# is( $k_result_title, $k_title, 'Koushoku title parsing test' );
|
||||
#}
|
||||
|
||||
note("Hitomi Tests");
|
||||
|
||||
{
|
||||
my $hi_gID = "2261881";
|
||||
my $hi_gID = "2261881";
|
||||
my %hi_hashdata = trap { LANraragi::Plugin::Metadata::Hitomi::get_tags_from_Hitomi( $hi_gID, 1 ); };
|
||||
|
||||
ok( length $hi_hashdata{tags} > 0, 'Hitomi API Tag retrieval test' );
|
||||
|
@ -0,0 +1,47 @@
|
||||
[
|
||||
{
|
||||
"title": "(c20) [auth (circ)] some series 3 [peepee] [poopoo]",
|
||||
"artists": [
|
||||
"plop"
|
||||
],
|
||||
"femaleTags": [
|
||||
"bilbul"
|
||||
],
|
||||
"language": "english",
|
||||
"category": "manga",
|
||||
"createdAt": 1255081730000,
|
||||
"locations": [
|
||||
"whatever"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "(c20) [auth (circ)] some series 1 [peepee] [poopoo]",
|
||||
"artists": [
|
||||
"the artist"
|
||||
],
|
||||
"femaleTags": [
|
||||
"penis"
|
||||
],
|
||||
"language": "english",
|
||||
"category": "manga",
|
||||
"createdAt": 1255081730000,
|
||||
"locations": [
|
||||
"whatever 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "(c20) [auth (circ)] some series 2 [peepee] [poopoo]",
|
||||
"artists": [
|
||||
"the artist"
|
||||
],
|
||||
"femaleTags": [
|
||||
"penis"
|
||||
],
|
||||
"language": "english",
|
||||
"category": "manga",
|
||||
"createdAt": 1255081730000,
|
||||
"locations": [
|
||||
"whatever 2"
|
||||
]
|
||||
}
|
||||
]
|
@ -1,335 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US"><head data-x="0">
|
||||
<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"><title>title:futuregraph magazine:"comic kairakuten 2018-06" :: Koushoku</title><meta name="theme-color" content="#202020"><meta name="application-name" content="Koushoku"><meta property="og:site_name" content="Koushoku"><meta itemprop="name" content="title:futuregraph magazine:"comic kairakuten 2018-06" :: Koushoku"><meta property="og:title" content="title:futuregraph magazine:"comic kairakuten 2018-06" :: Koushoku"><meta property="twitter:title" content="title:futuregraph magazine:"comic kairakuten 2018-06" :: Koushoku"><meta property="og:url" content="https://ksk.moe/browse"><meta property="og:type" content="website"><meta property="og:locale" content="en-US"><meta name="twitter:card" content="summary"><meta itemprop="image" content="https://ksk.moe/images/cover.jpg"><meta property="og:image" content="https://ksk.moe/images/cover.jpg"><meta name="twitter:image" content="https://ksk.moe/images/cover.jpg"><meta property="og:image:alt" content="title:futuregraph magazine:"comic kairakuten 2018-06" :: Koushoku"><meta property="twitter:image:alt" content="title:futuregraph magazine:"comic kairakuten 2018-06" :: Koushoku"><meta name="twitter:dnt" content="on"><meta http-equiv="x-dns-prefetch-control" content="off"><link rel="icon" href="https://ksk.moe/favicon.ico"><link rel="icon" sizes="16x16" href="https://ksk.moe/favicon-16x16.png"><link rel="icon" sizes="32x32" href="https://ksk.moe/favicon-32x32.png"><link rel="icon" sizes="192x192" href="https://ksk.moe/android-chrome-192x192.png"><link rel="icon" sizes="512x512" href="https://ksk.moe/android-chrome-512x512.png"><link rel="apple-touch-icon" sizes="180x180" href="https://ksk.moe/apple-touch-icon.png"><link rel="canonical" href="https://ksk.moe/browse"><link rel="alternate" href="https://ksk.moe/rss/v2" type="application/rss+xml" title="Galleries RSS"><style>
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/inter-v12-latin-regular.eot");
|
||||
src: local(""), url("/fonts/inter-v12-latin-regular.eot?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/inter-v12-latin-regular.woff2") format("woff2"),
|
||||
url("/fonts/inter-v12-latin-regular.woff") format("woff"),
|
||||
url("/fonts/inter-v12-latin-regular.ttf") format("truetype"),
|
||||
url("/fonts/inter-v12-latin-regular.svg#Inter") format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/inter-v12-latin-500.eot");
|
||||
src: local(""), url("/fonts/inter-v12-latin-500.eot?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/inter-v12-latin-500.woff2") format("woff2"),
|
||||
url("/fonts/inter-v12-latin-500.woff") format("woff"),
|
||||
url("/fonts/inter-v12-latin-500.ttf") format("truetype"),
|
||||
url("/fonts/inter-v12-latin-500.svg#Inter") format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/inter-v12-latin-600.eot");
|
||||
src: local(""), url("/fonts/inter-v12-latin-600.eot?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/inter-v12-latin-600.woff2") format("woff2"),
|
||||
url("/fonts/inter-v12-latin-600.woff") format("woff"),
|
||||
url("/fonts/inter-v12-latin-600.ttf") format("truetype"),
|
||||
url("/fonts/inter-v12-latin-600.svg#Inter") format("svg");
|
||||
}
|
||||
body {
|
||||
font-family: "Inter", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
||||
}</style><script defer="defer" src="001_search_response_files/main.f3e75acf3452b8eeface.js" type="text/javascript"></script><link href="001_search_response_files/main.046e9f3b9c5c1248b508.css" rel="stylesheet"></head><body id="browse">
|
||||
<h1 hidden="">Koushoku</h1><header class="hs hst hsb">
|
||||
<nav>
|
||||
<button type="button" aria-label="Menu" title="Menu" data-ta="#hm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="mobile"><li>
|
||||
<a href="https://ksk.moe/">Home</a>
|
||||
</li>
|
||||
<li data-dropdown="">
|
||||
<button type="button" data-dropdown-toggle="">
|
||||
<span>Browse</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/browse">Browse</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/popular/weekly">Popular</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/artists">Artists</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/magazines">Magazines</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/tags">Tags</a>
|
||||
</li>
|
||||
<li>
|
||||
<form action="/random"><button type="submit">Random</button></form>
|
||||
</li>
|
||||
</ul>
|
||||
</li><li data-dropdown="">
|
||||
<button type="button" data-dropdown-toggle="">
|
||||
<span>Wiki</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/wiki/roles">User Roles</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/wiki/uploading">Uploading</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/upload">Upload</a>
|
||||
</li>
|
||||
</ul><form action="/browse">
|
||||
<input type="text" name="s" placeholder="tag:mating_press creampie -yaoi" autocomplete="off" value="title:futuregraph magazine:"comic kairakuten 2018-06"" required="">
|
||||
<button type="submit" aria-label="Search" title="Search">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</form><ul>
|
||||
<li>
|
||||
<button type="button" aria-label="Advanced Search" title="Advanced Search" data-ta="#advs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||
</svg>
|
||||
</button>
|
||||
</li><li class="mobile" data-dropdown="">
|
||||
<button data-type="button" aria-label="User Authentication" title="User Authentication" data-dropdown-toggle="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/login">Login</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/register">Register</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li></ul>
|
||||
</nav>
|
||||
</header>
|
||||
<div></div>
|
||||
<nav id="hm" class="hidden invisible" aria-label="Mobile" data-ti="0" data-g="s,body>header" data-h="">
|
||||
<div data-ta="#hm"></div>
|
||||
<div>
|
||||
<ul><li>
|
||||
<a href="https://ksk.moe/">Home</a>
|
||||
</li>
|
||||
<li data-dropdown="">
|
||||
<button type="button" data-dropdown-toggle="">
|
||||
<span>Browse</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/browse">Browse</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/popular/weekly">Popular</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/artists">Artists</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/magazines">Magazines</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/tags">Tags</a>
|
||||
</li>
|
||||
<li>
|
||||
<form action="/random"><button type="submit">Random</button></form>
|
||||
</li>
|
||||
</ul>
|
||||
</li><li>
|
||||
<a href="https://ksk.moe/login">Login</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/register">Register</a>
|
||||
</li></ul>
|
||||
</div>
|
||||
</nav><div class="bwrp btop">
|
||||
<div class="b"><script id="__clb-1978794" type="text/javascript" src="001_search_response_files/noop_002.js" data-cfasync="false" async=""></script></div>
|
||||
</div><main>
|
||||
<section class="feed galleries" id="galleries">
|
||||
<header>
|
||||
<h2>Search Results</h2><i>Found 1 result</i><form id="filter"><input type="hidden" name="s" value="title:futuregraph magazine:"comic kairakuten 2018-06""><div>
|
||||
<label for="sort">Sort</label>
|
||||
<div>
|
||||
<select id="sort" name="sort"><option value="4" selected="selected">Uploaded Date</option>
|
||||
<option value="16">Published Date</option>
|
||||
<option value="1">Title</option>
|
||||
<option value="2">Pages</option>
|
||||
<option value="32">Popularity</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="order">Order</label>
|
||||
<div>
|
||||
<select id="order" name="order">
|
||||
<option value="2" selected="selected">Descending</option>
|
||||
<option value="1">Ascending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form></header>
|
||||
<main><article><a href="https://ksk.moe/view/3077/f8d48ef8c7be" title="futuregraph #175" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #175" alt="Cover of futuregraph #175" src="001_search_response_files/01.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #175</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Twintails</span></footer></a></article></main>
|
||||
<footer></footer>
|
||||
</section>
|
||||
</main><div class="bwrp bbottom">
|
||||
<div class="b"><script id="__clb-1978793" type="text/javascript" src="001_search_response_files/noop.js" data-cfasync="false" async=""></script></div>
|
||||
</div><footer>
|
||||
<a href="mailto:B06hAfGx95OA2h@protonmail.com" title="B06hAfGx95OA2h@protonmail.com">[contact]</a>
|
||||
<a href="https://sukebe.moe/">[sukebe.moe]</a>
|
||||
</footer><div id="advs" class="popup hidden invisible" data-ti="0" data-g="s,body>header">
|
||||
<div data-ta="#advs"></div>
|
||||
<div id="advf">
|
||||
<form action="/browse">
|
||||
<header>
|
||||
<h2>Advanced Search</h2>
|
||||
<button type="button" data-ta="#advs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<div>
|
||||
<input type="text" name="s" placeholder="tag:mating_press creampie -yaoi" value="title:futuregraph magazine:"comic kairakuten 2018-06"">
|
||||
</div>
|
||||
<div class="categories">
|
||||
<strong>Categories</strong>
|
||||
<input type="hidden" name="cat" data-default="7">
|
||||
<div><button class="active" data-value="1" type="button">Manga</button><button class="active" data-value="2" type="button">Doujinshi</button><button class="active" data-value="4" type="button">Illustration</button></div>
|
||||
</div>
|
||||
</section>
|
||||
<div>
|
||||
<strong>Title</strong>
|
||||
<div>
|
||||
<div>
|
||||
<input type="text" name="title" placeholder="Search by title">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Tag</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="t" placeholder="mating press, creampie, -yaoi">
|
||||
<select name="tc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Artist</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="a" placeholder="Search by artist">
|
||||
<select name="ac">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Circle</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="c" placeholder="Search by circle">
|
||||
<select name="gc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Parody</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="p" placeholder="Search by parody">
|
||||
<select name="pc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Magazine</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="m" placeholder="Search by magazine">
|
||||
<select name="mc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Pages</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="number" name="ps" placeholder="Minimum" min="1">
|
||||
<input type="number" name="pe" placeholder="Maximum" min="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<button type="submit" name="adv" aria-label="Apply" title="Apply" value="1">Apply</button>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script async="" src="001_search_response_files/google-analytics_analytics.js"></script><script type="text/javascript" src="001_search_response_files/noop_003.js"></script></body></html>
|
@ -1,626 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US"><head data-x="0">
|
||||
<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"><title>Range Murata - futuregraph #175 :: Koushoku</title><meta name="theme-color" content="#202020"><meta name="application-name" content="Koushoku"><meta property="og:site_name" content="Koushoku"><meta itemprop="name" content="Range Murata - futuregraph #175 :: Koushoku"><meta property="og:title" content="Range Murata - futuregraph #175 :: Koushoku"><meta property="twitter:title" content="Range Murata - futuregraph #175 :: Koushoku"><meta property="og:url" content="https://ksk.moe/view/3077/f8d48ef8c7be"><meta property="og:type" content="article"><meta property="og:locale" content="en-US"><meta name="description" content="Read or download futuregraph #175 by Range Murata for free on Koushoku."><meta itemprop="description" content="Read or download futuregraph #175 by Range Murata for free on Koushoku."><meta property="og:description" content="Read or download futuregraph #175 by Range Murata for free on Koushoku."><meta name="twitter:description" content="Read or download futuregraph #175 by Range Murata for free on Koushoku."><meta name="twitter:card" content="summary"><meta itemprop="image" content="https://ksk-h7glm2.xyz/t/3077/f8d48ef8c7be/896/01.png"><meta property="og:image" content="https://ksk-h7glm2.xyz/t/3077/f8d48ef8c7be/896/01.png"><meta name="twitter:image" content="https://ksk-h7glm2.xyz/t/3077/f8d48ef8c7be/896/01.png"><meta property="og:image:alt" content="Range Murata - futuregraph #175 :: Koushoku"><meta property="twitter:image:alt" content="Range Murata - futuregraph #175 :: Koushoku"><meta name="twitter:dnt" content="on"><meta http-equiv="x-dns-prefetch-control" content="off"><link rel="icon" href="https://ksk.moe/favicon.ico"><link rel="icon" sizes="16x16" href="https://ksk.moe/favicon-16x16.png"><link rel="icon" sizes="32x32" href="https://ksk.moe/favicon-32x32.png"><link rel="icon" sizes="192x192" href="https://ksk.moe/android-chrome-192x192.png"><link rel="icon" sizes="512x512" href="https://ksk.moe/android-chrome-512x512.png"><link rel="apple-touch-icon" sizes="180x180" href="https://ksk.moe/apple-touch-icon.png"><link rel="canonical" href="https://ksk.moe/view/3077/f8d48ef8c7be"><link rel="alternate" href="https://ksk.moe/rss/v2" type="application/rss+xml" title="Galleries RSS"><style>
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/inter-v12-latin-regular.eot");
|
||||
src: local(""), url("/fonts/inter-v12-latin-regular.eot?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/inter-v12-latin-regular.woff2") format("woff2"),
|
||||
url("/fonts/inter-v12-latin-regular.woff") format("woff"),
|
||||
url("/fonts/inter-v12-latin-regular.ttf") format("truetype"),
|
||||
url("/fonts/inter-v12-latin-regular.svg#Inter") format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/inter-v12-latin-500.eot");
|
||||
src: local(""), url("/fonts/inter-v12-latin-500.eot?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/inter-v12-latin-500.woff2") format("woff2"),
|
||||
url("/fonts/inter-v12-latin-500.woff") format("woff"),
|
||||
url("/fonts/inter-v12-latin-500.ttf") format("truetype"),
|
||||
url("/fonts/inter-v12-latin-500.svg#Inter") format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/inter-v12-latin-600.eot");
|
||||
src: local(""), url("/fonts/inter-v12-latin-600.eot?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/inter-v12-latin-600.woff2") format("woff2"),
|
||||
url("/fonts/inter-v12-latin-600.woff") format("woff"),
|
||||
url("/fonts/inter-v12-latin-600.ttf") format("truetype"),
|
||||
url("/fonts/inter-v12-latin-600.svg#Inter") format("svg");
|
||||
}
|
||||
body {
|
||||
font-family: "Inter", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
||||
}</style><script defer="defer" src="002_gallery_front_files/main.f3e75acf3452b8eeface.js" type="text/javascript"></script><link href="002_gallery_front_files/main.046e9f3b9c5c1248b508.css" rel="stylesheet"></head><body id="gallery"><header class="hs hst hsb">
|
||||
<nav>
|
||||
<button type="button" aria-label="Menu" title="Menu" data-ta="#hm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="mobile"><li>
|
||||
<a href="https://ksk.moe/">Home</a>
|
||||
</li>
|
||||
<li data-dropdown="">
|
||||
<button type="button" data-dropdown-toggle="">
|
||||
<span>Browse</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/browse">Browse</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/popular/weekly">Popular</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/artists">Artists</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/magazines">Magazines</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/tags">Tags</a>
|
||||
</li>
|
||||
<li>
|
||||
<form action="/random"><button type="submit">Random</button></form>
|
||||
</li>
|
||||
</ul>
|
||||
</li><li data-dropdown="">
|
||||
<button type="button" data-dropdown-toggle="">
|
||||
<span>Wiki</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/wiki/roles">User Roles</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/wiki/uploading">Uploading</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/upload">Upload</a>
|
||||
</li>
|
||||
</ul><form action="/browse">
|
||||
<input type="text" name="s" placeholder="tag:mating_press creampie -yaoi" autocomplete="off" required="">
|
||||
<button type="submit" aria-label="Search" title="Search">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</form><ul>
|
||||
<li>
|
||||
<button type="button" aria-label="Advanced Search" title="Advanced Search" data-ta="#advs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||
</svg>
|
||||
</button>
|
||||
</li><li class="mobile" data-dropdown="">
|
||||
<button data-type="button" aria-label="User Authentication" title="User Authentication" data-dropdown-toggle="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/login">Login</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/register">Register</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li></ul>
|
||||
</nav>
|
||||
</header>
|
||||
<div></div>
|
||||
<nav id="hm" class="hidden invisible" aria-label="Mobile" data-ti="0" data-g="s,body>header" data-h="">
|
||||
<div data-ta="#hm"></div>
|
||||
<div>
|
||||
<ul><li>
|
||||
<a href="https://ksk.moe/">Home</a>
|
||||
</li>
|
||||
<li data-dropdown="">
|
||||
<button type="button" data-dropdown-toggle="">
|
||||
<span>Browse</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://ksk.moe/browse">Browse</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/popular/weekly">Popular</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/artists">Artists</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/magazines">Magazines</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/tags">Tags</a>
|
||||
</li>
|
||||
<li>
|
||||
<form action="/random"><button type="submit">Random</button></form>
|
||||
</li>
|
||||
</ul>
|
||||
</li><li>
|
||||
<a href="https://ksk.moe/login">Login</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ksk.moe/register">Register</a>
|
||||
</li></ul>
|
||||
</div>
|
||||
</nav><div class="bwrp btop">
|
||||
<div class="b"><script id="__clb-1978794" type="text/javascript" src="002_gallery_front_files/noop_002.js" data-cfasync="false" async=""></script></div>
|
||||
</div><main>
|
||||
<article>
|
||||
<div>
|
||||
<section id="cover"><a href="https://ksk.moe/read/3077/f8d48ef8c7be/1" rel="nofollow">
|
||||
<figure>
|
||||
<img title="Cover for futuregraph #175" alt="Cover for futuregraph #175" src="002_gallery_front_files/01_010.jpg" loading="lazy" width="896" height="1265">
|
||||
</figure>
|
||||
</a>
|
||||
</section>
|
||||
<div>
|
||||
<div><section id="metadata">
|
||||
<header>
|
||||
<h1>futuregraph #175</h1><h2>[Range Murata] futuregraph #175 (Comic Kairakuten 2018-06)</h2></header>
|
||||
<main>
|
||||
<div>
|
||||
<strong>Category</strong>
|
||||
<div class="l">
|
||||
<a href="https://ksk.moe/browse?cat=4">
|
||||
<span>Illustration</span>
|
||||
</a>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Artist</strong>
|
||||
<div class="l"><a href="https://ksk.moe/artists/Range%20Murata" rel="tag">
|
||||
<span>Range Murata</span><span>99</span>
|
||||
</a></div>
|
||||
</div><div>
|
||||
<strong>Parody</strong>
|
||||
<div class="l"><a href="https://ksk.moe/parodies/Original%20Work" rel="tag">
|
||||
<span>Original Work</span><span>11k</span>
|
||||
</a></div>
|
||||
</div><div>
|
||||
<strong>Magazine</strong>
|
||||
<div class="l"><a href="https://ksk.moe/magazines/Comic%20Kairakuten%202018-06" rel="tag">
|
||||
<span>Kairakuten 2018-06</span><span>26</span>
|
||||
</a></div>
|
||||
</div><div>
|
||||
<strong>Tag</strong>
|
||||
<div class="l"><a href="https://ksk.moe/tags/Color" rel="tag">
|
||||
<span>Color</span><span>1.7k</span>
|
||||
</a><a href="https://ksk.moe/tags/Illustration" rel="tag">
|
||||
<span>Illustration</span><span>863</span>
|
||||
</a><a href="https://ksk.moe/tags/Non-H" rel="tag">
|
||||
<span>Non-H</span><span>399</span>
|
||||
</a><a href="https://ksk.moe/tags/Twintails" rel="tag">
|
||||
<span>Twintails</span><span>1.5k</span>
|
||||
</a><a href="https://ksk.moe/tags/Unlimited" rel="tag">
|
||||
<span>Unlimited</span><span>9.7k</span>
|
||||
</a></div>
|
||||
</div><div>
|
||||
<strong>Length</strong>
|
||||
<div class="l">
|
||||
<a href="https://ksk.moe/browse?ps=1&adv=1">
|
||||
<span>1 Pages</span>
|
||||
</a>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Metadata</strong>
|
||||
<div class="l">
|
||||
<a href="https://ksk.moe/out/aHR0cHM6Ly93d3cuZmFra3UubmV0L2hlbnRhaS9mdXR1cmVncmFwaC0xNzUtZW5nbGlzaA==" rel="nofollow noopener" target="_blank"><span>FAKKU</span></a>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Statistic</strong>
|
||||
<div><span>32 Views</span></div>
|
||||
</div><div>
|
||||
<strong>Size (Ori.)</strong>
|
||||
<div>
|
||||
<span>1.9 MiB</span>
|
||||
<span>(1,954,207 bytes)</span>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Size (Res.)</strong>
|
||||
<div>
|
||||
<span>298 KiB</span>
|
||||
<span>(305,017 bytes)</span>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Uploaded</strong>
|
||||
<div>
|
||||
<time data-timestamp="1527582958">29.05.2018 08:35 UTC</time>
|
||||
<span>(5 years ago)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Archived</strong>
|
||||
<div>
|
||||
<time class="created createdAt" data-timestamp="1683160887">04.05.2023 00:41 UTC</time>
|
||||
<span>(14 days ago)</span>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Published</strong>
|
||||
<div>
|
||||
<time class="published publishedAt" data-timestamp="1683327314">05.05.2023 22:55 UTC</time>
|
||||
<span>(12 days ago)</span>
|
||||
</div>
|
||||
</div><div>
|
||||
<strong>Updated</strong>
|
||||
<div>
|
||||
<time class="modified modifiedAt updated updatedAt modTime" data-timestamp="1683192979">04.05.2023 09:36 UTC</time>
|
||||
<span>(14 days ago)</span>
|
||||
</div>
|
||||
</div></main>
|
||||
</section><section id="actions">
|
||||
<a class="read" href="https://ksk.moe/read/3077/f8d48ef8c7be" rel="nofollow">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
||||
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
||||
</svg>
|
||||
<span>Read</span>
|
||||
</a>
|
||||
<form method="post" action="/favorite/3077/f8d48ef8c7be">
|
||||
<button class="favorite" type="submit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
|
||||
</svg>
|
||||
<span>Favorite</span>
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="/download/3077/f8d48ef8c7be">
|
||||
<button class="original" type="submit" name="hash" value="0312e96f7785a9d45f6ad9a525dc1f0887e23e5022f3a0e1dc8705905dc672c7">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<span>Original</span>
|
||||
</button>
|
||||
</form><form method="post" action="/download/3077/f8d48ef8c7be">
|
||||
<button class="resampled" type="submit" name="hash" value="4bf19c6d7c2f6a28708f899e034d4d3990c5552b47fc6030f984bbc15f1088c6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<span>Resampled</span>
|
||||
</button>
|
||||
</form></section></div>
|
||||
</div>
|
||||
</div><section class="feed galleries" id="relateds">
|
||||
<header>
|
||||
<h3>More Like This</h3>
|
||||
</header>
|
||||
<main class="swiper swiper-initialized swiper-horizontal">
|
||||
<div class="swiper-wrapper" style="transition-duration: 0ms; transform: translate3d(-542.857px, 0px, 0px);"><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="0">
|
||||
<a href="https://ksk.moe/view/1888/6198e77a3c49" title="futuregraph #161" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #161" alt="Cover of futuregraph #161" src="002_gallery_front_files/01_002.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #161</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Slice of Life</span>, <span>Stockings</span></footer></a>
|
||||
</article><article class="swiper-slide swiper-slide-prev" style="width: 271.429px;" data-swiper-slide-index="1">
|
||||
<a href="https://ksk.moe/view/8667/a779c63583b1" title="futuregraph #222" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #222" alt="Cover of futuregraph #222" src="002_gallery_front_files/01_011.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #222</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Schoolgirl Outfit</span>, <span>Short Hair</span></footer></a>
|
||||
</article><article class="swiper-slide swiper-slide-active" style="width: 271.429px;" data-swiper-slide-index="2">
|
||||
<a href="https://ksk.moe/view/7579/319d27c93428" title="futuregraph #214" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #214" alt="Cover of futuregraph #214" src="002_gallery_front_files/01_009.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #214</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span>, <span>Schoolgirl Outfit</span></footer></a>
|
||||
</article><article class="swiper-slide swiper-slide-next" style="width: 271.429px;" data-swiper-slide-index="3">
|
||||
<a href="https://ksk.moe/view/2172/31aaf44befd6" title="futuregraph #166" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #166" alt="Cover of futuregraph #166" src="002_gallery_front_files/01_015.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #166</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Slice of Life</span>, <span>Swimsuit</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="4">
|
||||
<a href="https://ksk.moe/view/4861/4d54a2f48192" title="futuregraph #184" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #184" alt="Cover of futuregraph #184" src="002_gallery_front_files/01_006.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #184</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="5">
|
||||
<a href="https://ksk.moe/view/4149/34000d840152" title="futuregraph #183" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #183" alt="Cover of futuregraph #183" src="002_gallery_front_files/01_014.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #183</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span>, <span>Ponytail</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="6">
|
||||
<a href="https://ksk.moe/view/4873/8087ee3a76be" title="futuregraph #187" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #187" alt="Cover of futuregraph #187" src="002_gallery_front_files/01_008.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #187</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Schoolgirl Outfit</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="7">
|
||||
<a href="https://ksk.moe/view/5611/1782e87ffb77" title="futuregraph #195" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #195" alt="Cover of futuregraph #195" src="002_gallery_front_files/01_004.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #195</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Eyebrows</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span>, <span>Schoolgirl Outfit</span>, <span>Twintails</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="8">
|
||||
<a href="https://ksk.moe/view/7719/eff60869afb4" title="futuregraph #215" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #215" alt="Cover of futuregraph #215" src="002_gallery_front_files/01.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #215</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span>, <span>Swimsuit</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="9">
|
||||
<a href="https://ksk.moe/view/2811/475fe84e981b" title="futuregraph #173" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #173" alt="Cover of futuregraph #173" src="002_gallery_front_files/01_005.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #173</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="10">
|
||||
<a href="https://ksk.moe/view/5965/450de07a12de" title="futuregraph #200" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #200" alt="Cover of futuregraph #200" src="002_gallery_front_files/01_013.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #200</span>
|
||||
</h3>
|
||||
</main><footer><span>Busty</span>, <span>Color</span>, <span>Illustration</span>, <span>Non-H</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="11">
|
||||
<a href="https://ksk.moe/view/5759/09ee669936ed" title="futuregraph #198" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #198" alt="Cover of futuregraph #198" src="002_gallery_front_files/01_003.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #198</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="12">
|
||||
<a href="https://ksk.moe/view/9013/7b55c9cb9bcd" title="futuregraph #227" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #227" alt="Cover of futuregraph #227" src="002_gallery_front_files/01_007.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #227</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span>, <span>Short Hair</span>, <span>Swimsuit</span></footer></a>
|
||||
</article><article class="swiper-slide" style="width: 271.429px;" data-swiper-slide-index="13">
|
||||
<a href="https://ksk.moe/view/4865/ce3654818b11" title="futuregraph #185" rel="bookmark"><header>
|
||||
<figure>
|
||||
<img title="Cover of futuregraph #185" alt="Cover of futuregraph #185" src="002_gallery_front_files/01_012.jpg" loading="lazy" width="320" height="452">
|
||||
</figure>
|
||||
<div>
|
||||
<strong>1P</strong>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h3><span>Range Murata</span> - <span>futuregraph #185</span>
|
||||
</h3>
|
||||
</main><footer><span>Color</span>, <span>Illustration</span>, <span>Non-H</span>, <span>Petite</span>, <span>Swimsuit</span></footer></a>
|
||||
</article></div>
|
||||
<div class="swiper-button-prev"></div>
|
||||
<div class="swiper-button-next"></div>
|
||||
</main>
|
||||
</section></article>
|
||||
</main><div class="bwrp bbottom">
|
||||
<div class="b"><script id="__clb-1978793" type="text/javascript" src="002_gallery_front_files/noop.js" data-cfasync="false" async=""></script></div>
|
||||
</div><footer>
|
||||
<a href="mailto:B06hAfGx95OA2h@protonmail.com" title="B06hAfGx95OA2h@protonmail.com">[contact]</a>
|
||||
<a href="https://sukebe.moe/">[sukebe.moe]</a>
|
||||
</footer><div id="advs" class="popup hidden invisible" data-ti="0" data-g="s,body>header">
|
||||
<div data-ta="#advs"></div>
|
||||
<div id="advf">
|
||||
<form action="/browse">
|
||||
<header>
|
||||
<h2>Advanced Search</h2>
|
||||
<button type="button" data-ta="#advs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: currentColor;" data-darkreader-inline-stroke="">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<div>
|
||||
<input type="text" name="s" placeholder="tag:mating_press creampie -yaoi">
|
||||
</div>
|
||||
<div class="categories">
|
||||
<strong>Categories</strong>
|
||||
<input type="hidden" name="cat" data-default="7">
|
||||
<div><button class="active" data-value="1" type="button">Manga</button><button class="active" data-value="2" type="button">Doujinshi</button><button class="active" data-value="4" type="button">Illustration</button></div>
|
||||
</div>
|
||||
</section>
|
||||
<div>
|
||||
<strong>Title</strong>
|
||||
<div>
|
||||
<div>
|
||||
<input type="text" name="title" placeholder="Search by title">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Tag</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="t" placeholder="mating press, creampie, -yaoi">
|
||||
<select name="tc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Artist</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="a" placeholder="Search by artist">
|
||||
<select name="ac">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Circle</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="c" placeholder="Search by circle">
|
||||
<select name="gc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Parody</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="p" placeholder="Search by parody">
|
||||
<select name="pc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Magazine</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="text" name="m" placeholder="Search by magazine">
|
||||
<select name="mc">
|
||||
<option value="0" selected="selected">OR</option>
|
||||
<option value="1">AND</option>
|
||||
</select>
|
||||
</div>
|
||||
<i>Delimited by comma</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Pages</strong>
|
||||
<div>
|
||||
<div class="group">
|
||||
<input type="number" name="ps" placeholder="Minimum" min="1">
|
||||
<input type="number" name="pe" placeholder="Maximum" min="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<button type="submit" name="adv" aria-label="Apply" title="Apply" value="1">Apply</button>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script async="" src="002_gallery_front_files/google-analytics_analytics.js"></script><script type="text/javascript" src="002_gallery_front_files/noop_003.js"></script></body></html>
|
20
tests/samples/ksk/fake.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
Artist:
|
||||
- xXMidnightEssenceXx
|
||||
- bloodytearz666
|
||||
Description: The legendary fanfic, now illustrated
|
||||
with a line break
|
||||
Magazine:
|
||||
- My Immortal - Genesis
|
||||
Pages: 420
|
||||
Parody:
|
||||
- Harry Potter
|
||||
Publisher:
|
||||
- FanFiction.net
|
||||
Released: Mon, 01 Jan 1990 11:30:01 GMT
|
||||
Tags:
|
||||
- Harry Potter
|
||||
- Ebony Dark'ness Dementia Raven Way
|
||||
- Draco Malfoy
|
||||
Thumb: https://placekitten.com/g/400/300
|
||||
Title: My Immortal
|
||||
URL: https://www.fanfiction.net/s/6829556/1/My-Immortal
|
43
tests/tankoubon.t
Normal file
@ -0,0 +1,43 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Cwd;
|
||||
|
||||
use Mojo::Base 'Mojolicious';
|
||||
|
||||
use Test::More;
|
||||
use Test::Mojo;
|
||||
use Test::MockObject;
|
||||
use Mojo::JSON qw (decode_json);
|
||||
use Data::Dumper;
|
||||
|
||||
use LANraragi::Model::Tankoubon;
|
||||
use LANraragi::Model::Config;
|
||||
use LANraragi::Model::Stats;
|
||||
|
||||
# Mock Redis
|
||||
my $cwd = getcwd;
|
||||
require $cwd . "/tests/mocks.pl";
|
||||
setup_redis_mock();
|
||||
|
||||
my $redis = LANraragi::Model::Config->get_redis;
|
||||
|
||||
# Build search hashes
|
||||
LANraragi::Model::Stats::build_stat_hashes();
|
||||
|
||||
# Search queries
|
||||
my ( $total, $filtered, @rgs );
|
||||
|
||||
# Get Tankoubon
|
||||
my %tankoubon = LANraragi::Model::Tankoubon::get_tankoubon("TANK_1589141306", 0, 0);
|
||||
is($tankoubon{id}, "TANK_1589141306", 'ID test');
|
||||
is($tankoubon{name}, "Hello", 'Name test');
|
||||
ok($tankoubon{archives}[0] eq "28697b96f0ac5858be2614ed10ca47742c9522fd", 'Archives test');
|
||||
|
||||
# List Tankoubon
|
||||
( $total, $filtered, @rgs ) = LANraragi::Model::Tankoubon::get_tankoubon_list(0);
|
||||
is($total, 2, 'Total Test');
|
||||
is($filtered, 2, 'Count Test');
|
||||
ok($rgs[0]{name} eq "World" && $rgs[1]{name} eq "Hello", 'Tank List test');
|
||||
|
||||
done_testing();
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 621 KiB After Width: | Height: | Size: 558 KiB |
BIN
tools/Documentation/.screenshots/lrr_react.jpg
Normal file
After Width: | Height: | Size: 290 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 68 KiB |
@ -45,7 +45,14 @@ Here are some existing clients:
|
||||
![Tachiyomi](<../.screenshots/tachiyomi.jpg>)
|
||||
|
||||
The open-source [Tachiyomi](https://tachiyomi.org) Android reader has a readymade plugin to consume the LANraragi API.
|
||||
You can download it [here.](https://github.com/inorichi/tachiyomi-extensions/blob/repo/apk/tachiyomi-all.lanraragi-v1.2.1.apk)
|
||||
You can download it [here.](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/repo/apk/tachiyomi-all.lanraragi-v1.4.14.apk)
|
||||
|
||||
### LRR React Web
|
||||
|
||||
![LRR React-Web](../.screenshots/lrr_react.jpg)
|
||||
|
||||
A React-based frontend PWA making use of the Client API. "Works best on mobile and tablet viewports, but the desktop experience is okay."
|
||||
Check it out [here.](https://github.com/hibikikuze4dan/lanraragi-react-web)
|
||||
|
||||
## Generic OPDS readers
|
||||
|
||||
|
@ -18,6 +18,7 @@ Get the Archive Index in JSON form. You can use the IDs of this JSON with the ot
|
||||
"pagecount": 128,
|
||||
"progress": 0,
|
||||
"tags": "",
|
||||
"lastreadtime": 1589038280,
|
||||
"title": "Ghost in the Shell 01.5 - Human-Error Processor v01c01"
|
||||
}, {
|
||||
"arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
|
||||
@ -26,6 +27,7 @@ Get the Archive Index in JSON form. You can use the IDs of this JSON with the ot
|
||||
"pagecount": 34,
|
||||
"progress": 3,
|
||||
"tags": "parody:fate grand order, group:wadamemo, artist:wada rco, artbook, full color",
|
||||
"lastreadtime": 1337038281,
|
||||
"title": "Fate GO MEMO"
|
||||
}, {
|
||||
"arcid": "2810d5e0a8d027ecefebca6237031a0fa7b91eb3",
|
||||
@ -34,6 +36,7 @@ Get the Archive Index in JSON form. You can use the IDs of this JSON with the ot
|
||||
"pagecount": 0,
|
||||
"progress": 0,
|
||||
"tags": "parody:fate grand order, character:abigail williams, character:artoria pendragon alter, character:asterios, character:ereshkigal, character:gilgamesh, character:hans christian andersen, character:hassan of serenity, character:hector, character:helena blavatsky, character:irisviel von einzbern, character:jeanne alter, character:jeanne darc, character:kiara sessyoin, character:kiyohime, character:lancer, character:martha, character:minamoto no raikou, character:mochizuki chiyome, character:mordred pendragon, character:nitocris, character:oda nobunaga, character:osakabehime, character:penthesilea, character:queen of sheba, character:rin tosaka, character:saber, character:sakata kintoki, character:scheherazade, character:sherlock holmes, character:suzuka gozen, character:tamamo no mae, character:ushiwakamaru, character:waver velvet, character:xuanzang, character:zhuge liang, group:wadamemo, artist:wada rco, artbook, full color",
|
||||
"lastreadtime": 1337038282,
|
||||
"title": "Fate GO MEMO 2"
|
||||
}, {
|
||||
"arcid": "e69e43e1355267f7d32a4f9b7f2fe108d2401ebf",
|
||||
@ -42,6 +45,7 @@ Get the Archive Index in JSON form. You can use the IDs of this JSON with the ot
|
||||
"pagecount": 0,
|
||||
"progress": 0,
|
||||
"tags": "character:segata sanshiro",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Saturn Backup Cartridge - Japanese Manual"
|
||||
}, {
|
||||
"arcid": "e4c422fd10943dc169e3489a38cdbf57101a5f7e",
|
||||
@ -50,6 +54,7 @@ Get the Archive Index in JSON form. You can use the IDs of this JSON with the ot
|
||||
"pagecount": 0,
|
||||
"progress": 0,
|
||||
"tags": "parody: jojo's bizarre adventure",
|
||||
"lastreadtime": 0,
|
||||
"title": "Rohan Kishibe goes to Gucci"
|
||||
}]
|
||||
```
|
||||
@ -89,6 +94,7 @@ ID of the Archive to process.
|
||||
"pagecount": 34,
|
||||
"progress": 3,
|
||||
"tags": "character:segata sanshiro",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Saturn Backup Cartridge - Japanese Manual"
|
||||
}
|
||||
```
|
||||
@ -147,6 +153,29 @@ ID of the Archive to process.
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/archives/:id/tankoubons" method="get" summary="Get Archive Tankoubons" %}
|
||||
{% swagger-description %}
|
||||
Get all the Tankoubons which currently refer to this Archive ID.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="id" type="string" required="true" in="path" %}
|
||||
ID of the Archive to process.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"operation": "find_arc_tankoubons",
|
||||
"success": 1,
|
||||
"tankoubons": [
|
||||
"TANK_1688616437",
|
||||
"TANK_1688693913"
|
||||
]
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/archives/:id/thumbnail" method="get" summary="Get Archive Thumbnail" %}
|
||||
{% swagger-description %}
|
||||
Get a Thumbnail image for a given Archive. This endpoint will queue generation of the thumbnail in the background if it doesn't already exist, and return a placeholder image.
|
||||
@ -309,6 +338,8 @@ ID of the Archive to process
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/archives/:id/progress/:page" method="put" summary="Update Reading Progression" %}
|
||||
{% swagger-description %}
|
||||
Tell the server which page of this Archive you're currently showing/reading, so that it updates its internal reading progression accordingly.
|
||||
This endpoint will also update the date this Archive was last read, using the current server timestamp.
|
||||
|
||||
You should call this endpoint only when you're sure the user is currently reading the page you present.
|
||||
**Don't** use it when preloading images off the server.
|
||||
|
||||
@ -333,6 +364,7 @@ Current page to update the reading progress to. **Must** be a positive integer,
|
||||
"id": "75d18ce470dc99f83dc355bdad66319d1f33c82b",
|
||||
"operation": "update_progress",
|
||||
"page": 34,
|
||||
"lastreadtime": 123943543,
|
||||
"success": 1
|
||||
}
|
||||
```
|
||||
|
@ -14,80 +14,35 @@ ID of the category you want to restrict this search to.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="filter" type="string" required="false" in="query" %}
|
||||
Search query. You can use the following special characters in it:
|
||||
|
||||
\
|
||||
|
||||
|
||||
|
||||
|
||||
**Quotation Marks ("...")**
|
||||
|
||||
\
|
||||
|
||||
Search query. You can use the following special characters in it:
|
||||
|
||||
**Quotation Marks ("...")**
|
||||
Exact string search. Allows a search term to include spaces. Everything placed inside a pair of quotation marks is treated as a singular term. Wildcard characters are still interpreted as wildcards.
|
||||
|
||||
**Question Mark (?), Underscore (_)**
|
||||
|
||||
\
|
||||
|
||||
|
||||
**Question Mark (?), Underscore (_)**
|
||||
Wildcard. Can match any single character.
|
||||
|
||||
**Asterisk (*), Percentage Sign (%)**
|
||||
|
||||
\
|
||||
|
||||
|
||||
**Asterisk (*), Percentage Sign (%)**
|
||||
Wildcard. Can match any sequence of characters (including none).
|
||||
|
||||
**Subtraction Sign (-)**
|
||||
|
||||
\
|
||||
|
||||
|
||||
**Subtraction Sign (-)**
|
||||
Exclusion. When placed before a term, prevents search results from including that term.
|
||||
|
||||
**Dollar Sign ($)**
|
||||
|
||||
\
|
||||
|
||||
|
||||
**Dollar Sign ($)**
|
||||
Add at the end of a tag to perform an exact tag search rather than displaying all elements that start with the term. Only matches tags regardless of search parameters and can be used as an exclusion to ignore misc tags in the search query.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="start" type="string" required="false" in="query" %}
|
||||
From which archive in the total result count this enumeration should start. The total number of archives displayed depends on the server-side
|
||||
|
||||
_page size_
|
||||
|
||||
preference.
|
||||
|
||||
\
|
||||
|
||||
|
||||
From which archive in the total result count this enumeration should start. The total number of archives displayed depends on the server-side _page size_ preference.
|
||||
From 0.8.2 onwards, you can use "-1" here to get the full, unpaged data.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="sortby" type="string" required="false" in="query" %}
|
||||
Namespace by which you want to sort the results, or
|
||||
|
||||
_title_
|
||||
|
||||
if you want to sort by title. (Default value is title.)
|
||||
Namespace by which you want to sort the results, or _title_ if you want to sort by title. (Default value is title.)
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="order" type="string" required="false" in="query" %}
|
||||
Order of the sort, either
|
||||
|
||||
`asc`
|
||||
|
||||
or
|
||||
|
||||
`desc`
|
||||
|
||||
.
|
||||
Order of the sort, either `asc` or `desc`.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
@ -98,30 +53,35 @@ Order of the sort, either
|
||||
"isnew": "none",
|
||||
"extension": "zip",
|
||||
"tags": "parody:fate grand order, group:wadamemo, artist:wada rco, artbook, full color",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Fate GO MEMO"
|
||||
}, {
|
||||
"arcid": "2810d5e0a8d027ecefebca6237031a0fa7b91eb3",
|
||||
"isnew": "none",
|
||||
"extension": "rar",
|
||||
"tags": "parody:fate grand order, character:abigail williams, character:artoria pendragon alter, character:asterios, character:ereshkigal, character:gilgamesh, character:hans christian andersen, character:hassan of serenity, character:hector, character:helena blavatsky, character:irisviel von einzbern, character:jeanne alter, character:jeanne darc, character:kiara sessyoin, character:kiyohime, character:lancer, character:martha, character:minamoto no raikou, character:mochizuki chiyome, character:mordred pendragon, character:nitocris, character:oda nobunaga, character:osakabehime, character:penthesilea, character:queen of sheba, character:rin tosaka, character:saber, character:sakata kintoki, character:scheherazade, character:sherlock holmes, character:suzuka gozen, character:tamamo no mae, character:ushiwakamaru, character:waver velvet, character:xuanzang, character:zhuge liang, group:wadamemo, artist:wada rco, artbook, full color",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Fate GO MEMO 2"
|
||||
}, {
|
||||
"arcid": "4857fd2e7c00db8b0af0337b94055d8445118630",
|
||||
"isnew": "none",
|
||||
"extension": "pdf",
|
||||
"tags": "artist:shirow masamune",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Ghost in the Shell 1.5 - Human-Error Processor vol01ch01"
|
||||
}, {
|
||||
"arcid": "e4c422fd10943dc169e3489a38cdbf57101a5f7e",
|
||||
"isnew": "none",
|
||||
"extension": "epub",
|
||||
"tags": "parody: jojo's bizarre adventure",
|
||||
"lastreadtime": 0,
|
||||
"title": "Rohan Kishibe goes to Gucci"
|
||||
}, {
|
||||
"arcid": "e69e43e1355267f7d32a4f9b7f2fe108d2401ebf",
|
||||
"isnew": "none",
|
||||
"extension": "lzma",
|
||||
"tags": "character:segata sanshiro",
|
||||
"lastreadtime": 1337038236,
|
||||
"title": "Saturn Backup Cartridge - Japanese Manual"
|
||||
}],
|
||||
"draw": 0,
|
||||
@ -160,24 +120,28 @@ If the search doesn't return enough data to match your count, you will get the f
|
||||
"isnew": "none",
|
||||
"extension": "rar",
|
||||
"tags": "parody:fate grand order, character:abigail williams, character:artoria pendragon alter, character:asterios, character:ereshkigal, character:gilgamesh, character:hans christian andersen, character:hassan of serenity, character:hector, character:helena blavatsky, character:irisviel von einzbern, character:jeanne alter, character:jeanne darc, character:kiara sessyoin, character:kiyohime, character:lancer, character:martha, character:minamoto no raikou, character:mochizuki chiyome, character:mordred pendragon, character:nitocris, character:oda nobunaga, character:osakabehime, character:penthesilea, character:queen of sheba, character:rin tosaka, character:saber, character:sakata kintoki, character:scheherazade, character:sherlock holmes, character:suzuka gozen, character:tamamo no mae, character:ushiwakamaru, character:waver velvet, character:xuanzang, character:zhuge liang, group:wadamemo, artist:wada rco, artbook, full color",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Fate GO MEMO 2"
|
||||
}, {
|
||||
"arcid": "4857fd2e7c00db8b0af0337b94055d8445118630",
|
||||
"isnew": "none",
|
||||
"extension": "pdf",
|
||||
"tags": "artist:shirow masamune",
|
||||
"lastreadtime": 1337038234,
|
||||
"title": "Ghost in the Shell 1.5 - Human-Error Processor vol01ch01"
|
||||
}, {
|
||||
"arcid": "e4c422fd10943dc169e3489a38cdbf57101a5f7e",
|
||||
"isnew": "none",
|
||||
"extension": "epub",
|
||||
"tags": "parody: jojo's bizarre adventure",
|
||||
"lastreadtime": 0,
|
||||
"title": "Rohan Kishibe goes to Gucci"
|
||||
}, {
|
||||
"arcid": "e69e43e1355267f7d32a4f9b7f2fe108d2401ebf",
|
||||
"isnew": "none",
|
||||
"extension": "lzma",
|
||||
"tags": "character:segata sanshiro",
|
||||
"lastreadtime": 1337033234,
|
||||
"title": "Saturn Backup Cartridge - Japanese Manual"
|
||||
}
|
||||
]
|
||||
|
293
tools/Documentation/api-documentation/tankoubon-api.md
Normal file
@ -0,0 +1,293 @@
|
||||
# Tankoubon API
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons" method="get" summary="Get all tankoubons" %}
|
||||
{% swagger-description %}
|
||||
Get list of Tankoubons paginated.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="page" type="string" required="false" in="query" %}
|
||||
Page of the list of Tankoubons.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"filtered": 2,
|
||||
"result": [
|
||||
{
|
||||
"archives": [
|
||||
"28697b96f0ac5858be2614ed10ca47742c9522fd",
|
||||
"4857fd2e7c00db8b0af0337b94055d8445118630",
|
||||
"fa74bc15e7dd2b6ec0dc2e10cc7cd4942867318a"
|
||||
],
|
||||
"id": "TANK_1688616437",
|
||||
"name": "Test 1"
|
||||
},
|
||||
{
|
||||
"archives": [
|
||||
"fa74bc15e7dd2b6ec0dc2e10cc7cd4942867318a"
|
||||
],
|
||||
"id": "TANK_1688693913",
|
||||
"name": "Test 2"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons/:id" method="get" summary="Get a single tankoubon" %}
|
||||
{% swagger-description %}
|
||||
Get the details of the specified tankoubon ID, with the archives list paginated.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="id" type="string" required="true" in="path" %}
|
||||
ID of the Tankoubon desired.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="include_full_data" type="string" required="false" in="query" %}
|
||||
If set in 1, it appends a full_data array with Archive objects.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="page" type="string" required="false" in="query" %}
|
||||
Page of the Archives list.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="include_full_data = 0" %}
|
||||
```javascript
|
||||
{
|
||||
"filtered": 3,
|
||||
"result": {
|
||||
"archives": [
|
||||
"fa74bc15e7dd2b6ec0dc2e10cc7cd4942867318a",
|
||||
"28697b96f0ac5858be2614ed10ca47742c9522fd",
|
||||
"4857fd2e7c00db8b0af0337b94055d8445118630"
|
||||
],
|
||||
"id": "TANK_1688616437",
|
||||
"name": "Test 1"
|
||||
},
|
||||
"total": 3
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
|
||||
{% swagger-response status="200" description="include_full_data = 1" %}
|
||||
```javascript
|
||||
{
|
||||
"filtered": 3,
|
||||
"result": {
|
||||
"archives": [
|
||||
"fa74bc15e7dd2b6ec0dc2e10cc7cd4942867318a",
|
||||
"28697b96f0ac5858be2614ed10ca47742c9522fd",
|
||||
"4857fd2e7c00db8b0af0337b94055d8445118630"
|
||||
],
|
||||
"full_data": [
|
||||
{
|
||||
"arcid": "fa74bc15e7dd2b6ec0dc2e10cc7cd4942867318a",
|
||||
"extension": "zip",
|
||||
"isnew": "true",
|
||||
"lastreadtime": 0,
|
||||
"pagecount": 30,
|
||||
"progress": 0,
|
||||
"tags": "date_added:1688608157",
|
||||
"lastreadtime": 0,
|
||||
"title": "(C95) [wadamemo (WADA Rco)] Fate GO MEMO 3 (Fate Grand Order)"
|
||||
},
|
||||
{
|
||||
"arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
|
||||
"extension": "zip",
|
||||
"isnew": "true",
|
||||
"lastreadtime": 0,
|
||||
"pagecount": 30,
|
||||
"progress": 0,
|
||||
"tags": "date_added:1688608157",
|
||||
"lastreadtime": 0,
|
||||
"title": "FateGOMEMO"
|
||||
},
|
||||
{
|
||||
"arcid": "4857fd2e7c00db8b0af0337b94055d8445118630",
|
||||
"extension": "zip",
|
||||
"isnew": "true",
|
||||
"lastreadtime": 0,
|
||||
"pagecount": 26,
|
||||
"progress": 0,
|
||||
"tags": "date_added:1688608157",
|
||||
"lastreadtime": 0,
|
||||
"title": "gits (2)"
|
||||
}
|
||||
],
|
||||
"id": "TANK_1688616437",
|
||||
"name": "Test 1"
|
||||
},
|
||||
"total": 3
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
|
||||
{% swagger-response status="400" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "The given tankoubon does not exist.",
|
||||
"operation": "get_tankoubon",
|
||||
"success": 0
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons" method="put" summary="🔑Create a Tankoubon" %}
|
||||
{% swagger-description %}
|
||||
Create a new Tankoubon or updated the name of an existing one.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="name" type="string" required="true" in="query" %}
|
||||
Name of the Category.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"operation": "create_tankoubon",
|
||||
"success": 1,
|
||||
"tankoubon_id": "TANK_1690056313"
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
|
||||
{% swagger-response status="400" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "Tankoubon name not specified.",
|
||||
"operation": "create_tankoubon",
|
||||
"success": 0
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons/:id/archive" method="put" summary="🔑Update a Tankoubon" %}
|
||||
{% swagger-description %}
|
||||
Modify a Tankoubon using an ordered array that can add new files, remove files, and update order.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="id" type="string" required="true" in="path" %}
|
||||
ID of the Tankoubon to update.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="archives" type="array" required="true" in="body" %}
|
||||
Ordered array with the IDs of the archives.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "",
|
||||
"operation": "update_archive_list",
|
||||
"success": 1,
|
||||
"successMessage": "Updated archives of tankoubon \"Test 1\"!"
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
|
||||
{% swagger-response status="400" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "rr doesn't exist in the database!",
|
||||
"operation": "update_archive_list",
|
||||
"success": 0
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons/:id/:archive" method="put" summary="🔑Add an archive to a Tankoubon" %}
|
||||
{% swagger-description %}
|
||||
Append an archive at the final position of a Tankoubon.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="id" type="string" required="true" in="path" %}
|
||||
ID of the Tankoubon to update.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="archive" type="string" required="true" in="path" %}
|
||||
ID of the Archive to append.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "",
|
||||
"operation": "add_to_tankoubon",
|
||||
"success": 1,
|
||||
"successMessage": "Added \"(C95) [wadamemo (WADA Rco)] Fate GO MEMO 3 (Fate Grand Order)\" to tankoubon \"Test 1\"!"
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
|
||||
{% swagger-response status="400" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "rr doesn't exist in the database!",
|
||||
"operation": "add_to_tankoubon",
|
||||
"success": 0
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons/:id/:archive" method="delete" summary="🔑Remove an archive from a Tankoubon" %}
|
||||
{% swagger-description %}
|
||||
Remove an archive from a Tankoubon.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="id" type="string" required="true" in="path" %}
|
||||
ID of the Tankoubon to update.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-parameter name="archive" type="string" required="true" in="path" %}
|
||||
ID of the archive to remove.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "",
|
||||
"operation": "remove_from_tankoubon",
|
||||
"success": 1,
|
||||
"successMessage": "Removed \"(C95) [wadamemo (WADA Rco)] Fate GO MEMO 3 (Fate Grand Order)\" from tankoubon \"Test 1\"!"
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
|
||||
{% swagger-response status="400" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "rr doesn't exist in the database!",
|
||||
"operation": "remove_from_tankoubon",
|
||||
"success": 0
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
||||
|
||||
{% swagger baseUrl="http://lrr.tvc-16.science" path="/api/tankoubons/:id" method="delete" summary="🔑Delete a Tankoubon" %}
|
||||
{% swagger-description %}
|
||||
Remove a Tankoubon.
|
||||
{% endswagger-description %}
|
||||
|
||||
{% swagger-parameter name="id" type="string" required="true" in="path" %}
|
||||
ID of the Tankoubon to delete.
|
||||
{% endswagger-parameter %}
|
||||
|
||||
{% swagger-response status="200" description="" %}
|
||||
```javascript
|
||||
{
|
||||
"error": "",
|
||||
"operation": "delete_tankoubon",
|
||||
"success": 1,
|
||||
"successMessage": null
|
||||
}
|
||||
```
|
||||
{% endswagger-response %}
|
||||
{% endswagger %}
|
@ -18,7 +18,7 @@ Those variables were introduced for the Homebrew package, but they can be declar
|
||||
* `LRR_LOG_DIRECTORY` - Log directory override. Changes the location of the `log` folder.
|
||||
* `LRR_FORCE_DEBUG` - Debug Mode override. This will force Debug Mode to be enabled regardless of the user setting.
|
||||
* `LRR_NETWORK` - Network Interface. See the dedicated page in Advanced Operations.
|
||||
* `LRR_REDIS_ADDRESS` - Redis adress override. This has priority over the `redis_address` specified in `lrr.conf`.
|
||||
* `LRR_REDIS_ADDRESS` - Redis address override. This has priority over the `redis_address` specified in `lrr.conf`.
|
||||
|
||||
## Coding Style
|
||||
|
||||
@ -125,7 +125,8 @@ root/
|
||||
|- lrr.conf <- Mojolicious configuration file
|
||||
|- .perltidy.rc <- PerlTidy config file to match the coding style
|
||||
|- .eslintrc.json <- ESLint config file to match the coding style
|
||||
+- package.json <- NPM file, contains front-end dependency listing and shortcuts
|
||||
|- package.json <- NPM file, contains front-end dependency listing and shortcuts
|
||||
+- package-lock.json <- NPM lockfile used by installer/`npm ci` for reproducible builds
|
||||
```
|
||||
|
||||
## Shinobu Architecture
|
||||
|