summaryrefslogtreecommitdiff
path: root/.github/workflows/update-plugins-json.yml
blob: 665be2048ff194310d887af6ea312e3a96245c94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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@v4

    - name: Set up PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.4'
        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];

          // Skip non-array tokens unless we're in return statement
          if (!is_array($token)) {
            if ($in_return && $array_depth > 0) {
              if ($token === '[') {
                $array_depth++;
              } elseif ($token === ']' || $token === ')') {
                $array_depth--;
                if ($array_depth === 0) {
                  // End of return array - save any pending element
                  if ($current_element !== '') {
                    $array_elements[] = $current_element;
                  }
                  break;
                }
              } elseif ($token === ',') {
                // Comma separates array elements
                $array_elements[] = $current_element;
                $current_element = '';
              }
            }
            continue;
          }

          // Look for 'function about'
          if ($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 && $token[0] === T_RETURN) {
            $in_return = true;
            continue;
          }

          if ($in_return) {
            // Track array() syntax
            if ($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++;
              }
            } elseif ($array_depth > 0) {
              // We're inside the array, collect element values
              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];
              }
            }
          }

          // Handle [ syntax for arrays
          if ($in_return && !is_array($token) && $token === '[') {
            $array_depth = 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
      run: |
        # Save plugins.json to a safe location outside the repo
        cp plugins.json /tmp/plugins.json

        git config user.name "github-actions[bot]"
        git config user.email "github-actions[bot]@users.noreply.github.com"

        # 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
          git commit -m "Update plugins list [automated]"
          git push origin gh-pages
        fi