GitHub Action
commited on
Commit
·
2c5aea5
1
Parent(s):
1595318
Sync from GitHub with Git LFS
Browse files- scripts/publish_to_hashnode.py +28 -11
scripts/publish_to_hashnode.py
CHANGED
|
@@ -14,7 +14,9 @@ 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"):
|
|
@@ -23,19 +25,23 @@ def convert_md_links(md_text: str) -> str:
|
|
| 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)
|
|
@@ -44,24 +50,29 @@ def graphql_request(query, variables):
|
|
| 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 = {
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
return graphql_request(query, variables)["data"]["createDraft"]["draft"]
|
| 58 |
|
|
|
|
| 59 |
def update_post(draft_id, title, markdown_content):
|
|
|
|
| 60 |
query = """
|
| 61 |
mutation UpdateDraft($input: UpdateDraftInput!) {
|
| 62 |
-
updateDraft(input: $input) {
|
| 63 |
-
draft { id slug title }
|
| 64 |
-
}
|
| 65 |
}
|
| 66 |
"""
|
| 67 |
variables = {
|
|
@@ -83,6 +94,7 @@ def publish_draft(draft_id):
|
|
| 83 |
variables = {"input": {"draftId": draft_id}}
|
| 84 |
return graphql_request(query, variables)["data"]["publishDraft"]["post"]
|
| 85 |
|
|
|
|
| 86 |
def main(force=False):
|
| 87 |
published = load_published()
|
| 88 |
md_files = list(Path("docs").rglob("*.md"))
|
|
@@ -92,7 +104,8 @@ def main(force=False):
|
|
| 92 |
title = name if len(name) >= 6 else name + "-HMP"
|
| 93 |
slug = re.sub(r'[^a-z0-9-]', '-', title.lower()).strip('-')[:250]
|
| 94 |
|
| 95 |
-
md_text = f"Источник: [ {md_file.name} ](https://github.com/kagvi13/HMP/blob/main/docs/{md_file.name})\n\n"
|
|
|
|
| 96 |
md_text = convert_md_links(md_text)
|
| 97 |
h = file_hash(md_text)
|
| 98 |
|
|
@@ -102,15 +115,18 @@ def main(force=False):
|
|
| 102 |
|
| 103 |
try:
|
| 104 |
if name in published and "id" in published[name]:
|
| 105 |
-
update_post(published[name]["id"], title, md_text)
|
| 106 |
print(f"♻ Обновлён пост: https://hashnode.com/@yourusername/{post['slug']}")
|
| 107 |
else:
|
| 108 |
draft = create_post(title, slug, md_text)
|
| 109 |
post = publish_draft(draft["id"])
|
| 110 |
print(f"🆕 Пост опубликован: https://hashnode.com/@yourusername/{post['slug']}")
|
| 111 |
|
|
|
|
| 112 |
published[name] = {"id": post["id"], "slug": post["slug"], "hash": h}
|
| 113 |
save_published(published)
|
|
|
|
|
|
|
| 114 |
time.sleep(30)
|
| 115 |
|
| 116 |
except Exception as e:
|
|
@@ -118,9 +134,10 @@ def main(force=False):
|
|
| 118 |
save_published(published)
|
| 119 |
break
|
| 120 |
|
|
|
|
| 121 |
if __name__ == "__main__":
|
| 122 |
import argparse
|
| 123 |
parser = argparse.ArgumentParser()
|
| 124 |
-
parser.add_argument("--force", action="store_true")
|
| 125 |
args = parser.parse_args()
|
| 126 |
main(force=args.force)
|
|
|
|
| 14 |
HASHNODE_PUBLICATION_ID = os.environ["HASHNODE_PUBLICATION_ID"]
|
| 15 |
API_URL = "https://gql.hashnode.com"
|
| 16 |
|
| 17 |
+
|
| 18 |
def convert_md_links(md_text: str) -> str:
|
| 19 |
+
"""Конвертирует относительные ссылки (*.md) в абсолютные ссылки на GitHub Pages."""
|
| 20 |
def replacer(match):
|
| 21 |
text, link = match.groups()
|
| 22 |
if link.startswith("http://") or link.startswith("https://") or not link.endswith(".md"):
|
|
|
|
| 25 |
return f"[{text}]({abs_link})"
|
| 26 |
return re.sub(r"\[([^\]]+)\]\(([^)]+)\)", replacer, md_text)
|
| 27 |
|
| 28 |
+
|
| 29 |
def load_published():
|
| 30 |
if Path(PUBLISHED_FILE).exists():
|
| 31 |
with open(PUBLISHED_FILE, "r", encoding="utf-8") as f:
|
| 32 |
return json.load(f)
|
| 33 |
return {}
|
| 34 |
|
| 35 |
+
|
| 36 |
def save_published(data):
|
| 37 |
with open(PUBLISHED_FILE, "w", encoding="utf-8") as f:
|
| 38 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 39 |
|
| 40 |
+
|
| 41 |
def file_hash(md_text: str):
|
| 42 |
return hashlib.md5(md_text.encode("utf-8")).hexdigest()
|
| 43 |
|
| 44 |
+
|
| 45 |
def graphql_request(query, variables):
|
| 46 |
headers = {"Authorization": f"Bearer {HASHNODE_TOKEN}", "Content-Type": "application/json"}
|
| 47 |
resp = requests.post(API_URL, json={"query": query, "variables": variables}, headers=headers)
|
|
|
|
| 50 |
raise Exception(f"GraphQL errors: {data['errors']}")
|
| 51 |
return data
|
| 52 |
|
| 53 |
+
|
| 54 |
def create_post(title, slug, markdown_content):
|
| 55 |
query = """
|
| 56 |
mutation CreateDraft($input: CreateDraftInput!) {
|
| 57 |
+
createDraft(input: $input) { draft { id slug title } }
|
|
|
|
|
|
|
| 58 |
}
|
| 59 |
"""
|
| 60 |
+
variables = {
|
| 61 |
+
"input": {
|
| 62 |
+
"title": title,
|
| 63 |
+
"contentMarkdown": markdown_content,
|
| 64 |
+
"slug": slug,
|
| 65 |
+
"publicationId": HASHNODE_PUBLICATION_ID
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
return graphql_request(query, variables)["data"]["createDraft"]["draft"]
|
| 69 |
|
| 70 |
+
|
| 71 |
def update_post(draft_id, title, markdown_content):
|
| 72 |
+
"""Используем draftId для обновления, чтобы соответствовать текущему API Hashnode."""
|
| 73 |
query = """
|
| 74 |
mutation UpdateDraft($input: UpdateDraftInput!) {
|
| 75 |
+
updateDraft(input: $input) { draft { id slug title } }
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
"""
|
| 78 |
variables = {
|
|
|
|
| 94 |
variables = {"input": {"draftId": draft_id}}
|
| 95 |
return graphql_request(query, variables)["data"]["publishDraft"]["post"]
|
| 96 |
|
| 97 |
+
|
| 98 |
def main(force=False):
|
| 99 |
published = load_published()
|
| 100 |
md_files = list(Path("docs").rglob("*.md"))
|
|
|
|
| 104 |
title = name if len(name) >= 6 else name + "-HMP"
|
| 105 |
slug = re.sub(r'[^a-z0-9-]', '-', title.lower()).strip('-')[:250]
|
| 106 |
|
| 107 |
+
md_text = f"Источник: [ {md_file.name} ](https://github.com/kagvi13/HMP/blob/main/docs/{md_file.name})\n\n" \
|
| 108 |
+
+ md_file.read_text(encoding="utf-8")
|
| 109 |
md_text = convert_md_links(md_text)
|
| 110 |
h = file_hash(md_text)
|
| 111 |
|
|
|
|
| 115 |
|
| 116 |
try:
|
| 117 |
if name in published and "id" in published[name]:
|
| 118 |
+
post = update_post(published[name]["id"], title, md_text)
|
| 119 |
print(f"♻ Обновлён пост: https://hashnode.com/@yourusername/{post['slug']}")
|
| 120 |
else:
|
| 121 |
draft = create_post(title, slug, md_text)
|
| 122 |
post = publish_draft(draft["id"])
|
| 123 |
print(f"🆕 Пост опубликован: https://hashnode.com/@yourusername/{post['slug']}")
|
| 124 |
|
| 125 |
+
# Обновляем локальный JSON после публикации/обновления
|
| 126 |
published[name] = {"id": post["id"], "slug": post["slug"], "hash": h}
|
| 127 |
save_published(published)
|
| 128 |
+
|
| 129 |
+
print("⏱ Пауза 30 секунд перед следующим постом...")
|
| 130 |
time.sleep(30)
|
| 131 |
|
| 132 |
except Exception as e:
|
|
|
|
| 134 |
save_published(published)
|
| 135 |
break
|
| 136 |
|
| 137 |
+
|
| 138 |
if __name__ == "__main__":
|
| 139 |
import argparse
|
| 140 |
parser = argparse.ArgumentParser()
|
| 141 |
+
parser.add_argument("--force", action="store_true", help="Обновить все посты, даже без изменений")
|
| 142 |
args = parser.parse_args()
|
| 143 |
main(force=args.force)
|