Skip to content

Commit e1ab30b

Browse files
author
Ubuntu
committed
feat: seed public discussions via workflow
1 parent 7f1c046 commit e1ab30b

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
name: Seed Discussions
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: read
8+
discussions: write
9+
10+
jobs:
11+
seed:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Seed and pin starter discussions
15+
uses: actions/github-script@v7
16+
with:
17+
github-token: ${{ secrets.GITHUB_TOKEN }}
18+
script: |
19+
const owner = context.repo.owner;
20+
const repo = context.repo.repo;
21+
22+
const query = `
23+
query($owner: String!, $repo: String!) {
24+
repository(owner: $owner, name: $repo) {
25+
id
26+
discussionCategories(first: 20) {
27+
nodes {
28+
id
29+
slug
30+
}
31+
}
32+
discussions(first: 100) {
33+
nodes {
34+
id
35+
title
36+
category {
37+
slug
38+
}
39+
}
40+
}
41+
pinnedDiscussions(first: 10) {
42+
nodes {
43+
discussion {
44+
id
45+
title
46+
}
47+
}
48+
}
49+
}
50+
}
51+
`;
52+
53+
const data = await github.graphql(query, { owner, repo });
54+
const repository = data.repository;
55+
const categoryBySlug = Object.fromEntries(
56+
repository.discussionCategories.nodes.map((node) => [node.slug, node.id])
57+
);
58+
const existing = new Map(
59+
repository.discussions.nodes.map((node) => [`${node.category.slug}::${node.title}`, node])
60+
);
61+
const pinned = new Set(
62+
repository.pinnedDiscussions.nodes.map((node) => node.discussion.id)
63+
);
64+
65+
const seeds = [
66+
{
67+
category: "announcements",
68+
title: "Start Here: Subscription Links, Status, and Update Channels",
69+
pin: true,
70+
body: [
71+
"Use this thread as the main entry point for the public feed.",
72+
"",
73+
"Primary links:",
74+
"- Live site: https://au1rxx.github.io/free-vpn-subscriptions/",
75+
"- Clash: https://github.com/Au1rxx/free-vpn-subscriptions/releases/latest/download/clash.yaml",
76+
"- sing-box: https://github.com/Au1rxx/free-vpn-subscriptions/releases/latest/download/singbox.json",
77+
"- V2Ray: https://github.com/Au1rxx/free-vpn-subscriptions/releases/latest/download/v2ray-base64.txt",
78+
"- Status: https://github.com/Au1rxx/free-vpn-subscriptions/releases/latest/download/status.json",
79+
"",
80+
"How to use this repo:",
81+
"- Watch Releases if you want visible snapshot updates.",
82+
"- Use Discussions > Q&A for client setup help.",
83+
"- Use Discussions > Ideas for new client guide requests and landing-page suggestions.",
84+
"- Use Issues only when a public link, release asset, or Pages route is actually broken."
85+
].join("\n")
86+
},
87+
{
88+
category: "q-a",
89+
title: "Setup Help Thread: Ask Your Client Import Questions Here",
90+
pin: true,
91+
body: [
92+
"If you are blocked on import, post here with these details:",
93+
"",
94+
"- client name and version",
95+
"- platform",
96+
"- which subscription format you used",
97+
"- the exact step or error message",
98+
"",
99+
"Good examples:",
100+
"- Clash Verge Rev imported but shows zero nodes",
101+
"- v2rayNG rejects the subscription link",
102+
"- Shadowrocket imports but cannot connect",
103+
"",
104+
"Before posting, check the live status page and the matching setup guide in the repository."
105+
].join("\n")
106+
},
107+
{
108+
category: "ideas",
109+
title: "Client Guide Requests and SEO Landing Page Ideas",
110+
pin: true,
111+
body: [
112+
"Use this thread to request:",
113+
"",
114+
"- new client setup guides",
115+
"- device-specific import instructions",
116+
"- FAQ additions",
117+
"- search-driven landing pages for clients or common errors",
118+
"",
119+
"When suggesting an idea, include the actual search terms users would type. That helps prioritize pages that can bring in new traffic and reduce repetitive support work."
120+
].join("\n")
121+
}
122+
];
123+
124+
for (const seed of seeds) {
125+
const categoryId = categoryBySlug[seed.category];
126+
if (!categoryId) {
127+
core.warning(`Missing discussion category: ${seed.category}`);
128+
continue;
129+
}
130+
131+
const key = `${seed.category}::${seed.title}`;
132+
let discussion = existing.get(key);
133+
134+
if (!discussion) {
135+
const created = await github.graphql(
136+
`
137+
mutation($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
138+
createDiscussion(
139+
input: {
140+
repositoryId: $repositoryId
141+
categoryId: $categoryId
142+
title: $title
143+
body: $body
144+
}
145+
) {
146+
discussion {
147+
id
148+
title
149+
url
150+
category {
151+
slug
152+
}
153+
}
154+
}
155+
}
156+
`,
157+
{
158+
repositoryId: repository.id,
159+
categoryId,
160+
title: seed.title,
161+
body: seed.body
162+
}
163+
);
164+
discussion = created.createDiscussion.discussion;
165+
existing.set(key, discussion);
166+
core.info(`Created discussion: ${discussion.url}`);
167+
} else {
168+
core.info(`Discussion already exists: ${discussion.title}`);
169+
}
170+
171+
if (seed.pin && !pinned.has(discussion.id)) {
172+
try {
173+
await github.graphql(
174+
`
175+
mutation($discussionId: ID!) {
176+
pinDiscussion(input: { discussionId: $discussionId }) {
177+
discussion {
178+
id
179+
}
180+
}
181+
}
182+
`,
183+
{ discussionId: discussion.id }
184+
);
185+
pinned.add(discussion.id);
186+
core.info(`Pinned discussion: ${discussion.title}`);
187+
} catch (error) {
188+
core.warning(`Failed to pin discussion "${discussion.title}": ${error.message}`);
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)