Add markdown and emoji support, add some more information

This commit is contained in:
Midgard 2024-11-23 17:36:10 +01:00
parent acf86c3e6a
commit e200c72290
Signed by: midgard
GPG key ID: 511C112F1331BBB4
8 changed files with 170 additions and 15 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
mattermost_channels.html
*_credentials.json

1
builtin_emoji.json Normal file

File diff suppressed because one or more lines are too long

30
emoji.py Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
import sys
import os
import json
import re
mm_server = os.environ["MM_SERVER"]
with open(sys.argv[1]) as fh:
custom_emoji = json.load(fh)
with open("./builtin_emoji.json") as fh:
builtin_emoji = json.load(fh)
def translate_emoji(match_obj):
emoji_name = match_obj.group(1)
for emoji in builtin_emoji:
if emoji_name in emoji["aliases"]:
return "".join(
chr(int(unicode_codepoint, 16))
for unicode_codepoint in emoji["filename"].split("-")
)
for emoji in custom_emoji:
if emoji["name"] == emoji_name:
return f"<img src='/emoji_proxy/{mm_server}/{emoji['id']}' alt=':{emoji_name}:' class='emoji' />"
return f":{emoji_name}:"
# From https://mattermost.example.org/<team_name>/emoji/add:
# Specify an emoji name that's up to 64 characters. It can contain lowercase letters, numbers, and the symbols '-', '+' and '_'.
print(re.sub(r'(?!<[^>]*):([a-z0-9_+-]{1,64}):', translate_emoji, sys.stdin.read()))

95
emoji_proxy/emoji_proxy.py Executable file
View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
import os
import re
import json
import urllib.request
import urllib.error
from wsgiref.simple_server import make_server, demo_app
software_name = "emoji_proxy"
os.chdir(os.path.dirname(__file__))
with open("./mattermost_credentials.json") as fh:
credentials = json.load(fh)
HTTP_OK = "200 OK"
HTTP_NOT_FOUND = "404 Not Found"
HTTP_INTERNAL_SERVER_ERROR = "500 Internal Server Error"
class HTTPError(Exception):
def __init__(self, status, message):
self.status = status
self.message = message
def __repr__(self):
return f"<HTTPError {self.status}>"
default_headers = [
("Server", software_name),
]
def app(environ, start_response):
path = environ["PATH_INFO"]
m = re.fullmatch(r'/emoji_proxy/(.+)/([a-z0-9]{26})', path)
if not m:
raise HTTPError(HTTP_NOT_FOUND, "Path not recognized, expecting /emoji_proxy/<mattermost_server>/<emoji_id>\n<mattermost_server> may include slashes to specify a path, though this is not typical.")
server = m.group(1)
emoji_id = m.group(2)
if server not in credentials:
raise HTTPError(HTTP_NOT_FOUND, f"Server {server} not supported")
access_token = credentials[server]["access_token"]
req = urllib.request.Request(f"https://{server}/api/v4/emoji/{emoji_id}/image")
req.add_header("Referer", "https://{server}/")
req.add_header("User-Agent", software_name)
req.add_header("Authorization", "Bearer " + access_token)
try:
with urllib.request.urlopen(req) as upstream_response:
start_response(HTTP_OK, default_headers + [
("Cache-Control", "max-age=86400, public"),
] + [
(header_name, upstream_response.headers[header_name])
for header_name in upstream_response.headers
if header_name not in {"Server", "Connection", "Accept-Ranges", "Transfer-Encoding"} and not header_name.startswith("X-")
])
chunk = upstream_response.read1()
while chunk != b"":
yield chunk
chunk = upstream_response.read1()
except urllib.error.HTTPError as e:
if e.status == 404:
raise HTTPError(HTTP_NOT_FOUND, f"Emoji not found")
raise HTTPError(f"{e.status} {e.reason}", [b"The Mattermost server reported an error:\n\n", e.read()])
except urllib.error.URLError as e:
raise e
def app_wrap(environ, start_response):
try:
yield from app(environ, start_response)
except HTTPError as e:
start_response(e.status, default_headers + [
("Content-Type", "text/plain; charset=utf-8"),
])
if isinstance(e.message, str):
yield from [e.message.encode("utf-8")]
else:
yield from e.message
with make_server("", 8000, app_wrap) as httpd:
print("Serving HTTP on port 8000...")
# Respond to requests until process is killed
httpd.serve_forever()

View file

@ -0,0 +1,3 @@
{
"mattermost.example.org": {"access_token": "1ab2cd3ef4gh5ij6kl7mn8op9q"}
}

View file

@ -1,6 +1,6 @@
<footer>
<div>Last updated $lastupdated</div>
<div>Created by Midgard</div>
<div>Created with love by Zeus WPI</div>
</footer>
</body>
</html>

47
gen.sh
View file

@ -1,12 +1,16 @@
#!/bin/sh
set -euo pipefail
export MM_SERVER=zeus.mattermost.gent
mattermost_name="Zeus Mattermost"
team_name="zeus"
dir="$(dirname "$0")"
out=mattermost_channels.html
substitute_vars() {
sed 's/$lastupdated/'"$(date '+%Y-%m-%d %H:%M')"/g
}
custom_emoji_file="$(mktemp --tmpdir custom_emoji.XXXXXXXXXX.json)"
mmcli listcustomemoji | jq -s > "$custom_emoji_file"
jqo() {
json="$1"
@ -18,22 +22,43 @@ htmlescape() {
sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&#39;/g'
}
emoji() {
env --chdir "$dir" \
python3 "$dir/emoji.py" "$custom_emoji_file"
}
markdown() {
htmlescape | \
{ pandoc --from=markdown --to=html || printf "Markdown conversion failed\n" >&2; } | \
emoji
}
format_timestamp() {
format_str="$1"
xargs -Ixxx date --date "@xxx" "+$format_str"
}
substitute_vars < header.html > "$out"
channels="$(mmcli ls "$team_name" | jq -cs 'sort_by(- .last_post_at) | .[]')"
channel_count="$(jqo "$channels" -s 'length')"
mmcli ls zeus | \
jq -cs 'sort_by(- .last_post_at) | .[]' | \
while IFS="" read -r chan || [ -n "$chan" ]; do
substitute_vars() {
sed '
s/$lastupdated/'"$(date '+%Y-%m-%d %H:%M')"'/g
s/$mattermost_name/'"$mattermost_name"'/g
s/$team_name/'"$team_name"'/g
s/$channel_count/'"$channel_count"'/g
'
}
substitute_vars < "$dir"/header.html > "$out"
printf '%s\n' "$channels" | while IFS="" read -r chan || [ -n "$chan" ]; do
if [ "$(jqo "$chan" '.type')" != O ]; then continue; fi
name="$(jqo "$chan" '.name' | htmlescape)"
display_name="$(jqo "$chan" '.display_name' | htmlescape)"
purpose="$(jqo "$chan" '.purpose' | htmlescape)"
header="$(jqo "$chan" '.header' | htmlescape)"
purpose="$(jqo "$chan" '.purpose' | markdown)"
header="$(jqo "$chan" '.header' | markdown)"
create_at="$(jqo "$chan" '.create_at / 1000' | format_timestamp '%Y-%m-%d')"
last_post_at="$(jqo "$chan" '.last_post_at / 1000' | format_timestamp '%Y-%m-%d %H:%M')"
total_msg_count="$(jqo "$chan" '.total_msg_count')"
@ -61,4 +86,4 @@ while IFS="" read -r chan || [ -n "$chan" ]; do
HERE
done
substitute_vars < footer.html >> "$out"
substitute_vars < "$dir"/footer.html >> "$out"

View file

@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Zeus Mattermost channels</title>
<title>$mattermost_name channels</title>
</head>
<body>
<h1>Zeus Mattermost channels</h1>
<p>As of $lastupdated</p>
<h1>$mattermost_name channels</h1>
<p>A list of all $channel_count public channels in the team $team_name on $mattermost_name, as of $lastupdated</p>