diff --git a/bbcli/cli.py b/bbcli/cli.py index 58b5ac3785d5b33a3a767f0d22077e7533af095c..005d5846c9730f5618a708ffaa52aed073a3a37b 100644 --- a/bbcli/cli.py +++ b/bbcli/cli.py @@ -5,8 +5,6 @@ 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_contents -# 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 @@ -14,7 +12,7 @@ import click 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.commands.contents import list_contents, get_content from bbcli.services.authorization_service import login def initiate_session(): @@ -83,12 +81,8 @@ def contents(ctx): """ pass -entry_point.add_command(get_user) -entry_point.add_command(get_course_contents) -entry_point.add_command(get_contents) - contents.add_command(list_contents) -contents.add_command(create_content) +contents.add_command(get_content) load_dotenv() cookies = {'BbRouter' : os.getenv("BB_ROUTER")} diff --git a/bbcli/commands/contents.py b/bbcli/commands/contents.py index a80011fd1103e119f8e619a5cc12565fcaf4d543..e144b931804cd9720d56ea7f73008858ca224338 100644 --- a/bbcli/commands/contents.py +++ b/bbcli/commands/contents.py @@ -1,27 +1,65 @@ import click from bbcli.services import contents_service -from bbcli.views import content_view -import os +# from bbcli.views.contents_view import list_tree, create_tree, get_content +from bbcli.views import contents_view +import time +import click +from bbcli import check_response +from bbcli.entities.Node import Node +from bbcli.entities.Node2 import Node2 +from bbcli.utils.URL_builder import URLBuilder +from bbcli.utils.content_handler import content_handler -#, 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): +url_builder = URLBuilder() - """ - This command lists contents of a course. - """ - response = None +base_url = 'https://ntnu.blackboard.com/learn/api/public/v1/' - if content_id: - print('GEtting spesific content from a course') +@click.command(name='list') +@click.argument('course_id', default='_27251_1') +@click.option('--folder-id') +@click.pass_context +def list_contents(ctx, course_id: str, folder_id=None): + ''' + Get the contents\n + Folders are blue and have an id \n + Files are white + ''' + start = time.time() + response = contents_service.list_contents(ctx.obj['SESSION'], course_id, folder_id) + print(response) + if folder_id is not None: + data = response.json() + root = Node(data, True) + worklist = [root] + res = get_children(ctx, course_id, worklist, []) + contents_view.create_tree(root, res) else: - print('Printing content tree from a course, course', course_id) + folders = response.json()['results'] + root = None + for folder in folders: + root = Node(folder, True) + worklist = [root] + res = get_children(ctx, course_id, worklist, []) + contents_view.create_tree(root, res) + + end = time.time() + + print(f'\ndownload time: {end - start} seconds') +@click.command(name='get') +@click.argument('course_id', required=True, type=str) +@click.argument('node_id', required=True, type=str) +@click.pass_context +def get_content(ctx, course_id: str, node_id: str): + response = contents_service.get_content(ctx.obj['SESSION'], course_id, node_id) + data = response.json() + if data['contentHandler']['id'] == content_handler['document']: + contents_view.open_vim() + elif data['contentHandler']['id'] == content_handler['file'] or data['contentHandler']['id'] == content_handler['document'] or data['contentHandler']['id'] == content_handler['assignment']: + click.confirm("This is a .docx file, do you want to download it?", abort=True) + response = contents_service.get_file(ctx.obj['SESSION'], course_id, node_id) + @click.command(name='create') @click.argument('course_id', required=True, type=str) @@ -30,167 +68,25 @@ def list_contents(ctx, course_id: str=None, content_id: str=None): def create_content(ctx, course_id: str, content_id: str): contents_service.test_create_assignment(ctx.obj['SESSION'], course_id, content_id) - - - -import time -import requests -import bbcli.cli as cli -import click - -from anytree import Node as Nd, RenderTree -from colorama import Fore, Style - -from bbcli import check_response -from bbcli.entities.Node import Node -from bbcli.entities.Node2 import Node2 -from bbcli.utils.URL_builder import URLBuilder - -url_builder = URLBuilder() - -base_url = 'https://ntnu.blackboard.com/learn/api/public/v1/' - -def get_children(session, worklist, url, acc, count: int = 0): +def get_children(ctx, course_id, worklist, acc, count: int = 0): count = count + 1 key = 'hasChildren' if len(worklist) == 0: return acc else: node = worklist.pop() - id = node.data['id'] - tmp = url[:url.index('contents') + len('contents')] - old = f'{tmp}/{id}/children' - response = session.get(old, cookies=cli.cookies) + node_id = node.data['id'] + response = contents_service.get_children(ctx.obj['SESSION'], course_id, node_id) if check_response(response) == False: return acc else: children = response.json()['results'] for i in range(len(children)): - # TODO: Add list of children instead of bool if key in children[i] and children[i][key] == True: - # if children[i]['contentHandler'] == content_types['folder']: child = Node(children[i], True, node) worklist.append(child) acc.append(child) else: child = Node(children[i], False, node) acc.append(child) - return get_children(session, worklist, url, acc) - -def get_children2(session, worklist, url, acc, count: int = 0): - count = count + 1 - key = 'hasChildren' - if len(worklist) == 0: - return - else: - node = worklist.pop() - id = node.data['id'] - tmp = url[:url.index('contents') + len('contents')] - old = f'{tmp}/{id}/children' - response = session.get(old, cookies=cli.cookies) - if check_response(response) == False: - return acc - else: - children = response.json()['results'] - parent = Node2(node) - for i in range(len(children)): - # TODO: Add list of children instead of bool - if key in children[i] and children[i][key] == True: - # if children[i]['contentHandler'] == content_types['folder']: - # child = Node(children[i], True, parent) - child = Node2(children[i]) - parent.children.append(child) - worklist.append(child) - # acc.append(child) - else: - child = Node2(children[i]) - parent.children.append(child) - # child = Node(children[i], False, parent) - # acc.append(child) - return get_children(session, worklist, url, acc) - - -@click.command(name='get-contents') -@click.argument('course_id', default='_27251_1') -@click.option('--folder-id') -def get_contents(course_id: str, folder_id=None): - ''' - Get the contents\n - Folders are blue and have an id \n - Files are white - ''' - session = requests.Session() - if folder_id is not None: - url = f'{base_url}courses/{course_id}/contents/{folder_id}' - else: - url = f'{base_url}courses/{course_id}/contents' - start = time.time() - response = session.get(url, cookies=cli.cookies) - if check_response(response) == False: - return - else: - if folder_id is not None: - data = response.json() - root = Node(data, True) - worklist = [root] - res = get_children(session, worklist, url, []) - create_tree(root, res) - else: - folders = response.json()['results'] - root = None - for folder in folders: - root = Node(folder, True) - worklist = [root] - get_children(session, worklist, url, []) - print(root.data['title']) - - for child in root.children: - print(child.data['title']) - - end = time.time() - - print(f'\ndownload time: {end - start} seconds') - -def list_tree(root, contents): - for content in contents: - parent = Nd(content.parent.data['title']) - this = Nd(content.data['title'], parent) - - for pre, fill, node in RenderTree(root_node): - print("%s%s" % (pre, node.name)) - -def create_tree(root, nodes): - parents = [] - root_node = Nd(root.data['title']) - parent = root_node - parents.append(parent) - folders = dict() - folders[root.data['title']] = root.data['id'] - - for i in range(len(nodes)): - id = nodes[i].data['id'] - title = nodes[i].data['title'] - if (nodes[i].has_children): - for parent in parents: - if (parent.name == nodes[i].parent.data['title']): - node = Nd(title, parent) - folders[title] = id - parents.append(node) - continue - - node = Nd(title, root_node) - folders[title] = id - parents.append(node) - - else: - for parent in parents: - if (parent.name == nodes[i].parent.data['title']): - node = Nd(title, parent) - folders[title] = '' - - for pre, fill, node in RenderTree(root_node): - folder_id = folders[node.name] - if folder_id == '': - print("%s%s" % (pre, node.name)) - else: - print(f'{pre}{Fore.BLUE}{folder_id} {node.name} {Style.RESET_ALL}') + return get_children(ctx, course_id, worklist, acc) \ No newline at end of file diff --git a/bbcli/services/contents_service.py b/bbcli/services/contents_service.py index aa11c41624be9e4e0e42904fb34937ee9b9db91e..f78cb9884705eb71bf98321b2f30872f8101904f 100644 --- a/bbcli/services/contents_service.py +++ b/bbcli/services/contents_service.py @@ -8,20 +8,62 @@ import requests from bbcli.services.courses_service import list_courses from bbcli.utils.URL_builder import URLBuilder +from bbcli.utils.utils import check_response + 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!') - +def list_contents(session: requests.Session, course_id, folder_id): + if folder_id is not None: + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().add_id(folder_id).create() + else: + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().create() + response = session.get(url) + if check_response(response) is False: + return + else: + return response + +# get the children of a specific folder +def get_children(session: requests.Session, course_id: str, node_id: str): + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().add_id(node_id).add_children().create() + return session.get(url) # 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.') +def get_content(session: requests.Session, course_id: str, node_id: str): + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().add_id(node_id).create() + print(url) + return session.get(url) + +def get_file(session: requests.Session, course_id: str, node_id: str): + # https://ntnu.blackboard.com/learn/api/public/v1/courses/_27251_1/contents/_1685326_1 + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().add_id(node_id).add_attachments() + current = url.create() + response = session.get(current) + data = response.json() + if check_response(response) == False: + return + else: + print("kommer her") + id = data['results'][0]['id'] + # url = url.add_id(id).add_download().create() + url = url_builder.base_v1().add_courses().add_id(course_id).add_contents().add_id(node_id).add_attachments().add_id(id).add_download().create() + print(url) + response = session.get(url) + print(response.headers) + + import urllib.request + with urllib.request.urlopen(url) as f: + html = f.read().decode('utf-8') + print(html) + + return response + + # List all contents of type assignment, should be executed if a flag for example like --content-type assignment or smth is used diff --git a/bbcli/utils/URL_builder.py b/bbcli/utils/URL_builder.py index b8db2a82458fa540f8b6cce54bb44cbe153dcda1..9045c682f0b26f3f0736c13f2d2a77df92209fec 100644 --- a/bbcli/utils/URL_builder.py +++ b/bbcli/utils/URL_builder.py @@ -63,6 +63,10 @@ class Builder(ABC): def add_id(self, id: str, id_type: str = None) -> Builder: pass + @abstractmethod + def add_download(self) -> Builder: + pass + class URLBuilder(Builder): @@ -121,6 +125,10 @@ class URLBuilder(Builder): self._product.add('/attachments') return self + def add_download(self) -> Builder: + self._product.add('/download') + return self + def add_id(self, id:str, id_type:str=None) -> URLBuilder: if id_type: self._product.add(f'/{id_type}:{id}') @@ -128,6 +136,7 @@ class URLBuilder(Builder): self._product.add(f'/{id}') return self + def create(self) -> str: url = self._product.get_url() self._product = URL() diff --git a/bbcli/utils/content_handler.py b/bbcli/utils/content_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..006d63bc880b5b1874a663d057138daea1c4e847 --- /dev/null +++ b/bbcli/utils/content_handler.py @@ -0,0 +1,10 @@ +content_handler = dict() +content_handler['document'] = 'resource/x-bb-document' +content_handler['externallink'] = 'resource/x-bb-externallink' +content_handler['folder'] = 'resource/x-bb-folder' +content_handler['courselinnk'] = 'resource/x-bb-courselink' +content_handler['forumlink'] = 'resource/x-bb-forumlink' +content_handler['blti-link'] = 'resource/x-bb-blti-link' +content_handler['file'] = 'resource/x-bb-file' +content_handler['asmt-test-link'] = 'resource/x-bb-asmt-test-link' +content_handler['assignment'] = 'resource/x-bb-assignment' \ No newline at end of file diff --git a/bbcli/views/content_view.py b/bbcli/views/content_view.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/bbcli/views/contents_view.py b/bbcli/views/contents_view.py new file mode 100644 index 0000000000000000000000000000000000000000..626261fe41c693bc26263913c0bc86081e1d6f44 --- /dev/null +++ b/bbcli/views/contents_view.py @@ -0,0 +1,73 @@ +from anytree import Node as Nd, RenderTree +from colorama import Fore, Style +from bbcli.utils.utils import html_to_text +import click + +def list_tree(root, contents): + for content in contents: + parent = Nd(content.parent.data['title']) + this = Nd(content.data['title'], parent) + + for pre, fill, node in RenderTree(root_node): + print("%s%s" % (pre, node.name)) + +def create_tree(root, nodes): + parents = [] + root_node = Nd(root.data['title']) + parent = root_node + parents.append(parent) + folders = dict() + folders[root.data['title']] = root.data['id'] + + for i in range(len(nodes)): + id = nodes[i].data['id'] + title = nodes[i].data['title'] + if (nodes[i].has_children): + for parent in parents: + if (parent.name == nodes[i].parent.data['title']): + node = Nd(title, parent) + folders[title] = id + parents.append(node) + continue + + node = Nd(title, root_node) + folders[title] = id + parents.append(node) + + else: + for parent in parents: + if (parent.name == nodes[i].parent.data['title']): + node = Nd(title, parent) + folders[title] = '' + + for pre, fill, node in RenderTree(root_node): + folder_id = folders[node.name] + if folder_id == '': + click.echo("%s%s" % (pre, node.name)) + else: + click.echo(f'{pre}{Fore.BLUE}{folder_id} {node.name} {Style.RESET_ALL}') + +import sys, tempfile, os +from subprocess import call + +def open_vim(data): + str += data['title'] + '\n' + str += html_to_text(data['body']) + + EDITOR = os.environ.get('EDITOR','vim') #that easy! + + # initial_message = b"" # if you want to set up the file somehow + # initial_message = bytearray(str) + initial_message = bytearray(str, encoding='utf8') + + + with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: + tf.write(initial_message) + tf.flush() + call([EDITOR, tf.name]) + + # do the parsing with `tf` using regular File operations. + # for instance: + tf.seek(0) + edited_message = tf.read() + print (edited_message.decode("utf-8"))