diff --git a/.gitignore b/.gitignore index 82e7bbc2bb3e584a9ab48f7c5cc5fff1548241b4..b09fbf512c29d1135dd4c68158ed0ecddfb25a36 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,5 @@ coverage.xml docs/_build/ # enviroment variables -.env \ No newline at end of file +.env +venv/ \ No newline at end of file diff --git a/bbcli/cli.py b/bbcli/cli.py index 5c95cc5363b901648bdc567183b662bbf371eb5e..83865fbeb8be8136501c113b5669ef68e844032e 100644 --- a/bbcli/cli.py +++ b/bbcli/cli.py @@ -11,6 +11,8 @@ import click from bbcli.services import authorization_service from bbcli.services import announcement_service +from bbcli.services import course_service +from bbcli.utils.URL_builder import URLBuilder, URL @click.group() def entry_point(): @@ -19,8 +21,10 @@ def entry_point(): @click.command(name='announcements') def get_announcements(): - response = announcement_service.update_announcement(cookies, headers, '_33050_1', '_385022_1') + response = course_service.list_course(cookies, '_33050_1') click.echo(response) + + entry_point.add_command(get_announcements) diff --git a/bbcli/services/__init__.py b/bbcli/services/__init__.py index 540e4cbbe654d8299db8737108ad2ac292234b79..f30c4e42d3b55b2e31bb4a281935a9f0513de3f0 100644 --- a/bbcli/services/__init__.py +++ b/bbcli/services/__init__.py @@ -1 +1,2 @@ -from .authorization_service import * \ No newline at end of file +from .authorization_service import * +from bbcli.utils.URL_builder import URLBuilder \ No newline at end of file diff --git a/bbcli/services/announcement_service.py b/bbcli/services/announcement_service.py index 90177768b8a4ab591f54d25baec9d2e7c098df59..9fc38888dcedccef7869c3c1bec32671141d0682 100644 --- a/bbcli/services/announcement_service.py +++ b/bbcli/services/announcement_service.py @@ -5,6 +5,10 @@ import requests from bbcli.services.course_service import list_courses import click +from bbcli.utils.URL_builder import URLBuilder + +url_builder = URLBuilder() + def list_announcements(cookies: Dict, user_name: str): courses = list_courses(cookies=cookies, user_name=user_name) @@ -12,7 +16,8 @@ def list_announcements(cookies: Dict, user_name: str): announcements = [] for course in courses: - course_announcements = session.get('https://ntnu.blackboard.com/learn/api/public/v1/courses/{}/announcements'.format(course['id']), cookies=cookies) + url = url_builder.base_v1().add_courses().add_id(course['id']).add_announcements().create() + course_announcements = session.get(url, cookies=cookies) course_announcements = json.loads(course_announcements.text) # Adds the course name to each course announcement list to make it easier to display which course the announcement comes from @@ -25,12 +30,14 @@ def list_announcements(cookies: Dict, user_name: str): return announcements def list_course_announcements(cookies: Dict, course_id: str): - course_announcements = requests.get('https://ntnu.blackboard.com/learn/api/public/v1/courses/{}/announcements'.format(course_id), cookies=cookies) + url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().create() + course_announcements = requests.get(url, cookies=cookies) course_announcements = json.loads(course_announcements.text)['results'] return course_announcements def list_announcement(cookies: Dict, course_id: str, announcement_id: str): - announcement = requests.get('https://ntnu.blackboard.com/learn/api/public/v1/courses/{}/announcements/{}'.format(course_id, announcement_id), cookies=cookies) + url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().add_id(announcement_id).create() + announcement = requests.get(url, cookies=cookies) announcement = json.loads(announcement.text) return announcement @@ -49,13 +56,15 @@ def create_announcement(cookies: Dict, headers: Dict, course_id: str, title: str data = json.dumps(data) headers['Content-Type'] = 'application/json' - - response = requests.post('https://ntnu.blackboard.com/learn/api/public/v1/courses/{}/announcements'.format(course_id), cookies=cookies, headers=headers, data=data) + + url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().create() + response = requests.post(url, cookies=cookies, headers=headers, data=data) return response.text def delete_announcement(cookies: Dict, headers: Dict, course_id: str, announcement_id: str): - response = requests.delete('https://ntnu.blackboard.com/learn/api/public/v1/courses/{}/announcements/{}'.format(course_id, announcement_id), cookies=cookies, headers=headers) + url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().add_id(announcement_id).create() + response = requests.delete(url, cookies=cookies, headers=headers) if response.text == '': return 'Sucessfully deleted announcement!' else: @@ -76,6 +85,8 @@ def update_announcement(cookies: Dict, headers: Dict, course_id: str, announceme new_data = click.edit(announcement + '\n\n' + MARKER) headers['Content-Type'] = 'application/json' - response = requests.patch('https://ntnu.blackboard.com/learn/api/public/v1/courses/{}/announcements/{}'.format(course_id, announcement_id), cookies=cookies, headers=headers, data=new_data) + url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().add_id(announcement_id).create() + response = requests.patch(url, cookies=cookies, headers=headers, data=new_data) + response.raise_for_status() return response.text \ No newline at end of file diff --git a/bbcli/services/ContentService.py b/bbcli/services/content_service.py similarity index 100% rename from bbcli/services/ContentService.py rename to bbcli/services/content_service.py diff --git a/bbcli/services/course_service.py b/bbcli/services/course_service.py index 6b893e8b0e4e4b688237bc9a8bf9c2586ddc37f7..8d65278c031da654ba4615458ec9a3b490ec1802 100644 --- a/bbcli/services/course_service.py +++ b/bbcli/services/course_service.py @@ -3,6 +3,10 @@ from typing import Dict, Any import requests from datetime import date +from bbcli.utils.URL_builder import URLBuilder + +url_builder = URLBuilder() + def take_start_date(elem): return date.fromisoformat(elem['availability']['duration']['start'].split('T')[0]) @@ -10,7 +14,8 @@ def take_start_date(elem): def list_courses(cookies: Dict, user_name: str) -> Any: session = requests.Session() - terms = session.get('https://ntnu.blackboard.com/learn/api/public/v1/terms', cookies=cookies) + url = url_builder.base_v1().add_terms().create() + terms = session.get(url, cookies=cookies) terms = json.loads(terms.text)['results'] # Sort terms by start date to get the two most recent semesters to determine which courses to show @@ -22,14 +27,16 @@ def list_courses(cookies: Dict, user_name: str) -> Any: term_1 = terms[len(terms) - 1] term_2 = terms[len(terms) - 2] - course_memberships = session.get('https://ntnu.blackboard.com/learn/api/public/v1/users/userName:{}/courses'.format(user_name), cookies=cookies) + url = url_builder.base_v1().add_users().add_id(id=user_name, id_type='userName').create() + course_memberships = session.get(url, cookies=cookies) course_memberships = json.loads(course_memberships.text)['results'] course_list = [] # Get courses from the correct terms for course in course_memberships: - response = session.get('https://ntnu.blackboard.com/learn/api/public/v3/courses/{}'.format(course['courseId']), cookies=cookies, params={'fields': 'id, name, termId'}) + url = url_builder.base_v3().add_courses().add_id(course['courseId']).create() + response = session.get(url, cookies=cookies, params={'fields': 'id, name, termId'}) response = json.loads(response.text) if response['termId'] == term_1['id'] or response['termId'] == term_2['id']: course_list.append({ @@ -42,7 +49,8 @@ def list_courses(cookies: Dict, user_name: str) -> Any: return course_list def list_course(cookies: Dict, course_id:str) -> Any: - response = requests.get('https://ntnu.blackboard.com/learn/api/public/v3/courses/{}'.format(course_id), cookies=cookies) + url = url_builder.base_v3().add_courses().add_id(course_id).create() + response = requests.get(url, cookies=cookies) return json.loads(response.text) def list_favorite_courses(cookies: Dict, user_name: str) -> Any: diff --git a/bbcli/utils/URL_builder.py b/bbcli/utils/URL_builder.py new file mode 100644 index 0000000000000000000000000000000000000000..126e55cde999460ab73e36097d918b248143cbcb --- /dev/null +++ b/bbcli/utils/URL_builder.py @@ -0,0 +1,129 @@ +from __future__ import annotations +from typing import Any +from abc import ABC, abstractmethod + +DOMAIN = 'https://ntnu.blackboard.com' +API_BASE = '/learn/api/public' + + +class Builder(ABC): + + @property + @abstractmethod + def product(self) -> None: + pass + + + """ + Returns the base URL which includes the domain and first part of all the endpoints: domain/learn/api/public/vX, + where X is the version from 1 to 3. + """ + + @abstractmethod + def base_v1(self) -> Builder: + pass + + @abstractmethod + def base_v2(self) -> Builder: + pass + + @abstractmethod + def base_v3(self) -> Builder: + pass + + @abstractmethod + def add_courses(self) -> Builder: + pass + + @abstractmethod + def add_users(self) -> Builder: + pass + + @abstractmethod + def add_announcements(self) -> Builder: + pass + + @abstractmethod + def add_contents(self) -> Builder: + pass + + @abstractmethod + def add_terms(slef) -> Builder: + pass + + @abstractmethod + def add_id(self, id: str, id_type: str = None) -> Builder: + pass + + +class URLBuilder(Builder): + + def __init__(self) -> None: + self.reset() + + def reset(self) -> None: + self._product = URL() + + @property + def product(self) -> URL: + + product = self._product + self.reset() + return product + + + + def base_v1(self) -> URLBuilder: + self._product.add(f'{DOMAIN}{API_BASE}/v1') + return self + + def base_v2(self) -> URLBuilder: + self._product.add(f'{DOMAIN}{API_BASE}/v2') + return self + + def base_v3(self) -> URLBuilder: + self._product.add(f'{DOMAIN}{API_BASE}/v3') + return self + + def add_courses(self) -> URLBuilder: + self._product.add('/courses') + return self + + def add_users(self) -> URLBuilder: + self._product.add('/users') + return self + + def add_announcements(self) -> URLBuilder: + self._product.add('/announcements') + return self + + def add_contents(self) -> URLBuilder: + self._product.add('/contents') + return self + + def add_terms(self) -> URLBuilder: + self._product.add('/terms') + return self + + def add_id(self, id:str, id_type:str=None) -> URLBuilder: + if id_type: + self._product.add(f'/{id_type}:{id}') + else: + self._product.add(f'/{id}') + return self + + def create(self) -> str: + url = self._product.get_url() + self._product = URL() + return url + +class URL(): + + def __init__(self) -> None: + self.URL = '' + + def add(self, url_part: str) -> None: + self.URL += url_part + + def get_url(self) -> None: + return self.URL \ No newline at end of file diff --git a/bbcli/utils/error_handler.py b/bbcli/utils/error_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..3ef18c457e14b69fb63848806eca035cc32ac56f --- /dev/null +++ b/bbcli/utils/error_handler.py @@ -0,0 +1,13 @@ +import requests +import click + +# ERROR HANDLER SHOULD BE USED IN VIEW?? + +def HTTP_exception_handler(func): + def inner_function(*args, **kwargs): + try: + func(*args, **kwargs) + except requests.exceptions.HTTPError as err: + click.echo(err) + click.Abort() + return inner_function \ No newline at end of file