mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-02-16 23:29:56 +03:00
[core] improve pull request artifacts comment (#3705)
This commit is contained in:
parent
857e908929
commit
09f3c1532a
1 changed files with 143 additions and 97 deletions
240
.github/prtester.py
vendored
240
.github/prtester.py
vendored
|
@ -1,113 +1,159 @@
|
||||||
|
import argparse
|
||||||
import requests
|
import requests
|
||||||
import itertools
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Iterable
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
# This script is specifically written to be used in automation for https://github.com/RSS-Bridge/rss-bridge
|
# This script is specifically written to be used in automation for https://github.com/RSS-Bridge/rss-bridge
|
||||||
#
|
#
|
||||||
# This will scrape the whitelisted bridges in the current state (port 3000) and the PR state (port 3001) of
|
# This will scrape the whitelisted bridges in the current state (port 3000) and the PR state (port 3001) of
|
||||||
# RSS-Bridge, generate a feed for each of the bridges and save the output as html files.
|
# RSS-Bridge, generate a feed for each of the bridges and save the output as html files.
|
||||||
# It also replaces the default static CSS link with a hardcoded link to @em92's public instance, so viewing
|
# It also add a <base> tag with the url of em's public instance, so viewing
|
||||||
# the HTML file locally will actually work as designed.
|
# the HTML file locally will actually work as designed.
|
||||||
|
|
||||||
def testBridges(bridges,status):
|
class Instance:
|
||||||
for bridge in bridges:
|
name = ''
|
||||||
if bridge.get('data-ref'): # Some div entries are empty, this ignores those
|
url = ''
|
||||||
bridgeid = bridge.get('id')
|
|
||||||
bridgeid = bridgeid.split('-')[1] # this extracts a readable bridge name from the bridge metadata
|
def main(instances: Iterable[Instance], with_upload: bool, comment_title: str):
|
||||||
print(bridgeid + "\n")
|
start_date = datetime.now()
|
||||||
bridgestring = '/?action=display&bridge=' + bridgeid + '&format=Html'
|
table_rows = []
|
||||||
forms = bridge.find_all("form")
|
for instance in instances:
|
||||||
formid = 1
|
page = requests.get(instance.url) # Use python requests to grab the rss-bridge main page
|
||||||
for form in forms:
|
soup = BeautifulSoup(page.content, "html.parser") # use bs4 to turn the page into soup
|
||||||
# a bridge can have multiple contexts, named 'forms' in html
|
bridge_cards = soup.select('.bridge-card') # get a soup-formatted list of all bridges on the rss-bridge page
|
||||||
# this code will produce a fully working formstring that should create a working feed when called
|
table_rows += testBridges(instance, bridge_cards, with_upload) # run the main scraping code with the list of bridges and the info if this is for the current version or the pr version
|
||||||
# this will create an example feed for every single context, to test them all
|
with open(file=os.getcwd() + '/comment.txt', mode='w+', encoding='utf-8') as file:
|
||||||
formstring = ''
|
table_rows_value = '\n'.join(sorted(table_rows))
|
||||||
errormessages = []
|
file.write(f'''
|
||||||
parameters = form.find_all("input")
|
## {comment_title}
|
||||||
lists = form.find_all("select")
|
| Bridge | Context | Status |
|
||||||
# this for/if mess cycles through all available input parameters, checks if it required, then pulls
|
| - | - | - |
|
||||||
# the default or examplevalue and then combines it all together into the formstring
|
{table_rows_value}
|
||||||
# if an example or default value is missing for a required attribute, it will throw an error
|
|
||||||
# any non-required fields are not tested!!!
|
*last change: {start_date.strftime("%A %Y-%m-%d %H:%M:%S")}*
|
||||||
for parameter in parameters:
|
'''.strip())
|
||||||
if parameter.get('type') == 'hidden' and parameter.get('name') == 'context':
|
|
||||||
cleanvalue = parameter.get('value').replace(" ","+")
|
def testBridges(instance: Instance, bridge_cards: Iterable, with_upload: bool) -> Iterable:
|
||||||
formstring = formstring + '&' + parameter.get('name') + '=' + cleanvalue
|
instance_suffix = ''
|
||||||
if parameter.get('type') == 'number' or parameter.get('type') == 'text':
|
if instance.name:
|
||||||
if parameter.has_attr('required'):
|
instance_suffix = f' ({instance.name})'
|
||||||
if parameter.get('placeholder') == '':
|
table_rows = []
|
||||||
if parameter.get('value') == '':
|
for bridge_card in bridge_cards:
|
||||||
errormessages.append(parameter.get('name'))
|
bridgeid = bridge_card.get('id')
|
||||||
else:
|
bridgeid = bridgeid.split('-')[1] # this extracts a readable bridge name from the bridge metadata
|
||||||
formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('value')
|
print(f'{bridgeid}{instance_suffix}\n')
|
||||||
|
bridgestring = '/?action=display&bridge=' + bridgeid + '&format=Html'
|
||||||
|
bridge_name = bridgeid.replace('Bridge', '')
|
||||||
|
context_forms = bridge_card.find_all("form")
|
||||||
|
form_number = 1
|
||||||
|
for context_form in context_forms:
|
||||||
|
# a bridge can have multiple contexts, named 'forms' in html
|
||||||
|
# this code will produce a fully working formstring that should create a working feed when called
|
||||||
|
# this will create an example feed for every single context, to test them all
|
||||||
|
formstring = ''
|
||||||
|
error_messages = []
|
||||||
|
context_name = '*untitled*'
|
||||||
|
context_name_element = context_form.find_previous_sibling('h5')
|
||||||
|
if context_name_element and context_name_element.text.strip() != '':
|
||||||
|
context_name = context_name_element.text
|
||||||
|
parameters = context_form.find_all("input")
|
||||||
|
lists = context_form.find_all("select")
|
||||||
|
# this for/if mess cycles through all available input parameters, checks if it required, then pulls
|
||||||
|
# the default or examplevalue and then combines it all together into the formstring
|
||||||
|
# if an example or default value is missing for a required attribute, it will throw an error
|
||||||
|
# any non-required fields are not tested!!!
|
||||||
|
for parameter in parameters:
|
||||||
|
if parameter.get('type') == 'hidden' and parameter.get('name') == 'context':
|
||||||
|
cleanvalue = parameter.get('value').replace(" ","+")
|
||||||
|
formstring = formstring + '&' + parameter.get('name') + '=' + cleanvalue
|
||||||
|
if parameter.get('type') == 'number' or parameter.get('type') == 'text':
|
||||||
|
if parameter.has_attr('required'):
|
||||||
|
if parameter.get('placeholder') == '':
|
||||||
|
if parameter.get('value') == '':
|
||||||
|
name_value = parameter.get('name')
|
||||||
|
error_messages.append(f'Missing example or default value for parameter "{name_value}"')
|
||||||
else:
|
else:
|
||||||
formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('placeholder')
|
formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('value')
|
||||||
# same thing, just for checkboxes. If a checkbox is checked per default, it gets added to the formstring
|
|
||||||
if parameter.get('type') == 'checkbox':
|
|
||||||
if parameter.has_attr('checked'):
|
|
||||||
formstring = formstring + '&' + parameter.get('name') + '=on'
|
|
||||||
for listing in lists:
|
|
||||||
selectionvalue = ''
|
|
||||||
listname = listing.get('name')
|
|
||||||
cleanlist = []
|
|
||||||
for option in listing.contents:
|
|
||||||
if 'optgroup' in option.name:
|
|
||||||
cleanlist.extend(option)
|
|
||||||
else:
|
else:
|
||||||
cleanlist.append(option)
|
formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('placeholder')
|
||||||
firstselectionentry = 1
|
# same thing, just for checkboxes. If a checkbox is checked per default, it gets added to the formstring
|
||||||
for selectionentry in cleanlist:
|
if parameter.get('type') == 'checkbox':
|
||||||
if firstselectionentry:
|
if parameter.has_attr('checked'):
|
||||||
|
formstring = formstring + '&' + parameter.get('name') + '=on'
|
||||||
|
for listing in lists:
|
||||||
|
selectionvalue = ''
|
||||||
|
listname = listing.get('name')
|
||||||
|
cleanlist = []
|
||||||
|
for option in listing.contents:
|
||||||
|
if 'optgroup' in option.name:
|
||||||
|
cleanlist.extend(option)
|
||||||
|
else:
|
||||||
|
cleanlist.append(option)
|
||||||
|
firstselectionentry = 1
|
||||||
|
for selectionentry in cleanlist:
|
||||||
|
if firstselectionentry:
|
||||||
|
selectionvalue = selectionentry.get('value')
|
||||||
|
firstselectionentry = 0
|
||||||
|
else:
|
||||||
|
if 'selected' in selectionentry.attrs:
|
||||||
selectionvalue = selectionentry.get('value')
|
selectionvalue = selectionentry.get('value')
|
||||||
firstselectionentry = 0
|
break
|
||||||
else:
|
formstring = formstring + '&' + listname + '=' + selectionvalue
|
||||||
if 'selected' in selectionentry.attrs:
|
termpad_url = 'about:blank'
|
||||||
selectionvalue = selectionentry.get('value')
|
if error_messages:
|
||||||
break
|
status = '<br>'.join(map(lambda m: f'❌ `{m}`', error_messages))
|
||||||
formstring = formstring + '&' + listname + '=' + selectionvalue
|
else:
|
||||||
if not errormessages:
|
# if all example/default values are present, form the full request string, run the request, add a <base> tag with
|
||||||
# if all example/default values are present, form the full request string, run the request, replace the static css
|
# the url of em's public instance to the response text (so that relative paths work, e.g. to the static css file) and
|
||||||
# file with the url of em's public instance and then upload it to termpad.com, a pastebin-like-site.
|
# then upload it to termpad.com, a pastebin-like-site.
|
||||||
r = requests.get(URL + bridgestring + formstring)
|
response = requests.get(instance.url + bridgestring + formstring)
|
||||||
pagetext = r.text.replace('static/style.css','https://rss-bridge.org/bridge01/static/style.css')
|
page_text = response.text.replace('<head>','<head><base href="https://rss-bridge.org/bridge01/" target="_blank">')
|
||||||
pagetext = pagetext.encode("utf_8")
|
page_text = page_text.encode("utf_8")
|
||||||
termpad = requests.post(url="https://termpad.com/", data=pagetext)
|
soup = BeautifulSoup(page_text, "html.parser")
|
||||||
termpadurl = termpad.text
|
status_messages = list(map(lambda e: f'⚠️ `{e.text.strip().splitlines()[0]}`', soup.find_all('pre')))
|
||||||
termpadurl = termpadurl.replace('termpad.com/','termpad.com/raw/')
|
if response.status_code != 200:
|
||||||
termpadurl = termpadurl.replace('\n','')
|
status_messages = [f'❌ `HTTP status {response.status_code} {response.reason}`'] + status_messages
|
||||||
with open(os.getcwd() + '/comment.txt', 'a+') as file:
|
|
||||||
file.write("\n")
|
|
||||||
file.write("| [`" + bridgeid + '-' + status + '-context' + str(formid) + "`](" + termpadurl + ") | " + date_time + " |")
|
|
||||||
else:
|
else:
|
||||||
# if there are errors (which means that a required value has no example or default value), log out which error appeared
|
feed_items = soup.select('.feeditem')
|
||||||
termpad = requests.post(url="https://termpad.com/", data=str(errormessages))
|
feed_items_length = len(feed_items)
|
||||||
termpadurl = termpad.text
|
if feed_items_length <= 0:
|
||||||
termpadurl = termpadurl.replace('termpad.com/','termpad.com/raw/')
|
status_messages += [f'⚠️ `The feed has no items`']
|
||||||
termpadurl = termpadurl.replace('\n','')
|
elif feed_items_length == 1 and len(soup.select('.error')) > 0:
|
||||||
with open(os.getcwd() + '/comment.txt', 'a+') as file:
|
status_messages = [f'❌ `{feed_items[0].text.strip().splitlines()[0]}`'] + status_messages
|
||||||
file.write("\n")
|
status = '<br>'.join(status_messages)
|
||||||
file.write("| [`" + bridgeid + '-' + status + '-context' + str(formid) + "`](" + termpadurl + ") | " + date_time + " |")
|
if status.strip() == '':
|
||||||
formid += 1
|
status = '✔️'
|
||||||
|
if with_upload:
|
||||||
|
termpad = requests.post(url="https://termpad.com/", data=page_text)
|
||||||
|
termpad_url = termpad.text.strip()
|
||||||
|
termpad_url = termpad_url.replace('termpad.com/','termpad.com/raw/')
|
||||||
|
table_rows.append(f'| {bridge_name} | [{form_number} {context_name}{instance_suffix}]({termpad_url}) | {status} |')
|
||||||
|
form_number += 1
|
||||||
|
return table_rows
|
||||||
|
|
||||||
gitstatus = ["current", "pr"]
|
if __name__ == '__main__':
|
||||||
now = datetime.now()
|
parser = argparse.ArgumentParser()
|
||||||
date_time = now.strftime("%Y-%m-%d, %H:%M:%S")
|
parser.add_argument('-i', '--instances', nargs='+')
|
||||||
|
parser.add_argument('-nu', '--no-upload', action='store_true')
|
||||||
with open(os.getcwd() + '/comment.txt', 'w+') as file:
|
parser.add_argument('-t', '--comment-title', default='Pull request artifacts')
|
||||||
file.write(''' ## Pull request artifacts
|
args = parser.parse_args()
|
||||||
| file | last change |
|
instances = []
|
||||||
| ---- | ------ |''')
|
if args.instances:
|
||||||
|
for instance_arg in args.instances:
|
||||||
for status in gitstatus: # run this twice, once for the current version, once for the PR version
|
instance_arg_parts = instance_arg.split('::')
|
||||||
if status == "current":
|
instance = Instance()
|
||||||
port = "3000" # both ports are defined in the corresponding workflow .yml file
|
instance.name = instance_arg_parts[1] if len(instance_arg_parts) >= 2 else ''
|
||||||
elif status == "pr":
|
instance.url = instance_arg_parts[0]
|
||||||
port = "3001"
|
instances.append(instance)
|
||||||
URL = "http://localhost:" + port
|
else:
|
||||||
page = requests.get(URL) # Use python requests to grab the rss-bridge main page
|
instance = Instance()
|
||||||
soup = BeautifulSoup(page.content, "html.parser") # use bs4 to turn the page into soup
|
instance.name = 'current'
|
||||||
bridges = soup.find_all("section") # get a soup-formatted list of all bridges on the rss-bridge page
|
instance.url = 'http://localhost:3000'
|
||||||
testBridges(bridges,status) # run the main scraping code with the list of bridges and the info if this is for the current version or the pr version
|
instances.append(instance)
|
||||||
|
instance = Instance()
|
||||||
|
instance.name = 'pr'
|
||||||
|
instance.url = 'http://localhost:3001'
|
||||||
|
instances.append(instance)
|
||||||
|
main(instances=instances, with_upload=not args.no_upload, comment_title=args.comment_title);
|
Loading…
Add table
Reference in a new issue