Add markdown and emoji support, add some more information
This commit is contained in:
parent
acf86c3e6a
commit
e200c72290
8 changed files with 170 additions and 15 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
mattermost_channels.html
|
||||
*_credentials.json
|
||||
|
|
1
builtin_emoji.json
Normal file
1
builtin_emoji.json
Normal file
File diff suppressed because one or more lines are too long
30
emoji.py
Executable file
30
emoji.py
Executable 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
95
emoji_proxy/emoji_proxy.py
Executable 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()
|
3
emoji_proxy/mattermost_credentials.json.sample
Normal file
3
emoji_proxy/mattermost_credentials.json.sample
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"mattermost.example.org": {"access_token": "1ab2cd3ef4gh5ij6kl7mn8op9q"}
|
||||
}
|
|
@ -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
47
gen.sh
|
@ -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/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue