Initial repo setup

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2022-10-08 01:46:49 +02:00
parent 04cf00d63d
commit 1b58f13dc1
Signed by: luzifer
GPG key ID: D91C3E91E4CAD6F5
9 changed files with 334 additions and 0 deletions

67
.github/workflows/publish-index.yml vendored Normal file
View file

@ -0,0 +1,67 @@
---
name: Deploy Rules Index
on:
push:
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build-and-publish:
container:
image: luzifer/archlinux
defaults:
run:
shell: bash
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Install required packages
run: |
pacman -Syy --noconfirm \
git \
python \
python-pip \
make \
tar \
yamllint \
yq
- name: Checkout
uses: actions/checkout@v3
- name: Lint rules files
run: make rules_lint
- name: Build rules index
run: make index
- name: Setup Pages
if: github.ref_name == 'main'
uses: actions/configure-pages@v2
- name: Upload artifact
if: github.ref_name == 'main'
uses: actions/upload-pages-artifact@v1
- name: Deploy to GitHub Pages
id: deployment
if: github.ref_name == 'main'
uses: actions/deploy-pages@v1
...

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
_site
.venv

18
Makefile Normal file
View file

@ -0,0 +1,18 @@
OUTDIR :=_site
export RULE_BASE := https://github.com/Luzifer/twitch-bot-rules/raw/main/
default:
rules_lint:
bash ci/lint.sh
index: .venv
rm -rf $(OUTDIR)
mkdir $(OUTDIR)
./.venv/bin/python ci/indexer.py \
$(OUTDIR)/index.html \
rules
.venv:
python -m venv .venv
./.venv/bin/pip install -r ci/requirements.yml

68
ci/indexer.py Normal file
View file

@ -0,0 +1,68 @@
import jinja2
import os
import re
import sys
import yaml
FIELD_REGEX = r"^# @([^ ]+)\s+(.*)$"
def get_doc_field(content, field, default=None):
matches = re.finditer(FIELD_REGEX, content, re.MULTILINE)
fields = {}
for matchNum, match in enumerate(matches, start=1):
fields[match.groups()[0]] = match.groups()[1]
return fields[field] if field in fields else default
def get_rules_index(rules_dir):
rules = []
for file in os.listdir(rules_dir):
fullpath = '/'.join([rules_dir, file])
with open(fullpath, 'r') as rulefile:
content = rulefile.read()
rule = yaml.load(content, Loader=yaml.SafeLoader)
rules.append({
"actions": [x["type"] for x in rule["actions"]],
"author": get_doc_field(content, "author"),
"min_bot_ver": get_doc_field(content, "minBotVersion", "v3.x"),
"description": rule["description"],
"file": fullpath,
"shortened_id": rule["uuid"].split('-')[0],
"version": get_doc_field(content, "version", "v0"),
})
return sorted(rules, key=lambda x: x["description"])
def main(args):
outfile = args[1]
rules_dir = args[2]
rules = get_rules_index(rules_dir)
render_index(outfile, rules)
return 0
def render_index(outfile, rules):
with open('index.tpl.html', 'r') as template_source:
env = jinja2.Environment()
tpl = env.from_string(template_source.read())
with open(outfile, 'w') as output:
output.write(tpl.render(
rule_base=os.environ['RULE_BASE'] if 'RULE_BASE' in os.environ else '/',
rules=rules,
))
if __name__ == '__main__':
sys.exit(main(sys.argv))

44
ci/lint.sh Normal file
View file

@ -0,0 +1,44 @@
set -euo pipefail
exit_code=0
function error() {
log E "$@"
exit_code=1
}
function info() {
log I "$@"
}
function log() {
local level=$1
shift
echo "[$(date +%H:%M:%S)][$level] $@" >&2
}
required_tags=(
author
minBotVersion
version
)
for rule_file in rules/*.yml; do
info "Linting rules file ${rule_file}"
info "+++ Checking with YAMLlint..."
yamllint -c ci/yamllint.yml ${rule_file}
info "+++ Checking required tags..."
for tag in "${required_tags[@]}"; do
grep -Eq "^# @${tag} .+$" ${rule_file} || error "Missing required tag: ${tag}"
done
info "+++ Checking subscription URL..."
exp_url="${RULE_BASE}${rule_file}"
sub_url="$(yq -r '.subscribe_from' ${rule_file})"
[[ $sub_url == $exp_url ]] || error "Wrong subscription URL: expected ${exp_url}"
done
exit $exit_code

3
ci/requirements.yml Normal file
View file

@ -0,0 +1,3 @@
Jinja2==3.1.2
MarkupSafe==2.1.1
PyYAML==6.0

51
ci/yamllint.yml Normal file
View file

@ -0,0 +1,51 @@
---
# See documentation for configuration / rules here:
# https://yamllint.readthedocs.io/en/stable/configuration.html
#
# - Put this file in ~/.config/yamllint/config to apply the rules
# - Install yamllint using `pip install yamllint`
# - Execute the check using `yamllint <path to yaml> [<path to yaml>]`
extends: default
rules:
braces:
level: warning
min-spaces-inside: 1
max-spaces-inside: 1
min-spaces-inside-empty: 0
max-spaces-inside-empty: 1
brackets:
level: warning
colons:
level: warning
commas:
level: warning
document-end:
level: warning
present: true
document-start:
level: warning
present: true
empty-lines:
level: warning
hyphens:
level: warning
indentation:
level: warning
spaces: 2
indent-sequences: true
check-multi-line-strings: true
key-duplicates:
level: error
line-length:
level: warning
allow-non-breakable-inline-mappings: true
new-line-at-end-of-file:
level: warning
trailing-spaces:
level: warning
...
# vim: set ft=yaml:

60
index.tpl.html Normal file
View file

@ -0,0 +1,60 @@
<html>
<head>
<title>Twitch-Bot Rules Archive</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" integrity="sha256-KTPJY0ik6ufLv48oDKCYFYaptcCX75UrmWytfSjy+tA=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.2.1/dist/darkly/bootstrap.min.css" integrity="sha256-7mvDKMLoBU3B5Sj1sW2i33B9hPz1VSE9whs1QDJj01I=" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row mt-3">
<div class="col">
<table class="table table-hover table-striped">
<tr>
<td>Rule</td>
<td>Description</td>
<td>Req. Bot<br>Version</td>
</tr>
{% for rule in rules %}
<tr>
<td>
<a
data-bs-title="Click to Copy Subscribe-URL"
data-bs-toggle="tooltip"
href="{{ rule_base }}{{ rule.file }}"
onclick="navigator.clipboard.writeText('{{ rule_base }}{{ rule.file }}'); return false;"
>
{{ rule.shortened_id }}&hellip;
</a>
<p class="text-muted">
<small><b>by</b> {{ rule.author }}</small><br>
<small><b>ver</b> {{ rule.version}}</small>
</p>
</td>
<td>
<p>{{ rule.description }}</p>
<p>
{% for action in rule.actions %}
<span class="badge bg-secondary">{{ action }}</span>
{% endfor %}
</p>
</td>
<td>{{ rule.min_bot_ver }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha256-qFsv4wd3fI60fwah7sOZ/L3f6D0lL9IC0+E1gFH88n0=" crossorigin="anonymous"></script>
<script>
window.addEventListener('load', () => {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
})
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
---
# @author Luzifer
# @minBotVersion v3.0
# @version v1
uuid: a136bb59-02f9-4a2a-88a2-be719655a7e2
subscribe_from: https://github.com/Luzifer/twitch-bot-rules/raw/main/rules/a136bb59-02f9-4a2a-88a2-be719655a7e2_wanna-become-famous.yml
description: 'Spam: "Wanna become famous? Buy followers, primes and viewers"'
actions:
- type: delete
- type: ban
attributes:
reason: Chat-Spam "{{ group 1 }}"
match_message: (?i)(.*(?:Buy|Best)?(?:\s+(?:followers|primes|viewers)\s*(?:,|and|)){2,}\s+on.*)
disable_on_template: '{{ not (botHasBadge "moderator") }}'
...