Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Claude Code Review

permissions:
contents: write
pull-requests: write
issues: write
id-token: write

on:
# Automatic review when PRs target dev branch
pull_request:
types: [opened, synchronize]
branches:
- dev

# Manual @claude triggers in comments
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]

jobs:
# Automatic review for PRs targeting dev
auto-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "/review"
claude_args: "--max-turns 5"

# Manual @claude responses - restricted to authorized users
manual-claude:
if: |
(github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
contains(github.event.comment.body, '@claude') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--max-turns 10"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ docker-compose.override.*
# Default video download directory and README, all other files excluded
downloads/*
!downloads/README.md

# Backup archive location
backups/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Or remove/comment out the `YOUTARR_IMAGE` line in your `.env` file to use the de
- [External Database](docs/platforms/external-db.md) - Using existing MariaDB/MySQL

### Advanced Topics
- [Backup & Restore](docs/BACKUP_RESTORE.md) - Backup your configuration and database, restore to new systems
- [Database Management](docs/DATABASE.md) - Database configuration and maintenance
- [Docker Configuration](docs/DOCKER.md) - Advanced Docker settings
- [Development Guide](docs/DEVELOPMENT.md) - Contributing and development setup
Expand Down
10 changes: 6 additions & 4 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,10 @@
"src/**/*.{ts,tsx,js,jsx}",
"!src/types/**"
]
},
"overrides": {
"jsdom": {
"form-data": "^3.0.4"
}
}
}
82 changes: 74 additions & 8 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './App.css';
import packageJson from '../package.json';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import toplogo from './Youtarr_text.png';
import axios from 'axios';
import {
Expand Down Expand Up @@ -34,6 +34,7 @@ import { ThemeProvider } from '@mui/material/styles';
import CloseIcon from '@mui/icons-material/Close';
import MenuIcon from '@mui/icons-material/Menu';
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import Tooltip from '@mui/material/Tooltip';
import SettingsIcon from '@mui/icons-material/Settings';
import SubscriptionsIcon from '@mui/icons-material/Subscriptions';
import DownloadIcon from '@mui/icons-material/Download';
Expand All @@ -55,6 +56,7 @@ import { useConfig } from './hooks/useConfig';
import ErrorBoundary from './components/ErrorBoundary';
import DatabaseErrorOverlay from './components/DatabaseErrorOverlay';
import { lightTheme, darkTheme } from './theme';
import { YTDLP_UPDATED_EVENT } from './components/Configuration/hooks/useYtDlpUpdate';

// Event name for database error detection
const DB_ERROR_EVENT = 'db-error-detected';
Expand All @@ -76,6 +78,8 @@ function AppContent() {
const [dbErrors, setDbErrors] = useState<string[]>([]);
const [dbRecovered, setDbRecovered] = useState(false);
const [countdown, setCountdown] = useState(15);
const [ytDlpUpdateAvailable, setYtDlpUpdateAvailable] = useState(false);
const [ytDlpLatestVersion, setYtDlpLatestVersion] = useState('');
const location = useLocation();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
Expand Down Expand Up @@ -359,6 +363,53 @@ function AppContent() {
});
}, []);

const fetchYtDlpVersionInfo = useCallback(() => {
if (!token) {
setYtDlpUpdateAvailable(false);
setYtDlpLatestVersion('');
return;
}

fetch('/api/ytdlp/latest-version', {
headers: { 'x-access-token': token },
})
.then((response) => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to fetch yt-dlp version');
})
.then((data) => {
setYtDlpUpdateAvailable(data.updateAvailable || false);
setYtDlpLatestVersion(data.latestVersion || '');
if (data.currentVersion) {
setYtDlpVersion(data.currentVersion);
}
})
.catch((err) => {
console.error('Failed to fetch yt-dlp version info:', err);
setYtDlpUpdateAvailable(false);
setYtDlpLatestVersion('');
});
}, [token]);

useEffect(() => {
fetchYtDlpVersionInfo();
}, [fetchYtDlpVersionInfo]);

// Listen for yt-dlp update events to refresh version display
useEffect(() => {
const handleYtDlpUpdated = () => {
fetchYtDlpVersionInfo();
};

window.addEventListener(YTDLP_UPDATED_EVENT, handleYtDlpUpdated);

return () => {
window.removeEventListener(YTDLP_UPDATED_EVENT, handleYtDlpUpdated);
};
}, [fetchYtDlpVersionInfo]);

return (
<ThemeProvider theme={selectedTheme}>
<CssBaseline />
Expand Down Expand Up @@ -450,13 +501,28 @@ function AppContent() {
{clientVersion}
</Typography>
{ytDlpVersion && (
<Typography
fontSize='x-small'
color={'textSecondary'}
style={{ opacity: 0.7 }}
>
yt-dlp: {ytDlpVersion}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{ytDlpUpdateAvailable && (
<Tooltip title={`yt-dlp update available (${ytDlpLatestVersion}). Go to Configuration to update.`}>
<IconButton
component={Link}
to="/configuration"
size="small"
aria-label={`yt-dlp update available (${ytDlpLatestVersion}). Click to go to Configuration.`}
sx={{ p: 0.25 }}
>
<WarningAmberIcon sx={{ fontSize: 14, color: 'warning.main' }} />
</IconButton>
</Tooltip>
)}
<Typography
fontSize='x-small'
color={'textSecondary'}
style={{ opacity: 0.7 }}
>
yt-dlp: {ytDlpVersion}
</Typography>
</Box>
)}
</Box>
{/* This is the matching invisible IconButton */}
Expand Down
32 changes: 32 additions & 0 deletions client/src/components/Configuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
usePlexConnection,
useConfigSave,
} from './Configuration/hooks';
import { useYtDlpUpdate } from './Configuration/hooks/useYtDlpUpdate';
import { useStorageStatus } from '../hooks/useStorageStatus';
import { useConfig } from '../hooks/useConfig';
import { TRACKABLE_CONFIG_KEYS } from '../config/configSchema';
Expand Down Expand Up @@ -129,6 +130,34 @@ function Configuration({ token }: ConfigurationProps) {
checkPlexConnection,
});

const {
versionInfo: ytDlpVersionInfo,
updateStatus: ytDlpUpdateStatus,
errorMessage: ytDlpErrorMessage,
successMessage: ytDlpSuccessMessage,
performUpdate: performYtDlpUpdate,
clearMessages: clearYtDlpMessages,
} = useYtDlpUpdate(token);

// Show yt-dlp update snackbar messages
React.useEffect(() => {
if (ytDlpErrorMessage) {
setSnackbar({
open: true,
message: ytDlpErrorMessage,
severity: 'error',
});
clearYtDlpMessages();
} else if (ytDlpSuccessMessage) {
setSnackbar({
open: true,
message: ytDlpSuccessMessage,
severity: 'success',
});
clearYtDlpMessages();
}
}, [ytDlpErrorMessage, ytDlpSuccessMessage, clearYtDlpMessages]);

const handleOpenConfirmDialog = () => {
setOpenConfirmDialog(true);
};
Expand Down Expand Up @@ -200,6 +229,9 @@ function Configuration({ token }: ConfigurationProps) {
onConfigChange={handleConfigChange}
onMobileTooltipClick={setMobileTooltip}
token={token}
ytDlpVersionInfo={ytDlpVersionInfo}
ytDlpUpdateStatus={ytDlpUpdateStatus}
onYtDlpUpdate={performYtDlpUpdate}
/>

<PlexIntegrationSection
Expand Down
Loading