Compare commits

..

1 Commits

Author SHA1 Message Date
Bakerboy448
5e15054329 Avistaz response improvements 2023-01-05 15:40:22 -06:00
351 changed files with 7269 additions and 12025 deletions

View File

@@ -117,6 +117,7 @@ dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = suggestion
dotnet_diagnostic.CA1014.severity = suggestion
dotnet_diagnostic.CA1016.severity = suggestion
dotnet_diagnostic.CA1017.severity = suggestion
dotnet_diagnostic.CA1018.severity = suggestion
@@ -162,7 +163,6 @@ dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1419.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
@@ -178,6 +178,9 @@ dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
@@ -189,14 +192,13 @@ dotnet_diagnostic.CA1819.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1824.severity = suggestion
dotnet_diagnostic.CA1835.severity = suggestion
dotnet_diagnostic.CA1845.severity = suggestion
dotnet_diagnostic.CA1848.severity = suggestion
dotnet_diagnostic.CA1849.severity = suggestion
dotnet_diagnostic.CA2000.severity = suggestion
dotnet_diagnostic.CA2002.severity = suggestion
dotnet_diagnostic.CA2007.severity = suggestion
dotnet_diagnostic.CA2008.severity = suggestion
dotnet_diagnostic.CA2009.severity = suggestion
dotnet_diagnostic.CA2010.severity = suggestion
dotnet_diagnostic.CA2011.severity = suggestion
dotnet_diagnostic.CA2012.severity = suggestion
dotnet_diagnostic.CA2013.severity = suggestion
dotnet_diagnostic.CA2100.severity = suggestion
@@ -227,7 +229,6 @@ dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2244.severity = suggestion
dotnet_diagnostic.CA2245.severity = suggestion
dotnet_diagnostic.CA2246.severity = suggestion
dotnet_diagnostic.CA2254.severity = suggestion
dotnet_diagnostic.CA3061.severity = suggestion
dotnet_diagnostic.CA3075.severity = suggestion
dotnet_diagnostic.CA3076.severity = suggestion
@@ -254,7 +255,6 @@ dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion
dotnet_diagnostic.CA5401.severity = suggestion
dotnet_diagnostic.SYSLIB0014.severity = none

41
.github/workflows/azuresync.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
concurrency: azuresync-${{ github.event.issue.number }}
jobs:
alert:
runs-on: ubuntu-latest
steps:
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Prowlarr"
ado_wit: "Bug"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Prowlarr"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.3.1'
majorVersion: '1.1.0'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.405'
dotnetVersion: '6.0.404'
innoVersion: '6.2.0'
nodeVersion: '16.x'
windowsImage: 'windows-2022'

View File

@@ -39,7 +39,6 @@ module.exports = {
plugins: [
'filenames',
'react',
'react-hooks',
'simple-import-sort',
'import'
],
@@ -309,9 +308,7 @@ module.exports = {
'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2,
'react/sort-comp': 2,
'react/jsx-wrap-multilines': 2,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
'react/jsx-wrap-multilines': 2
},
overrides: [
{

View File

@@ -142,8 +142,8 @@ module.exports = (env) => {
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',

View File

@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import React, { Fragment, useCallback, useEffect } from 'react';
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
@@ -19,8 +19,7 @@ function createMapStateToProps() {
function ApplyTheme({ theme, children }) {
// Update the CSS Variables
const updateCSSVariables = useCallback(() => {
function updateCSSVariables() {
const arrayOfVariableKeys = Object.keys(themes[theme]);
const arrayOfVariableValues = Object.values(themes[theme]);
@@ -32,12 +31,12 @@ function ApplyTheme({ theme, children }) {
arrayOfVariableValues[index]
);
});
}, [theme]);
}
// On Component Mount and Component Update
useEffect(() => {
updateCSSVariables(theme);
}, [updateCSSVariables, theme]);
}, [theme]);
return <Fragment>{children}</Fragment>;
}

View File

@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
<div className={styles.addButtonContainer}>
<Button onPress={onAddCustomFilter}>
{translate('AddCustomFilter')}
Add Custom Filter
</Button>
</div>
</ModalBody>

View File

@@ -30,10 +30,10 @@ function ConfirmModal(props) {
useEffect(() => {
if (isOpen) {
bindShortcut('enter', onConfirm);
return () => unbindShortcut('enter', onConfirm);
} else {
unbindShortcut('enter', onConfirm);
}
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
}, [onConfirm]);
return (
<Modal

View File

@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
} = this.state;
const applyTagsOptions = [
{ key: 'add', value: translate('Add') },
{ key: 'remove', value: translate('Remove') },
{ key: 'replace', value: translate('Replace') }
{ key: 'add', value: 'Add' },
{ key: 'remove', value: 'Remove' },
{ key: 'replace', value: 'Replace' }
];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('Tags')}
Tags
</ModalHeader>
<ModalBody>

View File

@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Status')}
Status
</SortMenuItem>
<SortMenuItem
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Priority')}
{'Priority'}
</SortMenuItem>
<SortMenuItem
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Protocol')}
{'Protocol'}
</SortMenuItem>
<SortMenuItem
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Privacy')}
{'Privacy'}
</SortMenuItem>
</MenuContent>
</SortMenu>

View File

@@ -79,7 +79,6 @@ class IndexerIndexRow extends Component {
privacy,
priority,
status,
fields,
appProfile,
added,
capabilities,
@@ -97,8 +96,6 @@ class IndexerIndexRow extends Component {
isIndexerInfoModalOpen
} = this.state;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
return (
<>
{
@@ -248,12 +245,12 @@ class IndexerIndexRow extends Component {
/>
{
baseUrl ?
indexerUrls ?
<IconButton
className={styles.externalLink}
name={icons.EXTERNAL_LINK}
title={translate('Website')}
to={baseUrl.replace(/(:\/\/)api\./, '$1')}
to={indexerUrls[0].replace('api.', '')}
/> : null
}
@@ -302,7 +299,6 @@ IndexerIndexRow.propTypes = {
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
redirect: PropTypes.bool.isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
appProfile: PropTypes.object.isRequired,
status: PropTypes.object,
capabilities: PropTypes.object,

View File

@@ -20,14 +20,11 @@ function IndexerInfoModalContent(props) {
encoding,
language,
indexerUrls,
fields,
protocol,
capabilities,
onModalClose
} = props;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -60,7 +57,7 @@ function IndexerInfoModalContent(props) {
/>
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={baseUrl}>{baseUrl}</Link>
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
@@ -117,7 +114,6 @@ IndexerInfoModalContent.propTypes = {
encoding: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired,
capabilities: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -71,19 +71,6 @@ class SearchIndexRow extends Component {
});
};
onSavePress = () => {
const {
downloadUrl,
fileName,
onSavePress
} = this.props;
onSavePress({
downloadUrl,
fileName
});
};
//
// Render
@@ -98,6 +85,7 @@ class SearchIndexRow extends Component {
publishDate,
title,
infoUrl,
downloadUrl,
indexer,
size,
files,
@@ -312,7 +300,7 @@ class SearchIndexRow extends Component {
className={styles.downloadLink}
name={icons.SAVE}
title={translate('Save')}
onPress={this.onSavePress}
to={downloadUrl}
/>
</VirtualTableRowCell>
);
@@ -335,7 +323,6 @@ SearchIndexRow.propTypes = {
ageMinutes: PropTypes.number.isRequired,
publishDate: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
fileName: PropTypes.string.isRequired,
infoUrl: PropTypes.string.isRequired,
downloadUrl: PropTypes.string.isRequired,
indexerId: PropTypes.number.isRequired,
@@ -348,7 +335,6 @@ SearchIndexRow.propTypes = {
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onGrabPress: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
isGrabbing: PropTypes.bool.isRequired,
isGrabbed: PropTypes.bool.isRequired,
grabError: PropTypes.string,

View File

@@ -51,8 +51,7 @@ class SearchIndexTable extends Component {
timeFormat,
selectedState,
onSelectedChange,
onGrabPress,
onSavePress
onGrabPress
} = this.props;
const release = items[rowIndex];
@@ -72,7 +71,6 @@ class SearchIndexTable extends Component {
longDateFormat={longDateFormat}
timeFormat={timeFormat}
onGrabPress={onGrabPress}
onSavePress={onSavePress}
/>
</VirtualTableRow>
);
@@ -136,7 +134,6 @@ SearchIndexTable.propTypes = {
timeFormat: PropTypes.string.isRequired,
onSortPress: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired,
selectedState: PropTypes.object.isRequired,

View File

@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { grabRelease, saveRelease, setReleasesSort } from 'Store/Actions/releaseActions';
import { grabRelease, setReleasesSort } from 'Store/Actions/releaseActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import SearchIndexTable from './SearchIndexTable';
@@ -25,9 +25,6 @@ function createMapDispatchToProps(dispatch, props) {
},
onGrabPress(payload) {
dispatch(grabRelease(payload));
},
onSavePress(payload) {
dispatch(saveRelease(payload));
}
};
}

View File

@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('AddApplication')}
Add Application
</ModalHeader>
<ModalBody>

View File

@@ -71,14 +71,14 @@ class Application extends Component {
{
syncLevel === 'addOnly' &&
<Label kind={kinds.WARNING}>
{translate('AddRemoveOnly')}
Add and Remove Only
</Label>
}
{
syncLevel === 'fullSync' &&
<Label kind={kinds.SUCCESS}>
{translate('FullSync')}
Full Sync
</Label>
}
@@ -88,7 +88,7 @@ class Application extends Component {
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
Disabled
</Label>
}

View File

@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
Disabled
</Label> :
null
}

View File

@@ -56,9 +56,17 @@ class Notification extends Component {
id,
name,
onGrab,
onDownload,
onUpgrade,
onRename,
onDelete,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
supportsOnRename,
supportsOnDelete,
supportsOnHealthIssue,
supportsOnApplicationUpdate
} = this.props;
@@ -80,6 +88,34 @@ class Notification extends Component {
</Label>
}
{
supportsOnDelete && onDelete &&
<Label kind={kinds.SUCCESS}>
{translate('OnDelete')}
</Label>
}
{
supportsOnDownload && onDownload &&
<Label kind={kinds.SUCCESS}>
{translate('OnImport')}
</Label>
}
{
supportsOnUpgrade && onDownload && onUpgrade &&
<Label kind={kinds.SUCCESS}>
{translate('OnUpgrade')}
</Label>
}
{
supportsOnRename && onRename &&
<Label kind={kinds.SUCCESS}>
{translate('OnRename')}
</Label>
}
{
supportsOnHealthIssue && onHealthIssue &&
<Label kind={kinds.SUCCESS}>
@@ -96,7 +132,7 @@ class Notification extends Component {
}
{
!onGrab && !onHealthIssue && !onApplicationUpdate ?
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete && !onApplicationUpdate ?
<Label
kind={kinds.DISABLED}
outline={true}
@@ -131,9 +167,17 @@ Notification.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
onGrab: PropTypes.bool.isRequired,
onDownload: PropTypes.bool.isRequired,
onUpgrade: PropTypes.bool.isRequired,
onRename: PropTypes.bool.isRequired,
onDelete: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired,
supportsOnDelete: PropTypes.bool.isRequired,
supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired

View File

@@ -15,11 +15,8 @@ function NotificationEventItems(props) {
} = props;
const {
onGrab,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
includeManualGrabs,
supportsOnHealthIssue,
includeHealthWarnings,
supportsOnApplicationUpdate
@@ -34,31 +31,6 @@ function NotificationEventItems(props) {
link="https://wiki.servarr.com/prowlarr/settings#connections"
/>
<div className={styles.events}>
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onGrab"
helpText={translate('OnGrabHelpText')}
isDisabled={!supportsOnGrab.value}
{...onGrab}
onChange={onInputChange}
/>
</div>
{
onGrab.value &&
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="includeManualGrabs"
helpText={translate('IncludeManualGrabsHelpText')}
isDisabled={!supportsOnGrab.value}
{...includeManualGrabs}
onChange={onInputChange}
/>
</div>
}
<div>
<FormInputGroup
type={inputTypes.CHECK}

View File

@@ -20,7 +20,7 @@ function PendingChangesModal(props) {
useEffect(() => {
bindShortcut('enter', onConfirm);
}, [bindShortcut, onConfirm]);
}, [onConfirm]);
return (
<Modal

View File

@@ -103,6 +103,9 @@ export default {
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
selectedSchema.onRename = selectedSchema.supportsOnRename;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema;

View File

@@ -54,7 +54,7 @@ export const defaultState = {
},
{
name: 'grabTitle',
label: translate('GrabTitle'),
label: translate('Grab Title'),
isSortable: false,
isVisible: false
},
@@ -78,7 +78,7 @@ export const defaultState = {
},
{
name: 'elapsedTime',
label: translate('ElapsedTime'),
label: translate('Elapsed Time'),
isSortable: false,
isVisible: true
},

View File

@@ -1,4 +1,3 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
@@ -230,7 +229,6 @@ export const CANCEL_FETCH_RELEASES = 'releases/cancelFetchReleases';
export const SET_RELEASES_SORT = 'releases/setReleasesSort';
export const CLEAR_RELEASES = 'releases/clearReleases';
export const GRAB_RELEASE = 'releases/grabRelease';
export const SAVE_RELEASE = 'releases/saveRelease';
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
export const UPDATE_RELEASE = 'releases/updateRelease';
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
@@ -245,7 +243,6 @@ export const cancelFetchReleases = createThunk(CANCEL_FETCH_RELEASES);
export const setReleasesSort = createAction(SET_RELEASES_SORT);
export const clearReleases = createAction(CLEAR_RELEASES);
export const grabRelease = createThunk(GRAB_RELEASE);
export const saveRelease = createThunk(SAVE_RELEASE);
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
export const updateRelease = createAction(UPDATE_RELEASE);
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
@@ -307,32 +304,6 @@ export const actionHandlers = handleThunks({
});
},
[SAVE_RELEASE]: function(getState, payload, dispatch) {
const link = payload.downloadUrl;
const file = payload.fileName;
$.ajax({
url: link,
method: 'GET',
headers: {
'X-Prowlarr-Client': true
},
xhrFields: {
responseType: 'blob'
},
success: function(data) {
const a = document.createElement('a');
const url = window.URL.createObjectURL(data);
a.href = url;
a.download = file;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
});
},
[BULK_GRAB_RELEASES]: function(getState, payload, dispatch) {
dispatch(set({
section,

View File

@@ -25,7 +25,7 @@ const columns = [
},
{
name: 'size',
label: translate('Size'),
label: 'Size',
isVisible: true
},
{

View File

@@ -113,7 +113,7 @@ class Updates extends Component {
/>
<div className={styles.message}>
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
The latest version of Prowlarr is already installed
</div>
{

View File

@@ -16,11 +16,6 @@ function addApiKey(ajaxOptions) {
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
}
function addUIHeader(ajaxOptions) {
ajaxOptions.headers = ajaxOptions.headers || {};
ajaxOptions.headers['X-Prowlarr-Client'] = true;
}
function addContentType(ajaxOptions) {
if (
ajaxOptions.contentType == null &&
@@ -47,7 +42,6 @@ export default function createAjaxRequest(originalAjaxOptions) {
if (isRelative(ajaxOptions)) {
addRootUrl(ajaxOptions);
addApiKey(ajaxOptions);
addUIHeader(ajaxOptions);
addContentType(ajaxOptions);
}

View File

@@ -11,8 +11,7 @@
"lint": "esprint check",
"lint-fix": "esprint check --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
"check-modules": "are-you-es5 check . -r"
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
},
"repository": "https://github.com/Prowlarr/Prowlarr",
"author": "Team Prowlarr",
@@ -31,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@microsoft/signalr": "6.0.13",
"@microsoft/signalr": "6.0.11",
"@sentry/browser": "7.28.0",
"@sentry/integrations": "7.28.0",
"chart.js": "4.1.1",
@@ -94,7 +93,6 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.13",
"babel-loader": "9.1.0",
"babel-plugin-inline-classnames": "2.0.1",
@@ -105,7 +103,6 @@
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "8.0.0",
"esprint": "3.6.0",
"file-loader": "6.2.0",

View File

@@ -1,3 +0,0 @@
is_global = true
dotnet_diagnostic.CA1014.severity = none

View File

@@ -1,7 +1,6 @@
<Project>
<!-- Common to all Prowlarr Projects -->
<PropertyGroup>
<AnalysisLevel>6.0-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget>

View File

@@ -53,26 +53,6 @@ namespace NzbDrone.Common.Test.Http
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "./relative", "relative")]
[TestCase("/", "./relative", "/relative")]
[TestCase("/base", "./relative", "/relative")]
[TestCase("/base/sub", "./relative", "/base/relative")]
[TestCase("/base/sub/", "./relative", "/base/sub/relative")]
[TestCase("base/sub", "./relative", "base/relative")]
[TestCase("base/sub/", "./relative", "base/sub/relative")]
[TestCase("", "../relative", "relative")]
[TestCase("/", "../relative", "/relative")]
[TestCase("/base", "../relative", "/relative")]
[TestCase("/base/sub", "../relative", "/base/relative")]
[TestCase("/base/sub/", "../relative", "/base/sub/relative")]
[TestCase("base/sub", "../relative", "base/relative")]
[TestCase("base/sub/", "../relative", "base/sub/relative")]
public void should_combine_uri_with_dot_segment(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath) + new HttpUri(relativePath);
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "", "")]
[TestCase("/", "", "/")]
[TestCase("base", "", "base")]

View File

@@ -24,16 +24,12 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
// Indexer and Download Client Responses
// avistaz response
[TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")]
[TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")]
[TestCase(@"""token"":""2b51db35e1910123321025a12b9933d2""")]
// animebytes response
[TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")]

View File

@@ -278,7 +278,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetUpdateClientExePath()
{
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\prowlarr_update\Prowlarr.Update".AsOsAgnostic().ProcessNameToExe());
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\prowlarr_update\Prowlarr.Update.exe".AsOsAgnostic());
}
[Test]

View File

@@ -9,7 +9,6 @@ namespace NzbDrone.Common.EnvironmentInfo
bool IsAdmin { get; }
bool IsWindowsService { get; }
bool IsWindowsTray { get; }
bool IsStarting { get; set; }
bool IsExiting { get; set; }
bool IsTray { get; }
RuntimeMode Mode { get; }

View File

@@ -2,6 +2,13 @@ using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public enum PlatformType
{
DotNet = 0,
Mono = 1,
NetCore = 2
}
public interface IPlatformInfo
{
Version Version { get; }
@@ -9,18 +16,36 @@ namespace NzbDrone.Common.EnvironmentInfo
public class PlatformInfo : IPlatformInfo
{
private static PlatformType _platform;
private static Version _version;
static PlatformInfo()
{
_platform = PlatformType.NetCore;
_version = Environment.Version;
}
public static PlatformType Platform => _platform;
public static bool IsMono => Platform == PlatformType.Mono;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static bool IsNetCore => Platform == PlatformType.NetCore;
public static string PlatformName
{
get
{
return ".NET";
if (IsDotNet)
{
return ".NET";
}
else if (IsMono)
{
return "Mono";
}
else
{
return ".NET Core";
}
}
}

View File

@@ -19,7 +19,6 @@ namespace NzbDrone.Common.EnvironmentInfo
_logger = logger;
IsWindowsService = hostLifetime is WindowsServiceLifetime;
IsStarting = true;
// net6.0 will return Radarr.dll for entry assembly, we need the actual
// executable name (Radarr on linux). On mono this will return the location of
@@ -83,7 +82,6 @@ namespace NzbDrone.Common.EnvironmentInfo
public bool IsWindowsService { get; private set; }
public bool IsStarting { get; set; }
public bool IsExiting { get; set; }
public bool IsTray
{

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Common.Extensions
var info = new FileInfo(path.Trim());
// UNC
//UNC
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
{
return info.FullName.TrimEnd('/', '\\', ' ');
@@ -166,7 +166,7 @@ namespace NzbDrone.Common.Extensions
var parentDirInfo = dirInfo.Parent;
if (parentDirInfo == null)
{
// Drive letter
//Drive letter
return dirInfo.Name.ToUpper();
}
@@ -238,9 +238,9 @@ namespace NzbDrone.Common.Extensions
return null;
}
public static string ProcessNameToExe(this string processName)
public static string ProcessNameToExe(this string processName, PlatformType runtime)
{
if (OsInfo.IsWindows)
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
{
processName += ".exe";
}
@@ -248,6 +248,11 @@ namespace NzbDrone.Common.Extensions
return processName;
}
public static string ProcessNameToExe(this string processName)
{
return processName.ProcessNameToExe(PlatformInfo.Platform);
}
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
{
return appFolderInfo.AppDataFolder;
@@ -313,9 +318,9 @@ namespace NzbDrone.Common.Extensions
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
}
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
{
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
}
public static string GetDatabase(this IAppFolderInfo appFolderInfo)

View File

@@ -131,7 +131,7 @@ namespace NzbDrone.Common.Extensions
public static string WrapInQuotes(this string text)
{
if (!text.Contains(' '))
if (!text.Contains(" "))
{
return text;
}
@@ -255,20 +255,7 @@ namespace NzbDrone.Common.Extensions
public static string ToUrlHost(this string input)
{
return input.Contains(':') ? $"[{input}]" : input;
}
public static bool IsAllDigits(this string input)
{
foreach (var c in input)
{
if (c < '0' || c > '9')
{
return false;
}
}
return true;
return input.Contains(":") ? $"[{input}]" : input;
}
}
}

View File

@@ -113,7 +113,7 @@ namespace NzbDrone.Common.Http.Dispatchers
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
}
else
{

View File

@@ -76,7 +76,6 @@ namespace NzbDrone.Common.Http
get
{
var newUrl = Headers["Location"];
if (newUrl == null)
{
newUrl = Headers["Refresh"];

View File

@@ -166,37 +166,6 @@ namespace NzbDrone.Common.Http
return relativePath;
}
if (relativePath.StartsWith("./"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
}
if (relativePath.StartsWith("../"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
var secondLastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, secondLastIndex) + "/";
}
}
var baseSlashIndex = basePath.LastIndexOf('/');
if (baseSlashIndex >= 0)

View File

@@ -11,13 +11,15 @@ namespace NzbDrone.Common.Http
{
if (response.Headers.ContainsKey("Retry-After"))
{
var retryAfter = response.Headers["Retry-After"];
var retryAfter = response.Headers["Retry-After"].ToString();
int seconds;
DateTime date;
if (int.TryParse(retryAfter, out var seconds))
if (int.TryParse(retryAfter, out seconds))
{
RetryAfter = TimeSpan.FromSeconds(seconds);
}
else if (DateTime.TryParse(retryAfter, out var date))
else if (DateTime.TryParse(retryAfter, out date))
{
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
}

View File

@@ -1,4 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NzbDrone.Common.Http
{

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
@@ -7,10 +8,10 @@ namespace NzbDrone.Common.Instrumentation
{
public class CleanseLogMessage
{
private static readonly Regex[] CleansingRules =
{
private static readonly Regex[] CleansingRules = new[]
{
// Url
new Regex(@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?&: ;])(apikey|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -20,7 +21,6 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// UNIT3D
new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -58,10 +58,9 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
};
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -8,7 +8,6 @@ using System.Threading;
using NLog;
using NLog.Common;
using NLog.Targets;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
@@ -35,14 +34,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
SQLiteErrorCode.Auth
};
private static readonly HashSet<string> FilteredPostgresErrorCodes = new HashSet<string>
{
PostgresErrorCodes.OutOfMemory,
PostgresErrorCodes.TooManyConnections,
PostgresErrorCodes.DiskFull,
PostgresErrorCodes.ProgramLimitExceeded
};
// use string and not Type so we don't need a reference to the project
// where these are defined
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
@@ -248,19 +239,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
return false;
}
var pgEx = logEvent.Exception as PostgresException;
if (pgEx != null && FilteredPostgresErrorCodes.Contains(pgEx.SqlState))
{
return false;
}
// We don't care about transient network and timeout errors
var npgEx = logEvent.Exception as NpgsqlException;
if (npgEx != null && npgEx.IsTransient)
{
return false;
}
if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
{
return false;

View File

@@ -226,7 +226,7 @@ namespace NzbDrone.Common.OAuth
#if WINRT
return CultureInfo.InvariantCulture.CompareInfo.Compare(left, right, CompareOptions.IgnoreCase) == 0;
#else
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
return string.Compare(left, right, StringComparison.InvariantCultureIgnoreCase) == 0;
#endif
}

View File

@@ -4,13 +4,12 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.3.3" />
<PackageReference Include="DryIoc.dll" Version="5.3.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" />
<PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="Sentry" Version="3.24.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />

View File

@@ -64,7 +64,7 @@ namespace NzbDrone.Common
var args = $"create {serviceName} " +
$"DisplayName= \"{serviceName}\" " +
$"binpath= \"{Environment.ProcessPath}\" " +
$"binpath= \"{Process.GetCurrentProcess().MainModule.FileName}\" " +
"start= auto " +
"depend= EventLog/Tcpip/http " +
"obj= \"NT AUTHORITY\\LocalService\"";

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -19,7 +19,7 @@ namespace NzbDrone.Common.TPL
private readonly int _maxDegreeOfParallelism;
/// <summary>Whether the scheduler is currently processing work items.</summary>
private int _delegatesQueuedOrRunning;
private int _delegatesQueuedOrRunning = 0;
/// <summary>
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-15 04:26:21"));
torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -8,7 +8,7 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.FileList;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@@ -21,15 +21,10 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition
Subject.Definition = new IndexerDefinition()
{
Name = "FileList",
Settings = new FileListSettings
{
BaseUrl = "https://filelist.io/",
Username = "someuser",
Passkey = "somepass"
}
Settings = new FileListSettings() { Username = "someuser", Passkey = "somepass" }
};
}
@@ -40,9 +35,9 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>();
@@ -55,14 +50,12 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19").ToUniversalTime());
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(2 + 12);
torrentInfo.Seeders.Should().Be(12);
releases.Any(t => t.IndexerFlags.Contains(IndexerFlag.Internal)).Should().Be(true);
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.FileList;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -16,35 +16,34 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp]
public void Setup()
{
Subject.Settings = new FileListSettings
Subject.Settings = new FileListSettings()
{
BaseUrl = "https://filelist.io/",
Passkey = "abcd",
Username = "somename"
Username = "somename",
BaseUrl = "https://filelist.io"
};
Subject.Capabilities = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.FreeLeech,
IndexerFlag.Internal,
IndexerFlag.FreeLeech
}
};
@@ -54,7 +53,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
_movieSearchCriteria = new MovieSearchCriteria
{
SearchTerm = "Star Wars",
Categories = new[] { 2000 }
Categories = new int[] { 2000 }
};
}
@@ -66,13 +65,13 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_use_categories_for_feed()
{
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("&category=1%2C2");
page.Url.Query.Should().Contain("&category=1,2&");
}
[Test]
@@ -81,9 +80,9 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
_movieSearchCriteria.ImdbId = "0076759";
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("type=imdb");
page.Url.Query.Should().Contain("query=tt0076759");
@@ -96,12 +95,12 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("type=name");
page.Url.Query.Should().Contain("query=Star+Wars");
page.Url.Query.Should().Contain("query=Star Wars");
}
}
}

View File

@@ -10,7 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.HDBits;
using NzbDrone.Core.Indexers.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;

View File

@@ -5,7 +5,7 @@ using Newtonsoft.Json;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.HDBits;
using NzbDrone.Core.Indexers.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -14,13 +14,11 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
public class HDBitsRequestGeneratorFixture : CoreTest<HDBitsRequestGenerator>
{
private MovieSearchCriteria _movieSearchCriteria;
private TvSearchCriteria _tvSearchSeasonEpisodeCriteria;
private TvSearchCriteria _tvSearchDailyEpisodeCriteria;
[SetUp]
public void Setup()
{
Subject.Settings = new HDBitsSettings
Subject.Settings = new HDBitsSettings()
{
ApiKey = "abcd",
Username = "somename"
@@ -49,25 +47,9 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria
{
Categories = new[] { 2000, 2010 },
Categories = new int[] { 2000, 2010 },
ImdbId = "0076759"
};
_tvSearchSeasonEpisodeCriteria = new TvSearchCriteria
{
Categories = new[] { 5000, 5010 },
TvdbId = 392256,
Season = 1,
Episode = "3"
};
_tvSearchDailyEpisodeCriteria = new TvSearchCriteria
{
Categories = new[] { 5000, 5010 },
TvdbId = 289574,
Season = 2023,
Episode = "01/03"
};
}
[Test]
@@ -76,9 +58,9 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId);
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
@@ -88,49 +70,5 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
query.Category.Should().HaveCount(1);
query.ImdbInfo.Id.Should().Be(imdbQuery);
}
[Test]
public void should_search_by_tvdbid_season_episode_if_supported()
{
var results = Subject.GetSearchRequests(_tvSearchSeasonEpisodeCriteria);
var tvdbQuery = _tvSearchSeasonEpisodeCriteria.TvdbId;
results.Should().HaveCount(1);
var page = results.First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
var body = encoding.GetString(page.HttpRequest.ContentData);
var query = JsonConvert.DeserializeObject<TorrentQuery>(body);
query.Category.Should().HaveCount(3);
query.TvdbInfo.Id.Should().Be(tvdbQuery);
query.Search.Should().BeNullOrWhiteSpace();
query.TvdbInfo.Season.Should().Be(1);
query.TvdbInfo.Episode.Should().Be("3");
}
[Test]
public void should_search_by_tvdbid_daily_episode_if_supported()
{
var results = Subject.GetSearchRequests(_tvSearchDailyEpisodeCriteria);
var tvdbQuery = _tvSearchDailyEpisodeCriteria.TvdbId;
results.Should().HaveCount(1);
var page = results.First();
var encoding = HttpHeader.GetEncodingFromContentType(page.HttpRequest.Headers.ContentType);
var body = encoding.GetString(page.HttpRequest.ContentData);
var query = JsonConvert.DeserializeObject<TorrentQuery>(body);
query.Category.Should().HaveCount(3);
query.TvdbInfo.Id.Should().Be(tvdbQuery);
query.Search.Should().Be("2023-01-03");
query.TvdbInfo.Season.Should().BeNull();
query.TvdbInfo.Episode.Should().BeNull();
}
}
}

View File

@@ -51,9 +51,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_movieSearchCriteria.Offset = 0;
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var pages = results.Take(3).ToList();
var pages = results.GetAllTiers().First().Take(3).ToList();
pages[0].Url.FullUri.Should().Contain("&offset=0");
}
@@ -63,9 +63,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var pages = results.Take(500).ToList();
var pages = results.GetAllTiers().First().Take(500).ToList();
pages.Count.Should().BeLessThan(500);
}
@@ -77,9 +77,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetAllTiers().Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().NotContain("imdbid=0076759");
page.Url.Query.Should().Contain("q=Star");
@@ -92,9 +92,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetTier(0).Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("imdbid=0076759");
}
@@ -106,9 +106,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetTier(0).Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("tmdbid=11");
}
@@ -120,9 +120,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetTier(0).Should().HaveCount(1);
var page = results.First();
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("tmdbid=11");
page.Url.Query.Should().NotContain("imdbid=0076759");
@@ -136,9 +136,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.GetTier(0).Should().HaveCount(1);
var page = results.First();
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("tmdbid=11");
page.Url.Query.Should().Contain("imdbid=0076759");
@@ -150,9 +150,10 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.Tiers.Should().Be(1);
results.GetTier(0).Should().HaveCount(1);
var page = results.First();
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("q=");
}
@@ -166,7 +167,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var results = Subject.GetSearchRequests(_movieSearchCriteria);
var page = results.First();
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("q=");
}
@@ -177,9 +178,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId };
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.Should().HaveCount(1);
results.Tiers.Should().Be(1);
var pageTier2 = results.First();
var pageTier2 = results.GetTier(0).First().First();
pageTier2.Url.Query.Should().NotContain("tmdbid=11");
pageTier2.Url.Query.Should().NotContain("imdbid=0076759");
@@ -192,9 +193,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
_capabilities.TvSearchParams = new List<TvSearchParam> { TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep };
var results = Subject.GetSearchRequests(_tvSearchCriteria);
results.Should().HaveCount(1);
results.Tiers.Should().Be(1);
var pageTier = results.First();
var pageTier = results.GetTier(0).First().First();
pageTier.Url.Query.Should().Contain("season=00");
}

View File

@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
Subject.Definition = new IndexerDefinition()
{
Name = "Orpheus",
Settings = new OrpheusSettings { Apikey = "somekey" }
Settings = new OrpheusSettings() { Apikey = "somekey" }
};
}
@@ -37,14 +37,14 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
releases.Should().HaveCount(65);
releases.First().Should().BeOfType<GazelleInfo>();
var torrentInfo = releases.First() as GazelleInfo;
torrentInfo.Title.Should().Be("The Beatles - Abbey Road [1969] [Album] [2.0 Mix 2019] [MP3 V2 (VBR)] [BD]");
torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [MP3 V2 (VBR)] [BD]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448");
torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448");

View File

@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions.Rarbg;
using NzbDrone.Core.Indexers.Rarbg;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@@ -23,14 +23,14 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition
Subject.Definition = new IndexerDefinition()
{
Name = "Rarbg",
Settings = new RarbgSettings()
};
Mocker.GetMock<IRarbgTokenProvider>()
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), Subject.RateLimit))
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
.Returns("validtoken");
}
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
torrentInfo.Title.Should().Be("Sense8.S01E01.WEBRip.x264-FGT");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id=rralworP_{BuildInfo.Version}");
torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id={BuildInfo.AppName}");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-05 16:58:11 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be(564198371);

View File

@@ -9,7 +9,7 @@ using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Indexers.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;

View File

@@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
releaseInfo.InfoHash.Should().Be("(removed)");
releaseInfo.Seeders.Should().Be(3);
releaseInfo.Peers.Should().Be(3);
releaseInfo.Categories.Count.Should().Be(4);
releaseInfo.Categories.Count().Should().Be(4);
}
[Test]

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using FluentValidation.Results;
using NUnit.Framework;
@@ -55,11 +56,6 @@ namespace NzbDrone.Core.Test.NotificationTests
{
TestLogger.Info("OnApplicationUpdate was called");
}
public override void OnGrab(GrabMessage message)
{
TestLogger.Info("OnGrab was called");
}
}
private class TestNotificationWithNoEvents : NotificationBase<TestSetting>
@@ -80,7 +76,6 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnApplicationUpdate.Should().BeTrue();
notification.SupportsOnGrab.Should().BeTrue();
}
[Test]
@@ -90,7 +85,6 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnHealthIssue.Should().BeFalse();
notification.SupportsOnApplicationUpdate.Should().BeFalse();
notification.SupportsOnGrab.Should().BeFalse();
}
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Globalization;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class DateTimeUtilFixture : CoreTest
{
[TestCase("pt-BR")]
[TestCase("en-US")]
public void should_format_date_invariant(string culture)
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
var dateNow = DateTime.Now;
DateTimeUtil.FromUnknown(dateNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture))
.ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture)
.Should().Be(dateNow.ToString("ddd, dd MMM yyyy HH':'mm':'ss z", CultureInfo.InvariantCulture));
}
[TestCase("2022-08-08 02:07:39 -02:00", "2006-01-02 15:04:05 -07:00", "yyyy-MM-dd HH:mm:ss zzz", "2022-08-08 04:07:39 +00:00")]
[TestCase("2022-08-08 02:07:39 -02:00", "yyyy-MM-dd HH:mm:ss zzz", "yyyy-MM-dd HH:mm:ss zzz", "2022-08-08 04:07:39 +00:00")]
[TestCase("2022-08-08 -02:00", "2006-01-02 -07:00", "yyyy-MM-dd zzz", "2022-08-08 +00:00")]
[TestCase("2022-08-08 -02:00", "yyyy-MM-dd zzz", "yyyy-MM-dd zzz", "2022-08-08 +00:00")]
[TestCase("02:07:39 -02:00", "15:04:05 -07:00", "HH:mm:ss zzz", "04:07:39 +00:00")]
[TestCase("02:07:39 -02:00", "HH:mm:ss zzz", "HH:mm:ss zzz", "04:07:39 +00:00")]
[TestCase("-02:00", "zzz", "zzz", "+00:00")]
[TestCase("-02:00", "-07:00", "zzz", "+00:00")]
public void parse_datetime_golang(string dateInput, string format, string standardFormat, string expectedDate)
{
DateTimeUtil.ParseDateTimeGoLang(dateInput, format)
.ToUniversalTime()
.ToString(standardFormat, CultureInfo.InvariantCulture)
.Should().Be(expectedDate);
}
}
}

View File

@@ -52,16 +52,5 @@ namespace NzbDrone.Core.Test.ParserTests
{
ParseUtil.CoerceDouble(original).Should().Be(parsedInt);
}
[TestCase(null, null)]
[TestCase("", null)]
[TestCase("1", 1)]
[TestCase("1000 grabs", 1000)]
[TestCase("asdf123asdf", 123)]
[TestCase("asdf123asdf456asdf", 123)]
public void should_parse_long_from_string(string original, long? parsedInt)
{
ParseUtil.GetLongFromString(original).Should().Be(parsedInt);
}
}
}

View File

@@ -6,7 +6,7 @@
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="YamlDotNet" Version="13.0.0" />
<PackageReference Include="YamlDotNet" Version="12.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />

View File

@@ -40,11 +40,12 @@ namespace NzbDrone.Core.Test.UpdateTests
}
[Test]
[Ignore("TODO No Updates On Server")]
public void should_get_recent_updates()
{
const string branch = "develop";
UseRealHttp();
var recent = Subject.GetRecentUpdates(branch, new Version(1, 0), null);
var recent = Subject.GetRecentUpdates(branch, new Version(2, 0), null);
var recentWithChanges = recent.Where(c => c.Changes != null);
recent.Should().NotBeEmpty();

View File

@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.UpdateTests
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update".ProcessNameToExe()))))
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update.exe"))))
.Returns(true);
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.UpdateTests
public void should_return_with_warning_if_updater_doesnt_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update".ProcessNameToExe()))))
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Prowlarr.Update.exe"))))
.Returns(false);
Subject.Execute(new ApplicationUpdateCommand());

View File

@@ -147,7 +147,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
Categories = string.Join(",", indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Enabled = indexer.Enable,
Type = schema,
Priority = indexer.Priority
};
return lazyLibrarianIndexer;

View File

@@ -30,7 +30,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public bool Enabled { get; set; }
public string Altername { get; set; }
public LazyLibrarianProviderType Type { get; set; }
public int Priority { get; set; }
public bool Equals(LazyLibrarianIndexer other)
{
@@ -44,8 +43,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
other.Name == Name &&
other.Categories == Categories &&
other.Enabled == Enabled &&
other.Altername == Altername &&
other.Priority == Priority;
other.Altername == Altername;
}
}
}

View File

@@ -21,8 +21,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public class LazyLibrarianV1Proxy : ILazyLibrarianV1Proxy
{
private const int ProwlarrHighestPriority = 50;
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -92,8 +90,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "host", indexer.Host },
{ "prov_apikey", indexer.Apikey },
{ "enabled", indexer.Enabled.ToString().ToLower() },
{ "categories", indexer.Categories },
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
{ "categories", indexer.Categories }
};
var request = BuildRequest(settings, "/api", "addProvider", HttpMethod.Get, parameters);
@@ -111,8 +108,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "prov_apikey", indexer.Apikey },
{ "enabled", indexer.Enabled.ToString().ToLower() },
{ "categories", indexer.Categories },
{ "altername", indexer.Altername },
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
{ "altername", indexer.Altername }
};
var request = BuildRequest(settings, "/api", "changeProvider", HttpMethod.Get, parameters);
@@ -195,7 +191,5 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
return results;
}
private int CalculatePriority(int indexerPriority) => ProwlarrHighestPriority - indexerPriority + 1;
}
}

View File

@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Applications.Lidarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;

View File

@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Applications.Radarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}

View File

@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Applications.Readarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;

View File

@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Applications.Sonarr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;

View File

@@ -192,7 +192,7 @@ namespace NzbDrone.Core.Applications.Whisparr
if (indexer.Protocol == DownloadProtocol.Torrent)
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}

View File

@@ -19,14 +19,14 @@ namespace NzbDrone.Core.Authentication
public class UserService : IUserService
{
private const int ITERATIONS = 10000;
private const int SALT_SIZE = 128 / 8;
private const int NUMBER_OF_BYTES = 256 / 8;
private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
private static readonly int ITERATIONS = 10000;
private static readonly int SALT_SIZE = 128 / 8;
private static readonly int NUMBER_OF_BYTES = 256 / 8;
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
{
_repo = repo;

View File

@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(025)]
public class speedcd_userpasssettings_to_speedcdsettings : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "SpeedCDSettings" }).Where(new { Implementation = "SpeedCD" });
}
}
}

View File

@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(026)]
public class torrentday_cookiesettings_to_torrentdaysettings : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "TorrentDaySettings" }).Where(new { Implementation = "TorrentDay" });
}
}
}

View File

@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration;
[Migration(027)]
public class alpharatio_greatposterwall_config_contract : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "AlphaRatioSettings" }).Where(new { Implementation = "AlphaRatio" });
Update.Table("Indexers").Set(new { ConfigContract = "GreatPosterWallSettings" }).Where(new { Implementation = "GreatPosterWall" });
}
}

View File

@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration;
[Migration(028)]
public class remove_notwhatcd : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
// Remove, site dead
Delete.FromTable("Indexers").Row(new { Implementation = "NotWhatCD" });
}
}

View File

@@ -1,15 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(029)]
public class add_on_grab_to_notifications : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnGrab").AsBoolean().WithDefaultValue(false);
Alter.Table("Notifications").AddColumn("IncludeManualGrabs").AsBoolean().WithDefaultValue(false).NotNullable();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
@@ -250,7 +250,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
}
Index = end + 1;
identifier.Append(Buffer.AsSpan(start, end - start));
identifier.Append(Buffer.Substring(start, end - start));
if (Buffer[Index] != escape)
{

View File

@@ -66,7 +66,6 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnHealthIssue)
.Ignore(i => i.SupportsOnApplicationUpdate);

View File

@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue;
private int _paramCount;
private bool _gotConcreteValue;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq)
{

View File

@@ -15,9 +15,9 @@ namespace NzbDrone.Core.Datastore
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue;
private int _paramCount;
private bool _gotConcreteValue;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Instrumentation.Extensions;
@@ -60,16 +61,15 @@ namespace NzbDrone.Core.Download
// Get the seed configuration for this release.
// remoteMovie.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(remoteMovie);
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
var grabEvent = new IndexerDownloadEvent(release, true, source, host, release.Title, release.DownloadUrl)
// Limit grabs to 2 per second.
if (release.DownloadUrl.IsNotNullOrWhiteSpace() && !release.DownloadUrl.StartsWith("magnet:"))
{
DownloadClient = downloadClient.Name,
DownloadClientId = downloadClient.Definition.Id,
DownloadClientName = downloadClient.Definition.Name,
Redirect = redirect,
GrabTrigger = source == "Prowlarr" ? GrabTrigger.Manual : GrabTrigger.Api
};
var url = new HttpUri(release.DownloadUrl);
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
}
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
string downloadClientId;
try
@@ -81,20 +81,19 @@ namespace NzbDrone.Core.Download
catch (ReleaseUnavailableException)
{
_logger.Trace("Release {0} no longer available on indexer.", release);
grabEvent.Successful = false;
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, release.DownloadUrl, redirect));
throw;
}
catch (DownloadClientRejectedReleaseException)
{
_logger.Trace("Release {0} rejected by download client, possible duplicate.", release);
grabEvent.Successful = false;
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, release.DownloadUrl, redirect));
throw;
}
catch (ReleaseDownloadException ex)
{
if (ex.InnerException is TooManyRequestsException http429)
var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
{
_indexerStatusService.RecordFailure(release.IndexerId, http429.RetryAfter);
}
@@ -103,21 +102,14 @@ namespace NzbDrone.Core.Download
_indexerStatusService.RecordFailure(release.IndexerId);
}
grabEvent.Successful = false;
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, release.DownloadUrl, redirect));
throw;
}
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
if (!string.IsNullOrWhiteSpace(downloadClientId))
{
grabEvent.DownloadId = downloadClientId;
}
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, true, source, host, release.Title, release.DownloadUrl, redirect));
}
public async Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title)
@@ -135,35 +127,22 @@ namespace NzbDrone.Core.Download
var success = false;
var downloadedBytes = Array.Empty<byte>();
var release = new ReleaseInfo
{
Title = title,
DownloadUrl = link,
IndexerId = indexerId,
Indexer = indexer.Definition.Name,
DownloadProtocol = indexer.Protocol
};
var grabEvent = new IndexerDownloadEvent(release, success, source, host, release.Title, release.DownloadUrl)
{
GrabTrigger = source == "Prowlarr" ? GrabTrigger.Manual : GrabTrigger.Api
};
try
{
downloadedBytes = await indexer.Download(url);
_indexerStatusService.RecordSuccess(indexerId);
grabEvent.Successful = true;
success = true;
}
catch (ReleaseUnavailableException)
{
_logger.Trace("Release {0} no longer available on indexer.", link);
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
throw;
}
catch (ReleaseDownloadException ex)
{
if (ex.InnerException is TooManyRequestsException http429)
var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
{
_indexerStatusService.RecordFailure(indexerId, http429.RetryAfter);
}
@@ -172,36 +151,17 @@ namespace NzbDrone.Core.Download
_indexerStatusService.RecordFailure(indexerId);
}
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
throw;
}
_logger.Trace("Downloaded {0} bytes from {1}", downloadedBytes.Length, link);
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title, url.AbsoluteUri));
return downloadedBytes;
}
public void RecordRedirect(string link, int indexerId, string source, string host, string title)
{
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(indexerId));
var release = new ReleaseInfo
{
Title = title,
DownloadUrl = link,
IndexerId = indexerId,
Indexer = indexer.Definition.Name,
DownloadProtocol = indexer.Protocol
};
var grabEvent = new IndexerDownloadEvent(release, true, source, host, release.Title, release.DownloadUrl)
{
Redirect = true,
GrabTrigger = source == "Prowlarr" ? GrabTrigger.Manual : GrabTrigger.Api
};
_eventAggregator.PublishEvent(grabEvent);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, true, source, host, title, link, true));
}
}
}

View File

@@ -34,8 +34,8 @@ namespace NzbDrone.Core.HealthCheck
private readonly ICached<HealthCheck> _healthCheckResults;
private bool _hasRunHealthChecksAfterGracePeriod;
private bool _isRunningHealthChecksAfterGracePeriod;
private bool _hasRunHealthChecksAfterGracePeriod = false;
private bool _isRunningHealthChecksAfterGracePeriod = false;
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
IServerSideNotificationService serverSideNotificationService,
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.HealthCheck
_startupHealthChecks = _healthChecks.Where(v => v.CheckOnStartup).ToArray();
_scheduledHealthChecks = _healthChecks.Where(v => v.CheckOnSchedule).ToArray();
_eventDrivenHealthChecks = GetEventDrivenHealthChecks();
_startupGracePeriodEndTime = runtimeInfo.StartTime.AddMinutes(15);
_startupGracePeriodEndTime = runtimeInfo.StartTime + TimeSpan.FromMinutes(15);
}
public List<HealthCheck> Results()

View File

@@ -52,7 +52,7 @@ namespace NzbDrone.Core.HealthCheck
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", "netcore")
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("branch", _configFileProvider.Branch)
.Build();
try

View File

@@ -19,7 +19,6 @@ namespace NzbDrone.Core.History
List<History> Since(DateTime date, HistoryEventType? eventType);
void Cleanup(int days);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit);
}
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
@@ -116,24 +115,5 @@ namespace NzbDrone.Core.History
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
}
}
public History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit)
{
var intEvents = eventTypes.Select(t => (int)t).ToList();
var builder = Builder()
.Where<History>(x => x.IndexerId == indexerId)
.Where<History>(x => x.Date >= date)
.Where<History>(x => intEvents.Contains((int)x.EventType));
var query = Query(builder);
if (limit > 0)
{
query = query.OrderByDescending(h => h.Date).Take(limit).ToList();
}
return query.MinBy(h => h.Date);
}
}
}

View File

@@ -27,7 +27,6 @@ namespace NzbDrone.Core.History
List<History> Between(DateTime start, DateTime end);
List<History> Since(DateTime date, HistoryEventType? eventType);
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit);
}
public class HistoryService : IHistoryService,
@@ -122,7 +121,7 @@ namespace NzbDrone.Core.History
{
Date = DateTime.UtcNow,
IndexerId = message.IndexerId,
EventType = message.Query.IsRssSearch ? HistoryEventType.IndexerRss : HistoryEventType.IndexerQuery,
EventType = message.Query.RssSearch ? HistoryEventType.IndexerRss : HistoryEventType.IndexerQuery,
Successful = message.QueryResult.Response?.StatusCode == HttpStatusCode.OK
};
@@ -174,7 +173,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty);
history.Data.Add("Source", message.Query.Source ?? string.Empty);
history.Data.Add("Host", message.Query.Host ?? string.Empty);
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count.ToString() ?? string.Empty);
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count().ToString() ?? string.Empty);
history.Data.Add("Url", message.QueryResult.Response?.Request.Url.FullUri ?? string.Empty);
_historyRepository.Insert(history);
@@ -185,7 +184,7 @@ namespace NzbDrone.Core.History
var history = new History
{
Date = DateTime.UtcNow,
IndexerId = message.Release.IndexerId,
IndexerId = message.IndexerId,
EventType = HistoryEventType.ReleaseGrabbed,
Successful = message.Successful
};
@@ -233,10 +232,5 @@ namespace NzbDrone.Core.History
{
return _historyRepository.CountSince(indexerId, date, eventTypes);
}
public History FindFirstForIndexerSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes, int limit)
{
return _historyRepository.FindFirstForIndexerSince(indexerId, date, eventTypes, limit);
}
}
}

View File

@@ -1,16 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Http.CloudFlare
{
public class CloudFlareDetectionService
{
private static readonly HashSet<string> CloudflareServerNames = new () { "cloudflare", "cloudflare-nginx", "ddos-guard" };
private static readonly HashSet<string> CloudflareServerNames = new HashSet<string> { "cloudflare", "cloudflare-nginx", "ddos-guard" };
private readonly Logger _logger;
public CloudFlareDetectionService(Logger logger)
@@ -30,11 +29,7 @@ namespace NzbDrone.Core.Http.CloudFlare
response.StatusCode.Equals(HttpStatusCode.Forbidden))
{
var responseHtml = response.Content;
if (responseHtml.Contains("<title>Just a moment...</title>") ||
responseHtml.Contains("<title>Access denied</title>") ||
responseHtml.Contains("<title>Attention Required! | Cloudflare</title>") ||
responseHtml.Trim().Equals("error code: 1020") ||
responseHtml.Contains("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase))
if (responseHtml.Contains("<title>Just a moment...") || responseHtml.Contains("<title>DDOS-GUARD"))
{
return true;
}
@@ -42,7 +37,7 @@ namespace NzbDrone.Core.Http.CloudFlare
// detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
if (response.Headers.Vary == "Accept-Encoding,User-Agent" &&
response.Headers.ContentEncoding.IsNullOrWhiteSpace() &&
response.Headers.ContentEncoding == "" &&
response.Content.ToLower().Contains("ddos"))
{
return true;

View File

@@ -170,7 +170,6 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
newRequest.Headers.ContentType = "application/json";
newRequest.Method = HttpMethod.Post;
newRequest.LogResponseContent = true;
newRequest.RequestTimeout = TimeSpan.FromSeconds(Settings.RequestTimeout + 5);
newRequest.SetContent(req.ToJson());
_logger.Debug("Cloudflare Detected, Applying FlareSolverr Proxy {0} to request {1}", Name, request.Url);

View File

@@ -10,15 +10,17 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; }
public string Genre { get; set; }
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && Author.IsNullOrWhiteSpace() && Title.IsNullOrWhiteSpace())
{
return true;
}
public override bool IsIdSearch =>
Author.IsNotNullOrWhiteSpace() ||
Title.IsNotNullOrWhiteSpace() ||
Publisher.IsNotNullOrWhiteSpace() ||
Genre.IsNotNullOrWhiteSpace() ||
Year.HasValue;
return false;
}
}
}
}

View File

@@ -13,17 +13,18 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; }
public string Genre { get; set; }
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TmdbId.HasValue && !TraktId.HasValue)
{
return true;
}
public override bool IsIdSearch =>
ImdbId.IsNotNullOrWhiteSpace() ||
Genre.IsNotNullOrWhiteSpace() ||
TmdbId.HasValue ||
TraktId.HasValue ||
DoubanId.HasValue ||
Year.HasValue;
return false;
}
}
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);

View File

@@ -11,16 +11,17 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public string Track { get; set; }
public int? Year { get; set; }
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && Album.IsNullOrWhiteSpace() && Artist.IsNullOrWhiteSpace() && Label.IsNullOrWhiteSpace())
{
return true;
}
public override bool IsIdSearch =>
Album.IsNotNullOrWhiteSpace() ||
Artist.IsNotNullOrWhiteSpace() ||
Label.IsNotNullOrWhiteSpace() ||
Genre.IsNotNullOrWhiteSpace() ||
Track.IsNotNullOrWhiteSpace() ||
Year.HasValue;
return false;
}
}
}
}

View File

@@ -7,39 +7,72 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
{
public abstract class SearchCriteriaBase
{
private static readonly Regex StandardizeDashesRegex = new (@"\p{Pd}+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex StandardizeSingleQuotesRegex = new (@"[\u0060\u00B4\u2018\u2019]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public virtual bool InteractiveSearch { get; set; }
public List<int> IndexerIds { get; set; }
public string SearchTerm { get; set; }
public int[] Categories { get; set; }
public string SearchType { get; set; }
public int Limit { get; set; }
public int Offset { get; set; }
public int? Limit { get; set; }
public int? Offset { get; set; }
public string Source { get; set; }
public string Host { get; set; }
public override string ToString() => $"{SearchQuery}, Offset: {Offset}, Limit: {Limit}, Categories: [{string.Join(", ", Categories)}]";
public virtual string SearchQuery => $"Term: [{SearchTerm}]";
public virtual bool IsRssSearch => SearchTerm.IsNullOrWhiteSpace();
public virtual bool IsIdSearch => false;
public string SanitizedSearchTerm => GetSanitizedTerm(SearchTerm);
private static string GetSanitizedTerm(string term)
public virtual string SearchQuery
{
term ??= "";
get
{
return $"Term: [{SearchTerm}]";
}
}
term = StandardizeDashesRegex.Replace(term, "-");
term = StandardizeSingleQuotesRegex.Replace(term, "'");
public override string ToString()
{
return $"{SearchQuery}, Offset: {Offset ?? 0}, Limit: {Limit ?? 0}, Categories: [{string.Join(", ", Categories)}]";
}
var safeTitle = term.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c) || c is '-' or '.' or '_' or '(' or ')' or '@' or '/' or '\'' or '[' or ']' or '+' or '%');
public virtual bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace())
{
return true;
}
return string.Concat(safeTitle);
return false;
}
}
public string SanitizedSearchTerm
{
get
{
var term = SearchTerm;
if (SearchTerm == null)
{
term = "";
}
var safeTitle = term.Where(c => (char.IsLetterOrDigit(c)
|| char.IsWhiteSpace(c)
|| c == '-'
|| c == '.'
|| c == '_'
|| c == '('
|| c == ')'
|| c == '@'
|| c == '/'
|| c == '\''
|| c == '['
|| c == ']'
|| c == '+'
|| c == '%'));
return string.Concat(safeTitle);
}
}
}
}

View File

@@ -21,25 +21,23 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Year { get; set; }
public string Genre { get; set; }
public string SanitizedTvSearchString => $"{SanitizedSearchTerm} {EpisodeSearchString}".Trim();
public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
public string EpisodeSearchString => GetEpisodeSearchString();
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
public override bool IsRssSearch =>
SearchTerm.IsNullOrWhiteSpace() &&
!IsIdSearch;
public override bool RssSearch
{
get
{
if (SearchTerm.IsNullOrWhiteSpace() && ImdbId.IsNullOrWhiteSpace() && !TvdbId.HasValue && !RId.HasValue && !TraktId.HasValue && !TvMazeId.HasValue)
{
return true;
}
public override bool IsIdSearch =>
Episode.IsNotNullOrWhiteSpace() ||
ImdbId.IsNotNullOrWhiteSpace() ||
Season.HasValue ||
TvdbId.HasValue ||
RId.HasValue ||
TraktId.HasValue ||
TvMazeId.HasValue ||
TmdbId.HasValue ||
DoubanId.HasValue;
return false;
}
}
public override string SearchQuery
{

View File

@@ -138,8 +138,8 @@ namespace NzbDrone.Core.IndexerSearch
spec.SearchTerm = query.q;
spec.SearchType = query.t;
spec.Limit = query.limit ?? 100;
spec.Offset = query.offset ?? 0;
spec.Limit = query.limit;
spec.Offset = query.offset;
spec.Source = query.source;
spec.Host = query.host;

View File

@@ -59,7 +59,7 @@ namespace NzbDrone.Core.IndexerStats
var elapsedTimeEvents = sortedEvents.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp))
.Select(h => temp);
indexerStats.AverageResponseTime = elapsedTimeEvents.Any() ? (int)elapsedTimeEvents.Average() : 0;
indexerStats.AverageResponseTime = elapsedTimeEvents.Count() > 0 ? (int)elapsedTimeEvents.Average() : 0;
foreach (var historyEvent in sortedEvents)
{

View File

@@ -29,14 +29,13 @@ namespace NzbDrone.Core.IndexerVersions
/* Update Service will fall back if version # does not exist for an indexer per Ta */
private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 9;
private const int DEFINITION_VERSION = 7;
// Used when moving yml to C#
private readonly List<string> _definitionBlocklist = new ()
//Used when moving yml to C#
private readonly List<string> _defintionBlocklist = new List<string>()
{
"aither",
"animeworld",
"audiobookbay",
"beyond-hd-oneurl",
"beyond-hd",
"blutopia",
@@ -90,7 +89,7 @@ namespace NzbDrone.Core.IndexerVersions
{
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_definitionBlocklist.Contains(i.File)).ToList();
indexerList = response.Resource.Where(i => !_defintionBlocklist.Contains(i.File)).ToList();
}
catch
{
@@ -126,7 +125,7 @@ namespace NzbDrone.Core.IndexerVersions
public List<string> GetBlocklist()
{
return _definitionBlocklist;
return _defintionBlocklist;
}
private List<CardigannMetaDefinition> ReadDefinitionsFromDisk(List<CardigannMetaDefinition> defs, string path, SearchOption options = SearchOption.TopDirectoryOnly)
@@ -228,10 +227,10 @@ namespace NzbDrone.Core.IndexerVersions
if (definition.Settings == null)
{
definition.Settings = new List<SettingsField>
{
new () { Name = "username", Label = "Username", Type = "text" },
new () { Name = "password", Label = "Password", Type = "password" }
};
{
new SettingsField { Name = "username", Label = "Username", Type = "text" },
new SettingsField { Name = "password", Label = "Password", Type = "password" }
};
}
if (definition.Encoding == null)

View File

@@ -1,120 +1,85 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using NLog;
using NzbDrone.Core.Annotations;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions;
public class AlphaRatio : GazelleBase<AlphaRatioSettings>
namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "AlphaRatio";
public override string[] IndexerUrls => new[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AlphaRatio(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
public class AlphaRatio : Gazelle.Gazelle
{
}
public override string Name => "AlphaRatio";
public override string[] IndexerUrls => new string[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AlphaRatioRequestGenerator(Settings, Capabilities, _httpClient, _logger);
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
public AlphaRatio(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : GazelleRequestGenerator
{
protected override bool ImdbInTags => true;
private readonly AlphaRatioSettings _settings;
public AlphaRatioRequestGenerator(AlphaRatioSettings settings,
IndexerCapabilities capabilities,
IIndexerHttpClient httpClient,
Logger logger)
: base(settings, capabilities, httpClient, logger)
{
_settings = settings;
}
protected override NameValueCollection GetBasicSearchParameters(string term, int[] categories)
{
var parameters = base.GetBasicSearchParameters(term, categories);
if (_settings.FreeleechOnly)
{
parameters.Set("freetorrent", "1");
}
if (_settings.ExcludeScene)
public override IIndexerRequestGenerator GetRequestGenerator()
{
parameters.Set("scene", "0");
return new AlphaRatioRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities
};
}
return parameters;
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVSD, "TvSD");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD, "TvHD");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVUHD, "TvUHD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD, "TvDVDRip");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSD, "TvPackSD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVHD, "TvPackHD");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVUHD, "TvPackUHD");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesSD, "MovieSD");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesHD, "MovieHD");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesUHD, "MovieUHD");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesSD, "MoviePackSD");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.MoviesHD, "MoviePackHD");
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.MoviesUHD, "MoviePackUHD");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.XXX, "MovieXXX");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Bluray");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVAnime, "AnimeSD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVAnime, "AnimeHD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.PCGames, "GamesPC");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.ConsoleXBox, "GamesxBox");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.ConsolePS4, "GamesPS");
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.ConsoleWii, "GamesNin");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PC0day, "AppsWindows");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMac, "AppsMAC");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "AppsLinux");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCMobileOther, "AppsMobile");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.XXX, "0dayXXX");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "eBook");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.AudioAudiobook, "AudioBook");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.Other, "Misc");
return caps;
}
}
public class AlphaRatioRequestGenerator : Gazelle.GazelleRequestGenerator
{
protected override bool ImdbInTags => true;
}
}
public class AlphaRatioSettings : GazelleSettings
{
[FieldDefinition(6, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(7, Label = "Exclude Scene", Type = FieldType.Checkbox, HelpText = "Exclude Scene torrents from results")]
public bool ExcludeScene { get; set; }
}

View File

@@ -1,349 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Anidex : TorrentIndexerBase<AnidexSettings>
{
public override string Name => "Anidex";
public override string[] IndexerUrls => new[] { "https://anidex.info/" };
public override string Description => "Anidex is a Public torrent tracker and indexer, primarily for English fansub groups of anime";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anidex(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnidexRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new AnidexParser(Settings, Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q,
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q,
}
};
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.TVAnime, "Anime - Sub");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TVAnime, "Anime - Raw");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TVAnime, "Anime - Dub");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.TVAnime, "LA - Sub");
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.TVAnime, "LA - Raw");
caps.Categories.AddCategoryMapping("6", NewznabStandardCategory.BooksEBook, "Light Novel");
caps.Categories.AddCategoryMapping("7", NewznabStandardCategory.BooksComics, "Manga - TLed");
caps.Categories.AddCategoryMapping("8", NewznabStandardCategory.BooksComics, "Manga - Raw");
caps.Categories.AddCategoryMapping("9", NewznabStandardCategory.AudioMP3, "♫ - Lossy");
caps.Categories.AddCategoryMapping("10", NewznabStandardCategory.AudioLossless, "♫ - Lossless");
caps.Categories.AddCategoryMapping("11", NewznabStandardCategory.AudioVideo, "♫ - Video");
caps.Categories.AddCategoryMapping("12", NewznabStandardCategory.PCGames, "Games");
caps.Categories.AddCategoryMapping("13", NewznabStandardCategory.PC0day, "Applications");
caps.Categories.AddCategoryMapping("14", NewznabStandardCategory.XXXImageSet, "Pictures");
caps.Categories.AddCategoryMapping("15", NewznabStandardCategory.XXX, "Adult Video");
caps.Categories.AddCategoryMapping("16", NewznabStandardCategory.Other, "Other");
return caps;
}
}
public class AnidexRequestGenerator : IIndexerRequestGenerator
{
private readonly AnidexSettings _settings;
private readonly IndexerCapabilities _capabilities;
public AnidexRequestGenerator(AnidexSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IEnumerable<IndexerRequest> GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new List<IndexerRequest>();
}
public IEnumerable<IndexerRequest> GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
public IEnumerable<IndexerRequest> GetSearchRequests(TvSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
public IEnumerable<IndexerRequest> GetSearchRequests(BookSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
public IEnumerable<IndexerRequest> GetSearchRequests(BasicSearchCriteria searchCriteria)
{
return GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories);
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var parameters = new NameValueCollection
{
{ "page", "search" },
{ "s", "upload_timestamp" },
{ "o", "desc" },
{ "group_id", "0" }, // All groups
{ "q", term ?? string.Empty }
};
if (_settings.AuthorisedOnly)
{
parameters.Add("a", "1");
}
var searchUrl = $"{_settings.BaseUrl}?{parameters.GetQueryString()}";
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (queryCats.Any())
{
searchUrl += "&id=" + string.Join(",", queryCats);
}
if (_settings.LanguagesOnly.Any())
{
searchUrl += "&lang_id=" + string.Join(",", _settings.LanguagesOnly);
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidexParser : IParseIndexerResponse
{
private readonly AnidexSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AnidexParser(AnidexSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Anidex search returned unexpected result. Expected 200 OK but got {indexerResponse.HttpResponse.StatusCode}.");
}
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("div#content table > tbody > tr");
foreach (var row in rows)
{
var downloadUrl = _settings.BaseUrl + row.QuerySelector("a[href^=\"/dl/\"]")?.GetAttribute("href");
var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(3) a")?.GetAttribute("href");
var title = row.QuerySelector("td:nth-child(3) span")?.GetAttribute("title")?.Trim();
var language = row.QuerySelector("td:nth-child(1) img")?.GetAttribute("title")?.Trim();
if (language.IsNotNullOrWhiteSpace())
{
title += $" [{language}]";
}
var categoryLink = row.QuerySelector("td:nth-child(1) a").GetAttribute("href");
var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, "id");
var categories = _categories.MapTrackerCatToNewznab(cat);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)")?.TextContent);
var peers = seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(10)")?.TextContent.Trim());
var added = row.QuerySelector("td:nth-child(8)").GetAttribute("title").Trim();
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = downloadUrl,
MagnetUrl = row.QuerySelector("a[href^=\"magnet:?\"]")?.GetAttribute("href"),
Title = title,
Categories = categories,
Seeders = seeders,
Peers = peers,
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(7)")?.TextContent.Trim()),
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(11)")?.TextContent),
PublishDate = DateTime.ParseExact(added, "yyyy-MM-dd HH:mm:ss UTC", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1
};
releaseInfos.Add(release);
}
return releaseInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnidexSettings : NoAuthTorrentBaseSettings
{
public AnidexSettings()
{
AuthorisedOnly = false;
LanguagesOnly = Array.Empty<int>();
}
[FieldDefinition(2, Label = "Authorised Only", Type = FieldType.Checkbox, HelpText = "Search authorised torrents only")]
public bool AuthorisedOnly { get; set; }
[FieldDefinition(3, Label = "Languages Only", Type = FieldType.Select, SelectOptions = typeof(AnidexLanguage), HelpText = "Search selected languages only. None ticked = ALL.")]
public IEnumerable<int> LanguagesOnly { get; set; }
}
public enum AnidexLanguage
{
[FieldOption(Hint = "English")]
GB = 1,
[FieldOption(Hint = "Japanese")]
JP = 2,
[FieldOption(Hint = "Polish")]
PL = 3,
[FieldOption(Hint = "Serbo-Croatian")]
RS = 4,
[FieldOption(Hint = "Dutch")]
NL = 5,
[FieldOption(Hint = "Italian")]
IT = 6,
[FieldOption(Hint = "Russian")]
RU = 7,
[FieldOption(Hint = "German")]
DE = 8,
[FieldOption(Hint = "Hungarian")]
HU = 9,
[FieldOption(Hint = "French")]
FR = 10,
[FieldOption(Hint = "Finnish")]
FI = 11,
[FieldOption(Hint = "Vietnamese")]
VN = 12,
[FieldOption(Hint = "Greek")]
GR = 13,
[FieldOption(Hint = "Bulgarian")]
BG = 14,
[FieldOption(Hint = "Spanish (Spain)")]
ES = 15,
[FieldOption(Hint = "Portuguese (Brazil)")]
BR = 16,
[FieldOption(Hint = "Portuguese (Portugal)")]
PT = 17,
[FieldOption(Hint = "Swedish")]
SE = 18,
[FieldOption(Hint = "Arabic")]
SA = 19,
[FieldOption(Hint = "Danish")]
DK = 20,
[FieldOption(Hint = "Chinese (Simplified)")]
CN = 21,
[FieldOption(Hint = "Bengali")]
BD = 22,
[FieldOption(Hint = "Romanian")]
RO = 23,
[FieldOption(Hint = "Czech")]
CZ = 24,
[FieldOption(Hint = "Mongolian")]
MN = 25,
[FieldOption(Hint = "Turkish")]
TR = 26,
[FieldOption(Hint = "Indonesian")]
ID = 27,
[FieldOption(Hint = "Korean")]
KR = 28,
[FieldOption(Hint = "Spanish (LATAM)")]
MX = 29,
[FieldOption(Hint = "Persian")]
IR = 30,
[FieldOption(Hint = "Malaysian")]
MY = 31,
}
}

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