mirror of
https://git.tt-rss.org/fox/tt-rss.git
synced 2026-04-12 20:41:44 +02:00
323 lines
11 KiB
YAML
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
|