Cannot apply configs to any users- error message "Cannot read properties of null (reading 'value')" #30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Require a real title and evidence on issues | |
| # Every issue needs a title that says something beyond the template's "[Settings]" tag, | |
| # and bug reports need a screenshot, since the plugin keeps no diagnostic log of its own. | |
| # GitHub can't reject an issue at submission time, so this labels and asks instead, | |
| # and it catches the "n/a" that a required form field can't. | |
| on: | |
| issues: | |
| types: [opened, edited, reopened, labeled] | |
| issue_comment: | |
| types: [created, edited] | |
| permissions: | |
| issues: write | |
| jobs: | |
| check-issue: | |
| runs-on: ubuntu-latest | |
| # Don't spin up a runner to re-check a closed issue or to react to another bot. | |
| if: >- | |
| github.event.issue.state == 'open' && | |
| (github.event_name != 'issue_comment' || github.event.comment.user.type != 'Bot') | |
| steps: | |
| - name: Check title and screenshots | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const MARKER = '<!-- missing-evidence-bot -->'; | |
| const MIN_TITLE = 10; | |
| const CHECKS = { | |
| title: { | |
| name: 'Missing Title', | |
| color: 'c5def5', | |
| description: 'Issue still has the untouched template title', | |
| }, | |
| screenshot: { | |
| name: 'Missing Screenshot', | |
| color: 'fbca04', | |
| description: 'Bug reported without a screenshot', | |
| }, | |
| }; | |
| const issue = context.payload.issue; | |
| if (!issue || issue.pull_request) return; | |
| if (['OWNER', 'MEMBER', 'COLLABORATOR'].includes(issue.author_association)) return; | |
| // The templates prefill "[Plugin] ", "[Settings] ", "[Feature] " and "[Vent] ", | |
| // so split the leading tags off and judge the two halves apart. | |
| const title = issue.title || ''; | |
| const prefix = (title.match(/^\s*(?:\[[^\]]*\]\s*)+/) || [''])[0]; | |
| const rest = title.slice(prefix.length).replace(/[\s.!?-]+$/, '').trim(); | |
| const tagged = tag => prefix.toLowerCase().includes(`[${tag}]`); | |
| // Only the tag is worth reading here. The labels the forms ask for have never | |
| // been created on this repo, so no issue has ever carried one. | |
| const needsImage = tagged('plugin') || tagged('settings'); | |
| const GENERIC = /^(bug|issue|issues|problem|problems|help|help me|crash|crashes|broken|error|errors|fix|please fix|question|feature|request|vent|title|test|n\/?a|none|plugin|moonbase|settings|jellyfin|emby|seerr|not working|doesn'?t work|does not work|no work)$/i; | |
| const badTitle = rest.length < MIN_TITLE || GENERIC.test(rest); | |
| const labels = (issue.labels || []).map(l => l.name); | |
| // With nothing to ask for and no label left over from an earlier run, there's | |
| // no reason to spend an API call reading the comments. | |
| const leftover = Object.values(CHECKS).some(c => labels.includes(c.name)); | |
| if (!badTitle && !needsImage && !leftover) return; | |
| // Read the body and every human comment, so evidence posted later still counts. | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| per_page: 100, | |
| }); | |
| const authored = [issue.body || ''] | |
| .concat(comments.filter(c => c.user.type !== 'Bot').map(c => c.body || '')) | |
| .join('\n'); | |
| const hasImage = [ | |
| /!\[[^\]]*\]\(\s*https?:\/\/\S+/i, | |
| /<img\s[^>]*src\s*=/i, | |
| /<video\s/i, | |
| /https:\/\/github\.com\/user-attachments\/assets\/\S+/i, // current upload host | |
| /https:\/\/user-images\.githubusercontent\.com\/\S+/i, // the older upload host | |
| /https?:\/\/\S+\.(png|jpe?g|gif|webp|heic|bmp|mp4|mov|webm|m4v)(\?\S*)?/i, | |
| ].some(re => re.test(authored)); | |
| const wanted = []; | |
| if (badTitle) wanted.push('title'); | |
| if (needsImage && !hasImage) wanted.push('screenshot'); | |
| // Drop any label whose problem has since been fixed. | |
| for (const [kind, check] of Object.entries(CHECKS)) { | |
| if (!wanted.includes(kind) && labels.includes(check.name)) { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| name: check.name, | |
| }); | |
| } | |
| } | |
| if (!wanted.length) return; | |
| const toAdd = wanted.map(k => CHECKS[k]).filter(c => !labels.includes(c.name)); | |
| for (const check of toAdd) { | |
| try { | |
| await github.rest.issues.getLabel({ | |
| owner: context.repo.owner, repo: context.repo.repo, name: check.name, | |
| }); | |
| } catch (error) { | |
| if (error.status !== 404) throw error; | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: check.name, | |
| color: check.color, | |
| description: check.description, | |
| }); | |
| } | |
| } | |
| if (toAdd.length) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: toAdd.map(c => c.name), | |
| }); | |
| } | |
| // Ask once, not again on every edit. | |
| if (comments.some(c => (c.body || '').includes(MARKER))) return; | |
| const TITLE_ASK = [ | |
| '### The title still looks like the template', | |
| '', | |
| `This issue is titled \`${title}\`, which doesn't say what actually went wrong. Titles like that are impossible to scan, search, or spot as a duplicate.`, | |
| '', | |
| 'Please edit the title to describe the problem itself, keeping the tag on the front:', | |
| '', | |
| '- `[Settings] Expanded Home Rows never reaches the second client`', | |
| '- `[Plugin] MDBList ratings stop syncing after the quota check`', | |
| '- `[Plugin] Seerr requests fail with 403 behind a reverse proxy`', | |
| '', | |
| 'You can change it with the **Edit** button next to the title.', | |
| ].join('\n'); | |
| const SCREENSHOT_ASK = [ | |
| '### A screenshot is missing', | |
| '', | |
| "The plugin keeps no diagnostic log of its own, so a picture of what you're seeing is the fastest way to make a report actionable.", | |
| '', | |
| 'Drag a screenshot straight into a comment below, showing the plugin config page, the client screen, or whatever is wrong. For a sync problem, the same setting on each client side by side is what makes it obvious.', | |
| '', | |
| '- Windows: `Win + Shift + S`', | |
| '- macOS: `Cmd + Shift + 4`', | |
| '- Linux: `PrtSc`, or your desktop screenshot tool', | |
| '', | |
| "It's also worth pasting anything the server logged. Open the **Logs** page in your Jellyfin or Emby dashboard and search for `Moonfin`, which is the name the plugin logs under.", | |
| ].join('\n'); | |
| const named = wanted.map(k => `**${CHECKS[k].name}**`); | |
| const listed = named.length === 1 | |
| ? named[0] | |
| : `${named.slice(0, -1).join(', ')} and ${named[named.length - 1]}`; | |
| const parts = [ | |
| MARKER, | |
| `Thanks for the report, @${issue.user.login}.`, | |
| '', | |
| `Before anyone can pick this up it needs a little more from you, so it has been labelled ${listed}.`, | |
| '', | |
| ]; | |
| if (wanted.includes('title')) parts.push(TITLE_ASK, ''); | |
| if (wanted.includes('screenshot')) parts.push(SCREENSHOT_ASK, ''); | |
| parts.push('Each label comes off automatically once that piece is sorted.'); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| body: parts.join('\n'), | |
| }); |