1818 default : true
1919 type : boolean
2020 gist_description :
21- description : " Optional gist description"
21+ description : " Optional gist title/description. Defaults to the README H1."
22+ required : false
23+ default : " "
24+ type : string
25+ gist_id :
26+ description : " Optional existing gist ID or URL to update instead of creating a new gist."
2227 required : false
2328 default : " "
2429 type : string
3944 default : true
4045 type : boolean
4146 gist_description :
42- description : " Optional gist description"
47+ description : " Optional gist title/description. Defaults to the README H1."
48+ required : false
49+ default : " "
50+ type : string
51+ gist_id :
52+ description : " Optional existing gist ID or URL to update instead of creating a new gist."
4353 required : false
4454 default : " "
4555 type : string
4656 secrets :
4757 SERPGAMEDEVELOPER_GIST_TOKEN :
48- description : " PAT from the serpgamedeveloper account with gist scope "
58+ description : " PAT from the serpgamedeveloper account with gist write permission "
4959 required : true
5060 outputs :
5161 gist_url :
52- description : " Created gist URL"
62+ description : " Created or updated gist URL"
5363 value : ${{ jobs.create-readme-gist.outputs.gist_url }}
5464 gist_id :
55- description : " Created gist ID"
65+ description : " Created or updated gist ID"
5666 value : ${{ jobs.create-readme-gist.outputs.gist_id }}
5767
5868permissions :
@@ -74,139 +84,174 @@ jobs:
7484 INPUT_REF : ${{ inputs.ref }}
7585 GIST_PUBLIC : ${{ inputs.public }}
7686 GIST_DESCRIPTION : ${{ inputs.gist_description }}
87+ INPUT_GIST_ID : ${{ inputs.gist_id }}
7788 GITHUB_TOKEN : ${{ github.token }}
7889 SERPGAMEDEVELOPER_GIST_TOKEN : ${{ secrets.SERPGAMEDEVELOPER_GIST_TOKEN }}
7990 run : |
8091 node <<'NODE'
8192 const fs = require("fs");
8293
8394 async function main() {
95+ const apiVersion = "2022-11-28";
96+ const readToken = process.env.GITHUB_TOKEN || "";
97+ const gistToken = process.env.SERPGAMEDEVELOPER_GIST_TOKEN || "";
8498
85- const apiVersion = "2022-11-28";
86- const readToken = process.env.GITHUB_TOKEN || "";
87- const gistToken = process.env.SERPGAMEDEVELOPER_GIST_TOKEN || "";
88-
89- if (!gistToken) {
90- throw new Error("Missing SERPGAMEDEVELOPER_GIST_TOKEN secret. Add a serpgamedeveloper PAT with gist scope.");
91- }
99+ if (!gistToken) {
100+ throw new Error("Missing SERPGAMEDEVELOPER_GIST_TOKEN secret. Add a serpgamedeveloper PAT with gist write permission.");
101+ }
92102
93- const rawRepository = (process.env.INPUT_REPOSITORY || "").trim();
94- if (!rawRepository) {
95- throw new Error("The repository input is required.");
96- }
103+ const rawRepository = (process.env.INPUT_REPOSITORY || "").trim();
104+ if (!rawRepository) {
105+ throw new Error("The repository input is required.");
106+ }
97107
98- const parts = rawRepository.includes("/")
99- ? rawRepository.split("/")
100- : ["serpgames", rawRepository];
108+ const parts = rawRepository.includes("/")
109+ ? rawRepository.split("/")
110+ : ["serpgames", rawRepository];
101111
102- if (parts.length !== 2 || parts[0] !== "serpgames") {
103- throw new Error("repository must be a repo name in serpgames, or serpgames/repo-name.");
104- }
112+ if (parts.length !== 2 || parts[0] !== "serpgames") {
113+ throw new Error("repository must be a repo name in serpgames, or serpgames/repo-name.");
114+ }
105115
106- const [owner, repo] = parts;
107- if (!/^[A-Za-z0-9_.-]+$/.test(repo)) {
108- throw new Error(`Invalid repository name: ${repo}`);
109- }
116+ const [owner, repo] = parts;
117+ if (!/^[A-Za-z0-9_.-]+$/.test(repo)) {
118+ throw new Error(`Invalid repository name: ${repo}`);
119+ }
110120
111- const githubHeaders = {
112- "Accept": "application/vnd.github+json",
113- "X-GitHub-Api-Version": apiVersion,
114- "User-Agent": "serpgames-readme-gist-action",
115- };
116- if (readToken) {
117- githubHeaders.Authorization = `Bearer ${readToken}`;
118- }
121+ const githubHeaders = {
122+ "Accept": "application/vnd.github+json",
123+ "X-GitHub-Api-Version": apiVersion,
124+ "User-Agent": "serpgames-readme-gist-action",
125+ };
126+ if (readToken) {
127+ githubHeaders.Authorization = `Bearer ${readToken}`;
128+ }
119129
120- async function requestJson(url, options = {}) {
121- const response = await fetch(url, {
122- ...options,
123- headers: {
124- ...githubHeaders,
125- ...(options.headers || {}),
126- },
127- });
128- const text = await response.text();
129- let body = null;
130- if (text) {
131- try {
132- body = JSON.parse(text);
133- } catch {
134- body = text;
130+ async function requestJson(url, options = {}) {
131+ const response = await fetch(url, {
132+ ...options,
133+ headers: {
134+ ...githubHeaders,
135+ ...(options.headers || {}),
136+ },
137+ });
138+ const text = await response.text();
139+ let body = null;
140+ if (text) {
141+ try {
142+ body = JSON.parse(text);
143+ } catch {
144+ body = text;
145+ }
135146 }
147+ if (!response.ok) {
148+ const details = typeof body === "string" ? body : JSON.stringify(body);
149+ throw new Error(`GitHub API request failed: ${response.status} ${response.statusText} ${url} ${details}`);
150+ }
151+ return body;
136152 }
137- if (!response.ok) {
138- const details = typeof body === "string" ? body : JSON.stringify(body);
139- throw new Error(`GitHub API request failed: ${response.status} ${response.statusText} ${url} ${details}`);
153+
154+ function cleanReadmeForGist(markdown) {
155+ return markdown
156+ .replace(/\r\n/g, "\n")
157+ .replace(/\\n/g, "\n")
158+ .replace(/^\[!\[START GAME\]\(\.\/start-game\.(?:gif|svg)\)\]\(([^)]+)\)\s*$/m, "**[START GAME]($1)**")
159+ .trimEnd() + "\n";
140160 }
141- return body;
142- }
143161
144- const repoInfo = await requestJson(`https://api.github.com/repos/${owner}/${repo}`);
145- const requestedRef = (process.env.INPUT_REF || "").trim();
146- const ref = requestedRef || repoInfo.default_branch;
147- const readme = await requestJson(
148- `https://api.github.com/repos/${owner}/${repo}/readme?ref=${encodeURIComponent(ref)}`
149- );
162+ function readTitle(markdown, fallback) {
163+ const h1 = markdown.match(/^#\s+(.+?)\s*$/m);
164+ return h1 ? h1[1].trim() : fallback;
165+ }
150166
151- if (readme.encoding !== "base64") {
152- throw new Error(`Unsupported README encoding: ${readme.encoding}`);
153- }
167+ function normalizeGistId(input) {
168+ const value = (input || "").trim();
169+ if (!value) return "";
170+ const match = value.match(/([0-9a-f]{20,})$/i);
171+ return match ? match[1] : value;
172+ }
173+
174+ const repoInfo = await requestJson(`https://api.github.com/repos/${owner}/${repo}`);
175+ const requestedRef = (process.env.INPUT_REF || "").trim();
176+ const ref = requestedRef || repoInfo.default_branch;
177+ const readme = await requestJson(
178+ `https://api.github.com/repos/${owner}/${repo}/readme?ref=${encodeURIComponent(ref)}`
179+ );
180+
181+ if (readme.encoding !== "base64") {
182+ throw new Error(`Unsupported README encoding: ${readme.encoding}`);
183+ }
154184
155- const readmeMarkdown = Buffer.from(readme.content.replace(/\n/g, ""), "base64").toString("utf8");
156- const gistPublic = String(process.env.GIST_PUBLIC || "true").toLowerCase() !== "false";
157- const generatedAt = new Date().toISOString();
158- const description = (process.env.GIST_DESCRIPTION || "").trim()
159- || `${owner}/${repo} README from ${ref}`;
160-
161- const metadata = {
162- repository: `${owner}/${repo}`,
163- ref,
164- default_branch: repoInfo.default_branch,
165- readme_path: readme.path,
166- readme_sha: readme.sha,
167- readme_size: readme.size,
168- readme_html_url: readme.html_url,
169- readme_download_url: readme.download_url,
170- generated_at: generatedAt,
171- };
172-
173- const gist = await requestJson("https://api.github.com/gists", {
174- method: "POST",
175- headers: {
176- Authorization: `Bearer ${gistToken}`,
177- },
178- body: JSON.stringify({
179- description,
180- public: gistPublic,
181- files: {
182- [`${repo}-README.md`]: {
183- content: readmeMarkdown,
185+ const rawReadmeMarkdown = Buffer.from(readme.content.replace(/\n/g, ""), "base64").toString("utf8");
186+ const readmeMarkdown = cleanReadmeForGist(rawReadmeMarkdown);
187+ const title = (process.env.GIST_DESCRIPTION || "").trim()
188+ || readTitle(readmeMarkdown, `Play ${repo.replace(/-game$/, "").replace(/-/g, " ")} Game Online - Free Unblocked`);
189+ const gistPublic = String(process.env.GIST_PUBLIC || "true").toLowerCase() !== "false";
190+ const existingGistId = normalizeGistId(process.env.INPUT_GIST_ID);
191+
192+ let gist;
193+ if (existingGistId) {
194+ const existingGist = await requestJson(`https://api.github.com/gists/${existingGistId}`, {
195+ headers: {
196+ Authorization: `Bearer ${gistToken}`,
184197 },
185- [`${repo}-readme-metadata.json`]: {
186- content: JSON.stringify(metadata, null, 2),
198+ });
199+ const files = {};
200+ for (const filename of Object.keys(existingGist.files || {})) {
201+ files[filename] = null;
202+ }
203+ const firstMarkdownFile = Object.keys(existingGist.files || {}).find((filename) => filename.toLowerCase().endsWith(".md"));
204+ files[firstMarkdownFile || "README.md"] = {
205+ filename: "README.md",
206+ content: readmeMarkdown,
207+ };
208+ gist = await requestJson(`https://api.github.com/gists/${existingGistId}`, {
209+ method: "PATCH",
210+ headers: {
211+ Authorization: `Bearer ${gistToken}`,
187212 },
188- },
189- }),
190- });
213+ body: JSON.stringify({
214+ description: title,
215+ files,
216+ }),
217+ });
218+ } else {
219+ gist = await requestJson("https://api.github.com/gists", {
220+ method: "POST",
221+ headers: {
222+ Authorization: `Bearer ${gistToken}`,
223+ },
224+ body: JSON.stringify({
225+ description: title,
226+ public: gistPublic,
227+ files: {
228+ "README.md": {
229+ content: readmeMarkdown,
230+ },
231+ },
232+ }),
233+ });
234+ }
191235
192- fs.appendFileSync(process.env.GITHUB_OUTPUT, `gist_url=${gist.html_url}\n`);
193- fs.appendFileSync(process.env.GITHUB_OUTPUT, `gist_id=${gist.id}\n`);
194-
195- if (process.env.GITHUB_STEP_SUMMARY) {
196- fs.appendFileSync(
197- process.env.GITHUB_STEP_SUMMARY,
198- [
199- "## README gist created",
200- "",
201- `- Repository: ${owner}/${repo}`,
202- `- Ref: ${ref}`,
203- `- README: ${readme.html_url}`,
204- `- Gist: ${gist.html_url}`,
205- `- Public: ${gist.public}`,
206- "",
207- ].join("\n")
208- );
209- }
236+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `gist_url=${gist.html_url}\n`);
237+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `gist_id=${gist.id}\n`);
238+
239+ if (process.env.GITHUB_STEP_SUMMARY) {
240+ fs.appendFileSync(
241+ process.env.GITHUB_STEP_SUMMARY,
242+ [
243+ existingGistId ? "## README gist updated" : "## README gist created",
244+ "",
245+ `- Title: ${title}`,
246+ `- Repository: ${owner}/${repo}`,
247+ `- Ref: ${ref}`,
248+ `- README: ${readme.html_url}`,
249+ `- Gist: ${gist.html_url}`,
250+ `- Public: ${gist.public}`,
251+ "",
252+ ].join("\n")
253+ );
254+ }
210255 }
211256
212257 main().catch((error) => {
0 commit comments