Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ad5922a
Add simple function and script for testing
fungss Aug 24, 2023
e26d377
Organize src and test files and added github workflow for testing
fungss Aug 25, 2023
c1c849e
Remove python 3.7 and 3.8 as they have no required distribution of nu…
fungss Aug 25, 2023
31f9d6b
Change github workflow to only run on pull_request
fungss Aug 25, 2023
e876263
Add status badge to README
fungss Aug 25, 2023
333217d
Revise README layout
fungss Aug 25, 2023
d9063a5
Add test to check installed packages
fungss Aug 25, 2023
bf4ec55
Fix package-check error
fungss Aug 25, 2023
32b8537
Add comment to explain the usage of __init__.py
fungss Aug 30, 2023
b4f91ae
Rename workflow name to Unit Test Linux
fungss Aug 30, 2023
6b3d60b
Comment out pull_request as trigger condition
fungss Aug 30, 2023
ec81419
Rename badge to Unit Test Linux
fungss Aug 30, 2023
73c6c53
Update badge link
fungss Aug 30, 2023
e181b9c
Resume previous os setting to ensure actions job run successfully
fungss Aug 30, 2023
01895c4
Remove python 3.7 in future unit test pipeline
fungss Aug 30, 2023
c6f9f27
Stage changes to README.md
fungss Aug 31, 2023
7262b1a
Update badge link in README.md
fungss Aug 31, 2023
3cebb8e
Remove python 3.10 support
JustCallMeRay Aug 31, 2023
9223614
Add package flake8 to requirements.txt
fungss Sep 7, 2023
f9252f5
Add function-to-be-test and the corresponding unit test
fungss Sep 7, 2023
90663ac
Add check format step in github workflow
fungss Sep 7, 2023
c3e4539
Update GitHub CI workflow to only run on pull request.
fungss Sep 8, 2023
5a5e648
Back up misformatted py file as .txt as example
fungss Sep 18, 2023
546074e
Update py files formats according to flake8
fungss Sep 18, 2023
b2b1eeb
Remove function and test for testing flake8
fungss Sep 21, 2023
78d7f6f
Remove function and test for testing flake8
fungss Sep 21, 2023
bdcc0d6
Remove test packages in requirements.txt
fungss Sep 21, 2023
5ce29e4
Remove test packages in requirements.txt
fungss Sep 21, 2023
8372d65
Split workflows to different .yaml files, configured unit-test to be …
fungss Sep 21, 2023
d367225
Remove commented code
fungss Sep 22, 2023
ae56eb0
Remove exclude= section in setup.cfg
fungss Sep 22, 2023
e98eb34
Exclude unnecessary directory in setup.cfg and added flake8 package i…
fungss Sep 23, 2023
0cc39cc
Update workflow files
fungss Sep 23, 2023
4a54b4d
Update unit-tests to run on push
JustCallMeRay Sep 25, 2023
8a187c4
Add ReplitScrapper and GithubArchiver classes
fungss Oct 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
33 changes: 33 additions & 0 deletions .github/workflows/linting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Linting Linux

on:
pull_request:

jobs:
linting:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version:
- "3.11"

name: linting
runs-on: ${{ matrix.os }}

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version : ${{ matrix.python-version }}

- name: Install flake8
run: |
python -m pip install flake8

- name: Check format without making corrections
run: |
flake8
37 changes: 37 additions & 0 deletions .github/workflows/unit-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Unit Test Linux

on: push

jobs:
unit-test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version:
- "3.11"

name: unit-test
runs-on: ${{ matrix.os }}
if: ${{ github.event.workflow_run.conclusion == 'success' }}

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version : ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install -r requirements.txt

- name: Check if installed packages confirm with requirements.txt
run: |
pip freeze -r requirements.txt

- name: Run tests
run: |
python -m unittest discover -v
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Act bin file for local testing
bin/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -158,3 +161,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

playwright/.auth
screen-shots
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
test_all:
python -m unittest discover -v

test_replit_scrapper:
python -m unittest ./tests/test_replit_scrapper.py

test_github_archiver:
python -m unittest ./tests/test_github_archiver.py

lint:
flake8
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Helper app
# Helper app

[![Unit Test Linux](https://github.com/Python-Dojo/Dojo-Helper-App/actions/workflows/unit-test.yaml/badge.svg?branch=main)](https://github.com/Python-Dojo/Dojo-Helper-App/actions/workflows/unit-test.yaml)

This is an app to help the hosts of the fortnightly Dojo hosts do the common tasks quicky and effectively. This will include making the replit repos, placing invite links in the discord channel, coping the written code to the github archive and sending a link in the discord channel.
Updates and tickets can be found on the projects page for bounty hunters to complete.
1 change: 1 addition & 0 deletions funcs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# placeholder file to modularize (to package) .py files under the directory "tests", making the files importable.
2 changes: 2 additions & 0 deletions funcs/func.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def sum(x: float, y: float) -> float:
return x + y
121 changes: 121 additions & 0 deletions funcs/github_archiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from abc import ABC, abstractmethod
import os
from github import Github, Auth, InputGitTreeElement
import base64


class GithubArchiverInterface(ABC):
@abstractmethod
def identify_target_files():
"""
Read list of target files to be pushed to github, excluding replit's system files.
Raise error if target folder does not exist or is empty.
"""
pass

@abstractmethod
def commit_to_github():
"""
Commit target files to github.
"""
pass


class GithubArchiver(GithubArchiverInterface):

def __init__(self, project_name, github_access_token, commit_message="Auto-archive") -> None:
self._project_name = project_name
self._file_paths = dict()
self._file_list = list()
self._commit_sha = ""
self.__github_access_token = github_access_token
self._commit_message = commit_message

def get_project_name(self) -> str:
return self._project_name

def identify_target_files(self) -> None:
print("GithubArchiver: Begin to parse target files...")
download_folder_path = "./screen-shots"
extracted_folder_path = os.path.join(download_folder_path, self.get_project_name())
assert os.path.isdir(extracted_folder_path) is True, "Target folder does not exist"
assert len(os.listdir(extracted_folder_path)) != 0, "Target folder is empty"

replit_junk = [
'.cache',
'.upm',
'.replit',
'poetry.lock',
'pyproject.toml',
'replit_zip_error_log.txt',
'replit.nix',
]

# Walk through the directory and its subdirectories
for root, dirs, files in os.walk(extracted_folder_path):
for file in files:
file_full_path = os.path.join(root, file)
file_relative_path = file_full_path.replace(extracted_folder_path, self.get_project_name())
if not any(excluded in file_relative_path for excluded in replit_junk):
self._file_paths[file_relative_path] = file_full_path
self._file_list.append(file_relative_path)

print("GithubArchiver: Target files are parsed")

def get_target_files(self) -> list:
return self._file_list

def commit_to_github(self) -> None:
print("GithubArchiver: Begin to upload files to Github...")
assert len(self._file_list) != 0, "Target files are not identified"
auth = Auth.Token(self.__github_access_token)
g = Github(auth=auth)
repo = g.get_user().get_repo('The-Archive')
main_branch = repo.get_branch("main")
main_tree = repo.get_git_tree(sha=main_branch.commit.sha)

tree = list()
for file_relative_path, file_full_path in self._file_paths.items():

with open(file_full_path, "rb") as file:
file_content = file.read()

file_content_based64 = base64.b64encode(file_content)

blob = repo.create_git_blob(
content=file_content_based64.decode('utf-8'),
encoding="base64"
)

tree.append(
InputGitTreeElement(
path=file_relative_path,
mode="100644",
type="blob",
sha=blob.sha,
)
)

new_tree = repo.create_git_tree(
tree=tree,
base_tree=main_tree
)

commit = repo.create_git_commit(
message=self._commit_message,
tree=repo.get_git_tree(sha=new_tree.sha),
parents=[repo.get_git_commit(main_branch.commit.sha)],
)

archive_ref = repo.get_git_ref(ref='heads/main')
print(f"GithubArchiver: Archive_ref is {archive_ref}")
self._commit_sha = commit.sha

# Commit to Github
archive_ref.edit(sha=commit.sha)
print("GithubArchiver: Upload complete")

g.close()

def get_commit_sha(self) -> str:
return self._commit_sha
125 changes: 125 additions & 0 deletions funcs/replit_scrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync


class ReplitScrapper():
user_agent = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/116.0.0.0 "
"Safari/537.36 "
"Edg/116.0.1938.81"
)

def __init__(self, login_name, login_password):
self.__login_name = login_name
self.__login_password = login_password
self._replit_url = None
self._downloaded_filename = None

def set_replit_url(self, replit_url) -> None:
if replit_url is None:
raise ValueError
self._replit_url = replit_url

def get_replit_url(self) -> str:
if self._replit_url is None:
raise ValueError("Missing replit_url")
return self._replit_url

def _set_downloaded_filename(self, filename) -> None:
if filename is None:
raise ValueError("ReplitScrapper._set_downloaded_filename() argument is None")
self._downloaded_filename = filename

def get_downloaded_filename(self) -> str:
if self._downloaded_filename is None:
raise ValueError("Missing downloaded_filename")
return self._downloaded_filename

def _visit_replit_repo(self, page) -> None:
response = page.goto(self.get_replit_url(), wait_until="domcontentloaded")
if response.status != 200:
if response.status == 404:
print(f"response.status = {response.status}")
raise ValueError("Invalid replit_url")
else:
print(f"response.status = {response.status}")
raise ValueError("ReplitScrapper._visit_replit_repo() something other than 404 happened")

def _login_replit(self, page) -> None:
# Login
page.goto('https://replit.com/login', wait_until="domcontentloaded")
page.screenshot(path="./screen-shots/replit.png")
url_init = "https://identitytoolkit.googleapis.com/v1/accounts"
with page.expect_response(lambda response: url_init in response.url) as response_info:
page.locator(
"xpath=/html/body/div[1]/div/div[2]/div/main/div[2]/div/form/div[1]/input"
).fill(self.__login_name)
page.locator(
"xpath=/html/body/div[1]/div/div[2]/div/main/div[2]/div/form/div[2]/div/input"
).fill(self.__login_password)
page.locator(
"xpath=/html/body/div[1]/div/div[2]/div/main/div[2]/div/form/div[3]/button"
).click()
response = response_info.value
if response.status != 200:
print(response)
if response.status == 400:
print(f"response.status = {response.status}")
raise ValueError("Invalid login credentials")
else:
print(f"response.status = {response.status}")
raise ValueError("ReplitScrapper._login_replit() something other than 401 happened")
page.wait_for_url("https://replit.com/~")
page.screenshot(path="./screen-shots/replit_after_login.png")

def _download_as_zip(self, page) -> None:
# Wait for page load
page.locator(
"xpath=/html/body/div[1]/div[1]/div[1]/div[2]/div/div[1]/div/div[3]/div/div[1]/button/div/span"
).wait_for()
while page.locator(
"xpath=/html/body/div[1]/div[1]/div[1]/div[2]/header/div[2]/button"
).text_content() != "Run":
print(page.locator(
"xpath=/html/body/div[1]/div[1]/div[1]/div[2]/header/div[2]/button"
).text_content())
page.wait_for_timeout(2000)
page.screenshot(path="./screen-shots/target_page.png")

# Begin downloading
page.locator(
"xpath=/html/body/div[1]/div[1]/div[1]/div[2]/div/div[1]/div/div[2]/div[1]/div[1]/div/button[3]"
).click()
with page.expect_download() as download_info:
page.locator(
"xpath=/html/body/div[@class='css-1o92kwk']//div[@id='item-4']//div[@class='css-1l2rn59']"
).click()
download = download_info.value
self._set_downloaded_filename(download.suggested_filename)
download.save_as(f"./screen-shots/{download.suggested_filename}")

def run(self):
print("ReplitScrapper: Begin downloading repo files...")
with sync_playwright() as p:
# Context setup
browser = p.chromium.launch(slow_mo=50)
# browser = p.chromium.launch(headless=False
# , slow_mo=50
# )
context = browser.new_context(user_agent=ReplitScrapper.user_agent)
page = context.new_page()
stealth_sync(page)

# Login replit
self._login_replit(page)

# Download repo files as zip
self._visit_replit_repo(page)
self._download_as_zip(page)

# Clean-up
context.close()
browser.close()
print("ReplitScrapper: Download complete")
31 changes: 31 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from funcs.replit_scrapper import ReplitScrapper
from funcs.github_archiver import GithubArchiver
import os
import zipfile
from dotenv import load_dotenv
load_dotenv()

WDIR = os.path.abspath(os.path.dirname(__name__))

if __name__ == "__main__":
test_url = "https://replit.com/@pythondojoarchi/SlipperyGargantuanDebuggers"
project_name = "SlipperyGargantuanDebuggers"

# Download repo files as zip
scrapper = ReplitScrapper(login_name=os.environ['EMAIL'], login_password=os.environ['PASSWORD'])
scrapper.set_replit_url(test_url)
scrapper.run()

# Unzip downloaded zip file
download_folder_path = os.path.join(WDIR, "screen-shots")
full_file_path = os.path.join(download_folder_path, project_name+".zip")
extracted_folder_path = os.path.join(download_folder_path, project_name)
zipfile.ZipFile(full_file_path).extractall(extracted_folder_path)

# Commit target files to Github
archiver = GithubArchiver(
project_name=project_name,
github_access_token=os.environ['GITHUB_ACCESS_TOKEN']
)
archiver.identify_target_files()
archiver.commit_to_github()
Loading