tt-rss/.github/workflows/update-plugins-json.yml
2025-12-04 02:45:52 +00:00

323 lines
11 KiB
YAML

name: Update Plugins JSON
on:
schedule:
# Run weekly on Mondays at 00:00 UTC
- cron: '0 0 * * 1'
# Allow manual triggering
workflow_dispatch:
permissions:
contents: write
jobs:
update-plugins:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
tools: none
- name: Fetch and process plugin repositories
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
#!/bin/bash
set -e
cat > /tmp/parse_plugin.php << 'PHPEOF'
<?php
$content = file_get_contents($argv[1]);
// Use token_get_all to parse PHP code more reliably
$tokens = token_get_all($content);
$class_name = '';
$description = '';
// Find class declaration
for ($i = 0; $i < count($tokens); $i++) {
if (is_array($tokens[$i]) && $tokens[$i][0] === T_CLASS) {
// Skip whitespace
$j = $i + 1;
while ($j < count($tokens) && is_array($tokens[$j]) && $tokens[$j][0] === T_WHITESPACE) {
$j++;
}
// Get class name
if ($j < count($tokens) && is_array($tokens[$j]) && $tokens[$j][0] === T_STRING) {
$class_name = $tokens[$j][1];
break;
}
}
}
// Extract the description from about()
$in_about = false;
$in_return = false;
$array_depth = 0;
$array_elements = [];
$current_element = '';
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
// Look for 'function about'
if (is_array($token) && $token[0] === T_FUNCTION) {
// Check if next non-whitespace token is 'about'
$j = $i + 1;
while ($j < count($tokens) && is_array($tokens[$j]) && $tokens[$j][0] === T_WHITESPACE) {
$j++;
}
if ($j < count($tokens) && is_array($tokens[$j]) && $tokens[$j][0] === T_STRING && $tokens[$j][1] === 'about') {
$in_about = true;
$i = $j;
continue;
}
}
if ($in_about && is_array($token) && $token[0] === T_RETURN) {
$in_return = true;
continue;
}
if ($in_return) {
// Handle [ syntax for arrays (short array syntax)
if (!is_array($token) && $token === '[') {
$array_depth++;
continue;
}
// Track array() syntax
if (is_array($token) && $token[0] === T_ARRAY) {
// Look ahead for opening parenthesis (skip whitespace)
$k = $i + 1;
while ($k < count($tokens) && is_array($tokens[$k]) && $tokens[$k][0] === T_WHITESPACE) {
$k++;
}
if ($k < count($tokens) && $tokens[$k] === '(') {
$array_depth++;
}
continue;
}
if ($array_depth > 0) {
// Check for array end
if (!is_array($token) && ($token === ']' || $token === ')')) {
$array_depth--;
if ($array_depth === 0) {
// End of return array - save any pending element
if ($current_element !== '') {
$array_elements[] = $current_element;
}
break;
}
continue;
}
// Check for comma separator
if (!is_array($token) && $token === ',') {
// Comma separates array elements
$array_elements[] = $current_element;
$current_element = '';
continue;
}
// Collect element values
if (is_array($token)) {
if ($token[0] === T_CONSTANT_ENCAPSED_STRING) {
// String literal
$str = $token[1];
$str = substr($str, 1, -1);
$str = stripcslashes($str);
$current_element = $str;
} elseif ($token[0] === T_STRING && in_array(strtolower($token[1]), ['null', 'true', 'false'])) {
// null, true, false keywords
$current_element = $token[1];
}
}
}
}
}
// Description is at index 1
if (isset($array_elements[1])) {
$description = $array_elements[1];
}
echo json_encode(['class_name' => $class_name, 'description' => $description], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
PHPEOF
# Get all repositories from tt-rss organization that start with tt-rss-plugin-
echo "Fetching repositories from tt-rss organization..."
plugins_json="[]"
page=1
while true; do
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/orgs/tt-rss/repos?type=public&per_page=100&page=$page")
# Check for API errors
if echo "$response" | jq -e '.message' >/dev/null 2>&1; then
echo "API Error: $(echo "$response" | jq -r '.message')"
exit 1
fi
# Check if we got an empty array (no more pages)
if [ "$(echo "$response" | jq '. | length')" -eq 0 ]; then
break
fi
# Filter repositories that start with tt-rss-plugin- and are not archived
filtered=$(echo "$response" | jq '[.[] | select(.name | startswith("tt-rss-plugin-")) | select(.archived == false)]')
# Process each repository
for repo in $(echo "$filtered" | jq -r '.[] | @base64'); do
_jq() {
echo "$repo" | base64 --decode | jq -r "$1"
}
repo_name=$(_jq '.name')
full_name=$(_jq '.full_name')
html_url=$(_jq '.html_url')
clone_url=$(_jq '.clone_url')
repo_description=$(_jq '.description // ""')
echo "Processing repository: $repo_name"
# Get the latest commit timestamp
commits_response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$full_name/commits?per_page=1")
# Check if commits response is valid
if echo "$commits_response" | jq -e '.[0]' >/dev/null 2>&1; then
last_update=$(echo "$commits_response" | jq -r '.[0].commit.committer.date')
else
echo " Warning: Could not fetch commit info for $repo_name"
last_update=""
fi
# Skip if no last_update (indicates repository might be empty or inaccessible)
if [ -z "$last_update" ]; then
echo " Skipping $repo_name: no commit history found"
continue
fi
# Try to fetch init.php
init_php=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3.raw" \
"https://api.github.com/repos/$full_name/contents/init.php" 2>/dev/null || echo "")
# Try to extract the class name and plugin description from init.php
class_name=""
description=""
if [ -n "$init_php" ]; then
# Save init.php to temporary file for PHP processing
temp_file=$(mktemp)
echo "$init_php" > "$temp_file"
php_output=$(php /tmp/parse_plugin.php "$temp_file" 2>/dev/null || echo '{"class_name":"","description":""}')
class_name=$(echo "$php_output" | jq -r '.class_name // ""')
description=$(echo "$php_output" | jq -r '.description // ""')
rm -f "$temp_file"
fi
# Skip if no class name was found
if [ -z "$class_name" ]; then
echo " Skipping $repo_name: no class name found in init.php"
continue
fi
# Fallback to repository description if no description was found
if [ -z "$description" ]; then
description="$repo_description"
fi
# Placeholder description if still empty
if [ -z "$description" ]; then
description=""
fi
# Create topics array
topics=$(echo "$class_name" | tr '[:upper:]' '[:lower:]')
topics_json="[\"$topics\"]"
# Build plugin JSON object
plugin_json=$(jq -n \
--arg name "$class_name" \
--arg description "$description" \
--argjson topics "$topics_json" \
--arg html_url "$html_url" \
--arg clone_url "$clone_url" \
--arg last_update "$last_update" \
'{
name: $name,
description: $description,
topics: $topics,
html_url: $html_url,
clone_url: $clone_url,
last_update: $last_update
}')
# Append to plugins array
plugins_json=$(echo "$plugins_json" | jq --argjson plugin "$plugin_json" '. += [$plugin]')
done
page=$((page + 1))
done
# Sort by name and save
echo "$plugins_json" | jq 'sort_by(.name)' > plugins.json
echo "Plugin list updated successfully!"
cat plugins.json
- name: Deploy to gh-pages branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Save plugins.json to a safe location outside the repo
cp plugins.json /tmp/plugins.json
# Fetch gh-pages branch
git fetch origin gh-pages 2>/dev/null || true
if git rev-parse --verify origin/gh-pages >/dev/null 2>&1; then
# gh-pages exists
# Remove the local plugins.json first to avoid conflicts
rm -f plugins.json
git checkout gh-pages
else
# Create new orphan gh-pages branch
git checkout --orphan gh-pages
git rm -rf . 2>/dev/null || true
fi
# Restore plugins.json from safe location
cp /tmp/plugins.json plugins.json
git add plugins.json
# Check if there are changes to commit
if git diff --staged --quiet; then
echo "No changes to commit"
else
# Configure git for GitHub Actions bot
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "Update plugins list [automated]"
# Push using GitHub CLI with GITHUB_TOKEN
gh auth setup-git
git push origin gh-pages || { echo "Failed to push to gh-pages"; exit 1; }
fi