🔧 Added DC++ search and download actions
This commit is contained in:
1008
package-lock.json
generated
1008
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -39,7 +39,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
||||||
"@elastic/elasticsearch": "^8.6.0",
|
"@elastic/elasticsearch": "^8.13.1",
|
||||||
"@jorgeferrero/stream-to-buffer": "^2.0.6",
|
"@jorgeferrero/stream-to-buffer": "^2.0.6",
|
||||||
"@npcz/magic": "^1.3.14",
|
"@npcz/magic": "^1.3.14",
|
||||||
"@root/walk": "^1.1.0",
|
"@root/walk": "^1.1.0",
|
||||||
@@ -48,7 +48,8 @@
|
|||||||
"@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",
|
||||||
"axios": "^0.25.0",
|
"airdcpp-apisocket": "^2.4.4",
|
||||||
|
"axios": "^1.6.8",
|
||||||
"axios-retry": "^3.2.4",
|
"axios-retry": "^3.2.4",
|
||||||
"bree": "^7.1.5",
|
"bree": "^7.1.5",
|
||||||
"calibre-opds": "^1.0.7",
|
"calibre-opds": "^1.0.7",
|
||||||
@@ -74,15 +75,15 @@
|
|||||||
"mongoose": "^6.10.4",
|
"mongoose": "^6.10.4",
|
||||||
"mongoose-paginate-v2": "^1.3.18",
|
"mongoose-paginate-v2": "^1.3.18",
|
||||||
"nats": "^1.3.2",
|
"nats": "^1.3.2",
|
||||||
"opds-extra": "^3.0.9",
|
"opds-extra": "^3.0.10",
|
||||||
"p7zip-threetwo": "^1.0.4",
|
"p7zip-threetwo": "^1.0.4",
|
||||||
"redis": "^4.6.5",
|
"redis": "^4.6.5",
|
||||||
"sanitize-filename-ts": "^1.0.2",
|
"sanitize-filename-ts": "^1.0.2",
|
||||||
"sharp": "^0.30.4",
|
"sharp": "^0.33.3",
|
||||||
"threetwo-ui-typings": "^1.0.14",
|
"threetwo-ui-typings": "^1.0.14",
|
||||||
"through2": "^4.0.2",
|
"through2": "^4.0.2",
|
||||||
"unrar": "^0.2.0",
|
"unrar": "^0.2.0",
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18.x.x"
|
"node": ">= 18.x.x"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { pubClient, subClient } from "../config/redis.config";
|
|||||||
const { MoleculerError } = require("moleculer").Errors;
|
const { MoleculerError } = require("moleculer").Errors;
|
||||||
const SocketIOService = require("moleculer-io");
|
const SocketIOService = require("moleculer-io");
|
||||||
const { v4: uuidv4 } = require("uuid");
|
const { v4: uuidv4 } = require("uuid");
|
||||||
|
import AirDCPPSocket from "../shared/airdcpp.socket";
|
||||||
|
|
||||||
export default class SocketService extends Service {
|
export default class SocketService extends Service {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -114,8 +115,200 @@ export default class SocketService extends Service {
|
|||||||
// {}
|
// {}
|
||||||
// );
|
// );
|
||||||
},
|
},
|
||||||
|
// AirDCPP Socket actions
|
||||||
|
|
||||||
|
search: {
|
||||||
|
params: {
|
||||||
|
query: "object",
|
||||||
|
config: "object",
|
||||||
|
},
|
||||||
|
async handler(ctx) {
|
||||||
|
const { query, config } = ctx.params;
|
||||||
|
const ADCPPSocket = new AirDCPPSocket(config);
|
||||||
|
try {
|
||||||
|
await ADCPPSocket.connect();
|
||||||
|
const instance = await ADCPPSocket.post(
|
||||||
|
"search",
|
||||||
|
query
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send the instance to the client
|
||||||
|
await this.io.emit("searchInitiated", {
|
||||||
|
instance,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setting up listeners
|
||||||
|
await ADCPPSocket.addListener(
|
||||||
|
`search`,
|
||||||
|
`search_result_added`,
|
||||||
|
(groupedResult) => {
|
||||||
|
this.io.emit(
|
||||||
|
"searchResultAdded",
|
||||||
|
groupedResult
|
||||||
|
);
|
||||||
|
},
|
||||||
|
instance.id
|
||||||
|
);
|
||||||
|
|
||||||
|
await ADCPPSocket.addListener(
|
||||||
|
`search`,
|
||||||
|
`search_result_updated`,
|
||||||
|
(updatedResult) => {
|
||||||
|
console.log("hi", updatedResult);
|
||||||
|
this.io.emit(
|
||||||
|
"searchResultUpdated",
|
||||||
|
updatedResult
|
||||||
|
);
|
||||||
|
},
|
||||||
|
instance.id
|
||||||
|
);
|
||||||
|
|
||||||
|
await ADCPPSocket.addListener(
|
||||||
|
`search`,
|
||||||
|
`search_hub_searches_sent`,
|
||||||
|
async (searchInfo) => {
|
||||||
|
await this.sleep(5000);
|
||||||
|
const currentInstance =
|
||||||
|
await ADCPPSocket.get(
|
||||||
|
`search/${instance.id}`
|
||||||
|
);
|
||||||
|
// Send the instance to the client
|
||||||
|
await this.io.emit("searchesSent", {
|
||||||
|
searchInfo,
|
||||||
|
});
|
||||||
|
if (currentInstance.result_count === 0) {
|
||||||
|
console.log("No more search results.");
|
||||||
|
this.io.emit("searchComplete", {
|
||||||
|
message: "No more search results.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
instance.id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perform the actual search
|
||||||
|
await ADCPPSocket.post(
|
||||||
|
`search/${instance.id}/hub_search`,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
await this.io.emit("searchError", error.message);
|
||||||
|
throw new MoleculerError(
|
||||||
|
"Search failed",
|
||||||
|
500,
|
||||||
|
"SEARCH_FAILED",
|
||||||
|
{ error }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// await ADCPPSocket.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
// params: {
|
||||||
|
// searchInstanceId: "string",
|
||||||
|
// resultId: "string",
|
||||||
|
// comicObjectId: "string",
|
||||||
|
// name: "string",
|
||||||
|
// size: "number",
|
||||||
|
// type: "any", // Define more specific type if possible
|
||||||
|
// config: "object",
|
||||||
|
// },
|
||||||
|
async handler(ctx) {
|
||||||
|
console.log(ctx.params);
|
||||||
|
const {
|
||||||
|
searchInstanceId,
|
||||||
|
resultId,
|
||||||
|
config,
|
||||||
|
comicObjectId,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
type,
|
||||||
|
} = ctx.params;
|
||||||
|
const ADCPPSocket = new AirDCPPSocket(config);
|
||||||
|
try {
|
||||||
|
await ADCPPSocket.connect();
|
||||||
|
const downloadResult = await ADCPPSocket.post(
|
||||||
|
`search/${searchInstanceId}/results/${resultId}/download`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (downloadResult && downloadResult.bundle_info) {
|
||||||
|
// Assume bundle_info is part of the response and contains the necessary details
|
||||||
|
const bundleDBImportResult = await ctx.call(
|
||||||
|
"library.applyAirDCPPDownloadMetadata",
|
||||||
|
{
|
||||||
|
bundleId: downloadResult.bundle_info.id,
|
||||||
|
comicObjectId,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
type,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
"Download and metadata update successful",
|
||||||
|
bundleDBImportResult
|
||||||
|
);
|
||||||
|
this.broker.emit(
|
||||||
|
"downloadCompleted",
|
||||||
|
bundleDBImportResult
|
||||||
|
);
|
||||||
|
return bundleDBImportResult;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Failed to download or missing download result information"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.broker.emit("downloadError", error.message);
|
||||||
|
throw new MoleculerError(
|
||||||
|
"Download failed",
|
||||||
|
500,
|
||||||
|
"DOWNLOAD_FAILED",
|
||||||
|
{ error }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// await ADCPPSocket.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
listenBundleTick: {
|
||||||
|
async handler(ctx) {
|
||||||
|
const { config } = ctx.params;
|
||||||
|
const ADCPPSocket = new AirDCPPSocket(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ADCPPSocket.connect();
|
||||||
|
console.log("Connected to AirDCPP successfully.");
|
||||||
|
|
||||||
|
ADCPPSocket.addListener(
|
||||||
|
"queue",
|
||||||
|
"queue_bundle_tick",
|
||||||
|
(tickData) => {
|
||||||
|
console.log(
|
||||||
|
"Received tick data: ",
|
||||||
|
tickData
|
||||||
|
);
|
||||||
|
this.io.emit("bundleTickUpdate", tickData);
|
||||||
|
},
|
||||||
|
null
|
||||||
|
); // Assuming no specific ID is needed here
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Error connecting to AirDCPP or setting listener:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sleep: (ms: number): Promise<NodeJS.Timeout> => {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {},
|
|
||||||
async started() {
|
async started() {
|
||||||
this.io.on("connection", async (socket) => {
|
this.io.on("connection", async (socket) => {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
72
shared/airdcpp.socket.ts
Normal file
72
shared/airdcpp.socket.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const WebSocket = require("ws");
|
||||||
|
const { Socket } = require("airdcpp-apisocket");
|
||||||
|
|
||||||
|
class AirDCPPSocket {
|
||||||
|
// Explicitly declare properties
|
||||||
|
options; // Holds configuration options
|
||||||
|
socketInstance; // Instance of the AirDCPP Socket
|
||||||
|
|
||||||
|
constructor(configuration: any) {
|
||||||
|
let socketProtocol = configuration.protocol === "https" ? "wss" : "ws";
|
||||||
|
this.options = {
|
||||||
|
url: `${socketProtocol}://${configuration.hostname}/api/v1/`,
|
||||||
|
autoReconnect: true,
|
||||||
|
reconnectInterval: 5000, // milliseconds
|
||||||
|
logLevel: "verbose",
|
||||||
|
ignoredListenerEvents: [
|
||||||
|
"transfer_statistics",
|
||||||
|
"hash_statistics",
|
||||||
|
"hub_counts_updated",
|
||||||
|
],
|
||||||
|
username: configuration.username,
|
||||||
|
password: configuration.password,
|
||||||
|
};
|
||||||
|
// Initialize the socket instance using the configured options and WebSocket
|
||||||
|
this.socketInstance = Socket(this.options, WebSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to ensure the socket connection is established if required by the library or implementation logic
|
||||||
|
async connect() {
|
||||||
|
// Here we'll check if a connect method exists and call it
|
||||||
|
if (
|
||||||
|
this.socketInstance &&
|
||||||
|
typeof this.socketInstance.connect === "function"
|
||||||
|
) {
|
||||||
|
await this.socketInstance.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to ensure the socket is disconnected properly if required by the library or implementation logic
|
||||||
|
async disconnect() {
|
||||||
|
// Similarly, check if a disconnect method exists and call it
|
||||||
|
if (
|
||||||
|
this.socketInstance &&
|
||||||
|
typeof this.socketInstance.disconnect === "function"
|
||||||
|
) {
|
||||||
|
await this.socketInstance.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to post data to an endpoint
|
||||||
|
async post(endpoint: any, data: any = {}) {
|
||||||
|
// Call post on the socket instance, assuming post is a valid method of the socket instance
|
||||||
|
return await this.socketInstance.post(endpoint, data);
|
||||||
|
}
|
||||||
|
async get(endpoint: any, data: any = {}) {
|
||||||
|
// Call post on the socket instance, assuming post is a valid method of the socket instance
|
||||||
|
return await this.socketInstance.get(endpoint, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to add listeners to the socket instance for handling real-time updates or events
|
||||||
|
async addListener(event: any, handlerName: any, callback: any, id: any) {
|
||||||
|
// Attach a listener to the socket instance
|
||||||
|
return await this.socketInstance.addListener(
|
||||||
|
event,
|
||||||
|
handlerName,
|
||||||
|
callback,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AirDCPPSocket;
|
||||||
Reference in New Issue
Block a user