LANraragi插件小记

警告
本文最后更新于 2023-01-03,文中内容可能已过时。
LANraragi.png

LANraragi提供了丰富的插件和脚本,借助插件能够以多种途径获取漫画的信息,但是这些插件并没有完全满足我的需求。

于是有了这篇文章,记录我的LANraragi插件编写过程。

在LANraragi中,漫画的信息被记录成tagscategories,即标签和种类。

我的需求是能从文件的目录结构提取出漫画的标签和种类,文件的结构如下:

1
2
3
4
5
6
7
8
9
category1
  --tag1
      --mangafile1
      --mangafile2
  --tag2
      --mangafile3
category2
  --tag3
      --mangafile4

LANraragi中并不存在实现上述功能的插件,于是我就参照已有的插件,实现了这些功能。

最终完成了一个脚本和一个插件:

  • TagFolder.pm(插件)

将文件所属的文件夹名作为漫画的tag

  • FolderToCat.pm(脚本)

将文件所属的文件夹名作为漫画的category,可选是否使用顶层文件夹

参照Filename Parsing v.1.0 by Difegue

使用说明:

  • Plugin Settings设置tag的类别,如图中所示则最终tag效果为artist: tagname
  • 开启Run Automatically则自动对新添加的漫画运行插件

下载地址:TagFolder.pm

文件内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package LANraragi::Plugin::Metadata::TagFolder;

use strict;
use warnings;

#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 File::Basename;

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

#Meta-information about your plugin.
sub plugin_info {

    return (
        #Standard metadata
        name      => "Use foldername as tag",
        type      => "metadata",
        namespace => "tagfolder",
        author    => "Ftbom",
        version   => "1.0",
        description =>
          "Generate the tag from the folder's name.",
        icon =>
          "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAL1JREFUOI1jZMABpNbH/sclx8DAwPAscDEjNnEMQUIGETIYhUOqYdgMhTPINQzdUEZqGIZsKBM1DEIGTOiuexqwCKdidDl0vtT62P9kuZCJEWuKYWBgYGBgRHbh04BFDNIb4jAUbbSrZTARUkURg6lD10OUC/0PNaMYgs1Skgwk1jCSDCQWoBg46dYmhite0+D8pwGLCMY6uotRDOy8toZBkI2HIhcO/pxCm8KBUkOxFl/kGoq3gCXFYFxVAACeoU/8xSNybwAAAABJRU5ErkJggg==",
        parameters => [ { type => "string", desc => "The category of tag" } ] #plugin设置
    );

}

#Mandatory function to be implemented by your plugin
sub get_tags {

    shift;
    my $lrr_info = shift;    # Global info hash
    my ($category) = @_;    # Plugin parameters

    my $logger = get_logger( "tagfolder", "plugins" );
    my $file   = $lrr_info->{file_path}; #完整文件地址
    my $tagstring;

    # 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.
    $file = redis_decode($file);

    # Get the filename from the file_path info field
    my ( $filename, $filepath, $suffix ) = fileparse( $file, qr/\.[^.]*/ );

    $_ = "$filepath"; #获取文件地址
    if (/\/([^\/]+)\/$/)
    {
        $tagstring="$category:$1"; #上级目录作tag名
    }
    $logger->debug( "Sending the following tags to LRR: " . $tagstring );
    return ( tags => $tagstring );

}
1;

参照Subfolders to Categories v.1.0 by Difegue

使用说明:

  • 点击Trigger Script运行脚本
  • Plugin Settings可进行设置:是否删除已存在的类别和是否使用顶层文件夹

下载地址:FolderToCat.pm

文件内容:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
package LANraragi::Plugin::Scripts::FolderToCat;

use strict;
use warnings;
use File::Find;
use File::Basename;
use Data::Dumper;

use LANraragi::Utils::Logging qw(get_logger);
use LANraragi::Utils::Generic qw(is_archive);
use LANraragi::Utils::Database qw(compute_id);
use LANraragi::Model::Category;

#Meta-information about your plugin.
sub plugin_info {

    return (
        #Standard metadata
        name      => "Subfolders to Categories",
        type      => "script",
        namespace => "fldr2cat",
        author    => "Difegue",
        version   => "1.0",
        icon =>
          "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAuJJREFUOI3FlE1sVFUUx3/nvVdaSqd0sAmElDYopOWjsKiEnYkLowU2uMGw0ajBhS5oqAtwo3FTaIQNCz7CBldEE01M+AglJMACUyBQrI6iQ2groNNxpjPz5n3MnXddvOnrfACNK09yk/fuved3//933j3wf8Thw6O6q6tHd3X16FOnTusX7ZW7I7H0kuUrV6hCtmHxt7UnOXHiDInEJAAzM49kscMtw2xesWHfKOTGASNa+GnmHfYP7gSgr28TY2PnF4UBWAD88Tlk7kFVyuDbX0ew43vzU/6bnc+12vnqx4OrX//i4gIQXS2uJkZGvqRXD3Q/V9LKXdjmSxcmjq7ahi6vtRazMKAHGua8oolbsHDyFk7iF4r/3IOyGl9QKI1u+vo2kUhMsmZ3T8Pa5c+E5vYYzbFWWkQTW23R+XKc+9/+eKUB+MbBHZWqhpW9MGSjtdDes57OzRtZ2hFD4tshcEAVkHIeVIapse8wDPNQxbKBDuDhxHKODd5myZ44698dBTQgUbEkex0CFzJXIfDC58CDwCP3Z1ZtPpAaD4FWnJIv2Nkm+j85AroE/jRoHwI/SgoBbt27h+54DS3je0VEh7VtimOnm2mKxRcguhpUDXvGUDb9bd3fh14rCtNPWuh9/6s6RfWq6mEetG0le/cb5KPbpajKActwChaIVNR5ddAqeLkCmt9TLjCbzEVFtQDstENTW0c4M58cJbiNsPmxtBs7eQ03p87VANP3J+n94CjiPKj76N4z7M4f6IPKk35YAFMORUCzpRU3/VdoV82BytUm+jMQlEArlBJmEyncORdnroTypxCRt7YMp5IRUBVzv/cPnV0n+VshyH8MgYuycyjHJjPt4GbyONki5VIAItfQnBeTG62YD1458DTF8EJXscSw1lEu4s0m8TJ/k/o5iZuZIygHlb+ZHwzkUoC+2T+cuiNSd08/re1qMjFa25YM5D0xjVtGe8fUhg9/zfMf41+ZdKPYI8TqHgAAAABJRU5ErkJggg==",
        description =>
          "Scan your Content Folder and automatically create Static Categories for each subfolder.<br>This Script will create a category for each subfolder with archives as direct children.",
        parameters =>
          [ { type => "bool", desc => "Delete all your static categories before creating the ones matching your subfolders" },
            { type => "bool", desc => "Create categories by the top level subfolder" }]
    );

}

# Mandatory function to be implemented by your script
sub run_script {
    shift;
    my $lrr_info          = shift;
    my ($delete_old_cats, $by_top_folder) = @_;
    my $logger            = get_logger( "Folder2Category", "plugins" );
    my $userdir           = LANraragi::Model::Config->get_userdir;

    my %subfolders;
    my @created_categories;
    my $dirname;

    if ($delete_old_cats) {
        $logger->info("Deleting all Static Categories before folder walking as instructed.");

        my @categories = LANraragi::Model::Category->get_static_category_list;
        for my $category (@categories) {
            my $cat_id = %{$category}{"id"};
            $logger->debug("Deleting '$cat_id'");
            LANraragi::Model::Category::delete_category($cat_id);
        }
    }

    # Walk through content folder and find all subfolders with files in them
    find(
        {   wanted => sub {
                return if $File::Find::dir eq $userdir;    # Direct children of the content dir are excluded
				if (not($by_top_folder)) {
                	$dirname = basename($File::Find::dir);
                }
                if ( is_archive($_) ) {
                    unless ( exists( $subfolders{$dirname} ) ) {
                        $subfolders{$dirname} = [];        # Create array in hash for this folder
                    }
                    push @{ $subfolders{$dirname} }, $_;
                }
                elsif ($by_top_folder) {
                	$dirname = basename($File::Find::dir);
                }	
            },
            no_chdir    => 1,
            follow_fast => 1
        },
        $userdir
    );

    $logger->debug( "Find routine results: " . Dumper %subfolders );

    # For each subfolder with file, create a category bearing its name and containing all its files
    for my $folder ( keys %subfolders ) {
        my $catID = LANraragi::Model::Category::create_category( $folder, "", 0, "" );
        push @created_categories, $catID;

        for my $file ( @{ $subfolders{$folder} } ) {
            eval {
                my $id = compute_id($file) || next;
                LANraragi::Model::Category::add_to_category( $catID, $id );
            };
        }
    }

    return ( created_categories => \@created_categories );

}

1;

简单分析一下这两个插件的代码,我并没有系统地学习过Perl语言,分析过程可能存在错误。

plugin_info函数返回插件的基本信息;TagFolder是元数据插件,主体内容是get_tags函数;FolderToCat是脚本,主体内容是run_script函数。

plugin_info函数的parameters返回值定义了插件的设置项,例如: TagFolder定义插件的设置项是string类型,获取输入字符;FolderToCat定义插件的设置项是bool类型,获取开关的状态。

在主体函数中可以通过@_获取插件的设置值。

TagFolder参照Filename Parsing,对其中关键代码的运行过程进行分析。

首先获取全局信息和插件设置:

1
2
my $lrr_info = shift; #全局信息
my ($category) = @_; #插件设置

全局信息中包括文件路径:

1
my $file   = $lrr_info->{file_path};

然后从文件路径中分离出文件名和文件所在文件夹路径,我们需要的是文件夹路径:

1
my ( $filename, $filepath, $suffix ) = fileparse( $file, qr/\.[^.]*/ );

从文件夹路径中获取文件夹名,得到tag字符串:

1
2
3
4
5
$_ = "$filepath"; #$_是默认的参数变量
if (/\/([^\/]+)\/$/)
{
    $tagstring="$category:$1"; #$1获取正则表达式匹配的第一项
}

最后输出tag信息:

1
return ( tags => $tagstring );

FolderToCat.pm仅在Subfolders to Categories的基础上做了一些小改动,改动部分主要为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
return if $File::Find::dir eq $userdir;    # Direct children of the content dir are excluded
if (not($by_top_folder)) {
    $dirname = basename($File::Find::dir);
}
if ( is_archive($_) ) {
    unless ( exists( $subfolders{$dirname} ) ) {
        $subfolders{$dirname} = [];        # Create array in hash for this folder
    }
    push @{ $subfolders{$dirname} }, $_;
}
elsif ($by_top_folder) {
    $dirname = basename($File::Find::dir);
}	

这里假设文件目录结构为,且假设插件设置使用顶层文件夹:

1
2
3
4
5
6
7
8
9
category1
  --tag1
      --mangafile1
      --mangafile2
  --tag2
      --mangafile3
category2
  --tag3
      --mangafile4

首先排除漫画目录的直接子文件夹,即categroy1、categroy2等文件夹:

1
return if $File::Find::dir eq $userdir;

对于其他的文件和文件夹,如果是文件夹,即tag1、tag2等文件夹,则将其父目录的名称赋值给dirname。即将categroy1、categroy2等赋值给dirname:

1
2
3
elsif ($by_top_folder) {
    $dirname = basename($File::Find::dir);
}

如果是文件,若其路径中存在dirname,则将其添加到对应数组。例如:mangafile1、mangafile2、mangafile3文件被添加到category1对应的数组:

1
2
3
4
5
6
if ( is_archive($_) ) {
    unless ( exists( $subfolders{$dirname} ) ) {
        $subfolders{$dirname} = [];        # Create array in hash for this folder
    }
    push @{ $subfolders{$dirname} }, $_;
}

最后通过这些数组来创建类别,并将对应漫画添加到类别中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
for my $folder ( keys %subfolders ) {
    my $catID = LANraragi::Model::Category::create_category( $folder, "", 0, "" );
    push @created_categories, $catID;

    for my $file ( @{ $subfolders{$folder} } ) {
        eval {
            my $id = compute_id($file) || next;
            LANraragi::Model::Category::add_to_category( $catID, $id );
        };
    }
}