Update v.0.9.0 3e109602

This commit is contained in:
WindyMadman 2023-11-12 03:45:11 +08:00
parent eeac283652
commit f232bcee8f
113 changed files with 4836 additions and 2304 deletions

View File

@ -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
View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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 {

View File

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

View 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;

View File

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

View File

@ -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();

View File

@ -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 ) );

View File

@ -1,7 +1,7 @@
package LANraragi::Controller::Index;
use Mojo::Base 'Mojolicious::Controller';
use utf8;
use URI::Escape;
use Redis;
use Encode;

View File

@ -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 ...");

View 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;

View File

@ -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 );

View File

@ -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 = (

View File

@ -2,7 +2,7 @@ package LANraragi::Model::Category;
use strict;
use warnings;
use utf8;
use Redis;
use Mojo::JSON qw(decode_json encode_json);

View File

@ -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;

View File

@ -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 ? "&amp;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 ? "&amp;key=" . $api_key : ""
);
}
}
sub generate_opds_item {

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View 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;

View File

@ -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();

View File

@ -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;

View File

@ -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 => "添加以下标签如果可用下载URLGallery 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 {

View File

@ -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;

View File

@ -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" );

View File

@ -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.

View File

@ -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";
}

View File

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

View File

@ -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;
}

View File

@ -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;

File diff suppressed because one or more lines are too long

View File

@ -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' ) {

View File

@ -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;

View 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;

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -2,7 +2,6 @@ package LANraragi::Utils::Logging;
use strict;
use warnings;
use utf8;
use open ':std', ':encoding(UTF-8)';
use feature 'say';

View File

@ -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...");

View File

@ -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;

View 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;

View File

@ -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; }

View File

@ -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);

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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"
}
}
}

View File

@ -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);

View File

@ -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({

View File

@ -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,

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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();

View File

@ -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 );

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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";

View File

@ -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;

View File

@ -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();

View 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();

View File

@ -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;

View File

@ -1,6 +1,6 @@
use strict;
use warnings;
use utf8;
use Data::Dumper;
use Test::More;

View 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();

View File

@ -1,6 +1,6 @@
use strict;
use warnings;
use utf8;
use Data::Dumper;
use LANraragi::Utils::Generic qw(flat);

View File

@ -1,8 +1,8 @@
use strict;
use warnings;
use Cwd;
use utf8;
use Cwd;
use Mojo::Base 'Mojolicious';
use Test::More tests => 1;

View File

@ -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 {

View File

@ -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

View File

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

View File

@ -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"
]
}
]

View File

@ -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:&quot;comic kairakuten 2018-06&quot; :: Koushoku"><meta property="og:title" content="title:futuregraph magazine:&quot;comic kairakuten 2018-06&quot; :: Koushoku"><meta property="twitter:title" content="title:futuregraph magazine:&quot;comic kairakuten 2018-06&quot; :: 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:&quot;comic kairakuten 2018-06&quot; :: Koushoku"><meta property="twitter:image:alt" content="title:futuregraph magazine:&quot;comic kairakuten 2018-06&quot; :: 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:&quot;comic kairakuten 2018-06&quot;" 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&gt;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:&quot;comic kairakuten 2018-06&quot;"><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&gt;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:&quot;comic kairakuten 2018-06&quot;">
</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>

View File

@ -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&gt;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&amp;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&gt;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>

View 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
View 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();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

After

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -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

View File

@ -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
}
```

View File

@ -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"
}
]

View 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 %}

View File

@ -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

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