You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

blackboardsync

Package Overview
Dependencies
Maintainers
1
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

blackboardsync - pypi Package Compare versions

Comparing version
0.17.4
to
0.17.5rc1
+124
blackboard_sync/content/templates.py
from textwrap import dedent
body_template = dedent("""
<html>
<head>
<meta name="viewport"
content="width=device-width, initial-scale=1" />
<script>
const openShare = () => {{
if(navigator.share) {{
navigator.share({{
title: '{title}',
text: '{body_text}'
}}).catch(console.error);
}}
}}
document.addEventListener('DOMContentLoaded', function() {{
const shareButton = document.getElementById('share-button');
shareButton.style.display = (navigator.share) ? 'block' : 'none';
}});
</script>
<style>
:root {{ color-scheme: light dark; }}
html {{ height: 100%; background-color: #212121; }}
body {{
height: calc(100% - 8rem);
display: flex;
flex-flow: column nowrap;
align-items: flex-start;
justify-content: stretch;
margin: 2rem;
padding: 2rem;
border-radius: 1.5rem;
box-shadow: 0px 2px 10px 2px black;
background-color: light-dark(white, #212121);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.5;
}}
header {{
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
width: 100%;
}}
header > h2 {{ flex: 1; }}
main {{ flex: 1; }}
footer {{
display: flex;
flex-flow: column nowrap;
align-self: center;
align-items: center;
font-size: 0.75rem;
color: grey;
}}
footer > p {{ margin: 0.25rem; }}
footer > a {{
text-decoration: none;
color: #a02c2c;
}}
ul#sharing {{
display: flex;
flex-flow: row nowrap;
justify-content: center;
margin: 0;
padding: 0;
grid-gap: 2rem;
}}
ul#sharing > li {{ list-style-type: none; }}
ul#sharing svg {{
fill: light-dark(black, white);
cursor: pointer;
}}
#share-button {{ display: none; }}
</style>
</head>
<body>
<header>
<h2>{title}</h2>
<ul id="sharing">
<li>
<a href="mailto:?subject={title}&body={body_text}">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" width="16" height="16">
<path d="M1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75
1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0
12.25v-8.5C0 2.784.784 2 1.75 2ZM1.5 12.251c0
.138.112.25.25.25h12.5a.25.25 0 0 0
.25-.25V5.809L8.38 9.397a.75.75 0 0 1-.76 0L1.5
5.809v6.442Zm13-8.181v-.32a.25.25 0 0
0-.25-.25H1.75a.25.25 0 0
0-.25.25v.32L8 7.88Z"></path></svg>
</a>
</li>
<li id="share-button" onClick="openShare()">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" width="16" height="16">
<path d="M15 3a3 3 0 0 1-5.175 2.066l-3.92 2.179a2.994 2.994
0 0 1 0 1.51l3.92 2.179a3 3 0 1 1-.73
1.31l-3.92-2.178a3 3 0 1 1 0-4.133l3.92-2.178A3
3 0 1 1 15 3Zm-1.5 10a1.5 1.5 0 1 0-3.001.001A1.5
1.5 0 0 0 13.5 13Zm-9-5a1.5 1.5 0 1 0-3.001.001A1.5
1.5 0 0 0 4.5 8Zm9-5a1.5 1.5 0 1 0-3.001.001A1.5
1.5 0 0 0 13.5 3Z"></path></svg>
</li>
</ul>
</header>
<main>{body_html}</main>
<footer>
<p>You may have to configure your browser to open
attachments with the right application instead of downloading
them again.</p>
<p>Content downloaded by
<a href="https://bbsync.app">Blackboard Sync</a></p>
</footer>
</body>
</html>""").lstrip()
def create_body(title: str, body_html: str, body_text: str) -> str:
return body_template.format(title=title,
body_html=body_html,
body_text=body_text)
+16
-5

@@ -7,7 +7,7 @@ from pathlib import Path

from .base import FStream
from .job import DownloadJob
from .templates import create_body
from .webdav import WebDavFile, ContentParser
from .job import DownloadJob
class ContentBody(FStream):

@@ -18,10 +18,21 @@ """Process the content body to find WebDav files."""

job: DownloadJob) -> None:
parser = ContentParser(content.body or "", job.session.instance_url)
self.body = parser.body
self.ignore = False
if not content.body:
self.ignore = True
return
title = content.title or "Untitled"
parser = ContentParser(content.body, job.session.instance_url)
self.body = create_body(title, parser.body, parser.text)
self.children = [WebDavFile(ln, job) for ln in parser.links]
def write(self, path: Path, executor: ThreadPoolExecutor) -> None:
super().write_base(path / f"{path.stem}.html", executor, self.body)
if self.ignore:
return
self.write_base(path / f"{path.stem}.html", executor, self.body)
for child in self.children:
child.write(path, executor)

@@ -40,3 +40,2 @@ import logging

Handler = Content.get_handler(content.contentHandler)
self.title = content.title_path_safe.replace('.', '_')

@@ -43,0 +42,0 @@

@@ -5,8 +5,5 @@ from pathlib import Path

from blackboard.blackboard import BBCourseContent
from blackboard.filters import BBAttachmentFilter
from bwfilters import BWFilter
from blackboard.filters import (
BBAttachmentFilter,
BWFilter
)
from .attachment import Attachment

@@ -13,0 +10,0 @@ from .api_path import BBContentPath

@@ -27,2 +27,3 @@ """

from bs4 import BeautifulSoup
from urllib.parse import unquote
from typing import List, NamedTuple

@@ -42,18 +43,33 @@ from pathvalidate import sanitize_filename

class ContentParser:
def __init__(self, body: str, base_url: str) -> None:
links = []
def __init__(self, body: str, base_url: str,
*, find_links: bool = True) -> None:
soup = BeautifulSoup(body, 'html.parser')
self._links = []
for link in soup.find_all('a'):
if find_links:
a = self._find_replace(soup, 'a', 'href', base_url)
img = self._find_replace(soup, 'img', 'src', base_url)
self._links = [*a, *img]
self._body = str(soup)
self._text = soup.text
def _find_replace(self, soup: BeautifulSoup,
tag: str, attr: str, base_url: str) -> list[Link]:
links = []
for el in soup.find_all(tag):
# Add link for later download
links.append(Link(href=link.get('href'), text=link.text.strip()))
uri = el.get(attr)
# Replace for local instance
if link['href'].startswith(base_url):
filename = link.text.strip()
link['href'] = filename
link.string = filename
self._links = links
self.soup = soup
if uri:
# Handle url-encoding
filename = unquote(uri.split('/')[-1])
links.append(Link(href=uri, text=filename))
# Replace for local instance
if uri.startswith(base_url):
el[attr] = filename
return links
@property

@@ -65,5 +81,9 @@ def links(self) -> List[Link]:

def body(self) -> str:
return str(self.soup)
return self._body
@property
def text(self) -> str:
return self._text
def validate_webdav_response(response: Response,

@@ -70,0 +90,0 @@ link: str, base_url: str) -> bool:

@@ -44,2 +44,2 @@ # Copyright (C) 2024, Jacob Sánchez Pérez

for future in done:
error = future.result()
future.result()

@@ -222,3 +222,3 @@ """

file_handler = logging.FileHandler(log_path)
file_handler.setLevel(logging.ERROR)
file_handler.setLevel(logging.WARNING)

@@ -225,0 +225,0 @@ logger.addHandler(file_handler)

@@ -23,3 +23,2 @@ """

import requests
from pathlib import Path
from packaging import version

@@ -26,0 +25,0 @@ from importlib.metadata import PackageNotFoundError

Metadata-Version: 2.1
Name: blackboardsync
Version: 0.17.4
Version: 0.17.5rc1
Summary: Sync your blackboard content to your device

@@ -30,2 +30,3 @@ Author-email: Jacob Sánchez <jacobszpz@protonmail.com>

Requires-Dist: whoisit
Requires-Dist: bwfilters
Provides-Extra: test

@@ -32,0 +33,0 @@ Requires-Dist: pytest; extra == "test"

@@ -13,2 +13,3 @@ pyqt6>=6.7.1

whoisit
bwfilters

@@ -15,0 +16,0 @@ [package]

@@ -47,2 +47,3 @@ .gitignore

blackboard_sync/content/job.py
blackboard_sync/content/templates.py
blackboard_sync/content/unhandled.py

@@ -81,3 +82,2 @@ blackboard_sync/content/webdav.py

docs/_static/custom.css
docs/dev/bb_api.rst
docs/dev/qt_api.rst

@@ -84,0 +84,0 @@ docs/dev/sync_api.rst

@@ -9,2 +9,16 @@ # Changelog

### Added
- Images contained within descriptions are also downloaded
- Body descriptions can be shared via WebShare or email
### Fixed
- Links are now not assumed to have an 'href' attribute
- Avoid parsing empty descriptions
- Webdav filenames are URL decoded
- Documentation build at readthedocs.org
### Changed
- File logging level is now WARNING rather than ERROR
- Body descriptions have custom styling rather than pure html
## [0.17.4] - 2024-10-12

@@ -11,0 +25,0 @@

@@ -18,2 +18,3 @@ # Configuration file for the Sphinx documentation builder.

from importlib.metadata import version as get_version
from importlib.metadata import PackageNotFoundError

@@ -29,3 +30,3 @@ from blackboard_sync import __about__

# The full version, including alpha/beta/rc tags
release = get_version("blackboard_sync")
release = get_version("blackboardsync")
version = ".".join(release.split('.')[:2])

@@ -32,0 +33,0 @@

@@ -9,5 +9,5 @@ .. _qt_api:

.. automodule:: blackboard_sync.qt.qt_elements
.. automodule:: blackboard_sync.qt
:members:
:undoc-members:
:exclude-members: app_logo

@@ -46,3 +46,3 @@ .. BlackboardSync documentation master file, created by

$ python3 -m pip install blackboardsync
$ python3 -m blackboard_sync # notice the underscore
$ blackboardsync

@@ -60,4 +60,3 @@

dev/bb_api
dev/sync_api
dev/qt_api
-i https://pypi.org/simple
recommonmark==0.7.1
sphinx==7.4.7; python_version >= '3.9'
sphinx-rtd-theme==2.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
sphinx==8.1.3; python_version >= '3.9'
sphinx-rtd-theme==3.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
pyqt6==6.7.1
pyqt6-webengine==6.7.0
pydantic==2.9.2
pyqt5==5.15.11
appdirs==1.4.4

@@ -11,2 +12,7 @@ python-dateutil==2.9.0.post0

pathvalidate==3.2.1
pyqtwebengine==5.15.7
whoisit==3.0.4
bblearn==0.3.6
bwfilters==0.1.0
setuptools==75.1.0
setuptools-scm==8.1.0
-e .

@@ -19,3 +19,4 @@ [[source]]

bblearn = ">=0.3.4.post0"
whoisit = "*"
whoisit = ">=3.0.4"
bwfilters = "==0.1.0"

@@ -22,0 +23,0 @@ [dev-packages]

Metadata-Version: 2.1
Name: blackboardsync
Version: 0.17.4
Version: 0.17.5rc1
Summary: Sync your blackboard content to your device

@@ -30,2 +30,3 @@ Author-email: Jacob Sánchez <jacobszpz@protonmail.com>

Requires-Dist: whoisit
Requires-Dist: bwfilters
Provides-Extra: test

@@ -32,0 +33,0 @@ Requires-Dist: pytest; extra == "test"

@@ -34,3 +34,4 @@ [build-system]

"bblearn>=0.3.0",
"whoisit"
"whoisit",
"bwfilters"
]

@@ -37,0 +38,0 @@

.. _bb_api:
Blackboard API Reference
========================
Blackboard REST API
-------------------
REST API call parameters must be specified in keyword form.
.. automodule:: blackboard_sync.blackboard.api
:members:
:undoc-members:
Blackboard Data Classes
-----------------------
.. automodule:: blackboard_sync.blackboard.blackboard
:members:
:undoc-members:

Sorry, the diff of this file is too big to display