GitHub Action commited on
Commit
afcd954
·
1 Parent(s): 7cabfe5

Sync from GitHub with Git LFS

Browse files
Files changed (1) hide show
  1. scripts/publish_to_hashnode.py +29 -103
scripts/publish_to_hashnode.py CHANGED
@@ -6,188 +6,114 @@ import re
6
  from pathlib import Path
7
 
8
  import requests
9
- import markdown
10
- from markdown.extensions import tables, fenced_code, codehilite, toc
11
 
12
  PUBLISHED_FILE = "published_posts.json"
13
  GH_PAGES_BASE = "https://kagvi13.github.io/HMP/"
14
- HMP_TAGS = ["HMP"] # сюда можно добавлять другие тэги при необходимости
15
 
16
  HASHNODE_TOKEN = os.environ["HASHNODE_TOKEN"]
17
  HASHNODE_PUBLICATION_ID = os.environ["HASHNODE_PUBLICATION_ID"]
18
  API_URL = "https://gql.hashnode.com"
19
 
20
-
21
  def convert_md_links(md_text: str) -> str:
22
- """Конвертирует относительные ссылки (*.md) в абсолютные ссылки на GitHub Pages."""
23
  def replacer(match):
24
- text = match.group(1)
25
- link = match.group(2)
26
  if link.startswith("http://") or link.startswith("https://") or not link.endswith(".md"):
27
  return match.group(0)
28
  abs_link = GH_PAGES_BASE + link.replace(".md", "").lstrip("./")
29
  return f"[{text}]({abs_link})"
30
  return re.sub(r"\[([^\]]+)\]\(([^)]+)\)", replacer, md_text)
31
 
32
-
33
  def load_published():
34
  if Path(PUBLISHED_FILE).exists():
35
  with open(PUBLISHED_FILE, "r", encoding="utf-8") as f:
36
  return json.load(f)
37
- print("⚠ published_posts.json не найден — начинаем с нуля.")
38
  return {}
39
 
40
-
41
  def save_published(data):
42
  with open(PUBLISHED_FILE, "w", encoding="utf-8") as f:
43
  json.dump(data, f, ensure_ascii=False, indent=2)
44
 
45
-
46
- def file_hash(path):
47
- return hashlib.md5(Path(path).read_bytes()).hexdigest()
48
-
49
 
50
  def graphql_request(query, variables):
51
- headers = {
52
- "Authorization": f"Bearer {HASHNODE_TOKEN}",
53
- "Content-Type": "application/json"
54
- }
55
- response = requests.post(API_URL, json={"query": query, "variables": variables}, headers=headers)
56
- try:
57
- resp_json = response.json()
58
- except json.JSONDecodeError:
59
- raise Exception(f"GraphQL вернул не JSON: {response.text}")
60
-
61
- print("DEBUG: GraphQL response:", json.dumps(resp_json, indent=2))
62
-
63
- if response.status_code != 200:
64
- raise Exception(f"GraphQL request failed with {response.status_code}: {response.text}")
65
- if "errors" in resp_json:
66
- raise Exception(f"GraphQL errors: {resp_json['errors']}")
67
- return resp_json
68
-
69
 
70
  def create_post(title, slug, markdown_content):
71
  query = """
72
  mutation CreateDraft($input: CreateDraftInput!) {
73
  createDraft(input: $input) {
74
- draft {
75
- id
76
- slug
77
- title
78
- }
79
  }
80
  }
81
  """
82
- variables = {
83
- "input": {
84
- "title": title,
85
- "contentMarkdown": markdown_content,
86
- "slug": slug,
87
- "publicationId": HASHNODE_PUBLICATION_ID,
88
- "tags": [{"name": tag} for tag in HMP_TAGS] # <-- добавляем теги
89
- }
90
- }
91
  return graphql_request(query, variables)["data"]["createDraft"]["draft"]
92
 
93
-
94
- def update_post(draft_id, title, markdown_content):
95
  query = """
96
  mutation UpdateDraft($id: ID!, $input: UpdateDraftInput!) {
97
  updateDraft(id: $id, input: $input) {
98
- draft {
99
- id
100
- slug
101
- title
102
- }
103
  }
104
  }
105
  """
106
- variables = {
107
- "id": draft_id,
108
- "input": {
109
- "title": title,
110
- "contentMarkdown": markdown_content,
111
- "tags": [{"name": tag} for tag in HMP_TAGS]
112
- }
113
- }
114
  return graphql_request(query, variables)["data"]["updateDraft"]["draft"]
115
 
116
-
117
  def publish_draft(draft_id):
118
  query = """
119
  mutation PublishDraft($input: PublishDraftInput!) {
120
- publishDraft(input: $input) {
121
- post {
122
- id
123
- slug
124
- url
125
- }
126
- }
127
  }
128
  """
129
  variables = {"input": {"draftId": draft_id}}
130
  return graphql_request(query, variables)["data"]["publishDraft"]["post"]
131
 
132
-
133
  def main(force=False):
134
  published = load_published()
135
  md_files = list(Path("docs").rglob("*.md"))
136
 
137
  for md_file in md_files:
138
  name = md_file.stem
 
 
139
 
140
- # Если короткое имя, добавляем суффикс
141
- if len(name) < 6:
142
- title = name + "-HMP"
143
- else:
144
- title = name
145
-
146
- # slug формируем из title, чтобы Hashnode не ругался
147
- slug = re.sub(r'[^a-z0-9-]', '-', title.lower())
148
- slug = re.sub(r'-+', '-', slug).strip('-')
149
- slug = slug[:250]
150
-
151
- md_text = md_file.read_text(encoding="utf-8")
152
- source_link = f"Источник: [ {md_file.name} ](https://github.com/kagvi13/HMP/blob/main/docs/{md_file.name})\n\n"
153
- md_text = source_link + md_text
154
  md_text = convert_md_links(md_text)
 
155
 
156
- # Хэш после добавления источника
157
- h = hashlib.md5(md_text.encode("utf-8")).hexdigest()
158
-
159
- # Проверка публикации по title
160
- if not force and title in published and published[title]["hash"] == h:
161
- print(f"✅ Пост '{title}' без изменений — пропускаем.")
162
  continue
163
 
164
  try:
165
- if title in published and "id" in published[title]:
166
- draft_id = published[title]["id"]
167
- post = update_post(draft_id, title, md_text)
168
  print(f"♻ Обновлён пост: https://hashnode.com/@yourusername/{post['slug']}")
169
  else:
170
- post = create_post(title, slug, md_text)
171
- post = publish_draft(post["id"])
172
  print(f"🆕 Пост опубликован: https://hashnode.com/@yourusername/{post['slug']}")
173
 
174
-
175
- published[title] = {"id": post["id"], "slug": post["slug"], "hash": h}
176
  save_published(published)
177
-
178
- print("⏱ Пауза 30 секунд перед следующим постом...")
179
  time.sleep(30)
180
 
181
  except Exception as e:
182
- print(f"❌ Ошибка при публикации {title}: {e}")
183
  save_published(published)
184
  break
185
 
186
-
187
  if __name__ == "__main__":
188
  import argparse
189
  parser = argparse.ArgumentParser()
190
- parser.add_argument("--force", action="store_true", help="Обновить все посты, даже без изменений")
191
  args = parser.parse_args()
192
-
193
  main(force=args.force)
 
6
  from pathlib import Path
7
 
8
  import requests
 
 
9
 
10
  PUBLISHED_FILE = "published_posts.json"
11
  GH_PAGES_BASE = "https://kagvi13.github.io/HMP/"
 
12
 
13
  HASHNODE_TOKEN = os.environ["HASHNODE_TOKEN"]
14
  HASHNODE_PUBLICATION_ID = os.environ["HASHNODE_PUBLICATION_ID"]
15
  API_URL = "https://gql.hashnode.com"
16
 
 
17
  def convert_md_links(md_text: str) -> str:
 
18
  def replacer(match):
19
+ text, link = match.groups()
 
20
  if link.startswith("http://") or link.startswith("https://") or not link.endswith(".md"):
21
  return match.group(0)
22
  abs_link = GH_PAGES_BASE + link.replace(".md", "").lstrip("./")
23
  return f"[{text}]({abs_link})"
24
  return re.sub(r"\[([^\]]+)\]\(([^)]+)\)", replacer, md_text)
25
 
 
26
  def load_published():
27
  if Path(PUBLISHED_FILE).exists():
28
  with open(PUBLISHED_FILE, "r", encoding="utf-8") as f:
29
  return json.load(f)
 
30
  return {}
31
 
 
32
  def save_published(data):
33
  with open(PUBLISHED_FILE, "w", encoding="utf-8") as f:
34
  json.dump(data, f, ensure_ascii=False, indent=2)
35
 
36
+ def file_hash(md_text: str):
37
+ return hashlib.md5(md_text.encode("utf-8")).hexdigest()
 
 
38
 
39
  def graphql_request(query, variables):
40
+ headers = {"Authorization": f"Bearer {HASHNODE_TOKEN}", "Content-Type": "application/json"}
41
+ resp = requests.post(API_URL, json={"query": query, "variables": variables}, headers=headers)
42
+ data = resp.json()
43
+ if "errors" in data:
44
+ raise Exception(f"GraphQL errors: {data['errors']}")
45
+ return data
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  def create_post(title, slug, markdown_content):
48
  query = """
49
  mutation CreateDraft($input: CreateDraftInput!) {
50
  createDraft(input: $input) {
51
+ draft { id slug title }
 
 
 
 
52
  }
53
  }
54
  """
55
+ variables = {"input": {"title": title, "contentMarkdown": markdown_content,
56
+ "slug": slug, "publicationId": HASHNODE_PUBLICATION_ID}}
 
 
 
 
 
 
 
57
  return graphql_request(query, variables)["data"]["createDraft"]["draft"]
58
 
59
+ def update_post(post_id, title, markdown_content):
 
60
  query = """
61
  mutation UpdateDraft($id: ID!, $input: UpdateDraftInput!) {
62
  updateDraft(id: $id, input: $input) {
63
+ draft { id slug title }
 
 
 
 
64
  }
65
  }
66
  """
67
+ variables = {"id": post_id, "input": {"title": title, "contentMarkdown": markdown_content}}
 
 
 
 
 
 
 
68
  return graphql_request(query, variables)["data"]["updateDraft"]["draft"]
69
 
 
70
  def publish_draft(draft_id):
71
  query = """
72
  mutation PublishDraft($input: PublishDraftInput!) {
73
+ publishDraft(input: $input) { post { id slug url } }
 
 
 
 
 
 
74
  }
75
  """
76
  variables = {"input": {"draftId": draft_id}}
77
  return graphql_request(query, variables)["data"]["publishDraft"]["post"]
78
 
 
79
  def main(force=False):
80
  published = load_published()
81
  md_files = list(Path("docs").rglob("*.md"))
82
 
83
  for md_file in md_files:
84
  name = md_file.stem
85
+ title = name if len(name) >= 6 else name + "-HMP"
86
+ slug = re.sub(r'[^a-z0-9-]', '-', title.lower()).strip('-')[:250]
87
 
88
+ md_text = f"Источник: [ {md_file.name} ](https://github.com/kagvi13/HMP/blob/main/docs/{md_file.name})\n\n" + md_file.read_text(encoding="utf-8")
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  md_text = convert_md_links(md_text)
90
+ h = file_hash(md_text)
91
 
92
+ if not force and name in published and published[name].get("hash") == h:
93
+ print(f"✅ Пост '{name}' без изменений — пропускаем.")
 
 
 
 
94
  continue
95
 
96
  try:
97
+ if name in published and "id" in published[name]:
98
+ post = update_post(published[name]["id"], title, md_text)
 
99
  print(f"♻ Обновлён пост: https://hashnode.com/@yourusername/{post['slug']}")
100
  else:
101
+ draft = create_post(title, slug, md_text)
102
+ post = publish_draft(draft["id"])
103
  print(f"🆕 Пост опубликован: https://hashnode.com/@yourusername/{post['slug']}")
104
 
105
+ published[name] = {"id": post["id"], "slug": post["slug"], "hash": h}
 
106
  save_published(published)
 
 
107
  time.sleep(30)
108
 
109
  except Exception as e:
110
+ print(f"❌ Ошибка при публикации {name}: {e}")
111
  save_published(published)
112
  break
113
 
 
114
  if __name__ == "__main__":
115
  import argparse
116
  parser = argparse.ArgumentParser()
117
+ parser.add_argument("--force", action="store_true")
118
  args = parser.parse_args()
 
119
  main(force=args.force)