Skip to content

Commit 828373a

Browse files
authored
Merge pull request #32 from IMXEren/refactor/changelogs-format
refactor: modify the format for changelog doc
2 parents 52ff309 + 0152f40 commit 828373a

File tree

7 files changed

+179
-51
lines changed

7 files changed

+179
-51
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ EXTRA_FILES=https://github.com/WSTxda/MicroG-RE/releases/latest@MicroG-RE.apk
1717
# YOUTUBE_KEYSTORE_FILE_NAME=revanced.keystore
1818
# YOUTUBE_ARCHS_TO_BUILD=arm64-v8a
1919
YOUTUBE_CLI_DL=https://github.com/inotia00/revanced-cli/releases/latest
20-
YOUTUBE_PATCHES_DL=https://github.com/inotia00/revanced-patches/releases/latest
20+
YOUTUBE_PATCHES_DL=https://github.com/revanced/revanced-patches/releases/latest
2121
# YOUTUBE_SPACE_FORMATTED_PATCHES=True
2222
YOUTUBE_INCLUDE_PATCH=change-version-code
2323
YOUTUBE_EXCLUDE_PATCH=custom-package-name,materialyou,custom-branding-name-for-youtube,custom-header-for-youtube,force-hide-player-buttons-background,hide-shortcuts

.github/workflows/auto-release.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ jobs:
5858
output=$(python check_resource_updates.py)
5959
should_build="$(grep -m1 '^PATCH_APPS=' <<< "$output" | cut -d= -f2- | tr -d '\r' || printf '')"
6060
echo "SHOULD_BUILD=$should_build" >> $GITHUB_OUTPUT
61+
62+
- name: Upload patch updates changelog
63+
uses: actions/upload-artifact@main
64+
if: ${{ steps.should_build.outputs.SHOULD_BUILD != '' }}
65+
with:
66+
name: Patch-Updates
67+
path: |
68+
patch-updates.md
69+
if-no-files-found: error
70+
retention-days: 0
71+
6172
outputs:
6273
SHOULD_BUILD: ${{ steps.should_build.outputs.SHOULD_BUILD }}
6374

@@ -72,4 +83,4 @@ jobs:
7283
cancel-in-progress: true
7384
with:
7485
TELEGRAM_NO_ROOT_UPLOAD: ${{ inputs.TELEGRAM_NO_ROOT_UPLOAD || false }}
75-
PREFERRED_PATCH_APPS: ${{ needs.release-check.outputs.SHOULD_BUILD }}
86+
PREFERRED_PATCH_APPS: ${{ needs.release-check.outputs.SHOULD_BUILD }}

.github/workflows/build-apk.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,27 @@ jobs:
135135
uses: actions/download-artifact@main
136136
with:
137137
name: Built-APKs
138+
139+
- name: Check if patch updates changelog
140+
id: check-patch-updates
141+
uses: softwareforgood/check-artifact-v4-existence@v0
142+
with:
143+
name: Patch-Updates
144+
145+
- name: Download patch updates changelog
146+
uses: actions/download-artifact@main
147+
if: ${{ steps.check-patch-updates.outputs.exists == 'true' }}
148+
with:
149+
name: Patch-Updates
150+
138151
- name: Get Date
139152
id: get-date
140153
run: |
141154
echo "date=$(TZ='Asia/Kolkata' date +"%Y.%m.%d-%H.%M.%S")" >> $GITHUB_OUTPUT
142-
curl "https://raw.githubusercontent.com/${{ github.repository }}/check-updates/changelog.md" > changelog.md
155+
# curl "https://raw.githubusercontent.com/${{ github.repository }}/check-updates/patch-updates.md" > patch-updates.md
156+
if [ ! -f ./patch-updates.md ]; then
157+
cp ./changelog.md ./patch-updates.md
158+
fi
143159
144160
- name: Delete Older Releases
145161
uses: nikhilbadyal/ghaction-rm-releases@v0.0.5
@@ -155,7 +171,7 @@ jobs:
155171
artifacts: "apks/*-output.apk,updates.json,changelog.json,changelog.md"
156172
token: ${{ secrets.GITHUB_TOKEN }}
157173
tag: Build-${{ steps.get-date.outputs.date }}
158-
bodyFile: changelog.md
174+
bodyFile: patch-updates.md
159175
artifactErrorsFailBuild: true
160176
prerelease: ${{ inputs.PRE_RELEASE }}
161177

check_resource_updates.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
"""Check patching resource updates."""
22

3+
from pathlib import Path
34
from threading import Lock
45

6+
import requests
57
from environs import Env
68
from loguru import logger
79

810
from main import get_app
11+
from src.app import APP
912
from src.config import RevancedConfig
13+
from src.downloader.github import Github
1014
from src.manager.github import GitHubManager
11-
from src.utils import default_build, patches_dl_list_key, patches_versions_key
15+
from src.metadata.github import GithubSourceMetadata
16+
from src.utils import (
17+
default_build,
18+
format_changelog,
19+
handle_request_response,
20+
patches_dl_list_key,
21+
patches_versions_key,
22+
request_timeout,
23+
)
1224

1325

1426
def check_if_build_is_required() -> bool:
1527
"""Read resource version."""
1628
env = Env()
1729
env.read_env()
1830
config = RevancedConfig(env)
19-
needs_to_repatched = []
31+
needs_to_repatched: list[APP] = []
2032
resource_cache: dict[str, tuple[str, str]] = {}
2133
resource_lock = Lock()
2234
for app_name in env.list("PATCH_APPS", default_build):
@@ -49,7 +61,7 @@ def check_if_build_is_required() -> bool:
4961
logger.info(
5062
f"New build can be triggered due to change in number of patch bundles or sources, info: {caused_by}",
5163
)
52-
needs_to_repatched.append(app_name)
64+
needs_to_repatched.append(app_obj)
5365
continue
5466

5567
for old_version, old_source, new_version, new_source in zip(
@@ -73,13 +85,53 @@ def check_if_build_is_required() -> bool:
7385
},
7486
}
7587
logger.info(f"New build can be triggered caused by {caused_by}")
76-
needs_to_repatched.append(app_name)
88+
needs_to_repatched.append(app_obj)
7789
break
78-
logger.info(f"{needs_to_repatched} are need to repatched.")
90+
needs_to_repatched_names = [app.app_name for app in needs_to_repatched]
91+
logger.info(f"{needs_to_repatched_names} are need to repatched.")
7992
if needs_to_repatched:
80-
print(f"PATCH_APPS={",".join(needs_to_repatched)}") # noqa: T201
93+
print(f"PATCH_APPS={','.join(needs_to_repatched_names)}") # noqa: T201
94+
write_patch_updates_changelog(config, needs_to_repatched)
8195
return True
8296
return False
8397

8498

99+
def _fetch_metadata(url: str, access_token: str | None = None) -> GithubSourceMetadata:
100+
owner, repo_name, release_tag = Github._extract_repo_owner_and_tag(url) # noqa: SLF001
101+
repo_url = f"https://api.github.com/repos/{owner}/{repo_name}/releases/{release_tag}"
102+
headers = {
103+
"Content-Type": "application/vnd.github.v3+json",
104+
}
105+
if access_token:
106+
logger.debug("Using personal access token")
107+
headers["Authorization"] = f"Bearer {access_token}"
108+
logger.debug(f"Fetching metadata from {repo_url}")
109+
response = requests.get(repo_url, headers=headers, timeout=request_timeout)
110+
handle_request_response(response, repo_url)
111+
return GithubSourceMetadata.from_json(response.json())
112+
113+
114+
def write_patch_updates_changelog(config: RevancedConfig, apps: list[APP]) -> None:
115+
"""Write patch updates changelog."""
116+
patches_dl_set: set[str] = set()
117+
for app_obj in apps:
118+
for dl in app_obj.patches_dl_list:
119+
if dl.startswith("https://github.com/"):
120+
patches_dl_set.add(dl)
121+
122+
metadata_set: set[GithubSourceMetadata] = set()
123+
for dl in patches_dl_set:
124+
metadata_set.add(_fetch_metadata(dl, config.personal_access_token))
125+
126+
changelog_doc = ""
127+
changelog_doc_file = "patch-updates.md"
128+
sorted_metadata_list = GithubSourceMetadata.sort_by_latest_release(metadata_set)
129+
if sorted_metadata_list:
130+
logger.info(f"Writing patch updates changelog to {changelog_doc_file}.")
131+
for app_data in sorted_metadata_list:
132+
changelog_doc += format_changelog(app_data)
133+
with Path(changelog_doc_file).open("w", encoding="utf_8") as file1:
134+
file1.write(changelog_doc)
135+
136+
85137
check_if_build_is_required()

src/metadata/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Represents metadata of downloaded resources."""

src/metadata/github.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Github Source Metadata."""
2+
3+
from collections.abc import Iterable
4+
from dataclasses import dataclass
5+
from datetime import UTC, datetime
6+
from typing import Self
7+
8+
9+
@dataclass(unsafe_hash=True)
10+
class GithubSourceMetadata:
11+
"""Represents github release tag metadata."""
12+
13+
name: str
14+
tag: str
15+
body: str
16+
html_url: str
17+
published_at: datetime
18+
19+
@classmethod
20+
def from_json(cls: type[Self], response: dict[str, str]) -> Self:
21+
"""Create a GithubSourceMetadata instance from a GitHub release JSON response.
22+
23+
Parameters
24+
----------
25+
response : dict[str, str]
26+
JSON response for a GitHub release as returned by the API; expected to
27+
contain the keys 'html_url', 'tag_name', 'body' and 'published_at'.
28+
29+
Returns
30+
-------
31+
GithubSourceMetadata
32+
A new instance populated from the response.
33+
34+
Raises
35+
------
36+
KeyError
37+
If required keys are missing in the response.
38+
"""
39+
name = "/".join(response["html_url"].split("/")[3:5])
40+
tag = response["tag_name"]
41+
body = response["body"]
42+
html_url = response["html_url"]
43+
published_at = datetime.strptime(response["published_at"], "%Y-%m-%dT%H:%M:%SZ").astimezone(UTC)
44+
return cls(name, tag, body, html_url, published_at)
45+
46+
def get_release_date(self) -> str:
47+
"""Return the release date as a formatted UTC string.
48+
49+
Returns
50+
-------
51+
str
52+
The published date formatted as "Month DD, YYYY, HH:MM:SS UTC".
53+
"""
54+
return self.published_at.strftime("%B %d, %Y, %H:%M:%S UTC")
55+
56+
@staticmethod
57+
def sort_by_latest_release(items: Iterable["GithubSourceMetadata"]) -> list["GithubSourceMetadata"]:
58+
"""Sort by latest release first."""
59+
return sorted(items, key=lambda m: m.published_at, reverse=True)

src/utils.py

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Utilities."""
22

3-
import inspect
43
import json
54
import re
65
import subprocess
@@ -28,6 +27,7 @@
2827

2928
from src.downloader.sources import APK_MIRROR_APK_CHECK
3029
from src.exceptions import ScrapingError
30+
from src.metadata.github import GithubSourceMetadata
3131

3232
default_build = [
3333
"youtube",
@@ -57,7 +57,7 @@
5757

5858
updates_file = "updates.json"
5959
updates_file_url = "https://raw.githubusercontent.com/{github_repository}/{branch_name}/{updates_file}"
60-
changelogs: dict[str, dict[str, str]] = {}
60+
changelogs: dict[str, GithubSourceMetadata] = {}
6161
time_zone = "Asia/Kolkata"
6262
app_version_key = "app_version"
6363
patches_versions_key = "patches_versions"
@@ -97,60 +97,49 @@ def update_changelog(name: str, response: dict[str, str]) -> None:
9797
in the dictionary represent the type of change (e.g., "bug fix", "feature", "documentation"), and
9898
the values represent the specific changes made for each type.
9999
"""
100-
app_change_log = format_changelog(name, response)
101-
changelogs[name] = app_change_log
100+
source_metadata = GithubSourceMetadata.from_json(response)
101+
changelogs[name] = source_metadata
102102

103103

104-
def format_changelog(name: str, response: dict[str, str]) -> dict[str, str]:
104+
def format_changelog(metadata: GithubSourceMetadata) -> str:
105105
"""The `format_changelog` returns formatted changelog string.
106106
107107
Parameters
108108
----------
109-
name : str
110-
The `name` parameter is a string that represents the name of the changelog. It is used to create a
111-
collapsible section in the formatted changelog.
112-
response : Dict[str, str]
113-
The `response` parameter is a dictionary that contains information about a release. It has the
114-
following keys:
109+
metadata : GithubSourceMetadata
110+
Represents the release metadata with sufficient info.
115111
116112
Returns
117113
-------
118-
a formatted changelog as a dict.
114+
a formatted changelog as str
119115
"""
120-
final_name = f"[{name}]({response['html_url']})"
121-
return {
122-
"ResourceName": final_name,
123-
"Version": response["tag_name"],
124-
"Changelog": response["body"],
125-
"PublishedOn": response["published_at"],
126-
}
116+
content = (
117+
f"# {metadata.name}\n\n"
118+
f"***Release Version: [{metadata.tag}]({metadata.html_url})*** \n"
119+
f"***Release Date: {metadata.get_release_date()}*** \n"
120+
f"<details>\n<summary><b><i>Changelog:</i></b></summary>\n\n{metadata.body}</details>\n\n"
121+
)
122+
return f"{content}"
127123

128124

129125
def write_changelog_to_file(updates_info: dict[str, Any]) -> None:
130126
"""The function `write_changelog_to_file` writes a given changelog json to a file."""
131-
markdown_table = inspect.cleandoc(
132-
"""
133-
| Resource Name | Version | Changelog | Published On | Build By|
134-
|---------------|---------|-----------|--------------|---------|
135-
""",
136-
)
137-
for app_data in changelogs.values():
138-
name_link = app_data["ResourceName"]
139-
version = app_data["Version"]
140-
changelog = app_data["Changelog"]
141-
published_at = app_data["PublishedOn"]
142-
built_by = get_parent_repo()
143-
144-
# Clean up changelog for markdown
145-
changelog = changelog.replace("\r\n", "<br>")
146-
changelog = changelog.replace("\n", "<br>")
147-
changelog = changelog.replace("|", "\\|")
148-
149-
# Add row to the Markdown table string
150-
markdown_table += f"\n| {name_link} | {version} | {changelog} | {published_at} | {built_by} |"
127+
changelog_doc = ""
128+
metadata_list = GithubSourceMetadata.sort_by_latest_release(changelogs.values())
129+
for data in metadata_list:
130+
changelog_doc += format_changelog(data)
151131
with Path(changelog_file).open("w", encoding="utf_8") as file1:
152-
file1.write(markdown_table)
153-
Path(changelog_json_file).write_text(json.dumps(changelogs, indent=4) + "\n")
132+
file1.write(changelog_doc)
133+
134+
def encoder_default(obj: Any) -> Any:
135+
if isinstance(obj, datetime):
136+
return obj.isoformat()
137+
if hasattr(obj, "__dict__"):
138+
return obj.__dict__
139+
msg = f"Object of type {type(obj).__name__} is not JSON serializable"
140+
raise TypeError(msg)
141+
142+
Path(changelog_json_file).write_text(json.dumps(changelogs, default=encoder_default, indent=4) + "\n")
154143
Path(updates_file).write_text(json.dumps(updates_info, indent=4, default=str) + "\n")
155144

156145

0 commit comments

Comments
 (0)