forked from Mirroring/github-release-notifier
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a43d462bf1 | |||
| 2d48a37ccb | |||
| 68fd6e78d9 | |||
| e57d9cf656 | |||
| 8dba474e23 | |||
| 07dccee235 | |||
| fd7e6b73d1 | |||
| 0ee46a5d9e | |||
| 9709947c28 | |||
| 89e88c8a1b | |||
| 148c647ac7 | |||
| 02fad7767c |
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Black format commit
|
||||
89e88c8a1b26e2c6ca242d0bba6cdba8da35c3ae
|
||||
28
.pre-commit-config.yaml
Normal file
28
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-toml
|
||||
- id: check-added-large-files
|
||||
- id: check-merge-conflict
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.11.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.1.1
|
||||
hooks:
|
||||
- id: autoflake
|
||||
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
|
||||
rev: v2.11.0
|
||||
hooks:
|
||||
- id: pretty-format-yaml
|
||||
args: [--autofix, --indent, '2', --offset, '2']
|
||||
72
Justfile
72
Justfile
@@ -1,17 +1,67 @@
|
||||
# https://github.com/casey/just
|
||||
|
||||
up: build
|
||||
docker compose up -d
|
||||
docker compose logs
|
||||
venv := "./venv"
|
||||
pip := venv / "bin/pip"
|
||||
python := venv / "bin/python"
|
||||
|
||||
build:
|
||||
docker compose build
|
||||
last_commit_sha1 := `git rev-parse --short HEAD`
|
||||
remote_image_name := "gitea.gdemontauzan.fr/maxenceg2m/github-release-notifier"
|
||||
remote_build_image := remote_image_name + ":" + last_commit_sha1
|
||||
|
||||
rebuild: down
|
||||
docker compose build
|
||||
# Run the script
|
||||
run: _ensure_venv_is_ok
|
||||
{{ python }} notifier.py
|
||||
|
||||
down:
|
||||
docker compose down
|
||||
# Init python virtual env
|
||||
init:
|
||||
python3 -m venv venv
|
||||
{{ pip }} install --requirement requirements.txt
|
||||
sha256sum requirements.txt > {{ venv }}/requirements.sha
|
||||
|
||||
force-build:
|
||||
docker compose build --no-cache
|
||||
# Inspiration: https://github.com/behave/behave/blob/afb6b6716cd0f3e028829416475312db804a6aa9/justfile
|
||||
_ensure_venv_is_ok:
|
||||
#!/usr/bin/env python3
|
||||
from subprocess import run
|
||||
from os import path
|
||||
if run("sha256sum -c {{ venv }}/requirements.sha", shell=True).returncode != 0:
|
||||
run("just init", shell=True)
|
||||
|
||||
# Clean workspace - remove venv - and init
|
||||
reinit: hclean init
|
||||
|
||||
# Remove virtual env (venv)
|
||||
hclean:
|
||||
rm -fr venv
|
||||
|
||||
# Run docker compose then show logs
|
||||
dup: dbuild
|
||||
docker compose up -d
|
||||
docker compose logs
|
||||
|
||||
# Build with docker compose
|
||||
dbuild:
|
||||
docker compose build
|
||||
|
||||
# Down docker compose then build
|
||||
drebuild: ddown dbuild
|
||||
|
||||
# Down docker compose
|
||||
ddown:
|
||||
docker compose down
|
||||
|
||||
# Docker build without cache
|
||||
dforce-build:
|
||||
docker compose build --no-cache
|
||||
|
||||
# Push a working images on registry, tagged with commit-sha1
|
||||
dpush: dbuild
|
||||
docker tag github-release-notifier {{ remote_build_image }}
|
||||
docker push {{ remote_build_image }}
|
||||
echo "To push a tagged version, do 'just release <version>'"
|
||||
|
||||
# Use just a number! Without 'v'! Release a version - create a tagged images, push it and create git tag.
|
||||
release version: dbuild
|
||||
docker tag github-release-notifier {{ remote_image_name }}:{{ version }}
|
||||
docker push {{ remote_image_name }}:{{ version }}
|
||||
git tag -a v{{ version }} -m ""
|
||||
git push --tags
|
||||
|
||||
@@ -47,5 +47,3 @@ Hey boy, what is the `pit.db` file?
|
||||
Oh, just for fun, and because I love this project, I use [pit by michaeldv](https://github.com/michaeldv/pit) to follow my task etc.
|
||||
|
||||
It makes me think I should push my python version of this project on occasion when I will take the time to do...
|
||||
|
||||
|
||||
|
||||
2
conf.ini
2
conf.ini
@@ -1,7 +1,7 @@
|
||||
[config]
|
||||
github_api_url = https://api.github.com/repos/
|
||||
smtp_port = 1025
|
||||
smtp_server = mailhog
|
||||
smtp_server = mailpit
|
||||
sender_email = sender@host.eu
|
||||
receiver_email = receiver@anotherhost.eu
|
||||
|
||||
|
||||
@@ -3,13 +3,22 @@ version: '3'
|
||||
services:
|
||||
notifier:
|
||||
build: .
|
||||
image: github-release-notifier:1
|
||||
image: github-release-notifier
|
||||
container_name: github-release-notifier
|
||||
volumes:
|
||||
- ./conf.ini:/app/conf.ini
|
||||
|
||||
mailhog:
|
||||
image: mailhog/mailhog:v1.0.1
|
||||
mailpit:
|
||||
image: axllent/mailpit
|
||||
ports:
|
||||
- "8025:8025"
|
||||
- "1025:1025"
|
||||
|
||||
ofelia:
|
||||
image: mcuadros/ofelia:latest
|
||||
depends_on:
|
||||
- notifier
|
||||
command: daemon --config=/opt/config.ini
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./ofelia.ini:/opt/config.ini
|
||||
|
||||
165
notifier.py
165
notifier.py
@@ -1,113 +1,138 @@
|
||||
import os
|
||||
from configparser import ConfigParser
|
||||
import json
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
import configparser
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
import requests
|
||||
|
||||
SMTP_PORT = 0
|
||||
SMTP_SERVER = 'null'
|
||||
SENDER_EMAIL = 'a@b.c'
|
||||
RECEIVER_EMAIL = 'd@e.f'
|
||||
|
||||
class EnvInjection(configparser.Interpolation):
|
||||
"""
|
||||
Derived interpolation to take env variable before file variable.
|
||||
Permit to keep the ini file for local / traditionnal use
|
||||
And use env variable to overload configuration in a Docker usage.
|
||||
"""
|
||||
|
||||
def before_get(self, parser, section, option, value, defaults):
|
||||
file_value = super().before_get(parser, section, option, value, defaults)
|
||||
if section != parser.default_section:
|
||||
return file_value
|
||||
|
||||
env_value = os.getenv(option.upper())
|
||||
return env_value if env_value else file_value
|
||||
|
||||
|
||||
def main():
|
||||
global SMTP_PORT, SMTP_SERVER, SENDER_EMAIL, RECEIVER_EMAIL
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
conf_file = os.path.join(script_dir, 'conf.ini')
|
||||
template_file = os.path.join(script_dir, 'template.html')
|
||||
conf_file = os.path.join(script_dir, "conf.ini")
|
||||
template_file = os.path.join(script_dir, "template.html")
|
||||
|
||||
parser = ConfigParser()
|
||||
parser = configparser.ConfigParser(
|
||||
default_section="config", interpolation=EnvInjection()
|
||||
)
|
||||
parser.read(conf_file)
|
||||
default_config = parser["config"]
|
||||
|
||||
SMTP_PORT = parser.get('config', 'smtp_port')
|
||||
SMTP_SERVER = parser.get('config', 'smtp_server')
|
||||
SENDER_EMAIL = parser.get('config', 'sender_email')
|
||||
RECEIVER_EMAIL = parser.get('config', 'receiver_email')
|
||||
try:
|
||||
projects = json.loads(parser.get("projects", "projects"))
|
||||
except json.decoder.JSONDecodeError as jse:
|
||||
print("ERROR: config file is not correctly JSON formatted!", end="\n\t")
|
||||
print(jse)
|
||||
sys.exit(1)
|
||||
|
||||
projects = json.loads(parser.get('projects', 'projects'))
|
||||
new_releases = []
|
||||
new_projects = []
|
||||
|
||||
if not parser.has_section('release'):
|
||||
parser.add_section('release')
|
||||
if not parser.has_section("release"):
|
||||
parser.add_section("release")
|
||||
|
||||
for project in projects:
|
||||
last_release = get_last_release(project)
|
||||
|
||||
if not parser.has_option('release', project):
|
||||
if not parser.has_option("release", project):
|
||||
new_projects.append(last_release)
|
||||
else:
|
||||
last_config_tag = parser.get('release', project)
|
||||
if last_config_tag != last_release['release_tag']:
|
||||
last_release['preview_tag'] = last_config_tag
|
||||
last_config_tag = parser.get("release", project)
|
||||
if last_config_tag != last_release["release_tag"]:
|
||||
last_release["previous_tag"] = last_config_tag
|
||||
new_releases.append(last_release)
|
||||
parser.set('release', project, last_release['release_tag'])
|
||||
parser.set("release", project, last_release["release_tag"])
|
||||
|
||||
if not new_releases and not new_projects:
|
||||
print('No new projets or new release detected. Bye!')
|
||||
print("No new projets or new release detected. Bye!")
|
||||
return
|
||||
|
||||
content = ""
|
||||
|
||||
for new_r in new_releases:
|
||||
content += """
|
||||
<li><a href="{}" target="_blank">{}</a>: new release <a href="{}" target="_blank">{}</a> available (old: {}).
|
||||
(published {})</li>
|
||||
""".format(
|
||||
new_r['release_url'],
|
||||
new_r['project_name'],
|
||||
new_r['release_url'],
|
||||
new_r['release_tag'],
|
||||
new_r['preview_tag'],
|
||||
convert_date(new_r['published_date']))
|
||||
content += f"""
|
||||
<li>{get_html_project_link(new_r)}
|
||||
: new release
|
||||
{get_html_release_link(new_r)}
|
||||
available (old: {new_r["previous_tag"]}).
|
||||
(published {convert_date(new_r["published_date"])})</li>"""
|
||||
|
||||
for new_p in new_projects:
|
||||
content += """
|
||||
<li><a href="{}" target="_blank">{}</a> was added to your configuration. Last release: <a href="{}" target="_blank">{}</a>
|
||||
(published {})</li>""".format(
|
||||
new_p['release_url'],
|
||||
new_p['project_name'],
|
||||
new_p['release_url'],
|
||||
new_p['release_tag'],
|
||||
convert_date(new_p['published_date']))
|
||||
content += f"""
|
||||
<li>{get_html_project_link(new_p)}
|
||||
was added to your configuration.
|
||||
Last release:
|
||||
{get_html_release_link(new_p)}
|
||||
(published {convert_date(new_p["published_date"])})</li>"""
|
||||
|
||||
# print(content)
|
||||
|
||||
with open(template_file, 'r') as f_template:
|
||||
with open(template_file, "r", encoding="utf-8") as f_template:
|
||||
template = f_template.read()
|
||||
|
||||
send_mail(template.replace('{{content}}', content))
|
||||
send_mail(template.replace("{{content}}", content), default_config)
|
||||
|
||||
with open('conf.ini', 'w') as configfile:
|
||||
with open("conf.ini", "w", encoding="utf-8") as configfile:
|
||||
parser.write(configfile)
|
||||
|
||||
|
||||
def get_last_release(project):
|
||||
url = 'https://api.github.com/repos/{}/releases/latest'.format(project)
|
||||
result = requests.get(url)
|
||||
url = f"https://api.github.com/repos/{project}/releases/latest"
|
||||
result = requests.get(url, timeout=10)
|
||||
|
||||
print(project)
|
||||
print(url)
|
||||
release = result.json()
|
||||
release_tag = release['tag_name']
|
||||
published_date = release['published_at']
|
||||
release_tag = release["tag_name"]
|
||||
published_date = release["published_at"]
|
||||
# body = release['body']
|
||||
release_url = release['html_url']
|
||||
release_url = release["html_url"]
|
||||
|
||||
return {'release_tag': release_tag,
|
||||
'published_date': published_date,
|
||||
# 'body': body,
|
||||
'project_name': project,
|
||||
'release_url': release_url}
|
||||
return {
|
||||
"release_tag": release_tag,
|
||||
"published_date": published_date,
|
||||
# 'body': body,
|
||||
"project_name": project,
|
||||
"release_url": release_url,
|
||||
}
|
||||
|
||||
|
||||
def get_html_project_link(el):
|
||||
project_url = f'https://github.com/{el["project_name"]}'
|
||||
return f'<a href="{project_url}" target="_blank">{el["project_name"]}</a>'
|
||||
|
||||
|
||||
def get_html_release_link(el):
|
||||
return f'<a href="{el["release_url"]}" target="_blank">{el["release_tag"]}</a>'
|
||||
|
||||
|
||||
def send_mail(content, config):
|
||||
smtp_port = config.get("smtp_port")
|
||||
smtp_server = config.get("smtp_server")
|
||||
sender_email = config.get("sender_email")
|
||||
receiver_email = config.get("receiver_email")
|
||||
|
||||
def send_mail(content):
|
||||
message = MIMEMultipart("alternative")
|
||||
message["Subject"] = "New Github releases"
|
||||
message["From"] = SENDER_EMAIL
|
||||
message["To"] = RECEIVER_EMAIL
|
||||
message["From"] = sender_email
|
||||
message["To"] = receiver_email
|
||||
|
||||
# part1 = MIMEText(text, "plain")
|
||||
part2 = MIMEText(content, "html")
|
||||
@@ -115,11 +140,13 @@ def send_mail(content):
|
||||
# message.attach(part1)
|
||||
message.attach(part2)
|
||||
|
||||
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
|
||||
server.sendmail(SENDER_EMAIL, RECEIVER_EMAIL, message.as_string())
|
||||
with smtplib.SMTP(smtp_server, smtp_port) as server:
|
||||
server.sendmail(sender_email, receiver_email, message.as_string())
|
||||
|
||||
|
||||
def convert_date(date: str, dest_format="%d %b %Y at %H:%M") -> str:
|
||||
return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ").strftime(dest_format)
|
||||
|
||||
def convert_date(date: str, format='%d %b %Y at %H:%M') -> str:
|
||||
return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ").strftime(format)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
9
ofelia.ini
Normal file
9
ofelia.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
[global]
|
||||
smtp-host = mailpit
|
||||
smtp-port = 1025
|
||||
email-to = max@ence.fr
|
||||
email-from = ofelia@container.sh
|
||||
|
||||
[job-run "notifier"]
|
||||
schedule = @every 30s
|
||||
container = github-release-notifier
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
requests
|
||||
pre-commit
|
||||
black
|
||||
@@ -1,66 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
|
||||
<style>
|
||||
button,hr,input{overflow:visible}audio,canvas,progress,video{display:inline-block}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}a
|
||||
udio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{color:inherit;display:table;max-width:100%;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=se
|
||||
arch]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none}
|
||||
body {
|
||||
margin: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale; }
|
||||
|
||||
.wysiwyg-content main > :first-child {
|
||||
padding-top: 0 !important;
|
||||
margin-top: 0 !important; }
|
||||
.wysiwyg-content main > :last-child {
|
||||
padding-bottom: 0 !important;
|
||||
margin-bottom: 0 !important; }
|
||||
|
||||
.wysiwyg-content img {
|
||||
max-width: 100%; }
|
||||
|
||||
.wysiwyg-content iframe {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
border: 0; }
|
||||
|
||||
.wysiwyg-content .emoji .emoji-text {
|
||||
font-size: 0; }
|
||||
|
||||
|
||||
/* https://github.com/sindresorhus/github-markdown-css/blob/gh-pages/github-markdown.css */
|
||||
/* From GitHub markdown view style */
|
||||
body {
|
||||
max-width: 980px; }
|
||||
main {
|
||||
padding: 45px; }
|
||||
|
||||
|
||||
/* https://github.com/sindresorhus/github-markdown-css/blob/gh-pages/github-markdown.css */
|
||||
|
||||
.markdown-body hr::after,.markdown-body:after{clear:both}.markdown-body hr::after,.markdown-body hr::before,.markdown-body:after,.markdown-body:before{display:table;content:""}@font-face{font-family:octicons-link;src:url(data:font/woff;charset=utf-8;base64,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) format('woff')}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;color:#333;font-family:"Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size
|
||||
:16px;line-height:1.6;word-wrap:break-word}.markdown-body a{background-color:transparent;-webkit-text-decoration-skip:objects;color:#4078c0;text-decoration:none}.markdown-body a:active,.markdown-body a:hover{outline-width:0;text-decoration:underline}.markdown-body strong{font-weight:bolder}.markdown-body h1{margin:.67em 0}.markdown-body img{border-style:none}.markdown-body svg:not(:root){overflow:hidden}.markdown-body hr{box-sizing:content-box}.markdown-body input{margin:0;overflow:visible;font:13px/1.4 Helvetica,arial,nimbussansl,liberationsans,freesans,clean,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"}.markdown-body [type=button]:-moz-focusring,.markdown-body [type=reset]:-moz-focusring,.markdown-body [type=submit]:-moz-focusring,.markdown-body button:-moz-focusring{outline:ButtonText dotted 1px}.markdown-body [type=checkbox]{box-sizing:border-box;padding:0}.mar
|
||||
kdown-body td,.markdown-body th{padding:0}.markdown-body h1,.markdown-body h2{padding-bottom:.3em;border-bottom:1px solid #eee}.markdown-body *{box-sizing:border-box}.markdown-body blockquote{margin:0}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}.markdown-body pre{font:12px Consolas,"Liberation Mono",Menlo,Courier,monospace;word-wrap:normal}.markdown-body .pl-0{padding-left:0!important}.markdown-body .pl-1{padding-left:3px!important}.markdown-body .pl-2{padding-left:6px!important}.markdown-body .pl-3{padding-left:12px!important}.markdown-body .pl-4{padding-left:24px!important}.markdown-body .pl-5{padding-left:36px!important}.m
|
||||
arkdown-body .pl-6{padding-left:48px!important}.markdown-body .form-select::-ms-expand{opacity:0}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .anchor{display:inline-block;padding-right:2px;margin-left:-18px}.markdown-body .anchor:focus{outline:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:1em;margin-bottom:16px;font-weight:700;line-height:1.4}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#000;vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.
|
||||
markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1{font-size:2.25em;line-height:1.2}.markdown-body h1 .anchor{line-height:1}.markdown-body h2{font-size:1.75em;line-height:1.225}.markdown-body h2 .anchor{line-height:1}.markdown-body h3{font-size:1.5em;line-height:1.43}.markdown-body h3 .anchor,.markdown-body h4 .anchor{line-height:1.2}.markdown-body h4{font-size:1.25em}.markdown-body h5 .anchor,.markdown-body h6 .anchor{line-height:1.1}.markdown-body h5{font-size:1em}.markdown-body h6{font-size:1em;color:#777}.markd
|
||||
own-body blockquote,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:16px}.markdown-body hr{overflow:hidden;background:#e7e7e7;height:4px;padding:0;margin:16px 0;border:0}.markdown-body ol,.markdown-body ul{padding-left:2em}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body blockquote{padding:0 15px;color:#777;border-left:4px solid #ddd}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body table{border-spacing:0;border-collapse:collapse;display:bl
|
||||
ock;width:100%;overflow:auto;word-break:normal;word-break:keep-all}.markdown-body table th{font-weight:700}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid #ddd}.markdown-body table tr{background-color:#fff;border-top:1px solid #ccc}.markdown-body table tr:nth-child(2n){background-color:#f8f8f8}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff}.markdown-body code{padding:.2em 0;margin:0;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px}.markdown-body code:after,.markdown-body code:before{letter-spacing:-.2em;content:"\00a0"}.markdown-body pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:0 0;border:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre,.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f
|
||||
7;border-radius:3px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body pre code{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown-body pre code:after,.markdown-body pre code:before{content:normal}.markdown-body .pl-c{color:#969896}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:#0086b3}.markdown-body .pl-e,.markdown-body .pl-en{color:#795da3}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:#333}.markdown-body .pl-ent{color:#63a35c}.markdown-body .pl-k{color:#a71d5d}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:#183691}.markdown-body .pl-v{color:#ed6a43}.markdown-body.pl-id{color:#b52a1d}.mark
|
||||
down-body .pl-ii{background-color:#b52a1d;color:#f8f8f8}.markdown-body .pl-sr .pl-cce{color:#63a35c;font-weight:700}.markdown-body .pl-ml{color:#693a17}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{color:#1d3e81;font-weight:700}.markdown-body .pl-mq{color:teal}.markdown-body .pl-mi{color:#333;font-style:italic}.markdown-body .pl-mb{color:#333;font-weight:700}.markdown-body .pl-md{background-color:#ffecec;color:#bd2c00}.markdown-body .pl-mi1{background-color:#eaffea;color:#55a532}.markdown-body .pl-mdr{color:#795da3;font-weight:700}.markdown-body .pl-mo{color:#1d3e81}.markdown-body kbd{display:inline-block;padding:3px 5px;font:11px Consolas,"Liberation Mono",Menlo,Courier,monospace;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:1px solid #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb}
|
||||
.markdown-body .full-commit .btn-outline:not(:disabled):hover{color:#4078c0;border:1px solid #4078c0}.markdown-body :checked+.radio-label{position:relative;z-index:1;border-color:#4078c0}.markdown-body .octicon{display:inline-block;vertical-align:text-top;fill:currentColor}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item+.task-list-item{margin-top:3px}.markdown-body .task-list-item input{margin:0 .2em .25em -1.6em;vertical-align:middle}.markdown-body hr{border-bottom-color:#eee}
|
||||
</style>
|
||||
</head>
|
||||
<body class="markdown-body wysiwyg-content">
|
||||
<main><h1 id="-some-new-release-on-github-project-available-">
|
||||
<img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" height="32">
|
||||
Some new release on Github project available!</h1>
|
||||
<p><ul>{{content}}</ul></p>
|
||||
</main>
|
||||
</body>
|
||||
<style>
|
||||
.markdown-body a,.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor {text-decoration: none}
|
||||
body {margin: auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;max-width: 980px;padding: 45px}
|
||||
.markdown-body {-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #333;font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-size: 16px;line-height: 1.6;word-wrap: break-word}
|
||||
.markdown-body a {background-color: transparent;-webkit-text-decoration-skip: objects;color: #4078c0}
|
||||
.markdown-body a:hover {outline-width: 0;text-decoration: underline}
|
||||
.markdown-body strong {font-weight: bolder}
|
||||
.markdown-body h1 {margin: .67em 0}
|
||||
.markdown-body h1,.markdown-body h2 {padding-bottom: .3em;border-bottom: 1px solid #eee}
|
||||
.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6 {margin-top: 1em;margin-bottom: 16px;font-weight: 700;line-height: 1.4}
|
||||
.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link {color: #000;vertical-align: middle;visibility: hidden}
|
||||
.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link {visibility: visible}
|
||||
.markdown-body h1 {font-size: 2.25em;line-height: 1.2}
|
||||
.markdown-body h1 .anchor,.markdown-body h2 .anchor {line-height: 1}
|
||||
.markdown-body h2 {font-size: 1.75em;line-height: 1.225}
|
||||
.markdown-body h3 {font-size: 1.5em;line-height: 1.43}
|
||||
.markdown-body h3 .anchor,.markdown-body h4 .anchor {line-height: 1.2}
|
||||
.markdown-body .octicon {display: inline-block;vertical-align: text-top;fill: currentColor}
|
||||
.markdown-body hr {border-bottom-color: #eee}
|
||||
</style>
|
||||
</head>
|
||||
<div class="markdown-body wysiwyg-content">
|
||||
<h1 id="-some-new-release-on-github-project-available-">
|
||||
<img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" height="32">
|
||||
Some new release on Github project available!
|
||||
</h1>
|
||||
<p>
|
||||
<ul>{{content}}</ul>
|
||||
</p>
|
||||
</div>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user