Replace entire codebase with homarr-labs/homarr
This commit is contained in:
175
packages/integrations/test/aria2.spec.ts
Normal file
175
packages/integrations/test/aria2.spec.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import type { StartedTestContainer } from "testcontainers";
|
||||
import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers";
|
||||
import { beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { Aria2Integration } from "../src";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
|
||||
return {
|
||||
...actual,
|
||||
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const API_KEY = "ARIA2_API_KEY";
|
||||
const IMAGE_NAME = "hurlenko/aria2-ariang:latest";
|
||||
|
||||
describe("Aria2 integration", () => {
|
||||
beforeAll(async () => {
|
||||
const containerRuntimeClient = await getContainerRuntimeClient();
|
||||
await containerRuntimeClient.image.pull(ImageName.fromString(IMAGE_NAME));
|
||||
}, 100_000);
|
||||
|
||||
test("Test connection should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createAria2Container().start();
|
||||
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
|
||||
|
||||
// Act
|
||||
const result = await aria2Integration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000);
|
||||
|
||||
test("pauseQueueAsync should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createAria2Container().start();
|
||||
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
|
||||
|
||||
// Acts
|
||||
const actAsync = async () => await aria2Integration.pauseQueueAsync();
|
||||
const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({ status: { paused: true } });
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Items should be empty", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createAria2Container().start();
|
||||
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
|
||||
|
||||
// Act
|
||||
const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({
|
||||
items: [],
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("1 Items should exist after adding one", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createAria2Container().start();
|
||||
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
|
||||
await aria2AddItemAsync(startedContainer, API_KEY, aria2Integration);
|
||||
|
||||
// Act
|
||||
const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.not.toThrow();
|
||||
expect((await getAsync()).items).toHaveLength(1);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Delete item should result in empty items", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createAria2Container().start();
|
||||
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
|
||||
const item = await aria2AddItemAsync(startedContainer, API_KEY, aria2Integration);
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await aria2Integration.deleteItemAsync(item, true);
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
// NzbGet is slow and we wait for a second before querying the items. Test was flaky without this.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
const result = await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
expect(result.items).toHaveLength(0);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
});
|
||||
|
||||
const createAria2Container = () => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withExposedPorts(8080)
|
||||
.withEnvironment({
|
||||
PUID: "1000",
|
||||
PGID: "1000",
|
||||
ARIA2RPCPORT: "443",
|
||||
RPC_SECRET: API_KEY,
|
||||
})
|
||||
.withWaitStrategy(Wait.forLogMessage("listening on TCP port"));
|
||||
};
|
||||
|
||||
const createAria2Intergration = (container: StartedTestContainer, apikey: string) => {
|
||||
return new Aria2Integration({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "apiKey",
|
||||
value: apikey,
|
||||
},
|
||||
],
|
||||
name: "Aria2",
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(8080)}`,
|
||||
externalUrl: null,
|
||||
});
|
||||
};
|
||||
|
||||
const aria2AddItemAsync = async (container: StartedTestContainer, apiKey: string, integration: Aria2Integration) => {
|
||||
await fetch(`http://${container.getHost()}:${container.getMappedPort(8080)}/jsonrpc`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: btoa(["Homarr", Date.now().toString(), Math.random()].join(".")), // unique id per request
|
||||
method: "aria2.addUri",
|
||||
params: [`token:${apiKey}`, ["https://google.com"]],
|
||||
}),
|
||||
});
|
||||
|
||||
await delay(3000);
|
||||
|
||||
const {
|
||||
items: [item],
|
||||
} = await integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
if (!item) {
|
||||
throw new Error("No item found");
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
const delay = (microseconds: number) => new Promise((resolve) => setTimeout(resolve, microseconds));
|
||||
69
packages/integrations/test/base.spec.ts
Normal file
69
packages/integrations/test/base.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import type { IntegrationTestingInput } from "../src/base/integration";
|
||||
import { Integration } from "../src/base/integration";
|
||||
import type { TestingResult } from "../src/base/test-connection/test-connection-service";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
|
||||
return {
|
||||
...actual,
|
||||
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe("Base integration", () => {
|
||||
test("testConnectionAsync should handle errors", async () => {
|
||||
const responseError = new ResponseError({ status: 500, url: "https://example.com" });
|
||||
const integration = new FakeIntegration(undefined, responseError);
|
||||
|
||||
const result = await integration.testConnectionAsync();
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
expect(result.error.type === "statusCode").toBe(true);
|
||||
if (result.error.type !== "statusCode") return;
|
||||
expect(result.error.data.statusCode).toBe(500);
|
||||
expect(result.error.data.url).toContain("https://example.com");
|
||||
expect(result.error.data.reason).toBe("internalServerError");
|
||||
});
|
||||
});
|
||||
|
||||
class FakeIntegration extends Integration {
|
||||
constructor(
|
||||
private testingResult?: TestingResult,
|
||||
private error?: Error,
|
||||
) {
|
||||
super({
|
||||
id: "test",
|
||||
name: "Test",
|
||||
url: "https://example.com",
|
||||
decryptedSecrets: [],
|
||||
externalUrl: null,
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
protected testingAsync(_: IntegrationTestingInput): Promise<TestingResult> {
|
||||
if (this.error) {
|
||||
return Promise.reject(this.error);
|
||||
}
|
||||
|
||||
return Promise.resolve(this.testingResult ?? { success: true });
|
||||
}
|
||||
}
|
||||
113
packages/integrations/test/home-assistant.spec.ts
Normal file
113
packages/integrations/test/home-assistant.spec.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { join } from "path";
|
||||
import type { StartedTestContainer } from "testcontainers";
|
||||
import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers";
|
||||
import { beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { HomeAssistantIntegration } from "../src";
|
||||
import { TestConnectionError } from "../src/base/test-connection/test-connection-error";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
|
||||
return {
|
||||
...actual,
|
||||
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const DEFAULT_API_KEY =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNjQwY2VjNDFjOGU0NGM5YmRlNWQ4ZmFjMjUzYWViZiIsImlhdCI6MTcxODQ3MTE1MSwiZXhwIjoyMDMzODMxMTUxfQ.uQCZ5FZTokipa6N27DtFhLHkwYEXU1LZr0fsVTryL2Q";
|
||||
const IMAGE_NAME = "ghcr.io/home-assistant/home-assistant:stable";
|
||||
|
||||
describe("Home Assistant integration", () => {
|
||||
beforeAll(async () => {
|
||||
const containerRuntimeClient = await getContainerRuntimeClient();
|
||||
await containerRuntimeClient.image.pull(ImageName.fromString(IMAGE_NAME));
|
||||
}, 100_000);
|
||||
|
||||
test("Test connection should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await prepareHomeAssistantContainerAsync();
|
||||
const homeAssistantIntegration = createHomeAssistantIntegration(startedContainer);
|
||||
|
||||
// Act
|
||||
const result = await homeAssistantIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
test("Test connection should fail with wrong credentials", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await prepareHomeAssistantContainerAsync();
|
||||
const homeAssistantIntegration = createHomeAssistantIntegration(startedContainer, "wrong-api-key");
|
||||
|
||||
// Act
|
||||
const result = await homeAssistantIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
|
||||
expect(result.error).toBeInstanceOf(TestConnectionError);
|
||||
expect(result.error.type).toBe("authorization");
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
});
|
||||
|
||||
const prepareHomeAssistantContainerAsync = async () => {
|
||||
const homeAssistantContainer = createHomeAssistantContainer();
|
||||
const startedContainer = await homeAssistantContainer.start();
|
||||
await startedContainer.exec(["unzip", "-o", "/tmp/config.zip", "-d", "/config"]);
|
||||
await startedContainer.restart();
|
||||
return startedContainer;
|
||||
};
|
||||
|
||||
const createHomeAssistantContainer = () => {
|
||||
return (
|
||||
new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/home-assistant-config.zip"),
|
||||
target: "/tmp/config.zip",
|
||||
},
|
||||
])
|
||||
.withPrivilegedMode()
|
||||
.withExposedPorts(8123)
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/onboarding.html", 8123))
|
||||
);
|
||||
};
|
||||
|
||||
const createHomeAssistantIntegration = (container: StartedTestContainer, apiKeyOverride?: string) => {
|
||||
console.log("Creating Home Assistant integration...");
|
||||
return new HomeAssistantIntegration({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "apiKey",
|
||||
value: apiKeyOverride ?? DEFAULT_API_KEY,
|
||||
},
|
||||
],
|
||||
name: "Home assistant",
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(8123)}`,
|
||||
externalUrl: null,
|
||||
});
|
||||
};
|
||||
231
packages/integrations/test/nzbget.spec.ts
Normal file
231
packages/integrations/test/nzbget.spec.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import type { StartedTestContainer } from "testcontainers";
|
||||
import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers";
|
||||
import { beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { NzbGetIntegration } from "../src";
|
||||
import { TestConnectionError } from "../src/base/test-connection/test-connection-error";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
|
||||
return {
|
||||
...actual,
|
||||
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const username = "nzbget";
|
||||
const password = "tegbzn6789";
|
||||
const IMAGE_NAME = "linuxserver/nzbget:latest";
|
||||
|
||||
describe("Nzbget integration", () => {
|
||||
beforeAll(async () => {
|
||||
const containerRuntimeClient = await getContainerRuntimeClient();
|
||||
await containerRuntimeClient.image.pull(ImageName.fromString(IMAGE_NAME));
|
||||
}, 100_000);
|
||||
|
||||
test("Test connection should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password);
|
||||
|
||||
// Act
|
||||
const result = await nzbGetIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000);
|
||||
|
||||
test("Test connection should fail with wrong credentials", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, "wrong-user", "wrong-password");
|
||||
|
||||
// Act
|
||||
const result = await nzbGetIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
|
||||
expect(result.error).toBeInstanceOf(TestConnectionError);
|
||||
expect(result.error.type).toBe("authorization");
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("pauseQueueAsync should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password);
|
||||
|
||||
// Acts
|
||||
const actAsync = async () => await nzbGetIntegration.pauseQueueAsync();
|
||||
const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({ status: { paused: true } });
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("resumeQueueAsync should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password);
|
||||
await nzbGetIntegration.pauseQueueAsync();
|
||||
|
||||
// Acts
|
||||
const actAsync = async () => await nzbGetIntegration.resumeQueueAsync();
|
||||
const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({
|
||||
status: { paused: false },
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Items should be empty", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password);
|
||||
|
||||
// Act
|
||||
const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({
|
||||
items: [],
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("1 Items should exist after adding one", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password);
|
||||
await nzbGetAddItemAsync(startedContainer, username, password, nzbGetIntegration);
|
||||
|
||||
// Act
|
||||
const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.not.toThrow();
|
||||
expect((await getAsync()).items).toHaveLength(1);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Delete item should result in empty items", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createNzbGetContainer().start();
|
||||
const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password);
|
||||
const item = await nzbGetAddItemAsync(startedContainer, username, password, nzbGetIntegration);
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await nzbGetIntegration.deleteItemAsync(item, true);
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
// NzbGet is slow and we wait for a few seconds before querying the items. Test was flaky without this.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
const result = await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
expect(result.items).toHaveLength(0);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
});
|
||||
|
||||
const createNzbGetContainer = () => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withExposedPorts(6789)
|
||||
.withEnvironment({ PUID: "0", PGID: "0" })
|
||||
.withWaitStrategy(Wait.forLogMessage("[ls.io-init] done."));
|
||||
};
|
||||
|
||||
const createNzbGetIntegration = (container: StartedTestContainer, username: string, password: string) => {
|
||||
return new NzbGetIntegration({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "username",
|
||||
value: username,
|
||||
},
|
||||
{
|
||||
kind: "password",
|
||||
value: password,
|
||||
},
|
||||
],
|
||||
name: "NzbGet",
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(6789)}`,
|
||||
externalUrl: null,
|
||||
});
|
||||
};
|
||||
|
||||
const nzbGetAddItemAsync = async (
|
||||
container: StartedTestContainer,
|
||||
username: string,
|
||||
password: string,
|
||||
integration: NzbGetIntegration,
|
||||
) => {
|
||||
const fileContent = await readFile(join(__dirname, "/volumes/usenet/test_download_100MB.nzb"), "base64");
|
||||
// Trigger scanning of the watch folder (Only available way to add an item except "append" which is too complex and unnecessary)
|
||||
await fetch(`http://${container.getHost()}:${container.getMappedPort(6789)}/${username}:${password}/jsonrpc`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
method: "append",
|
||||
params: [
|
||||
"/downloads/nzb/test_download_100MB.nzb", // NZBFilename
|
||||
fileContent, // Content
|
||||
"", // Category
|
||||
0, // Priority
|
||||
true, // AddToTop
|
||||
false, // Paused
|
||||
"random", // DupeKey
|
||||
1000, // DupeScore
|
||||
"all", // DupeMode
|
||||
[], // PPParameters
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const {
|
||||
items: [item],
|
||||
} = await integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
if (!item) {
|
||||
throw new Error("No item found");
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
250
packages/integrations/test/pi-hole.spec.ts
Normal file
250
packages/integrations/test/pi-hole.spec.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import type { StartedTestContainer } from "testcontainers";
|
||||
import { GenericContainer, Wait } from "testcontainers";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { PiHoleIntegrationV5, PiHoleIntegrationV6 } from "../src";
|
||||
import type { SessionStore } from "../src/base/session-store";
|
||||
import { TestConnectionError } from "../src/base/test-connection/test-connection-error";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
|
||||
return {
|
||||
...actual,
|
||||
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const DEFAULT_PASSWORD = "12341234";
|
||||
const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password
|
||||
|
||||
describe("Pi-hole v5 integration", () => {
|
||||
test("getSummaryAsync should return summary from pi-hole", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, DEFAULT_API_KEY);
|
||||
|
||||
// Act
|
||||
const result = await piHoleIntegration.getSummaryAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.adsBlockedToday).toBe(0);
|
||||
expect(result.adsBlockedTodayPercentage).toBe(0);
|
||||
expect(result.dnsQueriesToday).toBe(0);
|
||||
expect(result.domainsBeingBlocked).toBeGreaterThan(1);
|
||||
|
||||
// Cleanup
|
||||
await piholeContainer.stop();
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("testConnectionAsync should be successful", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, DEFAULT_API_KEY);
|
||||
|
||||
// Act
|
||||
const result = await piHoleIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Cleanup
|
||||
await piholeContainer.stop();
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("testConnectionAsync should fail with unauthorized for wrong credentials", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, "wrong-api-key");
|
||||
|
||||
// Act
|
||||
const result = await piHoleIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
expect(result.error).toBeInstanceOf(TestConnectionError);
|
||||
expect(result.error.type).toBe("authorization");
|
||||
|
||||
// Cleanup
|
||||
await piholeContainer.stop();
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
});
|
||||
|
||||
vi.mock("../src/base/session-store", () => ({
|
||||
createSessionStore: () =>
|
||||
({
|
||||
async getAsync() {
|
||||
return await Promise.resolve(null);
|
||||
},
|
||||
async setAsync() {
|
||||
return await Promise.resolve();
|
||||
},
|
||||
async clearAsync() {
|
||||
return await Promise.resolve();
|
||||
},
|
||||
}) satisfies SessionStore<string>,
|
||||
}));
|
||||
|
||||
describe("Pi-hole v6 integration", () => {
|
||||
test("getSummaryAsync should return summary from pi-hole", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
||||
|
||||
// Act
|
||||
const result = await piHoleIntegration.getSummaryAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.status).toBe("enabled");
|
||||
expect(result.adsBlockedToday).toBe(0);
|
||||
expect(result.adsBlockedTodayPercentage).toBe(0);
|
||||
expect(result.dnsQueriesToday).toBe(0);
|
||||
expect(result.domainsBeingBlocked).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Cleanup
|
||||
await piholeContainer.stop();
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("enableAsync should enable pi-hole", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
||||
|
||||
// Disable pi-hole
|
||||
await piholeContainer.exec(["pihole", "disable"]);
|
||||
|
||||
// Act
|
||||
await piHoleIntegration.enableAsync();
|
||||
|
||||
// Assert
|
||||
const status = await piHoleIntegration.getDnsBlockingStatusAsync();
|
||||
expect(status.blocking).toContain("enabled");
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("disableAsync should disable pi-hole", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
||||
|
||||
// Act
|
||||
await piHoleIntegration.disableAsync();
|
||||
|
||||
// Assert
|
||||
const status = await piHoleIntegration.getDnsBlockingStatusAsync();
|
||||
expect(status.blocking).toBe("disabled");
|
||||
expect(status.timer).toBe(null);
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("disableAsync should disable pi-hole with timer", async () => {
|
||||
// Arrange
|
||||
const timer = 10 * 60; // 10 minutes
|
||||
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
||||
|
||||
// Act
|
||||
await piHoleIntegration.disableAsync(timer);
|
||||
|
||||
// Assert
|
||||
const status = await piHoleIntegration.getDnsBlockingStatusAsync();
|
||||
expect(status.blocking).toBe("disabled");
|
||||
expect(status.timer).toBeGreaterThan(timer - 10);
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("testConnectionAsync should be successful", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
||||
|
||||
// Act
|
||||
const result = await piHoleIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Cleanup
|
||||
await piholeContainer.stop();
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
|
||||
test("testConnectionAsync should fail with unauthorized for wrong credentials", async () => {
|
||||
// Arrange
|
||||
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
||||
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, "wrong-api-key");
|
||||
|
||||
// Act
|
||||
const result = await piHoleIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
expect(result.error).toBeInstanceOf(TestConnectionError);
|
||||
expect(result.error.type).toBe("authorization");
|
||||
|
||||
// Cleanup
|
||||
await piholeContainer.stop();
|
||||
}, 20_000); // Timeout of 20 seconds
|
||||
});
|
||||
|
||||
const createPiHoleV5Container = (password: string) => {
|
||||
return new GenericContainer("pihole/pihole:2024.07.0") // v5
|
||||
.withEnvironment({
|
||||
WEBPASSWORD: password,
|
||||
})
|
||||
.withExposedPorts(80)
|
||||
.withWaitStrategy(Wait.forLogMessage("Pi-hole Enabled"));
|
||||
};
|
||||
|
||||
const createPiHoleIntegrationV5 = (container: StartedTestContainer, apiKey: string) => {
|
||||
return new PiHoleIntegrationV5({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "apiKey",
|
||||
value: apiKey,
|
||||
},
|
||||
],
|
||||
name: "Pi hole",
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(80)}`,
|
||||
externalUrl: null,
|
||||
});
|
||||
};
|
||||
|
||||
const createPiHoleV6Container = (password: string) => {
|
||||
return (
|
||||
new GenericContainer("pihole/pihole:latest")
|
||||
.withEnvironment({
|
||||
FTLCONF_webserver_api_password: password,
|
||||
})
|
||||
.withExposedPorts(80)
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/admin/login", 80))
|
||||
);
|
||||
};
|
||||
|
||||
const createPiHoleIntegrationV6 = (container: StartedTestContainer, apiKey: string) => {
|
||||
return new PiHoleIntegrationV6({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "apiKey",
|
||||
value: apiKey,
|
||||
},
|
||||
],
|
||||
name: "Pi hole",
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(80)}`,
|
||||
externalUrl: null,
|
||||
});
|
||||
};
|
||||
265
packages/integrations/test/sabnzbd.spec.ts
Normal file
265
packages/integrations/test/sabnzbd.spec.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { join } from "path";
|
||||
import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers";
|
||||
import type { StartedTestContainer } from "testcontainers";
|
||||
import { beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { SabnzbdIntegration } from "../src";
|
||||
import { TestConnectionError } from "../src/base/test-connection/test-connection-error";
|
||||
import type { DownloadClientItem } from "../src/interfaces/downloads/download-client-items";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
|
||||
return {
|
||||
...actual,
|
||||
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const DEFAULT_API_KEY = "8r45mfes43s3iw7x3oecto6dl9ilxnf9";
|
||||
const IMAGE_NAME = "linuxserver/sabnzbd:latest";
|
||||
|
||||
describe("Sabnzbd integration", () => {
|
||||
beforeAll(async () => {
|
||||
const containerRuntimeClient = await getContainerRuntimeClient();
|
||||
await containerRuntimeClient.image.pull(ImageName.fromString(IMAGE_NAME));
|
||||
}, 100_000);
|
||||
|
||||
test("Test connection should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
|
||||
// Act
|
||||
const result = await sabnzbdIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Test connection should fail with wrong ApiKey", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, "wrong-api-key");
|
||||
|
||||
// Act
|
||||
const result = await sabnzbdIntegration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
expect(result.error).toBeInstanceOf(TestConnectionError);
|
||||
expect(result.error.type).toBe("authorization");
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("pauseQueueAsync should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
|
||||
// Acts
|
||||
const actAsync = async () => await sabnzbdIntegration.pauseQueueAsync();
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({ status: { paused: true } });
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("resumeQueueAsync should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
await sabnzbdIntegration.pauseQueueAsync();
|
||||
|
||||
// Acts
|
||||
const actAsync = async () => await sabnzbdIntegration.resumeQueueAsync();
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({
|
||||
status: { paused: false },
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Items should be empty", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
|
||||
// Act
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({
|
||||
items: [],
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("1 Items should exist after adding one", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
await sabNzbdAddItemAsync(startedContainer, DEFAULT_API_KEY, sabnzbdIntegration);
|
||||
|
||||
// Act
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.not.toThrow();
|
||||
expect((await getAsync()).items).toHaveLength(1);
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Pause item should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
const item = await sabNzbdAddItemAsync(startedContainer, DEFAULT_API_KEY, sabnzbdIntegration);
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await sabnzbdIntegration.pauseItemAsync(item);
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.toMatchObject({ items: [{ ...item, state: "downloading" }] });
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({ items: [{ ...item, state: "paused" }] });
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Resume item should work", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
const item = await sabNzbdAddItemAsync(startedContainer, DEFAULT_API_KEY, sabnzbdIntegration);
|
||||
await sabnzbdIntegration.pauseItemAsync(item);
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await sabnzbdIntegration.resumeItemAsync(item);
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(getAsync()).resolves.toMatchObject({ items: [{ ...item, state: "paused" }] });
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({ items: [{ ...item, state: "downloading" }] });
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
|
||||
test("Delete item should result in empty items", async () => {
|
||||
// Arrange
|
||||
const startedContainer = await createSabnzbdContainer().start();
|
||||
const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY);
|
||||
const item = await sabNzbdAddItemAsync(startedContainer, DEFAULT_API_KEY, sabnzbdIntegration);
|
||||
|
||||
// Act - fromDisk already doesn't work for sabnzbd, so only test deletion itself.
|
||||
const actAsync = async () =>
|
||||
await sabnzbdIntegration.deleteItemAsync({ ...item, progress: 0 } as DownloadClientItem, false);
|
||||
const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
await expect(getAsync()).resolves.toMatchObject({ items: [] });
|
||||
|
||||
// Cleanup
|
||||
await startedContainer.stop();
|
||||
}, 30_000); // Timeout of 30 seconds
|
||||
});
|
||||
|
||||
const createSabnzbdContainer = () => {
|
||||
return (
|
||||
new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/usenet/sabnzbd.ini"),
|
||||
target: "/config/sabnzbd.ini",
|
||||
},
|
||||
])
|
||||
.withExposedPorts(1212)
|
||||
.withEnvironment({ PUID: "0", PGID: "0" })
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/sabnzbd/wizard/", 1212))
|
||||
);
|
||||
};
|
||||
|
||||
const createSabnzbdIntegration = (container: StartedTestContainer, apiKey: string) => {
|
||||
return new SabnzbdIntegration({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "apiKey",
|
||||
value: apiKey,
|
||||
},
|
||||
],
|
||||
name: "Sabnzbd",
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(1212)}`,
|
||||
externalUrl: null,
|
||||
});
|
||||
};
|
||||
|
||||
const sabNzbdAddItemAsync = async (
|
||||
container: StartedTestContainer,
|
||||
apiKey: string,
|
||||
integration: SabnzbdIntegration,
|
||||
) => {
|
||||
// Add nzb file in the watch folder
|
||||
await container.copyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/usenet/test_download_100MB.nzb"),
|
||||
target: "/nzb/test_download_100MB.nzb",
|
||||
},
|
||||
]);
|
||||
// Adding file is faster than triggering scan of the watch folder
|
||||
// (local add: 1.4-1.6s, scan trigger: 2.5-2.7s, auto scan: 2.9-3s)
|
||||
await fetch(
|
||||
`http://${container.getHost()}:${container.getMappedPort(1212)}/api` +
|
||||
"?mode=addlocalfile" +
|
||||
"&name=%2Fnzb%2Ftest_download_100MB.nzb" +
|
||||
`&apikey=${apiKey}`,
|
||||
);
|
||||
// Retries up to 5 times to let SabNzbd scan and process the nzb (1 retry should suffice tbh)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const {
|
||||
items: [item],
|
||||
} = await integration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
if (item) return item;
|
||||
}
|
||||
// Throws if it can't find the item
|
||||
throw new Error("No item found");
|
||||
};
|
||||
BIN
packages/integrations/test/volumes/home-assistant-config.zip
Normal file
BIN
packages/integrations/test/volumes/home-assistant-config.zip
Normal file
Binary file not shown.
407
packages/integrations/test/volumes/usenet/sabnzbd.ini
Executable file
407
packages/integrations/test/volumes/usenet/sabnzbd.ini
Executable file
@@ -0,0 +1,407 @@
|
||||
__version__ = 19
|
||||
__encoding__ = utf-8
|
||||
[misc]
|
||||
pre_script = None
|
||||
queue_complete = ""
|
||||
queue_complete_pers = 0
|
||||
bandwidth_perc = 100
|
||||
refresh_rate = 1
|
||||
interface_settings = '{"dateFormat":"fromNow","extraQueueColumns":[],"extraHistoryColumns":[],"displayCompact":false,"displayFullWidth":false,"displayTabbed":false,"confirmDeleteQueue":true,"confirmDeleteHistory":true,"keyboardShortcuts":true}'
|
||||
queue_limit = 20
|
||||
config_lock = 0
|
||||
sched_converted = 0
|
||||
notified_new_skin = 2
|
||||
direct_unpack_tested = 1
|
||||
check_new_rel = 0
|
||||
auto_browser = 0
|
||||
language = en
|
||||
enable_https_verification = 1
|
||||
host = 127.0.0.1
|
||||
port = 1212
|
||||
https_port = 1212
|
||||
username = ""
|
||||
password = ""
|
||||
bandwidth_max = 1125M
|
||||
cache_limit = 128G
|
||||
web_dir = Glitter
|
||||
web_color = Auto
|
||||
https_cert = server.cert
|
||||
https_key = server.key
|
||||
https_chain = ""
|
||||
enable_https = 0
|
||||
inet_exposure = 4
|
||||
local_ranges = ,
|
||||
api_key = 8r45mfes43s3iw7x3oecto6dl9ilxnf9
|
||||
nzb_key = nc6q489idfb4fmdjh0uuqlsn4fjawrub
|
||||
permissions = ""
|
||||
download_dir = /temp
|
||||
download_free = ""
|
||||
complete_dir = /downloads
|
||||
complete_free = ""
|
||||
fulldisk_autoresume = 0
|
||||
script_dir = ""
|
||||
nzb_backup_dir = ""
|
||||
admin_dir = /admin
|
||||
dirscan_dir = /nzb
|
||||
dirscan_speed = 1
|
||||
password_file = ""
|
||||
log_dir = logs
|
||||
max_art_tries = 3
|
||||
load_balancing = 2
|
||||
top_only = 0
|
||||
sfv_check = 1
|
||||
quick_check_ext_ignore = nfo, sfv, srr
|
||||
script_can_fail = 0
|
||||
enable_recursive = 1
|
||||
flat_unpack = 0
|
||||
par_option = ""
|
||||
pre_check = 0
|
||||
nice = ""
|
||||
win_process_prio = 3
|
||||
ionice = ""
|
||||
fail_hopeless_jobs = 1
|
||||
fast_fail = 1
|
||||
auto_disconnect = 1
|
||||
no_dupes = 0
|
||||
no_series_dupes = 0
|
||||
series_propercheck = 1
|
||||
pause_on_pwrar = 1
|
||||
ignore_samples = 0
|
||||
deobfuscate_final_filenames = 0
|
||||
auto_sort = ""
|
||||
direct_unpack = 1
|
||||
direct_unpack_threads = 6
|
||||
propagation_delay = 0
|
||||
folder_rename = 1
|
||||
replace_spaces = 0
|
||||
replace_dots = 0
|
||||
safe_postproc = 1
|
||||
pause_on_post_processing = 0
|
||||
sanitize_safe = 0
|
||||
cleanup_list = ,
|
||||
unwanted_extensions = ,
|
||||
action_on_unwanted_extensions = 0
|
||||
new_nzb_on_failure = 0
|
||||
history_retention = ""
|
||||
enable_meta = 1
|
||||
quota_size = ""
|
||||
quota_day = ""
|
||||
quota_resume = 0
|
||||
quota_period = m
|
||||
rating_enable = 0
|
||||
rating_host = ""
|
||||
rating_api_key = ""
|
||||
rating_filter_enable = 0
|
||||
rating_filter_abort_audio = 0
|
||||
rating_filter_abort_video = 0
|
||||
rating_filter_abort_encrypted = 0
|
||||
rating_filter_abort_encrypted_confirm = 0
|
||||
rating_filter_abort_spam = 0
|
||||
rating_filter_abort_spam_confirm = 0
|
||||
rating_filter_abort_downvoted = 0
|
||||
rating_filter_abort_keywords = ""
|
||||
rating_filter_pause_audio = 0
|
||||
rating_filter_pause_video = 0
|
||||
rating_filter_pause_encrypted = 0
|
||||
rating_filter_pause_encrypted_confirm = 0
|
||||
rating_filter_pause_spam = 0
|
||||
rating_filter_pause_spam_confirm = 0
|
||||
rating_filter_pause_downvoted = 0
|
||||
rating_filter_pause_keywords = ""
|
||||
enable_tv_sorting = 0
|
||||
tv_sort_string = ""
|
||||
tv_sort_countries = 1
|
||||
tv_categories = ,
|
||||
enable_movie_sorting = 0
|
||||
movie_sort_string = ""
|
||||
movie_sort_extra = -cd%1
|
||||
movie_extra_folder = 0
|
||||
movie_categories = movies,
|
||||
enable_date_sorting = 0
|
||||
date_sort_string = ""
|
||||
date_categories = tv,
|
||||
schedlines = ,
|
||||
rss_rate = 60
|
||||
ampm = 0
|
||||
replace_illegal = 1
|
||||
start_paused = 0
|
||||
enable_all_par = 0
|
||||
enable_par_cleanup = 1
|
||||
enable_unrar = 1
|
||||
enable_unzip = 1
|
||||
enable_7zip = 1
|
||||
enable_filejoin = 1
|
||||
enable_tsjoin = 1
|
||||
overwrite_files = 0
|
||||
ignore_unrar_dates = 0
|
||||
backup_for_duplicates = 1
|
||||
empty_postproc = 0
|
||||
wait_for_dfolder = 0
|
||||
rss_filenames = 0
|
||||
api_logging = 1
|
||||
html_login = 1
|
||||
osx_menu = 1
|
||||
osx_speed = 1
|
||||
warn_dupl_jobs = 1
|
||||
helpfull_warnings = 1
|
||||
keep_awake = 1
|
||||
win_menu = 1
|
||||
allow_incomplete_nzb = 0
|
||||
enable_broadcast = 1
|
||||
max_art_opt = 0
|
||||
ipv6_hosting = 0
|
||||
fixed_ports = 1
|
||||
api_warnings = 1
|
||||
disable_api_key = 0
|
||||
no_penalties = 0
|
||||
x_frame_options = 1
|
||||
require_modern_tls = 0
|
||||
num_decoders = 3
|
||||
rss_odd_titles = nzbindex.nl/, nzbindex.com/, nzbclub.com/
|
||||
req_completion_rate = 100.2
|
||||
selftest_host = self-test.sabnzbd.org
|
||||
movie_rename_limit = 100M
|
||||
size_limit = 0
|
||||
show_sysload = 2
|
||||
history_limit = 10
|
||||
wait_ext_drive = 5
|
||||
max_foldername_length = 246
|
||||
nomedia_marker = ""
|
||||
ipv6_servers = 1
|
||||
url_base = /sabnzbd
|
||||
host_whitelist = ,
|
||||
max_url_retries = 10
|
||||
downloader_sleep_time = 10
|
||||
ssdp_broadcast_interval = 15
|
||||
email_server = ""
|
||||
email_to = ,
|
||||
email_from = ""
|
||||
email_account = ""
|
||||
email_pwd = ""
|
||||
email_endjob = 0
|
||||
email_full = 0
|
||||
email_dir = ""
|
||||
email_rss = 0
|
||||
email_cats = *,
|
||||
unwanted_extensions_mode = 0
|
||||
preserve_paused_state = 0
|
||||
process_unpacked_par2 = 1
|
||||
helpful_warnings = 1
|
||||
allow_old_ssl_tls = 0
|
||||
episode_rename_limit = 20M
|
||||
socks5_proxy_url = ""
|
||||
num_simd_decoders = 2
|
||||
ext_rename_ignore = ,
|
||||
sorters_converted = 1
|
||||
backup_dir = ""
|
||||
replace_underscores = 0
|
||||
tray_icon = 1
|
||||
enable_season_sorting = 1
|
||||
receive_threads = 6
|
||||
switchinterval = 0.005
|
||||
enable_multipar = 1
|
||||
verify_xff_header = 0
|
||||
end_queue_script = None
|
||||
no_smart_dupes = 0
|
||||
dupes_propercheck = 1
|
||||
history_retention_option = all
|
||||
history_retention_number = 1
|
||||
ipv6_staging = 0
|
||||
[logging]
|
||||
log_level = 1
|
||||
max_log_size = 5242880
|
||||
log_backups = 5
|
||||
[ncenter]
|
||||
ncenter_enable = 0
|
||||
ncenter_cats = *,
|
||||
ncenter_prio_startup = 1
|
||||
ncenter_prio_download = 0
|
||||
ncenter_prio_pause_resume = 0
|
||||
ncenter_prio_pp = 0
|
||||
ncenter_prio_complete = 1
|
||||
ncenter_prio_failed = 1
|
||||
ncenter_prio_disk_full = 1
|
||||
ncenter_prio_new_login = 0
|
||||
ncenter_prio_warning = 0
|
||||
ncenter_prio_error = 0
|
||||
ncenter_prio_queue_done = 1
|
||||
ncenter_prio_other = 1
|
||||
[acenter]
|
||||
acenter_enable = 0
|
||||
acenter_cats = *,
|
||||
acenter_prio_startup = 0
|
||||
acenter_prio_download = 0
|
||||
acenter_prio_pause_resume = 0
|
||||
acenter_prio_pp = 0
|
||||
acenter_prio_complete = 1
|
||||
acenter_prio_failed = 1
|
||||
acenter_prio_disk_full = 1
|
||||
acenter_prio_new_login = 0
|
||||
acenter_prio_warning = 0
|
||||
acenter_prio_error = 0
|
||||
acenter_prio_queue_done = 1
|
||||
acenter_prio_other = 1
|
||||
[ntfosd]
|
||||
ntfosd_enable = 1
|
||||
ntfosd_cats = *,
|
||||
ntfosd_prio_startup = 1
|
||||
ntfosd_prio_download = 0
|
||||
ntfosd_prio_pause_resume = 0
|
||||
ntfosd_prio_pp = 0
|
||||
ntfosd_prio_complete = 1
|
||||
ntfosd_prio_failed = 1
|
||||
ntfosd_prio_disk_full = 1
|
||||
ntfosd_prio_new_login = 0
|
||||
ntfosd_prio_warning = 0
|
||||
ntfosd_prio_error = 0
|
||||
ntfosd_prio_queue_done = 1
|
||||
ntfosd_prio_other = 1
|
||||
[prowl]
|
||||
prowl_enable = 0
|
||||
prowl_cats = *,
|
||||
prowl_apikey = ""
|
||||
prowl_prio_startup = -3
|
||||
prowl_prio_download = -3
|
||||
prowl_prio_pause_resume = -3
|
||||
prowl_prio_pp = -3
|
||||
prowl_prio_complete = 0
|
||||
prowl_prio_failed = 1
|
||||
prowl_prio_disk_full = 1
|
||||
prowl_prio_new_login = -3
|
||||
prowl_prio_warning = -3
|
||||
prowl_prio_error = -3
|
||||
prowl_prio_queue_done = 0
|
||||
prowl_prio_other = 0
|
||||
[pushover]
|
||||
pushover_token = ""
|
||||
pushover_userkey = ""
|
||||
pushover_device = ""
|
||||
pushover_emergency_expire = 3600
|
||||
pushover_emergency_retry = 60
|
||||
pushover_enable = 0
|
||||
pushover_cats = *,
|
||||
pushover_prio_startup = -3
|
||||
pushover_prio_download = -2
|
||||
pushover_prio_pause_resume = -2
|
||||
pushover_prio_pp = -3
|
||||
pushover_prio_complete = -1
|
||||
pushover_prio_failed = -1
|
||||
pushover_prio_disk_full = 1
|
||||
pushover_prio_new_login = -3
|
||||
pushover_prio_warning = 1
|
||||
pushover_prio_error = 1
|
||||
pushover_prio_queue_done = -1
|
||||
pushover_prio_other = -1
|
||||
[pushbullet]
|
||||
pushbullet_enable = 0
|
||||
pushbullet_cats = *,
|
||||
pushbullet_apikey = ""
|
||||
pushbullet_device = ""
|
||||
pushbullet_prio_startup = 0
|
||||
pushbullet_prio_download = 0
|
||||
pushbullet_prio_pause_resume = 0
|
||||
pushbullet_prio_pp = 0
|
||||
pushbullet_prio_complete = 1
|
||||
pushbullet_prio_failed = 1
|
||||
pushbullet_prio_disk_full = 1
|
||||
pushbullet_prio_new_login = 0
|
||||
pushbullet_prio_warning = 0
|
||||
pushbullet_prio_error = 0
|
||||
pushbullet_prio_queue_done = 0
|
||||
pushbullet_prio_other = 1
|
||||
[nscript]
|
||||
nscript_enable = 0
|
||||
nscript_cats = *,
|
||||
nscript_script = ""
|
||||
nscript_parameters = ""
|
||||
nscript_prio_startup = 1
|
||||
nscript_prio_download = 0
|
||||
nscript_prio_pause_resume = 0
|
||||
nscript_prio_pp = 0
|
||||
nscript_prio_complete = 1
|
||||
nscript_prio_failed = 1
|
||||
nscript_prio_disk_full = 1
|
||||
nscript_prio_new_login = 0
|
||||
nscript_prio_warning = 0
|
||||
nscript_prio_error = 0
|
||||
nscript_prio_queue_done = 1
|
||||
nscript_prio_other = 1
|
||||
[servers]
|
||||
[categories]
|
||||
[[audio]]
|
||||
name = audio
|
||||
order = 0
|
||||
pp = ""
|
||||
script = Default
|
||||
dir = ""
|
||||
newzbin = ""
|
||||
priority = -100
|
||||
[[software]]
|
||||
name = software
|
||||
order = 0
|
||||
pp = ""
|
||||
script = Default
|
||||
dir = ""
|
||||
newzbin = ""
|
||||
priority = -100
|
||||
[[books]]
|
||||
name = books
|
||||
order = 1
|
||||
pp = ""
|
||||
script = Default
|
||||
dir = books
|
||||
newzbin = ""
|
||||
priority = -100
|
||||
[[tv]]
|
||||
name = tv
|
||||
order = 0
|
||||
pp = ""
|
||||
script = Default
|
||||
dir = tvshows
|
||||
newzbin = ""
|
||||
priority = -100
|
||||
[[movies]]
|
||||
name = movies
|
||||
order = 0
|
||||
pp = ""
|
||||
script = Default
|
||||
dir = movies
|
||||
newzbin = ""
|
||||
priority = -100
|
||||
[[*]]
|
||||
name = *
|
||||
order = 0
|
||||
pp = 3
|
||||
script = Default
|
||||
dir = ""
|
||||
newzbin = ""
|
||||
priority = 0
|
||||
[rss]
|
||||
[apprise]
|
||||
apprise_enable = 0
|
||||
apprise_cats = *,
|
||||
apprise_urls = ""
|
||||
apprise_target_startup = ""
|
||||
apprise_target_startup_enable = 0
|
||||
apprise_target_download = ""
|
||||
apprise_target_download_enable = 0
|
||||
apprise_target_pause_resume = ""
|
||||
apprise_target_pause_resume_enable = 0
|
||||
apprise_target_pp = ""
|
||||
apprise_target_pp_enable = 0
|
||||
apprise_target_complete = ""
|
||||
apprise_target_complete_enable = 1
|
||||
apprise_target_failed = ""
|
||||
apprise_target_failed_enable = 1
|
||||
apprise_target_disk_full = ""
|
||||
apprise_target_disk_full_enable = 0
|
||||
apprise_target_new_login = ""
|
||||
apprise_target_new_login_enable = 1
|
||||
apprise_target_warning = ""
|
||||
apprise_target_warning_enable = 0
|
||||
apprise_target_error = ""
|
||||
apprise_target_error_enable = 0
|
||||
apprise_target_queue_done = ""
|
||||
apprise_target_queue_done_enable = 0
|
||||
apprise_target_other = ""
|
||||
apprise_target_other_enable = 1
|
||||
@@ -0,0 +1,317 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE nzb PUBLIC "-//newzBin//DTD NZB 1.1//EN" "http://www.newzbin.com/DTD/nzb/nzb-1.1.dtd">
|
||||
<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672638" subject="reftestnzb_100MB_a82beff8e340 [01/20] - "sometestfile-100MB.part01.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739737" number="1">EkZuHcMrVxSbBbBaSuFgVbMm-1658672638813@nyuu</segment>
|
||||
<segment bytes="739573" number="2">HeTrDnAcEoFtWyJhTdPkNhPe-1658672638816@nyuu</segment>
|
||||
<segment bytes="739672" number="3">OyFbRuRmVxPzXrZoKpWhJsKm-1658672638821@nyuu</segment>
|
||||
<segment bytes="739807" number="4">NwNaDvOgStUoRfVbXmZxKeQf-1658672638822@nyuu</segment>
|
||||
<segment bytes="739558" number="5">DpZgLkTwXvIePlNdDiCcEkXu-1658672638853@nyuu</segment>
|
||||
<segment bytes="739741" number="6">HlTnIiCoXaLbOyOpXyIsMjJo-1658672638855@nyuu</segment>
|
||||
<segment bytes="739665" number="7">YqLsWoYuZnHbYvCjSuZpJdQx-1658672638856@nyuu</segment>
|
||||
<segment bytes="739603" number="8">TgRzBeNrGuQhTxIdLbZgGnNv-1658672638857@nyuu</segment>
|
||||
<segment bytes="739514" number="9">BtUmYfDwAaSdWgRnWjKfRkMl-1658672638862@nyuu</segment>
|
||||
<segment bytes="739612" number="10">EoUoUdSxYgIhVlQrPpMtHzFg-1658672638883@nyuu</segment>
|
||||
<segment bytes="739650" number="11">MeEhBmZsBzSqEtZcFzLqUwMr-1658672639406@nyuu</segment>
|
||||
<segment bytes="739796" number="12">VwBfZmSuHdVuUfJsUnCiKgAl-1658672639428@nyuu</segment>
|
||||
<segment bytes="739593" number="13">IfJyWnIgPhKkFvEmSqQiQzSd-1658672639461@nyuu</segment>
|
||||
<segment bytes="739576" number="14">LiRyAkTyOwRkVzMnJpAzJlQr-1658672639469@nyuu</segment>
|
||||
<segment bytes="464856" number="15">PwMjYlAzKaMbOcWjGrNhLvNc-1658672639479@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672639" subject="reftestnzb_100MB_a82beff8e340 [02/20] - "sometestfile-100MB.part02.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739650" number="1">UtJrLhPwPvHrZjTvUjZrCpKw-1658672639558@nyuu</segment>
|
||||
<segment bytes="739528" number="2">OzChWnChCwAeBsHrBvKvPcGr-1658672639883@nyuu</segment>
|
||||
<segment bytes="739692" number="3">VsLoYaHzQfOmDgNdPnTzPzLx-1658672639939@nyuu</segment>
|
||||
<segment bytes="739648" number="4">FkDiOcGkYxNwOgLrJaWcShKy-1658672640012@nyuu</segment>
|
||||
<segment bytes="739652" number="5">OyGyQxWoHmMxHzPaRdGeMlXz-1658672640035@nyuu</segment>
|
||||
<segment bytes="739643" number="6">NiFsKxNsWjCxXxQfPtNmGyOw-1658672640039@nyuu</segment>
|
||||
<segment bytes="739840" number="7">JqMdEoVhTaRtAdLeYfAeCvRi-1658672640078@nyuu</segment>
|
||||
<segment bytes="739454" number="8">NkOnBwJtHjBoMlUkHjHrNdGo-1658672640324@nyuu</segment>
|
||||
<segment bytes="739842" number="9">QwQnXeMrZjKiJoCbPuSbMjPq-1658672640398@nyuu</segment>
|
||||
<segment bytes="739521" number="10">ZiPvOsXwOjTqIjLnIrKbBdXv-1658672640437@nyuu</segment>
|
||||
<segment bytes="739598" number="11">DwJyDjVzBhKxRyLxIxXqRfHp-1658672640521@nyuu</segment>
|
||||
<segment bytes="739624" number="12">ArRbXcZqRaDhAgRsNmMsGeBh-1658672640545@nyuu</segment>
|
||||
<segment bytes="739597" number="13">UvYkRiCmXfBoIrYxCdEjMtJw-1658672640563@nyuu</segment>
|
||||
<segment bytes="739709" number="14">TqHaSdLuNlWvAaFqZlHqZzJr-1658672640762@nyuu</segment>
|
||||
<segment bytes="464981" number="15">SdNiSzMzVdJkFtTtGtJyEcKi-1658672640859@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672640" subject="reftestnzb_100MB_a82beff8e340 [03/20] - "sometestfile-100MB.part03.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739513" number="1">ZpUwNcWlXaVzJeJcBjXmBqCu-1658672640910@nyuu</segment>
|
||||
<segment bytes="739735" number="2">AtQtJrYjNnYdVbJsXuKvXkSc-1658672640969@nyuu</segment>
|
||||
<segment bytes="739535" number="3">NzBlVfVlKtTxFaIfZxDgAfHa-1658672641009@nyuu</segment>
|
||||
<segment bytes="739683" number="4">JmBgCqStEdWlXqCdWgMtRaKh-1658672641040@nyuu</segment>
|
||||
<segment bytes="739677" number="5">XyLjFiRjRuWmHeVhHmIhIzTg-1658672641200@nyuu</segment>
|
||||
<segment bytes="739566" number="6">IvKxDaYbZkGhMsJiZcXtMhFk-1658672641311@nyuu</segment>
|
||||
<segment bytes="739678" number="7">YuGtYkBnWtRiLdMeUrRxIbId-1658672641377@nyuu</segment>
|
||||
<segment bytes="739671" number="8">GcNoBzAgAhSjJbQkFyKuKbAj-1658672641454@nyuu</segment>
|
||||
<segment bytes="739500" number="9">RlLtFgVnBfWgJpPwTtPoJjLf-1658672641491@nyuu</segment>
|
||||
<segment bytes="739776" number="10">BxLxScLaVfDoNmDwRkMbUxPg-1658672641517@nyuu</segment>
|
||||
<segment bytes="739754" number="11">LhVnUdRnVqFyUiThZyIrNfMt-1658672641649@nyuu</segment>
|
||||
<segment bytes="739497" number="12">VoVyMbIvAuZnWhZoEqVuRpJd-1658672641769@nyuu</segment>
|
||||
<segment bytes="739614" number="13">GnMqIqUjMgVlPsZgAuLlAtGs-1658672641851@nyuu</segment>
|
||||
<segment bytes="739546" number="14">YzPbErKpMvWhVgMiNgJoRmNa-1658672641933@nyuu</segment>
|
||||
<segment bytes="465012" number="15">FaSoKgLtJxPsOsCyPuWgNzEe-1658672641972@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672642" subject="reftestnzb_100MB_a82beff8e340 [04/20] - "sometestfile-100MB.part04.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739583" number="1">PoGtRtIyThRwHgJbSrJhXuNq-1658672642009@nyuu</segment>
|
||||
<segment bytes="739617" number="2">CnRbGgFpMxKxKyKtMmZbSuSa-1658672642085@nyuu</segment>
|
||||
<segment bytes="739565" number="3">NrSeRjXxKaBqWnZaDzMiAmBi-1658672642217@nyuu</segment>
|
||||
<segment bytes="739533" number="4">WwMyBrUwInZkYjUfIsDkLkEr-1658672642300@nyuu</segment>
|
||||
<segment bytes="739641" number="5">DxRtSoVyQrChKqSySdPoDvGn-1658672642384@nyuu</segment>
|
||||
<segment bytes="739722" number="6">SaMlZuQzOiHtNpUcOzRqAkOw-1658672642443@nyuu</segment>
|
||||
<segment bytes="739655" number="7">RuGaHaWfEsAeGpLwLzQpFdOd-1658672642476@nyuu</segment>
|
||||
<segment bytes="739807" number="8">CmNcLvFdZfTjIlXcQiWdHuTe-1658672642524@nyuu</segment>
|
||||
<segment bytes="739636" number="9">UxRnTlCuZjUcIcXhAmYkDdQz-1658672642677@nyuu</segment>
|
||||
<segment bytes="739545" number="10">PvLmSwSzFzJjLoNiVdCpAqKp-1658672642735@nyuu</segment>
|
||||
<segment bytes="739607" number="11">VjVmMvDxOzXvNmOrOwGdBbVg-1658672642826@nyuu</segment>
|
||||
<segment bytes="739816" number="12">ToFnSxDePaBlQcEjViYzSdGo-1658672642903@nyuu</segment>
|
||||
<segment bytes="739556" number="13">LjHkMlYqRaQcBpVkFjCuXrHc-1658672642938@nyuu</segment>
|
||||
<segment bytes="739829" number="14">DpAmYrOyHoXkGkCfZcDiBiIn-1658672642972@nyuu</segment>
|
||||
<segment bytes="465138" number="15">DkGlMfAxEnPcPlAjIbBpNpFg-1658672643110@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672643" subject="reftestnzb_100MB_a82beff8e340 [05/20] - "sometestfile-100MB.part05.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739678" number="1">LkSeRbWaNmKoPaLxRpYcOjWf-1658672643185@nyuu</segment>
|
||||
<segment bytes="739820" number="2">MoUlMsBmZmWtBkFzJtRuYcJy-1658672643272@nyuu</segment>
|
||||
<segment bytes="739746" number="3">QmYmNyEiSmVgSqVaQmEvXuBh-1658672643358@nyuu</segment>
|
||||
<segment bytes="739763" number="4">EfHzOhLcXqKfCwRiKcAmYdCf-1658672643403@nyuu</segment>
|
||||
<segment bytes="739669" number="5">YpBuJtUmPlDvLiPpKlPoTcZg-1658672643425@nyuu</segment>
|
||||
<segment bytes="739657" number="6">PmGyAhWxVtBiOcOhGaIwSxRd-1658672643555@nyuu</segment>
|
||||
<segment bytes="739728" number="7">HaSrHrMhKqHxDdDcYqNkUdEp-1658672643632@nyuu</segment>
|
||||
<segment bytes="739449" number="8">QcDnQmPkMiHuWqIrHsRqWaYu-1658672643725@nyuu</segment>
|
||||
<segment bytes="739583" number="9">SjThZtNpPqLlReTpKlQpTwBk-1658672643799@nyuu</segment>
|
||||
<segment bytes="739761" number="10">KjMnVtRmOgJjYnYeQuLdRkNd-1658672643832@nyuu</segment>
|
||||
<segment bytes="739545" number="11">UeIdYsQtLaKkDqJpFjTrAjSp-1658672643871@nyuu</segment>
|
||||
<segment bytes="739505" number="12">LbVrYoMpQzDbNzXbAdNjEvAo-1658672643996@nyuu</segment>
|
||||
<segment bytes="739686" number="13">QdUwTpUvVhBtDaGvYsTrIuAt-1658672644078@nyuu</segment>
|
||||
<segment bytes="739756" number="14">FtFgNeYsBoLzImYqDbGfSdQf-1658672644160@nyuu</segment>
|
||||
<segment bytes="464954" number="15">OsQiRhKjTyYwAwUaGmUpUlLe-1658672644258@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672644" subject="reftestnzb_100MB_a82beff8e340 [06/20] - "sometestfile-100MB.part06.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739712" number="1">HwJpGfDtJuCiCnLgCuTxAgOe-1658672644335@nyuu</segment>
|
||||
<segment bytes="739540" number="2">DxAdNjPhGcJhNzQeYlJjYaAj-1658672644340@nyuu</segment>
|
||||
<segment bytes="739778" number="3">UsXkQuGsJuQgSpZzAqYqDhPm-1658672644365@nyuu</segment>
|
||||
<segment bytes="739697" number="4">OvDlAlUaWpZuIvImBcInGxZm-1658672644530@nyuu</segment>
|
||||
<segment bytes="739712" number="5">JrGfSbDkHsIrIiZzLpWuJzZj-1658672644599@nyuu</segment>
|
||||
<segment bytes="739687" number="6">TfYyCsKwKgUfYvXlZwGdLuJx-1658672644713@nyuu</segment>
|
||||
<segment bytes="739592" number="7">MgQiXjLvFpEqNdIoMxEkPoJb-1658672644783@nyuu</segment>
|
||||
<segment bytes="739813" number="8">TbBhHaXgToWiTjBkTvPfVjSf-1658672644805@nyuu</segment>
|
||||
<segment bytes="739419" number="9">NpZaHwXwIrZdCeQfBfJuZhVm-1658672644831@nyuu</segment>
|
||||
<segment bytes="739735" number="10">EvDjIqRhNmFzYsTqFxUfLmJo-1658672644945@nyuu</segment>
|
||||
<segment bytes="739727" number="11">YcAnJpKgSmDmTrExKtClJiJw-1658672645044@nyuu</segment>
|
||||
<segment bytes="739698" number="12">UdPfUkYtQqEfBiYsHeJnBoFv-1658672645173@nyuu</segment>
|
||||
<segment bytes="739628" number="13">GjXmLsWnXvQuOhZrXuFaQsWt-1658672645264@nyuu</segment>
|
||||
<segment bytes="739599" number="14">YoEsZaVnNrElAnIoBdZkEsUl-1658672645310@nyuu</segment>
|
||||
<segment bytes="465020" number="15">FiDqTcLyQjSiMoKwNkJcOpKu-1658672645321@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672645" subject="reftestnzb_100MB_a82beff8e340 [07/20] - "sometestfile-100MB.part07.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739612" number="1">BcCnTfTbAqSiUoOkJyMhOpGx-1658672645395@nyuu</segment>
|
||||
<segment bytes="739836" number="2">QsClUcDuHqEcDiTpOdGfDgFs-1658672645483@nyuu</segment>
|
||||
<segment bytes="739582" number="3">EsXdXwNbZnIsFhPxQkKhSaHj-1658672645616@nyuu</segment>
|
||||
<segment bytes="739722" number="4">XuDkCuXkIkJsIjAdKsTkLqWq-1658672645751@nyuu</segment>
|
||||
<segment bytes="739828" number="5">PfEjJjTjHiCsXqDxYiBcUxCw-1658672645777@nyuu</segment>
|
||||
<segment bytes="739674" number="6">IrGgIuRlScWcZwCtDjTrQdKy-1658672645805@nyuu</segment>
|
||||
<segment bytes="739612" number="7">LwVpWrWgUjSuHyLqXoZrMaKb-1658672645859@nyuu</segment>
|
||||
<segment bytes="739769" number="8">VxLjOjYzKeEoGxUgBeIwSfFb-1658672645927@nyuu</segment>
|
||||
<segment bytes="739698" number="9">OaQySkXmXlTnWbSoLkOmExIn-1658672646084@nyuu</segment>
|
||||
<segment bytes="739756" number="10">JmAsMqGcGaVsPiUcRbWqScYh-1658672646171@nyuu</segment>
|
||||
<segment bytes="739526" number="11">EcOcFyCdIaJvOnNjYkMgTyLc-1658672646251@nyuu</segment>
|
||||
<segment bytes="739937" number="12">JdTfZrWbKhYyPpCuOkEuUpWs-1658672646289@nyuu</segment>
|
||||
<segment bytes="739633" number="13">CtZkDeFoUhJyXeTsOxQcPfEg-1658672646332@nyuu</segment>
|
||||
<segment bytes="739722" number="14">ArAjLtAfYtVjRxHwPeTfSsPw-1658672646366@nyuu</segment>
|
||||
<segment bytes="465061" number="15">CmKcCzHnYhEqUxMoOyPwNgUe-1658672646549@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672646" subject="reftestnzb_100MB_a82beff8e340 [08/20] - "sometestfile-100MB.part08.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739536" number="1">ExOtMnHzYeTeXbPrQpMqQcEd-1658672646626@nyuu</segment>
|
||||
<segment bytes="739547" number="2">FvHhUgEeOuOmZuJrOeOdCmBp-1658672646690@nyuu</segment>
|
||||
<segment bytes="739691" number="3">OgIaDbSgOmMjGkNgYzAvOzEa-1658672646751@nyuu</segment>
|
||||
<segment bytes="739690" number="4">LxTeXnRpDpQsMjQxIpRzFfQo-1658672646793@nyuu</segment>
|
||||
<segment bytes="739819" number="5">VcToYzYiJqAwGvCmMiVsGqNj-1658672646834@nyuu</segment>
|
||||
<segment bytes="739598" number="6">XhBxFwNzQdHkIoGcHdMeDeSo-1658672646992@nyuu</segment>
|
||||
<segment bytes="739658" number="7">BmQxAvJfEwEdKzVoMxCoVmIr-1658672647067@nyuu</segment>
|
||||
<segment bytes="739732" number="8">PxYzIiRmDrSdFmKaSfTtQrWp-1658672647129@nyuu</segment>
|
||||
<segment bytes="739867" number="9">PnSpIsPeHgAfThOjXyOpNlCo-1658672647192@nyuu</segment>
|
||||
<segment bytes="739740" number="10">YfHjYuKnUvRlBnKdDqOoGnKo-1658672647224@nyuu</segment>
|
||||
<segment bytes="739780" number="11">WpPkPhPeXsBiMkAkUcEcZuQg-1658672647285@nyuu</segment>
|
||||
<segment bytes="739649" number="12">SlOeJlNtDjHqTuEkAeNxMdDk-1658672647439@nyuu</segment>
|
||||
<segment bytes="739899" number="13">AdCgCfRmEvZkRzXgCoLrHfGa-1658672647508@nyuu</segment>
|
||||
<segment bytes="739666" number="14">JrQhNuXmRiLjRaBvNlBzMgAd-1658672647568@nyuu</segment>
|
||||
<segment bytes="464911" number="15">XiYcGuBtZdChJfIeKeYwAsHy-1658672647654@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672647" subject="reftestnzb_100MB_a82beff8e340 [09/20] - "sometestfile-100MB.part09.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739437" number="1">YnIxYaQuEcKvMpSyYnEeAvVn-1658672647688@nyuu</segment>
|
||||
<segment bytes="739623" number="2">XrFdJwZqRcRtHnIcDnEzCoMc-1658672647743@nyuu</segment>
|
||||
<segment bytes="739486" number="3">WfRuRmRrPwGgKwZhPaAmNpOn-1658672647970@nyuu</segment>
|
||||
<segment bytes="739521" number="4">PkCrNtOrEvNvRoPaDuAuGqSf-1658672648003@nyuu</segment>
|
||||
<segment bytes="739505" number="5">KsFvGlAdBjRfTlZhVxEuWxJm-1658672648029@nyuu</segment>
|
||||
<segment bytes="739627" number="6">ZnIxXyHkDpGpBkLlPkQnHwWt-1658672648104@nyuu</segment>
|
||||
<segment bytes="739623" number="7">RhVuUwDpCoRiUoNzUpOpFrWp-1658672648150@nyuu</segment>
|
||||
<segment bytes="739569" number="8">JpYiPxYmAaClCuXtYwLcTkHb-1658672648199@nyuu</segment>
|
||||
<segment bytes="739753" number="9">GiSuQeEyMkMeVaJmReDyKgVt-1658672648484@nyuu</segment>
|
||||
<segment bytes="739515" number="10">LrFxMoDtJaNcEiLgDxQoFgIq-1658672648486@nyuu</segment>
|
||||
<segment bytes="739602" number="11">BfUcDaUxLsNrZzAvBrLcRdWw-1658672648524@nyuu</segment>
|
||||
<segment bytes="739622" number="12">MgHqJlXjFsEmGtCuCvTrMfDl-1658672648565@nyuu</segment>
|
||||
<segment bytes="739802" number="13">DgZiJzKyBoGaWvXrRcRfPbZy-1658672648642@nyuu</segment>
|
||||
<segment bytes="739798" number="14">SvAwVvXyWaFgPkAwAxLmAoVe-1658672648661@nyuu</segment>
|
||||
<segment bytes="464943" number="15">UwZyZtBxBlZdQnIbMkAmFrUx-1658672648994@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [11/20] - "sometestfile-100MB.part11.rar" yEnc (1/1) 1745">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="1965" number="1">MzGbXzBiDkQkCkPjHgRfYgSt-1658672650132@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672649" subject="reftestnzb_100MB_a82beff8e340 [10/20] - "sometestfile-100MB.part10.rar" yEnc (1/15) 10485760">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739508" number="1">QbGaVgPmAxUeYtGfRrNaCgPi-1658672649037@nyuu</segment>
|
||||
<segment bytes="739728" number="2">TlYaEwZcLkMxXiVlWnXbBhCo-1658672649043@nyuu</segment>
|
||||
<segment bytes="739729" number="3">JvFuObAaRgTpRdAaNsBnUjSf-1658672649046@nyuu</segment>
|
||||
<segment bytes="739717" number="4">ViJqMxYcZuCzRiXqZqPyXhVl-1658672649105@nyuu</segment>
|
||||
<segment bytes="739773" number="5">QaYyAkGsSmRwGwWlYwOcIdCh-1658672649138@nyuu</segment>
|
||||
<segment bytes="739801" number="6">KdLcItOyTuDfZlFvDgFoGjLx-1658672649505@nyuu</segment>
|
||||
<segment bytes="739517" number="7">DwEiPdQdSdKbYjQzSpCtNnBp-1658672649527@nyuu</segment>
|
||||
<segment bytes="739823" number="8">JjZfPzJoYrSnSqOzLfQdLaJe-1658672649559@nyuu</segment>
|
||||
<segment bytes="739682" number="9">JpAdAoOiWbLlElNnXyZqUrZk-1658672649591@nyuu</segment>
|
||||
<segment bytes="739581" number="10">KcXsPhOqSmVlImNiAaBxOeDg-1658672649593@nyuu</segment>
|
||||
<segment bytes="739466" number="11">OcKeQsZvSvCzZzGkSyYaIwLe-1658672649621@nyuu</segment>
|
||||
<segment bytes="739695" number="12">TpSgWsQbCxSvRhCpNeVxXpQp-1658672650017@nyuu</segment>
|
||||
<segment bytes="739642" number="13">FgLlWiOgZsXwZbDiUfRlUbAh-1658672650037@nyuu</segment>
|
||||
<segment bytes="739797" number="14">DpWgTfSaHsEyDoRwKmMrHlNg-1658672650077@nyuu</segment>
|
||||
<segment bytes="464958" number="15">ApWcZeAvJfEfArBnTqKaAlTi-1658672650131@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [12/20] - "sometestfile-100MB.par2" yEnc (1/1) 42740">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="44186" number="1">GiCuNqThWxXhQtBdIpCuSpTu-1658672650163@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [13/20] - "sometestfile-100MB.vol000+001.par2" yEnc (1/1) 95504">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="98613" number="1">XwSuKgDcYgYbThUtGbFnYlMr-1658672650478@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [14/20] - "sometestfile-100MB.vol001+002.par2" yEnc (1/1) 148268">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="153068" number="1">XtIpShElZfGwAxEdWcWmMoSg-1658672650533@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [15/20] - "sometestfile-100MB.vol003+004.par2" yEnc (1/1) 296420">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="305933" number="1">YjBcYcXxFoSmTdKrXbQaVcEc-1658672650555@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [16/20] - "sometestfile-100MB.vol007+008.par2" yEnc (1/1) 550100">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="567518" number="1">YrBmEjHwHgWrGpWwWvHnZsNr-1658672650628@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [17/20] - "sometestfile-100MB.vol015+016.par2" yEnc (1/2) 1014836">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739515" number="1">HmLkJbFaPwGnDeHoAsUeWvOx-1658672650641@nyuu</segment>
|
||||
<segment bytes="307638" number="2">BgMgXvTfPmQpMeIxMrVbSbWb-1658672650648@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672650" subject="reftestnzb_100MB_a82beff8e340 [18/20] - "sometestfile-100MB.vol031+032.par2" yEnc (1/3) 1901684">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739626" number="1">InAlBjYtHfRoZbUcLbLjVwGg-1658672650925@nyuu</segment>
|
||||
<segment bytes="739341" number="2">UeLxHaYaYrGcWoApMiIeUcFc-1658672650940@nyuu</segment>
|
||||
<segment bytes="482988" number="3">TmTyRsQpWjLvJoZtYvDxKfDk-1658672650960@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672651" subject="reftestnzb_100MB_a82beff8e340 [19/20] - "sometestfile-100MB.vol063+064.par2" yEnc (1/6) 3632756">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739390" number="1">AoFjKyMxTlDpZtDzHyJtFaVt-1658672651017@nyuu</segment>
|
||||
<segment bytes="739605" number="2">WbUyNnEyReLnSdNwBsVqVfVc-1658672651029@nyuu</segment>
|
||||
<segment bytes="739539" number="3">FaXfJfTqWuIiMvTlGdOfGgKi-1658672651035@nyuu</segment>
|
||||
<segment bytes="739543" number="4">NeEnAwTqVeRoJuStBvPhSsCf-1658672651324@nyuu</segment>
|
||||
<segment bytes="739399" number="5">FeFfMtWjQyDkIcPaPnFnTvZl-1658672651372@nyuu</segment>
|
||||
<segment bytes="50461" number="6">JxFgMzBwLqVoRcPuJzHoSgFy-1658672651406@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
<file poster="blablamannetje <blabla@example.com>" date="1658672651" subject="reftestnzb_100MB_a82beff8e340 [20/20] - "sometestfile-100MB.vol127+072.par2" yEnc (1/6) 4054868">
|
||||
<groups>
|
||||
<group>alt.binaries.test</group>
|
||||
</groups>
|
||||
<segments>
|
||||
<segment bytes="739529" number="1">ZrZzDkZqMlGxTlXsOxZzWkFy-1658672651436@nyuu</segment>
|
||||
<segment bytes="739638" number="2">EkIfIsZtKbFcHyLtEiOvCgUe-1658672651500@nyuu</segment>
|
||||
<segment bytes="739479" number="3">FdAlCsPqQgToRlEcZxCzHhFu-1658672651528@nyuu</segment>
|
||||
<segment bytes="739655" number="4">OnYrJuAaClWaDjEdFmYoDaKt-1658672651727@nyuu</segment>
|
||||
<segment bytes="739624" number="5">TsJbMqVtYcIaGqEvShTyEhWf-1658672651793@nyuu</segment>
|
||||
<segment bytes="485771" number="6">UbNvVcQoDxAfCiPsEqFfGkDu-1658672651860@nyuu</segment>
|
||||
</segments>
|
||||
</file>
|
||||
</nzb>
|
||||
Reference in New Issue
Block a user