diff --git a/bbcli/__main__.py b/bbcli/__main__.py index 81cea9af26ef451c3d159733f1ea2d00a48550c2..33805efd16f1afe6395f0f4453a68fd2e4100f90 100644 --- a/bbcli/__main__.py +++ b/bbcli/__main__.py @@ -1,5 +1,5 @@ from bbcli import __app_name__ -from .cli import entry_point +from bbcli.cli import entry_point def main(): entry_point() diff --git a/bbcli/__pycache__/string_builder.cpython-38.pyc b/bbcli/__pycache__/string_builder.cpython-38.pyc deleted file mode 100644 index a0ac78f407ebd5270e5a2ef7af6f9fc8197c94eb..0000000000000000000000000000000000000000 Binary files a/bbcli/__pycache__/string_builder.cpython-38.pyc and /dev/null differ diff --git a/bbcli/cli.py b/bbcli/cli.py index 6faabf7da09c271e0dcaa705dd067cc04a0d4307..2f8314e838e5ca74ecfa7f13b18210d6b1becdab 100644 --- a/bbcli/cli.py +++ b/bbcli/cli.py @@ -1,25 +1,89 @@ from typing import Optional from pkg_resources import EntryPoint +import requests +from bbcli.utils.utils import set_cookies, set_headers # import typer from bbcli import __app_name__, __version__ -from bbcli.endpoints import get_user, get_course_contents, get_assignments +# from bbcli.endpoints import get_user, get_course_contents, get_assignments import os from dotenv import load_dotenv from bbcli import check_valid_date, check_response import click -from bbcli.services import authorization_service +from bbcli.commands.courses import list_courses +from bbcli.commands.announcements import list_announcements, create_announcement, delete_announcement, update_announcement +from bbcli.commands.contents import list_contents, create_content +from bbcli.services.authorization_service import login + +def initiate_session(): + bb_cookie = { + 'name':'BbRouter', + 'value': os.getenv("BB_ROUTER") + } + xsrf = {'X-Blackboard-XSRF': os.getenv('XSRF')} + + session = requests.Session() + set_cookies(session, [bb_cookie]) + set_headers(session, [xsrf]) + return session + @click.group() -def entry_point(): +@click.pass_context +def entry_point(ctx): + ctx.ensure_object(dict) authorize_user() + + session = initiate_session() + ctx.obj['SESSION'] = session + pass +""" +COURSE COMMANDS ENTRY POINT +""" +@entry_point.group() +@click.pass_context +def courses(ctx): + """ + Commands for listing courses + """ + pass + +courses.add_command(list_courses) + + +""" +ANNOUNCEMENT COMMANDS ENTRY POINT +""" +@entry_point.group() +@click.pass_context +def announcements(ctx): + """ + Commands for listing, creating, deleting and updating announcements + """ + pass + +announcements.add_command(list_announcements) +announcements.add_command(create_announcement) +announcements.add_command(delete_announcement) +announcements.add_command(update_announcement) + +""" +CONTENT COMMANDS ENTRY POINT +""" + +@entry_point.group() +@click.pass_context +def contents(ctx): + """ + Commands for listing, creating, deleting, updating and downloading content + """ + pass -entry_point.add_command(get_user) -entry_point.add_command(get_course_contents) -entry_point.add_command(get_assignments) +contents.add_command(list_contents) +contents.add_command(create_content) load_dotenv() cookies = {'BbRouter' : os.getenv("BB_ROUTER")} @@ -29,4 +93,4 @@ headers = {'X-Blackboard-XSRF': os.getenv('XSRF')} # @app.command(name='login', help='Authorize the user.') def authorize_user(): if check_valid_date(cookies) == False: - authorization_service.login() + login() diff --git a/bbcli/commands/announcements.py b/bbcli/commands/announcements.py new file mode 100644 index 0000000000000000000000000000000000000000..070a5728c86f9461bf720d502bf4dfb312f37688 --- /dev/null +++ b/bbcli/commands/announcements.py @@ -0,0 +1,66 @@ +import click +from bbcli.services import announcements_service +from bbcli.views import announcement_view +import os + +@click.command(name='list') +@click.argument('course_id', required=False) +@click.argument('announcement_id', required=False) +@click.pass_context +def list_announcements(ctx,course_id=None, announcement_id=None): + + """ + This command lists your announcements. + Either all announcements, all announcements from a spesific course, or one announcement. + """ + response = None + + if announcement_id: + response = announcements_service.list_announcement(ctx.obj['SESSION'], course_id, announcement_id) + announcement_view.print_course_announcements([response]) + elif course_id: + response = announcements_service.list_course_announcements(ctx.obj['SESSION'], course_id) + announcement_view.print_course_announcements(response) + else: + user_name = os.getenv('BB_USERNAME') + response = announcements_service.list_announcements(ctx.obj['SESSION'], user_name) + announcement_view.print_announcements(response) + + +@click.command(name='create') +@click.argument('course_id', required=True, type=str) +@click.argument('title', required=True, type=str) +@click.pass_context +def create_announcement(ctx, course_id: str, title: str): + """ + This command creates an announcement. Add --help + for all options available + """ + response = announcements_service.create_announcement(ctx.obj['SESSION'], course_id, title) + click.echo(response) + + +@click.command(name='delete') +@click.argument('course_id', required=True, type=str) +@click.argument('announcement_id', required=True, type=str) +@click.pass_context +def delete_announcement(ctx, course_id: str, announcement_id: str): + """ + This command deletes an announcement. Add --help + for all options available + """ + response = announcements_service.delete_announcement(ctx.obj['SESSION'], course_id, announcement_id) + click.echo(response) + + +@click.command(name='update') +@click.argument('course_id', required=True, type=str) +@click.argument('announcement_id', required=True, type=str) +@click.pass_context +def update_announcement(ctx, course_id: str, announcement_id: str): + """ + This command updates an announcement. Add --help + for all options available + """ + response = announcements_service.update_announcement(ctx.obj['SESSION'], course_id, announcement_id) + click.echo(response) diff --git a/bbcli/commands/contents.py b/bbcli/commands/contents.py new file mode 100644 index 0000000000000000000000000000000000000000..c80e7bfb7a24e6f458d419669572f5a2d2241f33 --- /dev/null +++ b/bbcli/commands/contents.py @@ -0,0 +1,31 @@ +import click +from bbcli.services import contents_service +from bbcli.views import content_view +import os + + +#, help='List a spesific course with the corresponding id' +@click.command(name='list') +@click.argument('course_id', required=True, type=str) +@click.argument('content_id', required=False, type=str) +# @click.option('-a', '--all/--no-all', 'show_all', default=False, help='Lists all courses you have ever been signed up for') +@click.pass_context +def list_contents(ctx, course_id: str=None, content_id: str=None): + + """ + This command lists contents of a course. + """ + response = None + + if content_id: + print('GEtting spesific content from a course') + else: + print('Printing content tree from a course, course', course_id) + + +@click.command(name='create') +@click.argument('course_id', required=True, type=str) +@click.argument('content_id', required=True, type=str) +@click.pass_context +def create_content(ctx, course_id: str, content_id: str): + contents_service.test_create_assignment(ctx.obj['SESSION'], course_id, content_id) diff --git a/bbcli/commands/courses.py b/bbcli/commands/courses.py new file mode 100644 index 0000000000000000000000000000000000000000..5094e3fe77d7cb768cf50c5b160060c462714f8a --- /dev/null +++ b/bbcli/commands/courses.py @@ -0,0 +1,32 @@ +import click +from bbcli.services import courses_service +from bbcli.views import course_view +import os +import requests + + +#, help='List a spesific course with the corresponding id' +@click.command(name='list') +@click.argument('course_id', required=False) +@click.option('-a', '--all/--no-all', 'show_all', default=False, help='Lists all courses you have ever been signed up for') +@click.pass_context +def list_courses(ctx, course_id=None, show_all=False): + + """ + This command lists your courses, by default only the courses from + two last semesters + """ + + response = None + + if course_id: + response = courses_service.list_course(session=ctx.obj['SESSION'], course_id=course_id) + course_view.print_course(response) + else: + user_name = os.getenv('BB_USERNAME') + if show_all: + response = courses_service.list_all_courses(session=ctx.obj['SESSION'], user_name=user_name) + else: + response = courses_service.list_courses(session=ctx.obj['SESSION'], user_name=user_name) + course_view.print_courses(response) + diff --git a/bbcli/controllers/announcement_controller.py b/bbcli/controllers/announcement_controller.py deleted file mode 100644 index c43810c9611b288d90adfa19460609a4087b13f5..0000000000000000000000000000000000000000 --- a/bbcli/controllers/announcement_controller.py +++ /dev/null @@ -1,41 +0,0 @@ -from email.policy import default -import click -from bbcli.services import announcement_service -from bbcli.views import announcement_view -import os -import requests - -from bbcli.utils.utils import set_cookies, set_headers - -@click.command(name='announcements') -@click.argument('course_id', required=False) -@click.argument('announcement_id', required=False) -def list_announcements(course_id=None, announcement_id=None): - - """ - This command lists your announcements. - Either all announcements, all announcements from a spesific course, or one announcement. - """ - - bb_cookie = { - 'name':'BbRouter', - 'value': os.getenv("BB_ROUTER") - } - xsrf = {'X-Blackboard-XSRF': os.getenv('XSRF')} - user_name = os.getenv('BB_USERNAME') - - session = requests.Session() - set_cookies(session, [bb_cookie]) - set_headers(session, [xsrf]) - - response = None - - if announcement_id: - response = announcement_service.list_announcement(session, course_id, announcement_id) - announcement_view.print_course_announcements([response]) - elif course_id: - response = announcement_service.list_course_announcements(session, course_id) - announcement_view.print_course_announcements(response) - else: - response = announcement_service.list_announcements(session, user_name) - announcement_view.print_announcements(response) diff --git a/bbcli/controllers/course_controller.py b/bbcli/controllers/course_controller.py deleted file mode 100644 index 9a919341c20b031cf4d4d03c7f9f83bb92510c78..0000000000000000000000000000000000000000 --- a/bbcli/controllers/course_controller.py +++ /dev/null @@ -1,41 +0,0 @@ -from email.policy import default -import click -from bbcli.services import course_service -from bbcli.views import course_view -import os -import requests -from bbcli.utils.utils import set_cookies, set_headers - - -#, help='List a spesific course with the corresponding id' -@click.command(name='courses') -@click.argument('course_id', required=False) -@click.option('-a', '--all/--no-all', 'show_all', default=False, help='Lists all courses you have ever been signed up for') -def list_courses(course_id=None, show_all=False): - - """ - This command lists your courses, by default only the courses from - two last semesters - """ - - bb_cookie = { - 'name':'BbRouter', - 'value': os.getenv("BB_ROUTER") - } - user_name = os.getenv('BB_USERNAME') - - session = requests.Session() - set_cookies(session, [bb_cookie]) - - response = None - - if course_id: - response = course_service.list_course(session=session, course_id=course_id) - course_view.print_course(response) - else: - if show_all: - response = course_service.list_all_courses(session=session, user_name=user_name) - else: - response = course_service.list_courses(session=session, user_name=user_name) - course_view.print_courses(response) - diff --git a/bbcli/services/announcement_service.py b/bbcli/services/announcements_service.py similarity index 98% rename from bbcli/services/announcement_service.py rename to bbcli/services/announcements_service.py index 2de1420d927df92fb927c342a9a5d2e2d3e0b225..e7351e356de99138d94bd5e06463f5fa83f10480 100644 --- a/bbcli/services/announcement_service.py +++ b/bbcli/services/announcements_service.py @@ -2,7 +2,7 @@ import json from subprocess import call from typing import Dict, Any import requests -from bbcli.services.course_service import list_courses +from bbcli.services.courses_service import list_courses from bbcli.utils.utils import set_cookies import click diff --git a/bbcli/services/content_service.py b/bbcli/services/content_service.py deleted file mode 100644 index 182b1049c503dc120c461b1703254cdb1010f2a9..0000000000000000000000000000000000000000 --- a/bbcli/services/content_service.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -from subprocess import call -from typing import Dict, Any -import requests -from bbcli.services.course_service import list_courses -import click - - -# User gets a tree structure view of the courses content -# where each content is listed something like this: _030303_1 Lectures Folder -def list_course_content(cookies: Dict, course_id: str): - print('Getting course content!') - - -# If it is a folder, list it like a tree structure view like mentioned above. -# If it is a document, download and open the document maybe? -# Find all types of content and have an appropriate response for them. This -# should maybe be handled in the view... -def get_content(cookies: Dict, course_id: str, content_id: str): - print('Getting content by its ID.') - - -# List all contents of type assignment, should be executed if a flag for example like --content-type assignment or smth is used -def list_assignments(cookies: Dict, course_id: str): - print('Getting all assignments') - -# TODO: add methods for all content types like the one above - - -# Create content. This should have a flag which says what kind of content type it is - - -# Create assignment. Creates an assignment - -# Delete spesific content - -# Update spesific content \ No newline at end of file diff --git a/bbcli/services/contents_service.py b/bbcli/services/contents_service.py new file mode 100644 index 0000000000000000000000000000000000000000..aa11c41624be9e4e0e42904fb34937ee9b9db91e --- /dev/null +++ b/bbcli/services/contents_service.py @@ -0,0 +1,86 @@ +import base64 +import json +import os +from subprocess import call +from tarfile import ENCODING +from typing import Dict, Any +import requests +from bbcli.services.courses_service import list_courses +from bbcli.utils.URL_builder import URLBuilder + +url_builder = URLBuilder() + +# User gets a tree structure view of the courses content +# where each content is listed something like this: _030303_1 Lectures Folder +def list_course_content(cookies: Dict, course_id: str): + print('Getting course content!') + + +# If it is a folder, list it like a tree structure view like mentioned above. +# If it is a document, download and open the document maybe? +# Find all types of content and have an appropriate response for them. This +# should maybe be handled in the view... +def get_content(cookies: Dict, course_id: str, content_id: str): + print('Getting content by its ID.') + + +# List all contents of type assignment, should be executed if a flag for example like --content-type assignment or smth is used +def list_assignments(cookies: Dict, course_id: str): + print('Getting all assignments') + +# TODO: add methods for all content types like the one above + + +# Create content. This should have a flag which says what kind of content type it is + + +# Create assignment. Creates an assignment + +# Delete spesific content + +# Update spesific content + + +def test_create_assignment(session: requests.Session, course_id: str, content_id: str): + + with open('/home/magnus/Downloads/3_meeting_notes.pdf', 'rb') as f: + byte_content = f.read() + + base64_bytes = base64.b64encode(byte_content) + base64_string = base64_bytes.decode(ENCODING) + + data = { + "parentId": content_id, + "title": "Test file", + 'body': 'jaja', + "description": "string", + "position": 0, + "launchInNewWindow": True, + "availability": { + "available": "Yes", + "allowGuests": True, + "allowObservers": True, + "adaptiveRelease": { + "start": "2022-03-29T09:32:35.571Z", + } + }, + "contentHandler": { + 'id':'resource/x-bb-file', + 'file': base64_string + }, + } + data = json.dumps(data) + session.headers.update({'Content-Type': 'application/json'}) + # files = { + # 'pdf_document': open('/home/magnus/Downloads/3_meeting_notes.pdf', 'rb') + # } + + + + # Returns the string: domain + /learn/api/public/v1/courses/{courseId}/contents/{contentId}/children + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().add_id(content_id).add_children().create() + + response = session.post(url, data=data) + print(response.text) + + \ No newline at end of file diff --git a/bbcli/services/course_service.py b/bbcli/services/courses_service.py similarity index 100% rename from bbcli/services/course_service.py rename to bbcli/services/courses_service.py diff --git a/bbcli/utils/URL_builder.py b/bbcli/utils/URL_builder.py index 126e55cde999460ab73e36097d918b248143cbcb..b8db2a82458fa540f8b6cce54bb44cbe153dcda1 100644 --- a/bbcli/utils/URL_builder.py +++ b/bbcli/utils/URL_builder.py @@ -48,9 +48,17 @@ class Builder(ABC): pass @abstractmethod - def add_terms(slef) -> Builder: + def add_terms(self) -> Builder: pass + @abstractmethod + def add_children(self) -> Builder: + pass + + @abstractmethod + def add_attachments(self) -> Builder: + pass + @abstractmethod def add_id(self, id: str, id_type: str = None) -> Builder: pass @@ -105,6 +113,14 @@ class URLBuilder(Builder): self._product.add('/terms') return self + def add_children(self) -> Builder: + self._product.add('/children') + return self + + def add_attachments(self) -> Builder: + self._product.add('/attachments') + return self + def add_id(self, id:str, id_type:str=None) -> URLBuilder: if id_type: self._product.add(f'/{id_type}:{id}') diff --git a/bbcli/views/announcement_view.py b/bbcli/views/announcement_view.py index 866af312e79460ec6ed2910b0513da259d0f3f16..5ca6bbf469ca7dac40c7f3a72e6950dc366332c3 100644 --- a/bbcli/views/announcement_view.py +++ b/bbcli/views/announcement_view.py @@ -4,11 +4,12 @@ from bbcli.utils.utils import html_to_text def print_announcements(announcements: List): + announcements.reverse() for course in announcements: print_course_announcements(course['course_announcements'], course['course_name']) def print_course_announcements(course_announcements: List, course_name: str = None): - + course_announcements.reverse() for announcement in course_announcements: if 'body' in announcement: announcement_id = announcement['id']