Skip to content

Cannot apply configs to any users- error message "Cannot read properties of null (reading 'value')" #30

Cannot apply configs to any users- error message "Cannot read properties of null (reading 'value')"

Cannot apply configs to any users- error message "Cannot read properties of null (reading 'value')" #30

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'),
});