Migrate eslint to v9

This commit is contained in:
Chocobozzz 2025-05-06 16:02:38 +02:00
parent bad8ea2c2e
commit 034e1bf328
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
106 changed files with 2130 additions and 1445 deletions

View File

@ -1,151 +0,0 @@
{
"extends": "standard-with-typescript",
"root": true,
"rules": {
"eol-last": [
"error",
"always"
],
"indent": "off",
"no-lone-blocks": "off",
"no-mixed-operators": "off",
"max-len": [
"error",
{
"code": 140
}
],
"array-bracket-spacing": [
"error",
"always"
],
"quote-props": [
"error",
"consistent-as-needed"
],
"padded-blocks": "off",
"prefer-regex-literals": "off",
"no-async-promise-executor": "off",
"dot-notation": "off",
"promise/param-names": "off",
"import/first": "off",
"operator-linebreak": [
"error",
"after",
{
"overrides": {
"?": "before",
":": "before"
}
}
],
"quotes": "off",
"no-constant-binary-expression": "error",
"@typescript-eslint/indent": [
"error",
2,
{
"SwitchCase": 1,
"MemberExpression": "off",
// https://github.com/eslint/eslint/issues/15299
"ignoredNodes": ["PropertyDefinition", "TSTypeParameterInstantiation", "TSConditionalType *"]
}
],
"@typescript-eslint/consistent-type-assertions": [
"error",
{
"assertionStyle": "as"
}
],
"@typescript-eslint/array-type": [
"error",
{
"default": "array"
}
],
"@typescript-eslint/restrict-template-expressions": [
"off",
{
"allowNumber": "true"
}
],
"@typescript-eslint/no-this-alias": [
"error",
{
"allowDestructuring": true, // Allow `const { props, state } = this`; false by default
"allowedNames": ["self"] // Allow `const self = this`; `[]` by default
}
],
"@typescript-eslint/return-await": "off",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/method-signature-style": "off",
"@typescript-eslint/no-base-to-string": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/promise-function-async": "off",
"@typescript-eslint/no-dynamic-delete": "off",
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-extraneous-class": "off",
"@typescript-eslint/no-use-before-define": "off",
"require-await": "off",
"@typescript-eslint/require-await": "error",
// bugged but useful
"@typescript-eslint/restrict-plus-operands": "off",
// Requires strictNullChecks
"@typescript-eslint/prefer-nullish-coalescing": "off",
"@typescript-eslint/consistent-type-imports": "off",
"@typescript-eslint/consistent-indexed-object-style": "off",
"@typescript-eslint/no-confusing-void-expression": "off",
"@typescript-eslint/consistent-type-exports": "off",
"@typescript-eslint/key-spacing": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"{}": false,
"Function": false
},
"extendDefaults": true
}
]
},
"ignorePatterns": [
"node_modules",
"packages/tests/fixtures",
"apps/**/dist",
"packages/**/dist",
"server/dist",
"packages/types-generator/tests",
"*.js",
"/client",
"/dist"
],
"parserOptions": {
"project": [
"./tsconfig.eslint.json"
],
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true
}
}

View File

@ -16,7 +16,6 @@ export function defineAuthProgram () {
.option('-p, --password <token>', 'Password')
.option('--default', 'add the entry as the new default')
.action(options => {
/* eslint-disable no-import-assign */
prompt.override = options
prompt.start()
prompt.get({
@ -39,7 +38,6 @@ export function defineAuthProgram () {
}
}
}, async (_, result) => {
// Check credentials
try {
// Strip out everything after the domain:port.
@ -111,11 +109,13 @@ export function defineAuthProgram () {
}
})
program.addHelpText('after', '\n\n Examples:\n\n' +
' $ peertube auth add -u https://peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
' $ peertube auth add -u https://peertube.cpy.re -U root\n' +
' $ peertube auth list\n' +
' $ peertube auth del https://peertube.cpy.re\n'
program.addHelpText(
'after',
'\n\n Examples:\n\n' +
' $ peertube auth add -u https://peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
' $ peertube auth add -u https://peertube.cpy.re -U root\n' +
' $ peertube auth list\n' +
' $ peertube auth del https://peertube.cpy.re\n'
)
return program

View File

@ -127,7 +127,7 @@ async function buildVideoAttributesFromCommander (server: PeerTubeServer, option
waitTranscoding: true
}
const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } = {} as any
for (const key of Object.keys(defaultBooleanAttributes)) {
if (options[key] !== undefined) {

View File

@ -72,8 +72,7 @@ function getRemoteObjectOrDie (
settings: Settings,
netrc: Netrc
): { url: string, username: string, password: string } {
function exitIfNoOptions (optionNames: string[], errorPrefix: string = '') {
function exitIfNoOptions (optionNames: string[], errorPrefix = '') {
let exit = false
for (const key of optionNames) {
@ -126,9 +125,9 @@ function listOptions (val: string) {
function getServerCredentials (options: CommonProgramOptions) {
return Promise.all([ getSettings(), getNetrc() ])
.then(([ settings, netrc ]) => {
return getRemoteObjectOrDie(options, settings, netrc)
})
.then(([ settings, netrc ]) => {
return getRemoteObjectOrDie(options, settings, netrc)
})
}
function buildServer (url: string) {
@ -184,11 +183,8 @@ export {
getRemoteObjectOrDie,
writeSettings,
deleteSettings,
getServerCredentials,
listOptions,
getAdminTokenOrDie,
buildServer,
assignToken

View File

@ -1,181 +0,0 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*",
"node_modules/",
"src/standalone/embed-player-api/dist"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.eslint.json"
],
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
"createDefaultProgram": false
},
"extends": [
"../.eslintrc.json",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"jsdoc/newline-after-description": "off",
"jsdoc/check-alignment": "off",
"lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": [ "off" ],
"arrow-body-style": "off",
"no-underscore-dangle": "off",
"n/no-callback-literal": "off",
"@angular-eslint/component-selector": [
"error",
{
"type": [ "element", "attribute" ],
"prefix": "my",
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": [ "element", "attribute" ],
"prefix": "my",
"style": "camelCase"
}
],
"@typescript-eslint/no-this-alias": [
"error",
{
"allowDestructuring": true,
"allowedNames": ["self", "player"]
}
],
"@typescript-eslint/prefer-readonly": "off",
"@angular-eslint/use-component-view-encapsulation": "error",
"prefer-arrow/prefer-arrow-functions": "off",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/explicit-member-accessibility": [
"off",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/member-ordering": [
"off"
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/prefer-for-of": "off",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-shadow": [
"off",
{
"hoist": "all"
}
],
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unused-expressions": [
"error",
{
"allowTaggedTemplates": true,
"allowShortCircuit": true
}
],
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"error",
"never"
],
"brace-style": [
"error",
"1tbs"
],
"comma-dangle": "error",
"curly": [
"error",
"multi-line"
],
"dot-notation": "off",
"no-useless-return": "off",
"indent": "off",
"no-bitwise": "off",
"no-console": "off",
"no-return-assign": "off",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-duplicate-imports": "error",
"no-empty": "error",
"no-empty-function": [
"error",
{ "allow": [ "constructors" ] }
],
"no-invalid-regexp": "error",
"no-multiple-empty-lines": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-return-await": "error",
"no-shadow": "off",
"no-unused-expressions": "error",
"semi": "error",
"space-before-function-paren": [
"error",
"always"
],
"space-in-parens": [
"error",
"never"
],
"object-shorthand": [
"error",
"properties"
],
"quote-props": [
"error",
"consistent-as-needed"
],
"no-constant-binary-expression": "error",
"@typescript-eslint/unbound-method": [
"error",
{ "ignoreStatic": true }
],
"import/no-named-default": "off"
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended",
"plugin:@angular-eslint/template/accessibility"
],
"rules": {}
}
]
}

View File

@ -8,18 +8,18 @@ export class AdminRegistrationPage {
}
async accept (username: string, moderationResponse: string) {
const usernameEl = await $('*=' + username)
const usernameEl = $('*=' + username)
await usernameEl.waitForDisplayed()
const tr = await findParentElement(usernameEl, async el => await el.getTagName() === 'tr')
await tr.$('.action-cell .dropdown-root').click()
const accept = await $('span*=Accept this request')
const accept = $('span*=Accept this request')
await accept.waitForClickable()
await accept.click()
const moderationResponseTextarea = await $('#moderationResponse')
const moderationResponseTextarea = $('#moderationResponse')
await moderationResponseTextarea.waitForDisplayed()
await moderationResponseTextarea.setValue(moderationResponse)

View File

@ -3,7 +3,7 @@ import { getCheckbox } from '../utils'
export class AnonymousSettingsPage {
async openSettings () {
const link = await $('my-header .settings-button')
const link = $('my-header .settings-button')
await link.waitForClickable()
await link.click()
@ -11,7 +11,7 @@ export class AnonymousSettingsPage {
}
async closeSettings () {
const closeModal = await $('.modal.show .modal-header > button')
const closeModal = $('.modal.show .modal-header > button')
await closeModal.waitForClickable()
await closeModal.click()

View File

@ -171,7 +171,7 @@ export class MyAccountPage {
await selectCustomSelect('videoChannelId', 'Main root channel')
await selectCustomSelect('privacy', privacy)
const submit = await $('form input[type=submit]')
const submit = $('form input[type=submit]')
await submit.waitForClickable()
await submit.scrollIntoView()
await submit.click()

View File

@ -22,9 +22,7 @@ export class PlayerPage {
}
waitUntilPlayerWrapper () {
return browser.waitUntil(async () => {
return !!(await $('#placeholder-preview'))
})
return $('#placeholder-preview').waitForExist()
}
waitUntilPlaying () {
@ -98,7 +96,7 @@ export class PlayerPage {
async fillEmbedVideoPassword (videoPassword: string) {
const videoPasswordInput = $('input#video-password-input')
const confirmButton = await $('button#video-password-submit')
const confirmButton = $('button#video-password-submit')
await videoPasswordInput.clearValue()
await videoPasswordInput.setValue(videoPassword)

View File

@ -60,7 +60,7 @@ export class VideoListPage {
}
async getVideosListName () {
const elems = await $$('.videos .video-miniature .video-name')
const elems = $$('.videos .video-miniature .video-name')
const texts = await elems.map(e => e.getText())
return texts.map(t => t.trim())
@ -93,7 +93,7 @@ export class VideoListPage {
}
private async getVideoMiniature (name: string) {
const videoName = await $('.video-name=' + name)
const videoName = $('.video-name=' + name)
await videoName.waitForDisplayed()
return findParentElement(videoName, async el => await el.getTagName() === 'my-video-miniature')

View File

@ -4,7 +4,7 @@ import { FIXTURE_URLS } from '../utils'
export class VideoPublishPage extends VideoManage {
async navigateTo (tab?: 'Go live') {
const publishButton = await $('.publish-button > a')
const publishButton = $('.publish-button > a')
await publishButton.waitForClickable()
await publishButton.click()
@ -31,7 +31,7 @@ export class VideoPublishPage extends VideoManage {
await browser.pause(1000)
const elem = await $(fileInputSelector)
const elem = $(fileInputSelector)
await elem.chooseFile(fileToUpload)
// Wait for the upload to finish

View File

@ -108,12 +108,12 @@ export class VideoWatchPage {
}
async fillVideoPassword (videoPassword: string) {
const videoPasswordInput = await $('input#confirmInput')
const videoPasswordInput = $('input#confirmInput')
await videoPasswordInput.waitForClickable()
await videoPasswordInput.clearValue()
await videoPasswordInput.setValue(videoPassword)
const confirmButton = await $('input[value="Confirm"]')
const confirmButton = $('input[value="Confirm"]')
await confirmButton.waitForClickable()
return confirmButton.click()
}
@ -123,7 +123,7 @@ export class VideoWatchPage {
// ---------------------------------------------------------------------------
async like () {
const likeButton = await $('.action-button-like')
const likeButton = $('.action-button-like')
const isActivated = (await likeButton.getAttribute('class')).includes('activated')
let count: number
@ -150,7 +150,7 @@ export class VideoWatchPage {
async clickOnManage () {
await this.clickOnMoreDropdownIcon()
const items = await $$('.dropdown-menu.show .dropdown-item')
const items = $$('.dropdown-menu.show .dropdown-item')
for (const item of items) {
const content = await item.getText()
@ -205,36 +205,36 @@ export class VideoWatchPage {
// ---------------------------------------------------------------------------
async createThread (comment: string) {
const textarea = await $('my-video-comment-add textarea')
const textarea = $('my-video-comment-add textarea')
await textarea.waitForClickable()
await textarea.setValue(comment)
const confirmButton = await $('.comment-buttons .primary-button')
const confirmButton = $('.comment-buttons .primary-button')
await confirmButton.waitForClickable()
await confirmButton.click()
const createdComment = await (await $('.comment-html p')).getText()
const createdComment = await $('.comment-html p').getText()
return expect(createdComment).toBe(comment)
}
async createReply (comment: string) {
const replyButton = await $('button.comment-action-reply')
const replyButton = $('button.comment-action-reply')
await replyButton.waitForClickable()
await replyButton.scrollIntoView({ block: 'center' })
await replyButton.click()
const textarea = await $('my-video-comment my-video-comment-add textarea')
const textarea = $('my-video-comment my-video-comment-add textarea')
await textarea.waitForClickable()
await textarea.setValue(comment)
const confirmButton = await $('my-video-comment .comment-buttons .primary-button')
const confirmButton = $('my-video-comment .comment-buttons .primary-button')
await confirmButton.waitForClickable()
await replyButton.scrollIntoView({ block: 'center' })
await confirmButton.click()
const createdComment = await $('.is-child .comment-html p')
const createdComment = $('.is-child .comment-html p')
await createdComment.waitForDisplayed()
return expect(await createdComment.getText()).toBe(comment)

View File

@ -261,7 +261,7 @@ describe('NSFW', () => {
expect(await playerPage.getNSFWContentText()).toContain('This video contains sensitive content')
expect(await playerPage.hasPoster()).toBeTruthy()
const moreButton = await playerPage.getMoreNSFWInfoButton()
const moreButton = playerPage.getMoreNSFWInfoButton()
expect(await moreButton.isDisplayed()).toBeTruthy()
await moreButton.click()
@ -310,7 +310,7 @@ describe('NSFW', () => {
it('Should use a confirm modal when viewing the video and watch the video', async function () {
await go(videoUrl)
const confirmTitle = await videoWatchPage.getModalTitleEl()
const confirmTitle = videoWatchPage.getModalTitleEl()
await confirmTitle.waitForDisplayed()
expect(await confirmTitle.getText()).toContain('Mature or explicit content')

201
client/eslint.config.mjs Normal file
View File

@ -0,0 +1,201 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import love from 'eslint-config-love'
import stylistic from '@stylistic/eslint-plugin'
import angular from 'angular-eslint'
export default defineConfig([
globalIgnores([
'**/node_modules/',
'**/dist',
'**/build',
'.angular'
]),
{
extends: [
love,
angular.configs.tsRecommended
],
processor: angular.processInlineTemplates,
plugins: {
'@stylistic': stylistic
},
files: [
'src/**/*.ts',
'e2e/**/*.ts'
],
rules: {
'@angular-eslint/component-selector': [
'error',
{
'type': [ 'element', 'attribute' ],
'prefix': 'my',
'style': 'kebab-case'
}
],
'@angular-eslint/directive-selector': [
'error',
{
'type': [ 'element', 'attribute' ],
'prefix': 'my',
'style': 'camelCase'
}
],
'@angular-eslint/use-component-view-encapsulation': 'error',
'@typescript-eslint/prefer-readonly': 'off',
"n/no-callback-literal": "off",
'@stylistic/semi': [ 'error', 'never' ],
'eol-last': [ 'error', 'always' ],
'indent': 'off',
'no-lone-blocks': 'off',
'no-mixed-operators': 'off',
'max-len': [ 'error', {
code: 140
} ],
'array-bracket-spacing': [ 'error', 'always' ],
'quote-props': [ 'error', 'consistent-as-needed' ],
'padded-blocks': 'off',
'no-async-promise-executor': 'off',
'dot-notation': 'off',
'promise/param-names': 'off',
'import/first': 'off',
'operator-linebreak': [ 'error', 'after', {
overrides: {
'?': 'before',
':': 'before'
}
} ],
'@typescript-eslint/consistent-type-assertions': [ 'error', {
assertionStyle: 'as'
} ],
'@typescript-eslint/array-type': [ 'error', {
default: 'array'
} ],
'@typescript-eslint/restrict-template-expressions': [ 'off', {
allowNumber: 'true'
} ],
'@typescript-eslint/no-this-alias': [ 'error', {
allowDestructuring: true,
allowedNames: [ 'self' ]
} ],
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/quotes': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/promise-function-async': 'off',
'@typescript-eslint/no-dynamic-delete': 'off',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-extraneous-class': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/restrict-plus-operands': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
'no-implicit-globals': 'off',
'@typescript-eslint/no-confusing-void-expression': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'logical-assignment-operators': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-magic-numbers': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-type-assertion': 'off',
'@typescript-eslint/prefer-destructuring': 'off',
'promise/avoid-new': 'off',
'@typescript-eslint/class-methods-use-this': 'off',
'arrow-body-style': 'off',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
'@typescript-eslint/consistent-type-exports': 'off',
'@typescript-eslint/init-declarations': 'off',
'no-console': 'off',
'@typescript-eslint/dot-notation': 'off',
'@typescript-eslint/method-signature-style': 'off',
'eslint-comments/require-description': 'off',
'max-lines': 'off',
'@typescript-eslint/no-misused-spread': 'off',
'consistent-this': 'off',
'@typescript-eslint/no-empty-function': 'off',
'prefer-regex-literals': 'off',
'@typescript-eslint/prefer-regexp-exec': 'off',
'@typescript-eslint/prefer-promise-reject-errors': 'off',
'@typescript-eslint/no-unnecessary-template-expression': 'off',
'@typescript-eslint/no-loop-func': 'off',
'@typescript-eslint/switch-exhaustiveness-check': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-import-type-side-effects': 'off',
'@typescript-eslint/no-require-imports': 'off',
'no-useless-return': 'off',
'no-return-assign': 'off',
'@typescript-eslint/unbound-method': 'off',
'import/no-named-default': 'off',
"@typescript-eslint/no-deprecated": [ 'error', {
allow: [
{ from: 'package', package: 'video.js', name: 'options'}
]
}],
// Can be interesting to enable
'@typescript-eslint/no-unsafe-return': 'off',
// Can be interesting to enable
'complexity': 'off',
// Interesting but has a bug with specific cases
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
// TODO: enable
'@typescript-eslint/prefer-as-const': 'off',
// TODO: enable
'@typescript-eslint/max-params': 'off',
// TODO: enable
'@typescript-eslint/no-unsafe-function-type': 'off',
// TODO: enable
'@typescript-eslint/no-deprecated': 'off',
// TODO: enable
'@typescript-eslint/no-floating-promises': 'off',
// We use many nested callbacks in our tests
'max-nested-callbacks': 'off'
},
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: [ 'src/standalone/build-tools/vite-utils.ts' ]
}
}
}
},
{
files: [ '**/*.html' ],
extends: [
angular.configs.templateRecommended,
angular.configs.templateAccessibility,
],
rules: {
// TODO: enable
'@angular-eslint/template/button-has-type': 'off'
}
}
])

View File

@ -14,7 +14,7 @@
},
"scripts": {
"lint": "npm run lint-ts && npm run lint-scss",
"lint-ts": "eslint --cache --ext .ts src/standalone/**/*.ts && npm run ng lint",
"lint-ts": "eslint",
"lint-scss": "stylelint 'src/**/*.scss'",
"eslint": "eslint",
"ng": "ng",
@ -34,11 +34,6 @@
],
"typings": "*.d.ts",
"devDependencies": {
"@angular-eslint/builder": "^19.0.2",
"@angular-eslint/eslint-plugin": "^19.0.2",
"@angular-eslint/eslint-plugin-template": "^19.0.2",
"@angular-eslint/schematics": "^19.0.2",
"@angular-eslint/template-parser": "^19.0.2",
"@angular/animations": "^19.1.4",
"@angular/build": "^19.1.5",
"@angular/cdk": "^19.1.2",
@ -73,8 +68,6 @@
"@types/sanitize-html": "2.11.0",
"@types/sha.js": "^2.4.0",
"@types/video.js": "^7.3.40",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@wdio/browserstack-service": "^9.12.7",
"@wdio/cli": "^9.12.7",
"@wdio/local-runner": "^9.12.7",
@ -90,10 +83,6 @@
"core-js": "^3.22.8",
"debug": "^4.3.1",
"dompurify": "^3.1.6",
"eslint": "^8.28.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "^48.1.0",
"eslint-plugin-prefer-arrow": "latest",
"expect-webdriverio": "^4.2.3",
"hls.js": "~1.5.11",
"intl-messageformat": "^10.1.0",
@ -126,5 +115,10 @@
"vite-plugin-node-polyfills": "^0.23.0",
"zone.js": "~0.15.0"
},
"dependencies": {}
"dependencies": {
"@stylistic/eslint-plugin": "^4.2.0",
"angular-eslint": "^19.3.0",
"eslint": "^9.26.0",
"eslint-config-love": "^119.0.0"
}
}

View File

@ -15,6 +15,8 @@
<div *ngIf="formErrors.fromName" class="form-error" role="alert">{{ formErrors.fromName }}</div>
</div>
<button>toto</button>
<div class="form-group">
<label i18n for="fromEmail">Your email</label>
<input

View File

@ -194,8 +194,8 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
.pipe(pairwise())
.subscribe(([ oldValue, newValue ]) => {
if (oldValue === false && newValue === true) {
/* eslint-disable max-len */
this.signupAlertMessage =
// eslint-disable-next-line max-len
$localize`You enabled signup: we automatically enabled the "Block new videos automatically" checkbox of the "Videos" section just below.`
this.form().patchValue({

View File

@ -122,7 +122,6 @@ export class EditVODTranscodingComponent implements OnInit, OnChanges {
if (newValue === false && hlsControl.value === false) {
hlsControl.setValue(true)
// eslint-disable-next-line max-len
this.notifier.info(
$localize`Automatically enable HLS transcoding because at least 1 output format must be enabled when transcoding is enabled`,
'',

View File

@ -79,7 +79,6 @@ export class FollowersListComponent extends RestTable<ActorFollow> implements On
this.followService.acceptFollower(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = formatICU(
$localize`Accepted {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`,
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) }
@ -94,7 +93,6 @@ export class FollowersListComponent extends RestTable<ActorFollow> implements On
}
async rejectFollower (follows: ActorFollow[]) {
// eslint-disable-next-line max-len
const message = formatICU(
$localize`Do you really want to reject {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`,
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) }
@ -106,7 +104,6 @@ export class FollowersListComponent extends RestTable<ActorFollow> implements On
this.followService.rejectFollower(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = formatICU(
$localize`Rejected {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`,
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) }
@ -126,7 +123,6 @@ export class FollowersListComponent extends RestTable<ActorFollow> implements On
let message = $localize`Deleted followers will be able to send again a follow request.`
message += '<br /><br />'
// eslint-disable-next-line max-len
message += formatICU(
$localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`,
icuParams
@ -138,7 +134,6 @@ export class FollowersListComponent extends RestTable<ActorFollow> implements On
this.followService.removeFollower(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = formatICU(
$localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`,
icuParams

View File

@ -93,7 +93,6 @@ export class FollowingListComponent extends RestTable<ActorFollow> implements On
this.followService.unfollow(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = formatICU(
$localize`You are not following {count, plural, =1 {{entryName} anymore.} other {these {count} entries anymore.}}`,
icuParams

View File

@ -143,7 +143,6 @@ export class RegistrationListComponent extends RestTable<UserRegistration> imple
private async removeRegistrations (registrations: UserRegistration[]) {
const icuParams = { count: registrations.length, username: registrations[0].username }
// eslint-disable-next-line max-len
const message = formatICU(
$localize`Do you really want to delete {count, plural, =1 {{username} registration request?} other {{count} registration requests?}}`,
icuParams
@ -155,7 +154,6 @@ export class RegistrationListComponent extends RestTable<UserRegistration> imple
this.adminRegistrationService.removeRegistrations(registrations)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = formatICU(
$localize`Removed {count, plural, =1 {{username} registration request} other {{count} registration requests}}`,
icuParams

View File

@ -7,7 +7,6 @@ import { SelectOptionsItem } from '../../../../../types/select-options-item.mode
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export abstract class UserEdit extends FormReactive implements OnInit {
videoQuotaOptions: SelectOptionsItem[] = []
videoQuotaDailyOptions: SelectOptionsItem[] = []
@ -53,7 +52,7 @@ export abstract class UserEdit extends FormReactive implements OnInit {
.subscribe(translations => {
if (authUser.role.id === UserRole.ADMINISTRATOR) {
this.roles = Object.entries(USER_ROLE_LABELS)
.map(([ key, value ]) => ({ value: key.toString(), label: peertubeTranslate(value, translations) }))
.map(([ key, value ]) => ({ value: key.toString(), label: peertubeTranslate(value, translations) }))
return
}

View File

@ -372,13 +372,11 @@ export class VideoListComponent extends RestTable<Video> implements OnInit {
let message: string
if (type === 'hls') {
// eslint-disable-next-line max-len
message = formatICU(
$localize`Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {{count} HLS streaming playlists}}?`,
{ count: videos.length }
)
} else {
// eslint-disable-next-line max-len
message = formatICU(
$localize`Are you sure you want to delete Web Video files of {count, plural, =1 {1 video} other {{count} videos}}?`,
{ count: videos.length }

View File

@ -1,14 +1,14 @@
import { Component, ViewEncapsulation } from '@angular/core'
/*
* Allows to lazy load global player styles in the watch component
*/
* Allows to lazy load global player styles in the watch component
*/
@Component({
selector: 'my-player-styles',
template: '',
styleUrls: [ './player-styles.component.scss' ],
/* eslint-disable @angular-eslint/use-component-view-encapsulation */
// eslint-disable-next-line @angular-eslint/use-component-view-encapsulation
encapsulation: ViewEncapsulation.None,
standalone: true
})

View File

@ -179,7 +179,6 @@ export class VideoPublishComponent implements OnInit, CanComponentDeactivate {
}
private async buildUploadMessages () {
// eslint-disable-next-line max-len
const noQuota =
$localize`Sorry, the upload feature is disabled for your account. If you want to add videos, an admin must unlock your quota.`
const autoBlock =
@ -188,7 +187,6 @@ export class VideoPublishComponent implements OnInit, CanComponentDeactivate {
const quotaLeftDaily =
// eslint-disable-next-line max-len
$localize`Your daily video quota is insufficient. If you want to add more videos, you must wait for 24 hours or an admin must increase your daily quota.`
// eslint-disable-next-line max-len
const quotaLeft = $localize`Your video quota is insufficient. If you want to add more videos, an admin must increase your quota.`
const uploadMessages = {

View File

@ -74,7 +74,7 @@ type Form = {
previewfile: FormControl<Blob>
support: FormControl<string>
schedulePublicationAt: FormControl<Date>
pluginData: FormGroup<any>
pluginData: FormGroup
}
@Component({

View File

@ -372,7 +372,6 @@ export class VideoManageController implements OnDestroy {
let blockedWarning = ''
if (willBeBlocked) {
// eslint-disable-next-line max-len
blockedWarning = ' ' +
$localize`Your video will also be automatically blocked since video publication requires manual validation by moderators.`
}

View File

@ -256,7 +256,6 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
// Inject JS
if (this.serverConfig.instance.customizations.javascript) {
try {
/* eslint-disable no-eval */
window.eval(this.serverConfig.instance.customizations.javascript)
} catch (err) {
logger.error('Cannot eval custom JavaScript.', err)

View File

@ -36,7 +36,7 @@ export class Hotkey {
constructor (
public combo: string | string[],
public callback: (event: KeyboardEvent, combo: string) => any | boolean,
public callback: (event: KeyboardEvent, combo: string) => any,
public description?: string | Function
) {
this.combo = arrayify(combo)

View File

@ -40,7 +40,6 @@ export class ActorBannerEditComponent implements OnInit {
this.maxBannerSize = config.banner.file.size.max
this.bannerExtensions = config.banner.file.extensions.join(', ')
/* eslint-disable max-len */
this.bannerFormat = $localize`ratio 6/1, recommended size: 1920x317, max size: ${
getBytes(this.maxBannerSize)
}, extensions: ${this.bannerExtensions}`

View File

@ -39,6 +39,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
readonly placeholder = input($localize`Filter...`)
readonly inputId = input('table-filter')
// eslint-disable-next-line @angular-eslint/no-output-native
readonly search = output<string>()
searchValue: string

View File

@ -1,11 +1,10 @@
import { SortMeta } from 'primeng/api'
import { Directive, OnInit, inject } from '@angular/core'
import { Notifier, RestPagination, RestTable } from '@app/core'
import { SortMeta } from 'primeng/api'
import { AccountBlock } from './account-block.model'
import { BlocklistComponentType, BlocklistService } from './blocklist.service'
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class GenericAccountBlocklistComponent extends RestTable implements OnInit {
private notifier = inject(Notifier)
private blocklistService = inject(BlocklistService)

View File

@ -1,12 +1,11 @@
import { SortMeta } from 'primeng/api'
import { Directive, OnInit, inject, viewChild } from '@angular/core'
import { Notifier, RestPagination, RestTable } from '@app/core'
import { BatchDomainsModalComponent } from '@app/shared/shared-moderation/batch-domains-modal.component'
import { ServerBlock } from '@peertube/peertube-models'
import { SortMeta } from 'primeng/api'
import { BlocklistComponentType, BlocklistService } from './blocklist.service'
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class GenericServerBlocklistComponent extends RestTable implements OnInit {
protected notifier = inject(Notifier)
protected blocklistService = inject(BlocklistService)

View File

@ -398,9 +398,7 @@ export class VideosListComponent implements OnInit, OnDestroy {
for (const video of this.videos) {
const publishedDate = video.publishedAt
for (let i = 0; i < periods.length; i++) {
const period = periods[i]
for (const period of periods) {
if (currentGroupedDate <= period.value && period.validator(publishedDate)) {
if (currentGroupedDate !== period.value) {
if (period.value !== GroupDate.OLDER) onlyOlderPeriod = false

View File

@ -6,7 +6,7 @@ const dictionary: { max: number, type: string }[] = [
{ max: 1.125899906842624e15, type: 'TB' }
]
function getBytes (value: number, precision?: number | undefined): string | number {
function getBytes (value: number, precision?: number): string | number {
const format = dictionary.find(d => value < d.max) || dictionary[dictionary.length - 1]
const calc = value / (format.max / 1024)

View File

@ -121,7 +121,6 @@ class PluginsManager {
async runHook<T> (hookName: ClientHookName, resultArg?: T | Promise<T>, params?: any) {
if (!this.hooks[hookName]) {
// eslint-disable-next-line no-return-await
return await resultArg
}

View File

@ -35,6 +35,7 @@ export class ThemeManager {
loadThemeStyle (name: string) {
const links = document.getElementsByTagName('link')
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < links.length; i++) {
const link = links[i]
if (link.getAttribute('rel').includes('style') && link.getAttribute('title')) {

View File

@ -57,7 +57,6 @@ class ContextMenuPlugin extends Plugin {
menu.on('dispose', () => {
for (const event of [ 'click', 'tap' ]) {
// eslint-disable-next-line @typescript-eslint/unbound-method
videojs.off(documentEl as Element, event, menu.dispose)
}
@ -84,7 +83,6 @@ class ContextMenuPlugin extends Plugin {
}
for (const event of [ 'click', 'tap' ]) {
// eslint-disable-next-line @typescript-eslint/unbound-method
videojs.on(documentEl as Element, event, menu.dispose)
}
}

View File

@ -14,14 +14,15 @@ class ContextMenu extends Menu {
// Each menu component has its own `dispose` method that can be
// safely bound and unbound to events while maintaining its context.
// eslint-disable-next-line @typescript-eslint/unbound-method
this.dispose = videojs.bind(this, this.dispose)
for (const c of options.content()) {
this.addItem(new ContextMenuItem(player, {
label: c.label,
listener: videojs.bind(player, c.listener)
}))
this.addItem(
new ContextMenuItem(player, {
label: c.label,
listener: videojs.bind(player, c.listener)
})
)
}
}

View File

@ -6,7 +6,6 @@ const ClickableComponent = videojs.getComponent('ClickableComponent')
export class ProgressBarMarkerComponent extends ClickableComponent {
declare options_: ProgressBarMarkerComponentOptions & videojs.ComponentOptions
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor (player: videojs.Player, options?: ProgressBarMarkerComponentOptions & videojs.ComponentOptions) {
super(player, options)

View File

@ -240,11 +240,11 @@ class PeerTubeHotkeysPlugin extends Plugin {
const isNonLatin = key.charCodeAt(0) >= capitalHetaCode
if (isNonLatin) {
if (code.indexOf('Key') === 0 && code.length === 4) { // i.e. 'KeyW'
if (code.startsWith('Key') && code.length === 4) { // i.e. 'KeyW'
return code.charAt(3)
}
if (code.indexOf('Digit') === 0 && code.length === 6) { // i.e. 'Digit7'
if (code.startsWith('Digit') && code.length === 6) { // i.e. 'Digit7'
return code.charAt(5)
}
}

View File

@ -1,2 +0,0 @@
export * from './peertube-dock-component'
export * from './peertube-dock-plugin'

View File

@ -11,7 +11,7 @@ import { omit } from '@peertube/peertube-core-utils'
const HlsWithP2P = HlsJsP2PEngine.injectMixin(Hlsjs)
type ErrorCounts = {
[ type: string ]: number
[type: string]: number
}
// ---------------------------------------------------------------------------
@ -35,10 +35,8 @@ const registerSourceHandler = function (vjs: typeof videojs) {
if (alreadyRegistered) return
alreadyRegistered = true;
// FIXME: typings
(html5 as any).registerSourceHandler({
alreadyRegistered = true // FIXME: typings
;(html5 as any).registerSourceHandler({
canHandleSource: function (source: videojs.Tech.SourceObject) {
const hlsTypeRE = /^application\/x-mpegURL|application\/vnd\.apple\.mpegurl$/i
const hlsExtRE = /\.m3u8/i
@ -58,10 +56,8 @@ const registerSourceHandler = function (vjs: typeof videojs) {
return tech.hlsProvider
}
}, 0);
// FIXME: typings
(vjs as any).Html5Hlsjs = Html5Hlsjs
}, 0) // FIXME: typings
;(vjs as any).Html5Hlsjs = Html5Hlsjs
}
// ---------------------------------------------------------------------------
@ -71,7 +67,6 @@ const registerSourceHandler = function (vjs: typeof videojs) {
const Plugin = videojs.getPlugin('plugin')
class HLSJSConfigHandler extends Plugin {
constructor (player: videojs.Player, options: HlsjsConfigHandlerOptions) {
super(player, options)
@ -132,7 +127,7 @@ export class Html5Hlsjs {
private liveEnded = false
private handlers: { [ id in 'play' | 'error' ]: EventListener } = {
private handlers: { [id in 'play' | 'error']: EventListener } = {
play: null,
error: null
}
@ -143,8 +138,8 @@ export class Html5Hlsjs {
this.vjs = vjs
this.source = source
this.tech = tech;
(this.tech as any).name_ = 'Hlsjs'
this.tech = tech
;(this.tech as any).name_ = 'Hlsjs'
this.videoElement = tech.el() as HTMLVideoElement
this.player = vjs((tech.options_ as any).playerId)
@ -162,7 +157,7 @@ export class Html5Hlsjs {
break
case mediaError.MEDIA_ERR_DECODE:
errorTxt = 'The video playback was aborted due to a corruption problem or because the video used features ' +
'your browser did not support'
'your browser did not support'
this._handleMediaError(mediaError)
break
case mediaError.MEDIA_ERR_NETWORK:
@ -222,7 +217,7 @@ export class Html5Hlsjs {
}
}
private _handleUnrecovarableError (error: any) {
private _handleUnrecoverableError (error: any) {
if (this.hls.levels.filter(l => l.id > -1).length > 1) {
this._removeQuality(this.hls.loadLevel)
return
@ -255,7 +250,7 @@ export class Html5Hlsjs {
}
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
this._handleUnrecovarableError(error)
this._handleUnrecoverableError(error)
}
}
@ -264,9 +259,8 @@ export class Html5Hlsjs {
// We may have errors if the live ended because of a fast-restream in the same permanent live
if (this.liveEnded) {
logger.info('Forcing end of live stream after a network error');
(this.player as any)?.handleTechEnded_()
logger.info('Forcing end of live stream after a network error')
;(this.player as any)?.handleTechEnded_()
this.hls?.stopLoad()
return
@ -286,7 +280,7 @@ export class Html5Hlsjs {
return
}
this._handleUnrecovarableError(error)
this._handleUnrecoverableError(error)
}
private _onError (_event: any, data: ErrorData) {
@ -307,11 +301,14 @@ export class Html5Hlsjs {
if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
error.code = 2
this._handleNetworkError(error)
} else if (data.fatal && data.type === Hlsjs.ErrorTypes.MEDIA_ERROR && data.details !== 'manifestIncompatibleCodecsError') {
} else if (
data.fatal && data.type === Hlsjs.ErrorTypes.MEDIA_ERROR &&
data.details !== Hlsjs.ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR
) {
error.code = 3
this._handleMediaError(error)
} else if (data.fatal) {
this._handleUnrecovarableError(error)
this._handleUnrecoverableError(error)
}
}

View File

@ -144,9 +144,9 @@ class SettingsButton extends Button {
}
showDialog () {
this.player().peertube().onMenuOpened();
this.player().peertube().onMenuOpened()
(this.menu.el() as HTMLElement).style.opacity = '1'
;(this.menu.el() as HTMLElement).style.opacity = '1'
this.dialog.show()
this.el().setAttribute('aria-expanded', 'true')
@ -162,8 +162,8 @@ class SettingsButton extends Button {
this.dialog.hide()
this.el().setAttribute('aria-expanded', 'false')
this.setDialogSize(this.getComponentSize(this.menu));
(this.menu.el() as HTMLElement).style.opacity = '1'
this.setDialogSize(this.getComponentSize(this.menu))
;(this.menu.el() as HTMLElement).style.opacity = '1'
this.resetChildren()
}
@ -248,7 +248,6 @@ class SettingsButton extends Button {
// Hide children to avoid sub menus stacking on top of each other
// or having multiple menus open
// eslint-disable-next-line @typescript-eslint/unbound-method
settingsMenuItem.on('click', videojs.bind(this, this.hideChildren))
// Whether to add or remove selected class on the settings sub menu element
@ -273,7 +272,6 @@ class SettingsButton extends Button {
isInIframe () {
return window.self !== window.top
}
}
Component.registerComponent('SettingsButton', SettingsButton)

View File

@ -1,6 +1,6 @@
{
"extends": "../../../tsconfig.json",
"include": [
"./index.ts"
"./src/**/*.ts"
]
}

View File

@ -1,8 +0,0 @@
/* eslint-disable @typescript-eslint/array-type */
import { FormArray, FormControl, FormGroup } from '@angular/forms'
type Unbox<T> = T extends Array<infer V> ? V : T
export type ModelFormGroup<T> = FormGroup<
{ [K in keyof T]: T[K] extends Array<any> ? FormArray<FormControl<Unbox<T[K]>>> : FormControl<T[K]> }
>

View File

@ -1,13 +0,0 @@
{
"extends": "./tsconfig.json",
"include": [
// adjust "includes" to what makes sense for you and your project
"src/**/*.ts",
"e2e/**/*.ts"
],
"references": [
{ "path": "../packages/core-utils" },
{ "path": "../packages/models" },
{ "path": "../packages/typescript-utils" }
]
}

View File

@ -51,11 +51,9 @@
{ "path": "../packages/models" },
{ "path": "../packages/typescript-utils" }
],
"files": [
"src/polyfills.ts"
],
"include": [
"src/polyfills.ts",
"src/environments/*.ts",
"src/main*.ts",
"src/**/*.d.ts",
"src/app/**/*.ts",

File diff suppressed because it is too large Load Diff

161
eslint.config.mjs Normal file
View File

@ -0,0 +1,161 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import love from 'eslint-config-love'
import stylistic from '@stylistic/eslint-plugin'
export default defineConfig([
globalIgnores([
'**/node_modules/',
'node_modules',
'packages/tests/fixtures',
'apps/**/dist',
'packages/**/dist',
'server/dist',
'packages/types-generator',
'*.js',
'client',
'dist'
]),
{
extends: [ love ],
plugins: {
'@stylistic': stylistic
},
files: [
'server/**/*.ts',
'scripts/**/*.ts',
'packages/**/*.ts',
'apps/**/*.ts'
],
rules: {
'@stylistic/semi': [ 'error', 'never' ],
'eol-last': [ 'error', 'always' ],
'indent': 'off',
'no-lone-blocks': 'off',
'no-mixed-operators': 'off',
'max-len': [ 'error', {
code: 140
} ],
'array-bracket-spacing': [ 'error', 'always' ],
'quote-props': [ 'error', 'consistent-as-needed' ],
'padded-blocks': 'off',
'no-async-promise-executor': 'off',
'dot-notation': 'off',
'promise/param-names': 'off',
'import/first': 'off',
'operator-linebreak': [ 'error', 'after', {
overrides: {
'?': 'before',
':': 'before'
}
} ],
'@typescript-eslint/consistent-type-assertions': [ 'error', {
assertionStyle: 'as'
} ],
'@typescript-eslint/array-type': [ 'error', {
default: 'array'
} ],
'@typescript-eslint/restrict-template-expressions': [ 'off', {
allowNumber: 'true'
} ],
'@typescript-eslint/no-this-alias': [ 'error', {
allowDestructuring: true,
allowedNames: [ 'self' ]
} ],
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/quotes': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/promise-function-async': 'off',
'@typescript-eslint/no-dynamic-delete': 'off',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-extraneous-class': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/restrict-plus-operands': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
'no-implicit-globals': 'off',
'@typescript-eslint/no-confusing-void-expression': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'logical-assignment-operators': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-magic-numbers': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-type-assertion': 'off',
'@typescript-eslint/prefer-destructuring': 'off',
'promise/avoid-new': 'off',
'@typescript-eslint/class-methods-use-this': 'off',
'arrow-body-style': 'off',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
'@typescript-eslint/consistent-type-exports': 'off',
'@typescript-eslint/init-declarations': 'off',
'no-console': 'off',
'@typescript-eslint/dot-notation': 'off',
'@typescript-eslint/method-signature-style': 'off',
'eslint-comments/require-description': 'off',
'max-lines': 'off',
'@typescript-eslint/no-misused-spread': 'off',
'consistent-this': 'off',
'@typescript-eslint/no-empty-function': 'off',
'prefer-regex-literals': 'off',
'@typescript-eslint/prefer-regexp-exec': 'off',
'@typescript-eslint/prefer-promise-reject-errors': 'off',
'@typescript-eslint/no-unnecessary-template-expression': 'off',
'@typescript-eslint/no-loop-func': 'off',
'@typescript-eslint/switch-exhaustiveness-check': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-import-type-side-effects': 'off',
'@typescript-eslint/unbound-method': 'off',
"require-await": "off",
"@typescript-eslint/require-await": "error",
// Can be interesting to enable
'@typescript-eslint/no-unsafe-return': 'off',
// Can be interesting to enable
'complexity': 'off',
// Interesting but has a bug with specific cases
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
// TODO: enable
'@typescript-eslint/prefer-as-const': 'off',
// TODO: enable
'@typescript-eslint/max-params': 'off',
// TODO: enable
'@typescript-eslint/no-unsafe-function-type': 'off',
// We use many nested callbacks in our tests
'max-nested-callbacks': 'off'
},
languageOptions: {
parserOptions: {
projectService: true
}
},
linterOptions: {
reportUnusedDisableDirectives: 'off'
}
}
])

View File

@ -198,6 +198,7 @@
"devDependencies": {
"@peertube/maildev": "^1.2.0",
"@peertube/resolve-tspaths": "^0.8.14",
"@stylistic/eslint-plugin": "^4.2.0",
"@types/archiver": "^6.0.2",
"@types/bcrypt": "^5.0.0",
"@types/bencode": "^2.0.0",
@ -230,7 +231,6 @@
"@types/webtorrent": "^0.109.0",
"@types/ws": "^8.2.0",
"@types/yauzl": "^2.10.3",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"autocannon": "^8.0.0",
"chai": "^5.1.0",
"chai-json-schema": "^1.5.0",
@ -238,11 +238,8 @@
"concurrently": "^9.1.2",
"depcheck": "^1.4.2",
"esbuild": "^0.24.2",
"eslint": "8.56.0",
"eslint-config-standard-with-typescript": "43.0.1",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-n": "^16.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint": "^9.26.0",
"eslint-config-love": "^119.0.0",
"fast-xml-parser": "^4.0.0-beta.8",
"jpeg-js": "^0.4.4",
"jszip": "^3.10.1",

View File

@ -79,7 +79,7 @@ const I18N_LOCALE_ALIAS = {
'zh': 'zh-Hans-CN'
}
export const POSSIBLE_LOCALES = (Object.keys(I18N_LOCALES) as string[]).concat(Object.keys(I18N_LOCALE_ALIAS))
export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES).concat(Object.keys(I18N_LOCALE_ALIAS))
export function getDefaultLocale () {
return 'en-US'
@ -89,7 +89,7 @@ export function isDefaultLocale (locale: string) {
return getCompleteLocale(locale) === getCompleteLocale(getDefaultLocale())
}
export function peertubeTranslate (str: string, translations?: { [ id: string ]: string }) {
export function peertubeTranslate (str: string, translations?: { [id: string]: string }) {
if (!translations?.[str]) return str
return translations[str]

View File

@ -3,9 +3,7 @@ import { VideoResolution } from '@peertube/peertube-models'
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'
/**
*
* Helpers to run ffprobe and extract data from the JSON output
*
*/
function ffprobePromise (path: string) {
@ -23,9 +21,45 @@ function ffprobePromise (path: string) {
// ---------------------------------------------------------------------------
const imageCodecs = new Set([
'ansi', 'apng', 'bintext', 'bmp', 'brender_pix', 'dpx', 'exr', 'fits', 'gem', 'gif', 'jpeg2000', 'jpgls', 'mjpeg', 'mjpegb', 'msp2',
'pam', 'pbm', 'pcx', 'pfm', 'pgm', 'pgmyuv', 'pgx', 'photocd', 'pictor', 'png', 'ppm', 'psd', 'sgi', 'sunrast', 'svg', 'targa', 'tiff',
'txd', 'webp', 'xbin', 'xbm', 'xface', 'xpm', 'xwd'
'ansi',
'apng',
'bintext',
'bmp',
'brender_pix',
'dpx',
'exr',
'fits',
'gem',
'gif',
'jpeg2000',
'jpgls',
'mjpeg',
'mjpegb',
'msp2',
'pam',
'pbm',
'pcx',
'pfm',
'pgm',
'pgmyuv',
'pgx',
'photocd',
'pictor',
'png',
'ppm',
'psd',
'sgi',
'sunrast',
'svg',
'targa',
'tiff',
'txd',
'webp',
'xbin',
'xbm',
'xface',
'xpm',
'xwd'
])
async function isAudioFile (path: string, existingProbe?: FfprobeData) {
@ -61,7 +95,7 @@ async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
return { absolutePath: data.format.filename }
}
function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) {
function getMaxAudioBitrate (type: string, bitrate: number) {
const maxKBitrate = 384
const kToBits = (kbits: number) => kbits * 1000
@ -209,16 +243,13 @@ export {
ffprobePromise,
getAudioStream,
getChaptersFromContainer,
getMaxAudioBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamDuration,
getVideoStreamFPS,
hasAudioStream,
hasVideoStream,
isAudioFile
}

View File

@ -5,15 +5,15 @@ import { buildStreamSuffix } from '../ffmpeg-utils.js'
export function addDefaultEncoderGlobalParams (command: FfmpegCommand) {
// avoid issues when transcoding some files: https://trac.ffmpeg.org/ticket/6375
command.outputOption('-max_muxing_queue_size 1024')
// strip all metadata
.outputOption('-map_metadata -1')
// allows import of source material with incompatible pixel formats (e.g. MJPEG video)
.outputOption('-pix_fmt yuv420p')
// strip all metadata
.outputOption('-map_metadata -1')
// allows import of source material with incompatible pixel formats (e.g. MJPEG video)
.outputOption('-pix_fmt yuv420p')
}
export function addDefaultEncoderParams (options: {
command: FfmpegCommand
encoder: 'libx264' | string
encoder: string
fps: number
streamNum?: number

View File

@ -2,6 +2,6 @@ import { ServerHookName } from './server-hook.model.js'
export interface RegisterServerHookOptions {
target: ServerHookName
handler: Function
handler: () => any
priority?: number
}

View File

@ -17,5 +17,4 @@ export type VideoSortField =
// trending sorts
'trending' | '-trending' |
'hot' | '-hot' |
'best' | '-best' |
'best' | '-best'

View File

@ -122,7 +122,7 @@ export function makeUploadRequest (
method?: 'POST' | 'PUT'
fields: { [fieldName: string]: any }
attaches?: { [attachName: string]: any | any[] }
attaches?: { [attachName: string]: any }
}
) {
let req = options.method === 'PUT'

View File

@ -5,12 +5,14 @@ export function setDefaultVideoChannel (servers: PeerTubeServer[]) {
return Promise.all(
servers.map(s => {
return s.users.getMyInfo()
.then(user => { s.store.channel = user.videoChannels[0] })
.then(user => {
s.store.channel = user.videoChannels[0]
})
})
)
}
export async function setDefaultChannelAvatar (serversArg: PeerTubeServer | PeerTubeServer[], channelName: string = 'root_channel') {
export async function setDefaultChannelAvatar (serversArg: PeerTubeServer | PeerTubeServer[], channelName = 'root_channel') {
const servers = arrayify(serversArg)
return Promise.all(

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/indent */
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { pick } from '@peertube/peertube-core-utils'

View File

@ -170,7 +170,6 @@ export async function checkResolutionsInMasterPlaylist (options: {
if (splittedAudio && hasAudio && hasVideo) {
expect(masterPlaylist).to.match(
// eslint-disable-next-line max-len
new RegExp(
`#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="(group_Audio|audio)",NAME="(Audio|audio_0)"(,AUTOSELECT=YES)?,DEFAULT=YES,URI="[^.]*0.m3u8"`
)

View File

@ -5,6 +5,7 @@ export async function checkTrackerInfohash (serverUrl: string, infohash: string)
const path = '/tracker/announce'
// From bittorrent-tracker
// eslint-disable-next-line @typescript-eslint/no-deprecated
const infohashBinary = escape(Buffer.from(infohash, 'hex').toString('binary')).replace(/[@*/+]/g, function (char) {
return '%' + char.charCodeAt(0).toString(16).toUpperCase()
})

View File

@ -39,7 +39,7 @@ export class TranscriberFactory {
getEngineByName (engineName: string) {
const engine = this.engines.find(({ name }) => name === engineName)
if (!engine) {
throw new Error(`Unknow engine ${engineName}`)
throw new Error(`Unknown engine ${engineName}`)
}
return engine

View File

@ -30,7 +30,8 @@ export class TranscriptFile {
const guessableFormats = [ 'txt', 'vtt', 'srt' ]
assert(
guessableFormats.includes(format),
`Couldn't guess transcript format from extension "${format}". Valid formats are: ${guessableFormats.join(', ')}."`)
`Couldn't guess transcript format from extension "${format}". Valid formats are: ${guessableFormats.join(', ')}."`
)
return new TranscriptFile({ path, language, format: format as TranscriptFormat })
}
@ -49,7 +50,7 @@ export class TranscriptFile {
return new TranscriptFile({ path, language, format })
}
async equals (transcript: TranscriptFile, caseSensitive: boolean = true) {
async equals (transcript: TranscriptFile, caseSensitive = true) {
if (this.language !== transcript.language) {
return false
}

View File

@ -143,7 +143,7 @@ elif [ "$1" = "external-plugins" ]; then
runJSTest "$1" 1 $externalPluginsFiles
MOCHA_PARALLEL=true runJSTest "$1" $((2*$speedFactor)) $peertubeRunnerFiles
elif [ "$1" = "lint" ]; then
npm run eslint -- --ext .ts "server/**/*.ts" "scripts/**/*.ts" "packages/**/*.ts" "apps/**/*.ts"
npm run eslint
npm run swagger-cli -- validate support/doc/api/openapi.yaml

View File

@ -16,28 +16,32 @@ const lTags = loggerTagsFactory('api', 'runner')
const runnerJobFilesRouter = express.Router()
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality/audio',
runnerJobFilesRouter.post(
'/jobs/:jobUUID/files/videos/:videoId/max-quality/audio',
apiRateLimiter,
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
asyncMiddleware(getMaxQualitySeparatedAudioFile)
)
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality',
runnerJobFilesRouter.post(
'/jobs/:jobUUID/files/videos/:videoId/max-quality',
apiRateLimiter,
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
asyncMiddleware(getMaxQualityVideoFile)
)
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/previews/max-quality',
runnerJobFilesRouter.post(
'/jobs/:jobUUID/files/videos/:videoId/previews/max-quality',
apiRateLimiter,
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
getMaxQualityVideoPreview
)
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename',
runnerJobFilesRouter.post(
'/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename',
apiRateLimiter,
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
@ -59,7 +63,10 @@ async function getMaxQualitySeparatedAudioFile (req: express.Request, res: expre
const video = res.locals.videoAll
logger.info(
'Get max quality separated audio file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name,
'Get max quality separated audio file of video %s of job %s for runner %s',
video.uuid,
runnerJob.uuid,
runner.name,
lTags(runner.name, runnerJob.id, runnerJob.type)
)
@ -74,7 +81,10 @@ async function getMaxQualityVideoFile (req: express.Request, res: express.Respon
const video = res.locals.videoAll
logger.info(
'Get max quality file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name,
'Get max quality file of video %s of job %s for runner %s',
video.uuid,
runnerJob.uuid,
runner.name,
lTags(runner.name, runnerJob.id, runnerJob.type)
)
@ -124,7 +134,10 @@ function getMaxQualityVideoPreview (req: express.Request, res: express.Response)
const video = res.locals.videoAll
logger.info(
'Get max quality preview file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name,
'Get max quality preview file of video %s of job %s for runner %s',
video.uuid,
runnerJob.uuid,
runner.name,
lTags(runner.name, runnerJob.id, runnerJob.type)
)
@ -140,7 +153,11 @@ function getVideoStudioTaskFile (req: express.Request, res: express.Response) {
const filename = req.params.filename
logger.info(
'Get video studio task file %s of video %s of job %s for runner %s', filename, video.uuid, runnerJob.uuid, runner.name,
'Get video studio task file %s of video %s of job %s for runner %s',
filename,
video.uuid,
runnerJob.uuid,
runner.name,
lTags(runner.name, runnerJob.id, runnerJob.type)
)

View File

@ -164,7 +164,7 @@ async function replaceVideoSourceResumable (req: express.Request, res: express.R
async function addVideoJobsAfterUpload (video: MVideoFullLight, videoFile: MVideoFile) {
const jobs: (CreateJobArgument & CreateJobOptions)[] = [
{
type: 'manage-video-torrent' as 'manage-video-torrent',
type: 'manage-video-torrent' as const,
payload: {
videoId: video.id,
videoFileId: videoFile.id,
@ -175,7 +175,7 @@ async function addVideoJobsAfterUpload (video: MVideoFullLight, videoFile: MVide
buildStoryboardJobIfNeeded({ video, federate: false }),
{
type: 'federate-video' as 'federate-video',
type: 'federate-video' as const,
payload: {
videoUUID: video.uuid,
isNewVideoForFederation: false
@ -189,7 +189,7 @@ async function addVideoJobsAfterUpload (video: MVideoFullLight, videoFile: MVide
if (video.state === VideoState.TO_TRANSCODE) {
jobs.push({
type: 'transcoding-job-builder' as 'transcoding-job-builder',
type: 'transcoding-job-builder' as const,
payload: {
videoUUID: video.uuid,
optimizeJob: {

View File

@ -73,9 +73,7 @@ lazyStaticRouter.use(
// ---------------------------------------------------------------------------
export {
lazyStaticRouter,
getPreview,
getVideoCaption
lazyStaticRouter
}
// ---------------------------------------------------------------------------

View File

@ -33,9 +33,9 @@ async function getSitemap (req: express.Request, res: express.Response) {
const sitemapStream = new SitemapStream({
hostname: WEBSERVER.URL,
errorHandler: (err: Error, level: ErrorLevel) => {
if (level === 'warn') {
if (level === ErrorLevel.WARN) {
logger.warn('Warning in sitemap generation.', { err })
} else if (level === 'throw') {
} else if (level === ErrorLevel.THROW) {
logger.error('Error in sitemap generation.', { err })
throw err

View File

@ -208,7 +208,6 @@ function parseSemVersion (s: string) {
function execShell (command: string, options?: ExecOptions) {
return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
exec(command, options, (err, stdout, stderr) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) return rej({ err, stdout, stderr })
return res({ stdout, stderr })
@ -274,25 +273,17 @@ const pipelinePromise = promisify(pipeline)
export {
objectConverter,
mapToJSON,
sanitizeUrl,
sanitizeHost,
execShell,
pageToStartAndCount,
peertubeTruncate,
scryptPromise,
randomBytesPromise,
generateRSAKeyPairPromise,
generateED25519KeyPairPromise,
execPromise2,
execPromise,
pipelinePromise,
parseSemVersion
}

View File

@ -55,10 +55,9 @@ const STATIC_CACHE = {
const localCache = new Map<string, any>()
const nodeDocumentLoader = (jsonld as any).documentLoaders.node();
const nodeDocumentLoader = (jsonld as any).documentLoaders.node()
/* eslint-disable no-import-assign */
(jsonld as any).documentLoader = async (url: string) => {
;(jsonld as any).documentLoader = async (url: string) => {
if (url in STATIC_CACHE) {
return {
contextUrl: null,

View File

@ -6,7 +6,7 @@ function isWebfingerLocalResourceValid (value: string) {
if (!exists(value)) return false
if (value.startsWith('acct:') === false) return false
const actorWithHost = value.substr(5)
const actorWithHost = value.substring(5)
const actorParts = actorWithHost.split('@')
if (actorParts.length !== 2) return false

View File

@ -4,7 +4,7 @@
import { Module } from 'module'
import { extname } from 'path'
function decachePlugin (require: NodeRequire, libraryPath: string) {
function decachePlugin (require: NodeJS.Require, libraryPath: string) {
const moduleName = find(require, libraryPath)
if (!moduleName) return
@ -16,7 +16,7 @@ function decachePlugin (require: NodeRequire, libraryPath: string) {
})
}
function decacheModule (require: NodeRequire, name: string) {
function decacheModule (require: NodeJS.Require, name: string) {
const moduleName = find(require, name)
if (!moduleName) return
@ -37,7 +37,7 @@ export {
// ---------------------------------------------------------------------------
function find (require: NodeRequire, moduleName: string) {
function find (require: NodeJS.Require, moduleName: string) {
try {
return require.resolve(moduleName)
} catch {
@ -45,14 +45,14 @@ function find (require: NodeRequire, moduleName: string) {
}
}
function searchCache (require: NodeRequire, moduleName: string, callback: (current: NodeModule) => void) {
function searchCache (require: NodeJS.Require, moduleName: string, callback: (current: NodeJS.Module) => void) {
const resolvedModule = require.resolve(moduleName)
let mod: NodeModule
let mod: NodeJS.Module
const visited = {}
if (resolvedModule && ((mod = require.cache[resolvedModule]) !== undefined)) {
// Recursively go over the results
(function run (current) {
;(function run (current) {
visited[current.id] = true
current.children.forEach(function (child) {
@ -66,10 +66,10 @@ function searchCache (require: NodeRequire, moduleName: string, callback: (curre
callback(current)
})(mod)
}
};
}
function removeCachedPath (pluginPath: string) {
const pathCache = (Module as any)._pathCache as { [ id: string ]: string[] }
const pathCache = (Module as any)._pathCache as { [id: string]: string[] }
Object.keys(pathCache).forEach(function (cacheKey) {
if (cacheKey.includes(pluginPath)) {

View File

@ -58,8 +58,7 @@ export const unsafeSSRFGot = got.extend({
const bodyLimit = bodyKBLimit * 1000
/* eslint-disable @typescript-eslint/no-floating-promises */
promiseOrStream.on('downloadProgress', progress => {
void promiseOrStream.on('downloadProgress', progress => {
if (progress.transferred > bodyLimit && progress.percent !== 1) {
const message = `Exceeded the download limit of ${bodyLimit} B`
logger.warn(message, lTags())

View File

@ -20,10 +20,10 @@ class StreamReplacer extends Transform {
// readable side of the transform stream
while ((index = this.pendingChunk.indexOf('\n')) !== -1) {
// The `end` parameter is non-inclusive, so increase it to include the newline we found
const line = this.pendingChunk.slice(0, ++index)
const line = this.pendingChunk.subarray(0, ++index)
// `start` is inclusive, but we are already one char ahead of the newline -> all good
this.pendingChunk = this.pendingChunk.slice(index)
this.pendingChunk = this.pendingChunk.subarray(index)
// We have a single line here! Prepend the string we want
this.push(this.doReplace(line))

View File

@ -47,7 +47,7 @@ export async function unzip (options: {
const entryPath = join(destination, entry.fileName)
try {
if (/\/$/.test(entry.fileName)) {
if (entry.fileName.endsWith('/')) {
await ensureDir(entryPath)
logger.debug(`Creating directory from zip ${entryPath}`, lTags())

View File

@ -17,7 +17,6 @@ const lTags = loggerTagsFactory('youtube-dl')
const youtubeDLBinaryPath = join(CONFIG.STORAGE.BIN_DIR, CONFIG.IMPORT.VIDEOS.HTTP.YOUTUBE_DL_RELEASE.NAME)
export class YoutubeDLCLI {
static async safeGet () {
if (!await pathExists(youtubeDLBinaryPath)) {
await ensureDir(dirname(youtubeDLBinaryPath))
@ -86,7 +85,7 @@ export class YoutubeDLCLI {
* case #3 is the resolution-degraded equivalent of #1, and already a pretty safe fallback
*
* in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499
**/
*/
let result: string[] = []
@ -111,7 +110,6 @@ export class YoutubeDLCLI {
}
private constructor () {
}
download (options: {
@ -199,7 +197,7 @@ export class YoutubeDLCLI {
for (let i = 0, len = data.length; i < len; i++) {
const line = data[i]
if (line.indexOf(skipString) === 0) {
if (line.startsWith(skipString)) {
files.push(line.slice(skipString.length))
}
}

View File

@ -51,7 +51,7 @@ class ClientHtml {
}
}
function sendHTML (html: string, res: express.Response, localizedHTML: boolean = false) {
function sendHTML (html: string, res: express.Response, localizedHTML = false) {
res.set('Content-Type', 'text/html; charset=UTF-8')
res.set('Cache-Control', 'max-age=0, no-cache, must-revalidate')

View File

@ -117,7 +117,6 @@ export class TagsHtml {
// OEmbed
for (const oembedLinkTag of oembedLinkTags) {
// eslint-disable-next-line max-len
tagsStr += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${
escapeAttribute(oembedLinkTag.escapedTitle)
}" />`

View File

@ -13,18 +13,21 @@ export interface PeerTubeInternalEvents {
'chapters-updated': (options: { video: MVideoImmutable }) => void
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
declare interface InternalEventEmitter {
on<U extends keyof PeerTubeInternalEvents>(
event: U, listener: PeerTubeInternalEvents[U]
event: U,
listener: PeerTubeInternalEvents[U]
): this
emit<U extends keyof PeerTubeInternalEvents>(
event: U, ...args: Parameters<PeerTubeInternalEvents[U]>
event: U,
...args: Parameters<PeerTubeInternalEvents[U]>
): boolean
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
class InternalEventEmitter extends EventEmitter {
private static instance: InternalEventEmitter
static get Instance () {

View File

@ -77,34 +77,34 @@ import { processVideoTranscription } from './handlers/video-transcription.js'
import { processVideosViewsStats } from './handlers/video-views-stats.js'
export type CreateJobArgument =
{ type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
{ type: 'activitypub-http-broadcast-parallel', payload: ActivitypubHttpBroadcastPayload } |
{ type: 'activitypub-http-unicast', payload: ActivitypubHttpUnicastPayload } |
{ type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload } |
{ type: 'activitypub-cleaner', payload: {} } |
{ type: 'activitypub-follow', payload: ActivitypubFollowPayload } |
{ type: 'video-file-import', payload: VideoFileImportPayload } |
{ type: 'video-transcoding', payload: VideoTranscodingPayload } |
{ type: 'email', payload: EmailPayload } |
{ type: 'transcoding-job-builder', payload: TranscodingJobBuilderPayload } |
{ type: 'video-import', payload: VideoImportPayload } |
{ type: 'activitypub-refresher', payload: RefreshPayload } |
{ type: 'videos-views-stats', payload: {} } |
{ type: 'video-live-ending', payload: VideoLiveEndingPayload } |
{ type: 'actor-keys', payload: ActorKeysPayload } |
{ type: 'video-redundancy', payload: VideoRedundancyPayload } |
{ type: 'video-studio-edition', payload: VideoStudioEditionPayload } |
{ type: 'manage-video-torrent', payload: ManageVideoTorrentPayload } |
{ type: 'move-to-object-storage', payload: MoveStoragePayload } |
{ type: 'move-to-file-system', payload: MoveStoragePayload } |
{ type: 'video-channel-import', payload: VideoChannelImportPayload } |
{ type: 'after-video-channel-import', payload: AfterVideoChannelImportPayload } |
{ type: 'notify', payload: NotifyPayload } |
{ type: 'federate-video', payload: FederateVideoPayload } |
{ type: 'create-user-export', payload: CreateUserExportPayload } |
{ type: 'generate-video-storyboard', payload: GenerateStoryboardPayload } |
{ type: 'import-user-archive', payload: ImportUserArchivePayload } |
{ type: 'video-transcription', payload: VideoTranscriptionPayload }
| { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload }
| { type: 'activitypub-http-broadcast-parallel', payload: ActivitypubHttpBroadcastPayload }
| { type: 'activitypub-http-unicast', payload: ActivitypubHttpUnicastPayload }
| { type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload }
| { type: 'activitypub-cleaner', payload: {} }
| { type: 'activitypub-follow', payload: ActivitypubFollowPayload }
| { type: 'video-file-import', payload: VideoFileImportPayload }
| { type: 'video-transcoding', payload: VideoTranscodingPayload }
| { type: 'email', payload: EmailPayload }
| { type: 'transcoding-job-builder', payload: TranscodingJobBuilderPayload }
| { type: 'video-import', payload: VideoImportPayload }
| { type: 'activitypub-refresher', payload: RefreshPayload }
| { type: 'videos-views-stats', payload: {} }
| { type: 'video-live-ending', payload: VideoLiveEndingPayload }
| { type: 'actor-keys', payload: ActorKeysPayload }
| { type: 'video-redundancy', payload: VideoRedundancyPayload }
| { type: 'video-studio-edition', payload: VideoStudioEditionPayload }
| { type: 'manage-video-torrent', payload: ManageVideoTorrentPayload }
| { type: 'move-to-object-storage', payload: MoveStoragePayload }
| { type: 'move-to-file-system', payload: MoveStoragePayload }
| { type: 'video-channel-import', payload: VideoChannelImportPayload }
| { type: 'after-video-channel-import', payload: AfterVideoChannelImportPayload }
| { type: 'notify', payload: NotifyPayload }
| { type: 'federate-video', payload: FederateVideoPayload }
| { type: 'create-user-export', payload: CreateUserExportPayload }
| { type: 'generate-video-storyboard', payload: GenerateStoryboardPayload }
| { type: 'import-user-archive', payload: ImportUserArchivePayload }
| { type: 'video-transcription', payload: VideoTranscriptionPayload }
export type CreateJobOptions = {
delay?: number
@ -182,7 +182,6 @@ const jobTypes: JobType[] = [
const silentFailure = new Set<JobType>([ 'activitypub-http-unicast' ])
class JobQueue {
private static instance: JobQueue
private workers: { [id in JobType]?: Worker } = {}
@ -214,7 +213,9 @@ class JobQueue {
connection: Redis.getRedisClientOptions('FlowProducer'),
prefix: this.jobRedisPrefix
})
this.flowProducer.on('error', err => { logger.error('Error in flow producer', { err }) })
this.flowProducer.on('error', err => {
logger.error('Error in flow producer', { err })
})
this.addRepeatableJobs()
}
@ -237,7 +238,7 @@ class JobQueue {
return timeoutPromise(p, timeout)
}
const processor = async (jobArg: Job<any>) => {
const processor = async (jobArg: Job) => {
const job = await Hooks.wrapObject(jobArg, 'filter:job-queue.process.params', { type: handlerName })
return Hooks.wrapPromiseFun(handler, job, 'filter:job-queue.process.result')
@ -258,7 +259,9 @@ class JobQueue {
}
})
worker.on('error', err => { logger.error('Error in job worker %s.', handlerName, { err }) })
worker.on('error', err => {
logger.error('Error in job worker %s.', handlerName, { err })
})
this.workers[handlerName] = worker
}
@ -270,7 +273,9 @@ class JobQueue {
}
const queue = new Queue(handlerName, queueOptions)
queue.on('error', err => { logger.error('Error in job queue %s.', handlerName, { err }) })
queue.on('error', err => {
logger.error('Error in job queue %s.', handlerName, { err })
})
this.queues[handlerName] = queue
@ -286,7 +291,9 @@ class JobQueue {
}
const queueEvents = new QueueEvents(handlerName, queueEventsOptions)
queueEvents.on('error', err => { logger.error('Error in job queue events %s.', handlerName, { err }) })
queueEvents.on('error', err => {
logger.error('Error in job queue events %s.', handlerName, { err })
})
this.queueEvents[handlerName] = queueEvents
}
@ -345,7 +352,7 @@ class JobQueue {
createJobAsync (options: CreateJobArgument & CreateJobOptions): void {
this.createJob(options)
.catch(err => logger.error('Cannot create job.', { err, options }))
.catch(err => logger.error('Cannot create job.', { err, options }))
}
createJob (options: CreateJobArgument & CreateJobOptions | undefined) {
@ -557,5 +564,6 @@ class JobQueue {
// ---------------------------------------------------------------------------
export {
JobQueue, jobTypes
JobQueue,
jobTypes
}

View File

@ -49,6 +49,7 @@ interface MuxingSessionEvents {
'after-cleanup': (options: { videoUUID: string }) => void
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
declare interface MuxingSession {
on<U extends keyof MuxingSessionEvents>(
event: U,
@ -61,7 +62,8 @@ declare interface MuxingSession {
): boolean
}
class MuxingSession extends EventEmitter {
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
class MuxingSession extends EventEmitter implements MuxingSession {
private transcodingWrapper: AbstractTranscodingWrapper
private readonly context: any

View File

@ -10,13 +10,16 @@ interface TranscodingWrapperEvents {
'error': (options: { err: Error }) => void
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
declare interface AbstractTranscodingWrapper {
on<U extends keyof TranscodingWrapperEvents>(
event: U, listener: TranscodingWrapperEvents[U]
event: U,
listener: TranscodingWrapperEvents[U]
): this
emit<U extends keyof TranscodingWrapperEvents>(
event: U, ...args: Parameters<TranscodingWrapperEvents[U]>
event: U,
...args: Parameters<TranscodingWrapperEvents[U]>
): boolean
}
@ -48,6 +51,7 @@ interface AbstractTranscodingWrapperOptions {
outDirectory: string
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
abstract class AbstractTranscodingWrapper extends EventEmitter {
protected readonly videoLive: MVideoLiveVideo
@ -111,6 +115,5 @@ abstract class AbstractTranscodingWrapper extends EventEmitter {
export {
type AbstractTranscodingWrapperOptions,
AbstractTranscodingWrapper
}

View File

@ -200,15 +200,12 @@ function createAccountAbuse (options: {
export {
isLocalLiveVideoAccepted,
isLocalVideoFileAccepted,
isLocalVideoThreadAccepted,
isRemoteVideoCommentAccepted,
isLocalVideoCommentReplyAccepted,
isPreImportVideoAccepted,
isPostImportVideoAccepted,
createAbuse,
createVideoAbuse,
createVideoCommentAbuse,
createAccountAbuse

View File

@ -273,25 +273,18 @@ async function getObjectStorageFileSize (options: {
export {
type BucketInfo,
buildKey,
storeObject,
storeContent,
storeStream,
removeObject,
removeObjectByFullKey,
removePrefix,
makeAvailable,
updateObjectACL,
updatePrefixACL,
listKeysOfPrefix,
createObjectReadStream,
getObjectStorageFileSize
}
@ -344,11 +337,15 @@ async function uploadToStorage (options: {
logger.debug(
'Completed %s%s in bucket %s',
bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, { ...lTags(), responseMetadata: response.$metadata }
bucketInfo.PREFIX,
objectStorageKey,
bucketInfo.BUCKET_NAME,
{ ...lTags(), responseMetadata: response.$metadata }
)
return getInternalUrl(bucketInfo, objectStorageKey)
} catch (err) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw parseS3Error(err)
}
}

View File

@ -87,8 +87,7 @@ function handleObjectStorageFailure (res: express.Response, err: Error) {
return res.fail({
status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
message: err.message,
type: err.name
message: err.message
})
}

View File

@ -11,7 +11,6 @@ import { logger } from '@server/helpers/logger.js'
// Try to keep consistency with their metric name/description so it's easier to process (grafana dashboard template etc)
export class NodeJSObserversBuilder {
constructor (private readonly meter: Meter) {
}
@ -62,7 +61,6 @@ export class NodeJSObserversBuilder {
observableResult.observe(cpuTotal, (userUsageMicros + systemUsageMicros) / 1e6)
observableResult.observe(cpuUser, userUsageMicros / 1e6)
observableResult.observe(cpuSystem, systemUsageMicros / 1e6)
}, [ cpuTotal, cpuUser, cpuSystem ])
}
@ -119,7 +117,7 @@ export class NodeJSObserversBuilder {
}
private buildEventLoopLagObserver () {
const reportEventloopLag = (start: [ number, number ], observableResult: ObservableResult, res: () => void) => {
const reportEventloopLag = (start: [number, number], observableResult: ObservableResult, res: () => void) => {
const delta = process.hrtime(start)
const nanosec = delta[0] * 1e9 + delta[1]
const seconds = nanosec / 1e9
@ -180,6 +178,7 @@ export class NodeJSObserversBuilder {
const data = {}
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < resources.length; i++) {
const resource = resources[i]

View File

@ -68,8 +68,13 @@ async function registerOpentelemetryTracing () {
}, DiagLogLevel.INFO)
const tracerProvider = new NodeTracerProvider.default.NodeTracerProvider({
spanProcessors: [
new BatchSpanProcessor.default.BatchSpanProcessor(
new JaegerExporter({ endpoint: CONFIG.OPEN_TELEMETRY.TRACING.JAEGER_EXPORTER.ENDPOINT })
)
],
resource: new Resource.default.Resource({
[SemanticResourceAttributes.default.SemanticResourceAttributes.SERVICE_NAME]: 'peertube'
[SemanticResourceAttributes.default.ATTR_SERVICE_NAME]: 'peertube'
})
})
@ -92,16 +97,10 @@ async function registerOpentelemetryTracing () {
]
})
tracerProvider.addSpanProcessor(
new BatchSpanProcessor.default.BatchSpanProcessor(
new JaegerExporter({ endpoint: CONFIG.OPEN_TELEMETRY.TRACING.JAEGER_EXPORTER.ENDPOINT })
)
)
tracerProvider.register()
}
async function wrapWithSpanAndContext <T> (spanName: string, cb: () => Promise<T>) {
async function wrapWithSpanAndContext<T> (spanName: string, cb: () => Promise<T>) {
const { context, trace } = await import('@opentelemetry/api')
if (CONFIG.OPEN_TELEMETRY.TRACING.ENABLED !== true) {
@ -135,6 +134,5 @@ class TrackerMock {
class SpanMock {
end () {
}
}

View File

@ -11,10 +11,9 @@ import { authenticateRunnerSocket, authenticateSocket } from '../middlewares/ind
import { isDevInstance } from '@peertube/peertube-node-utils'
class PeerTubeSocket {
private static instance: PeerTubeSocket
private userNotificationSockets: { [ userId: number ]: Socket[] } = {}
private userNotificationSockets: { [userId: number]: Socket[] } = {}
private liveVideosNamespace: Namespace
private readonly runnerSockets = new Set<Socket>()
@ -51,16 +50,14 @@ class PeerTubeSocket {
const videoId = params.videoId + ''
if (!isIdValid(videoId)) return
/* eslint-disable @typescript-eslint/no-floating-promises */
socket.join(videoId)
void socket.join(videoId)
})
socket.on('unsubscribe', params => {
const videoId = params.videoId + ''
if (!isIdValid(videoId)) return
/* eslint-disable @typescript-eslint/no-floating-promises */
socket.leave(videoId)
void socket.leave(videoId)
})
})

View File

@ -54,13 +54,13 @@ export interface RegisteredPlugin {
// Only if this is a plugin
registerHelpers?: RegisterHelpers
unregister?: Function
unregister?: () => any
}
export interface HookInformationValue {
npmName: string
pluginName: string
handler: Function
handler: () => any
priority: number
}
@ -69,7 +69,6 @@ type PluginLocalesTranslations = {
}
export class PluginManager implements ServerHook {
private static instance: PluginManager
private registeredPlugins: { [name: string]: RegisteredPlugin } = {}
@ -267,7 +266,9 @@ export class PluginManager implements ServerHook {
hookType,
result,
params,
onError: err => { logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err }) }
onError: err => {
logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err })
}
})
}
@ -358,9 +359,8 @@ export class PluginManager implements ServerHook {
const packageJSON = await this.getPackageJSON(pluginName, pluginType)
this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, pluginType);
[ plugin ] = await PluginModel.upsert({
this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, pluginType)
;[ plugin ] = await PluginModel.upsert({
name: pluginName,
description: packageJSON.description,
homepage: packageJSON.homepage,
@ -662,7 +662,7 @@ export class PluginManager implements ServerHook {
const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType)
if (!packageJSONValid) {
const formattedFields = badFields.map(f => `"${f}"`)
.join(', ')
.join(', ')
throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`)
}

View File

@ -60,7 +60,7 @@ async function execYarn (command: string) {
} catch (result) {
logger.error('Cannot exec yarn.', { command, err: result.err, stderr: result.stderr })
throw result.err
throw result.err as Error
}
}

View File

@ -39,12 +39,14 @@ export function safeUploadXCleanup (file: FileQuery) {
.catch(err => logger.error('Cannot delete the file %s', file.name, { err }))
}
export function buildUploadXFile <T extends UploadXMetadata> (reqBody: T) {
export function buildUploadXFile<T extends UploadXMetadata> (reqBody: T) {
return {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...reqBody,
path: getResumableUploadPath(reqBody.name),
filename: reqBody.metadata.filename
filename: reqBody.metadata.filename,
originalname: reqBody.originalName
}
}
@ -70,21 +72,26 @@ export function setupUploadResumableRoutes (options: {
uploadDeleteMiddlewares = []
} = options
router.post(routePath,
router.post(
routePath,
authenticate,
...uploadInitBeforeMiddlewares,
resumableInitValidator,
...uploadInitAfterMiddlewares,
(req, res) => uploadx.upload(req, res) // Prevent next() call, explicitly tell to uploadx it's the end
// Prevent next() call, explicitly tell to uploadx it's the end
(req, res) => uploadx.upload(req, res)
)
router.delete(routePath,
router.delete(
routePath,
authenticate,
...uploadDeleteMiddlewares,
(req, res) => uploadx.upload(req, res) // Prevent next() call, explicitly tell to uploadx it's the end
// Prevent next() call, explicitly tell to uploadx it's the end
(req, res) => uploadx.upload(req, res)
)
router.put(routePath,
router.put(
routePath,
authenticate,
uploadx.upload, // uploadx doesn't next() before the file upload completes
...uploadedMiddlewares,

View File

@ -93,9 +93,7 @@ export class VideosExporter extends AbstractUserExporter<VideoExportJSON> {
const live = video.isLive
? await VideoLiveModel.loadByVideoIdWithSettings(videoId)
: undefined
// We already have captions, so we can set it to the video object
: undefined // We already have captions, so we can set it to the video object
;(video as any).VideoCaptions = captions
// Then fetch more attributes for AP serialization
const videoAP = await video.lightAPToFullAP(undefined)

View File

@ -5,10 +5,9 @@ import { AbstractUserImporter } from './abstract-user-importer.js'
type SanitizedObject = AutoTagPoliciesJSON['reviewComments']
// eslint-disable-next-line max-len
export class ReviewCommentsTagPoliciesImporter
extends AbstractUserImporter <AutoTagPoliciesJSON, AutoTagPoliciesJSON['reviewComments'] & { archiveFiles?: never }, SanitizedObject> {
extends AbstractUserImporter<AutoTagPoliciesJSON, AutoTagPoliciesJSON['reviewComments'] & { archiveFiles?: never }, SanitizedObject>
{
protected getImportObjects (json: AutoTagPoliciesJSON) {
if (!json.reviewComments) return []

View File

@ -20,11 +20,20 @@ import { pick } from '@peertube/peertube-core-utils'
const lTags = loggerTagsFactory('user-import')
type SanitizedObject = Pick<UserSettingsExportJSON, 'nsfwPolicy' | 'autoPlayVideo' | 'autoPlayNextVideo' | 'autoPlayNextVideo' |
'autoPlayNextVideoPlaylist' | 'p2pEnabled' | 'videosHistoryEnabled' | 'videoLanguages' | 'theme' | 'notificationSettings'>
export class UserSettingsImporter extends AbstractUserImporter <UserSettingsExportJSON, UserSettingsExportJSON, SanitizedObject> {
type SanitizedObject = Pick<
UserSettingsExportJSON,
| 'nsfwPolicy'
| 'autoPlayVideo'
| 'autoPlayNextVideo'
| 'autoPlayNextVideoPlaylist'
| 'p2pEnabled'
| 'videosHistoryEnabled'
| 'videoLanguages'
| 'theme'
| 'notificationSettings'
>
export class UserSettingsImporter extends AbstractUserImporter<UserSettingsExportJSON, UserSettingsExportJSON, SanitizedObject> {
protected getImportObjects (json: UserSettingsExportJSON) {
return [ json ]
}

View File

@ -1,10 +1,7 @@
import { VideoPlaylistPrivacy, VideoPlaylistType, VideoPlaylistsExportJSON } from '@peertube/peertube-models'
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
import { buildUUID } from '@peertube/peertube-node-utils'
import {
MChannelBannerAccountDefault, MVideoPlaylistFull,
MVideoPlaylistThumbnail
} from '@server/types/models/index.js'
import { MChannelBannerAccountDefault, MVideoPlaylistFull, MVideoPlaylistThumbnail } from '@server/types/models/index.js'
import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '@server/lib/activitypub/url.js'
import { VideoChannelModel } from '@server/models/video/video-channel.js'
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
@ -33,11 +30,9 @@ import { generateThumbnailForPlaylist } from '@server/lib/video-playlist.js'
const lTags = loggerTagsFactory('user-import')
type ImportObject = VideoPlaylistsExportJSON['videoPlaylists'][0]
type SanitizedObject = Pick<ImportObject, 'type' | 'displayName' | 'privacy' | 'elements' | 'description' | 'elements' | 'channel' |
'archiveFiles'>
export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylistsExportJSON, ImportObject, SanitizedObject> {
type SanitizedObject = Pick<ImportObject, 'type' | 'displayName' | 'privacy' | 'elements' | 'description' | 'channel' | 'archiveFiles'>
export class VideoPlaylistsImporter extends AbstractUserImporter<VideoPlaylistsExportJSON, ImportObject, SanitizedObject> {
protected getImportObjects (json: VideoPlaylistsExportJSON) {
return json.videoPlaylists
}

View File

@ -107,51 +107,51 @@ export class UserImporter {
// Keep consistency in import order (don't import videos before channels for example)
return [
{
name: 'account' as 'account',
name: 'account' as const,
importer: new AccountImporter(this.buildImporterOptions(user, 'account.json'))
},
{
name: 'userSettings' as 'userSettings',
name: 'userSettings' as const,
importer: new UserSettingsImporter(this.buildImporterOptions(user, 'user-settings.json'))
},
{
name: 'channels' as 'channels',
name: 'channels' as const,
importer: new ChannelsImporter(this.buildImporterOptions(user, 'channels.json'))
},
{
name: 'blocklist' as 'blocklist',
name: 'blocklist' as const,
importer: new BlocklistImporter(this.buildImporterOptions(user, 'blocklist.json'))
},
{
name: 'following' as 'following',
name: 'following' as const,
importer: new FollowingImporter(this.buildImporterOptions(user, 'following.json'))
},
{
name: 'videos' as 'videos',
name: 'videos' as const,
importer: new VideosImporter(this.buildImporterOptions(user, 'videos.json'))
},
{
name: 'likes' as 'likes',
name: 'likes' as const,
importer: new LikesImporter(this.buildImporterOptions(user, 'likes.json'))
},
{
name: 'dislikes' as 'dislikes',
name: 'dislikes' as const,
importer: new DislikesImporter(this.buildImporterOptions(user, 'dislikes.json'))
},
{
name: 'videoPlaylists' as 'videoPlaylists',
name: 'videoPlaylists' as const,
importer: new VideoPlaylistsImporter(this.buildImporterOptions(user, 'video-playlists.json'))
},
{
name: 'userVideoHistory' as 'userVideoHistory',
name: 'userVideoHistory' as const,
importer: new UserVideoHistoryImporter(this.buildImporterOptions(user, 'video-history.json'))
},
{
name: 'watchedWordsLists' as 'watchedWordsLists',
name: 'watchedWordsLists' as const,
importer: new WatchedWordsListsImporter(this.buildImporterOptions(user, 'watched-words-lists.json'))
},
{
name: 'commentAutoTagPolicies' as 'commentAutoTagPolicies',
name: 'commentAutoTagPolicies' as const,
importer: new ReviewCommentsTagPoliciesImporter(this.buildImporterOptions(user, 'automatic-tag-policies.json'))
}
]

View File

@ -134,7 +134,7 @@ export function buildSortDirectionAndField (value: string) {
let field: string
let direction: 'ASC' | 'DESC'
if (value.substring(0, 1) === '-') {
if (value.startsWith('-')) {
direction = 'DESC'
field = value.substring(1)
} else {

View File

@ -18,7 +18,7 @@ import { VideoCaptionModel } from '../video/video-caption.js'
import { VideoCommentModel } from '../video/video-comment.js'
import { VideoImportModel } from '../video/video-import.js'
import { VideoModel } from '../video/video.js'
import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder.js'
import { UserNotificationListQueryBuilder } from './sql/user-notification-list-query-builder.js'
import { UserRegistrationModel } from './user-registration.js'
import { UserModel } from './user.js'
import { ActorImageModel } from '../actor/actor-image.js'

View File

@ -1,4 +1,4 @@
import { HttpMethodType, PeerTubeProblemDocumentData, ServerLogLevel, VideoCreate } from '@peertube/peertube-models'
import { HttpMethodType, PeerTubeProblemDocumentData, ServerErrorCodeType, ServerLogLevel, VideoCreate } from '@peertube/peertube-models'
import { RegisterServerAuthExternalOptions } from '@server/types/index.js'
import {
MAbuseMessage,
@ -77,8 +77,8 @@ declare module 'express' {
// ---------------------------------------------------------------------------
// Upload file with a duration added by our middleware
export type VideoLegacyUploadFile = Pick<Express.Multer.File, 'path' | 'filename' | 'size', 'originalname'> & {
duration: number
export type VideoLegacyUploadFile = Pick<Express.Multer.File, 'path' | 'filename' | 'size' | 'originalname'> & {
duration?: number
}
// Our custom UploadXFile object using our custom metadata
@ -106,7 +106,7 @@ declare module 'express' {
title?: string
status?: number
type?: ServerErrorCode | string
type?: ServerErrorCodeType
instance?: string
data?: PeerTubeProblemDocumentData

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