🏗 OPDS service scaffold
This commit is contained in:
1497
package-lock.json
generated
1497
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,10 @@
|
|||||||
"@jorgeferrero/stream-to-buffer": "^2.0.6",
|
"@jorgeferrero/stream-to-buffer": "^2.0.6",
|
||||||
"@root/walk": "^1.1.0",
|
"@root/walk": "^1.1.0",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
|
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
||||||
|
"calibre-opds": "^1.0.7",
|
||||||
|
"opds-extra": "^3.0.9",
|
||||||
|
"http-response-stream": "^1.0.9",
|
||||||
"@types/mkdirp": "^1.0.0",
|
"@types/mkdirp": "^1.0.0",
|
||||||
"@types/node": "^13.9.8",
|
"@types/node": "^13.9.8",
|
||||||
"@types/string-similarity": "^4.0.0",
|
"@types/string-similarity": "^4.0.0",
|
||||||
|
|||||||
154
services/opds.service.ts
Normal file
154
services/opds.service.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { Context, Service, ServiceBroker, ServiceSchema } from "moleculer";
|
||||||
|
|
||||||
|
import { basename, extname, join } from "path";
|
||||||
|
import { lookup } from "mime-types";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import { responseStream } from "http-response-stream";
|
||||||
|
import { isUndefined } from "lodash";
|
||||||
|
import { buildAsync } from "calibre-opds";
|
||||||
|
import initMain from "calibre-opds/lib/index";
|
||||||
|
import { EnumLinkRel } from "opds-extra/lib/const";
|
||||||
|
import { async as FastGlob } from "@bluelovers/fast-glob/bluebird";
|
||||||
|
import { Entry, Feed } from "opds-extra/lib/v1";
|
||||||
|
import { Link } from "opds-extra/lib/v1/core";
|
||||||
|
import xml2js from "xml2js";
|
||||||
|
import { COMICS_DIRECTORY } from "../constants/directories";
|
||||||
|
|
||||||
|
export default class OpdsService extends Service {
|
||||||
|
// @ts-ignore
|
||||||
|
public constructor(
|
||||||
|
public broker: ServiceBroker,
|
||||||
|
schema: ServiceSchema<{}> = { name: "opds" }
|
||||||
|
) {
|
||||||
|
super(broker);
|
||||||
|
this.parseServiceSchema(
|
||||||
|
Service.mergeSchemas(
|
||||||
|
{
|
||||||
|
name: "opds",
|
||||||
|
mixins: [],
|
||||||
|
settings: {
|
||||||
|
port: process.env.PORT || 3001,
|
||||||
|
},
|
||||||
|
hooks: {},
|
||||||
|
actions: {
|
||||||
|
opds: {
|
||||||
|
rest: "POST /opds",
|
||||||
|
handler: async (ctx) => {
|
||||||
|
return buildAsync(
|
||||||
|
initMain({
|
||||||
|
title: `title`,
|
||||||
|
subtitle: `subtitle`,
|
||||||
|
icon: "/favicon.ico",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
async (feed: Feed) => {
|
||||||
|
feed.id =
|
||||||
|
"urn:uuid:2853dacf-ed79-42f5-8e8a-a7bb3d1ae6a2";
|
||||||
|
feed.books = feed.books || [];
|
||||||
|
await FastGlob(
|
||||||
|
[
|
||||||
|
"*.cbr",
|
||||||
|
"*.cbz",
|
||||||
|
"*.cb7",
|
||||||
|
"*.cba",
|
||||||
|
"*.cbt",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
cwd: COMICS_DIRECTORY,
|
||||||
|
}
|
||||||
|
).each((file, idx) => {
|
||||||
|
const ext = extname(file);
|
||||||
|
const title = basename(
|
||||||
|
file,
|
||||||
|
ext
|
||||||
|
);
|
||||||
|
const href = encodeURI(
|
||||||
|
`/api/file/${file}`
|
||||||
|
);
|
||||||
|
const type =
|
||||||
|
lookup(ext) ||
|
||||||
|
"application/octet-stream";
|
||||||
|
|
||||||
|
const entry =
|
||||||
|
Entry.deserialize<Entry>({
|
||||||
|
title,
|
||||||
|
id: idx.toString(),
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: EnumLinkRel.ACQUISITION,
|
||||||
|
href,
|
||||||
|
type,
|
||||||
|
} as Link,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isUndefined(feed) &&
|
||||||
|
!isUndefined(feed.books)
|
||||||
|
) {
|
||||||
|
feed.books.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return feed;
|
||||||
|
},
|
||||||
|
]
|
||||||
|
).then((feed) => {
|
||||||
|
ctx.meta.$responseHeaders = {
|
||||||
|
"Content-Type": " application/xml",
|
||||||
|
};
|
||||||
|
let data;
|
||||||
|
xml2js.parseString(
|
||||||
|
feed.toXML(),
|
||||||
|
(err, result) => {
|
||||||
|
result.feed.link = {
|
||||||
|
$: {
|
||||||
|
rel: "self",
|
||||||
|
href: "/opds-catalogs/root.xml",
|
||||||
|
type: "application/atom+xml;profile=opds-catalog;kind=navigation",
|
||||||
|
},
|
||||||
|
_: "",
|
||||||
|
};
|
||||||
|
const builder = new xml2js.Builder({
|
||||||
|
xmldec: {
|
||||||
|
version: "1.0",
|
||||||
|
encoding: "UTF-8",
|
||||||
|
standalone: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
data = builder.buildObject(result, {
|
||||||
|
renderOpts: {
|
||||||
|
pretty: true,
|
||||||
|
indent: " ",
|
||||||
|
newline: "\n",
|
||||||
|
allowEmpty: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
},
|
||||||
|
schema
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// router.use("/file/*", async (req, res) => {
|
||||||
|
// const file: string = req.params[0];
|
||||||
|
// const ext = extname(file);
|
||||||
|
|
||||||
|
// if ([".cbr", ".cbz", ".cb7", ".cba", ".cbt"].includes(ext)) {
|
||||||
|
// const content = await fs.readFile(join(path_of_books, file));
|
||||||
|
// const mime = lookup(ext) || "application/octet-stream";
|
||||||
|
// res.set("Content-Type", mime);
|
||||||
|
// return responseStream(res, content);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// res.status(404).end(`'${file}' not exists`);
|
||||||
|
// });
|
||||||
Reference in New Issue
Block a user