Skip to content
Snippets Groups Projects
Commit 8a664b69 authored by magnus2142's avatar magnus2142
Browse files

Merge branch 'magnus_feature_views' into main

parents 6e567093 b4f314ff
No related branches found
No related tags found
No related merge requests found
Showing
with 411 additions and 44 deletions
......@@ -17,7 +17,6 @@ def entry_point():
pass
entry_point.add_command(get_user)
entry_point.add_command(get_course_contents)
entry_point.add_command(get_assignments)
......
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)
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)
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
......@@ -3,16 +3,20 @@ from subprocess import call
from typing import Dict, Any
import requests
from bbcli.services.course_service import list_courses
from bbcli.utils.utils import set_cookies
import click
def list_announcements(cookies: Dict, user_name: str):
courses = list_courses(cookies=cookies, user_name=user_name)
from bbcli.utils.URL_builder import URLBuilder
session = requests.Session()
url_builder = URLBuilder()
def list_announcements(session: requests.Session, user_name: str):
courses = list_courses(session, user_name=user_name)
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)
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
......@@ -24,19 +28,21 @@ 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)
def list_course_announcements(session: requests.Session, course_id: str):
url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().create()
course_announcements = session.get(url)
course_announcements.raise_for_status()
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)
def list_announcement(session: requests.Session, course_id: str, announcement_id: str):
url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().add_id(announcement_id).create()
announcement = session.get(url)
announcement = json.loads(announcement.text)
return announcement
# TODO: Add compatibility for flags and options to make a more detailed announcement
def create_announcement(cookies: Dict, headers: Dict, course_id: str, title: str):
def create_announcement(session: requests.Session, course_id: str, title: str):
MARKER = '# Everything below is ignored\n'
body = click.edit('\n\n' + MARKER)
if body is not None:
......@@ -48,22 +54,24 @@ def create_announcement(cookies: Dict, headers: Dict, course_id: str, title: str
}
data = json.dumps(data)
headers['Content-Type'] = 'application/json'
session.headers.update({'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 = session.post(url, 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)
def delete_announcement(session: requests.Session, course_id: str, announcement_id: str):
url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().add_id(announcement_id).create()
response = session.delete(url)
if response.text == '':
return 'Sucessfully deleted announcement!'
else:
return response.text
def update_announcement(cookies: Dict, headers: Dict, course_id: str, announcement_id: str):
def update_announcement(session: requests.Session, course_id: str, announcement_id: str):
announcement = list_announcement(cookies=cookies, course_id=course_id, announcement_id=announcement_id)
announcement = list_announcement(session=session, course_id=course_id, announcement_id=announcement_id)
MARKER = '# Everything below is ignored\n'
editable_data = {
'title': announcement['title'],
......@@ -75,7 +83,9 @@ def update_announcement(cookies: Dict, headers: Dict, course_id: str, announceme
announcement = json.dumps(editable_data, indent=2)
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)
session.headers.update({'Content-Type': 'application/json'})
url = url_builder.base_v1().add_courses().add_id(course_id).add_announcements().add_id(announcement_id).create()
response = session.patch(url, data=new_data)
return response.text
\ No newline at end of file
......@@ -292,8 +292,9 @@ def write_to_env_data(session):
xsrf_value = xsrf[1]
f = open('.env', 'w')
f.write("BB_ROUTER=" + BB_ROUTER + "\n")
f.write("XSRF=" + xsrf_value + "\n")
f.write(f'BB_ROUTER={BB_ROUTER}\n')
f.write(f'XSRF={xsrf_value}\n')
f.write(f'BB_USERNAME={login_username}\n')
f.close()
......
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
import json
from typing import Dict, Any
from typing import Dict, Any, List
import requests
from datetime import date
def take_start_date(elem):
return date.fromisoformat(elem['availability']['duration']['start'].split('T')[0])
from bbcli.utils.URL_builder import URLBuilder
url_builder = URLBuilder()
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)
terms = json.loads(terms.text)['results']
# Sort terms by start date to get the two most recent semesters to determine which courses to show
for term in terms:
if term['availability']['duration']['type'] != 'DateRange':
terms.remove(term)
terms.sort(key=take_start_date)
def list_courses(session: requests.Session, user_name: str) -> Any:
terms = get_terms(session)
sort_terms(terms)
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)
course_memberships = json.loads(course_memberships.text)['results']
course_memberships = get_course_memberships(session, user_name)
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, 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({
......@@ -41,10 +35,53 @@ 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)
def list_all_courses(session: requests.Session, user_name: str) -> Any:
course_memberships = get_course_memberships(session, user_name)
course_list = []
for course in course_memberships:
url = url_builder.base_v3().add_courses().add_id(course['courseId']).create()
response = session.get(url, params={'fields': 'id, name'})
response = json.loads(response.text)
course_list.append(response)
return course_list
def list_course(session: requests.Session, course_id:str) -> Any:
url = url_builder.base_v3().add_courses().add_id(course_id).create()
response = session.get(url)
return json.loads(response.text)
def list_favorite_courses(cookies: Dict, user_name: str) -> Any:
def list_favorite_courses(session: requests.Session, user_name: str) -> Any:
return "Blackboard rest api do not have an option for this yet"
# response = requests.get('https://ntnu.blackboard.com/learn/api/public/v1/users/userName:{}/courses'.format(user_name), cookies=cookies)
"""
HELPER FUNCTIONS
"""
def take_start_date(elem):
return date.fromisoformat(elem['availability']['duration']['start'].split('T')[0])
def get_terms(session: requests.Session):
url = url_builder.base_v1().add_terms().create()
terms = session.get(url)
terms = json.loads(terms.text)['results']
return terms
def sort_terms(terms):
# Sort terms by start date to get the two most recent semesters to determine which courses to show
for term in terms:
if term['availability']['duration']['type'] != 'DateRange':
terms.remove(term)
terms.sort(key=take_start_date)
def get_course_memberships(session: requests.Session, user_name: str):
url = url_builder.base_v1().add_users().add_id(id=user_name, id_type='userName').add_courses().create()
course_memberships = session.get(url)
course_memberships = json.loads(course_memberships.text)['results']
return course_memberships
\ No newline at end of file
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
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
from datetime import datetime
from typing import Dict, List
from requests import Session
import html2text
def check_valid_key(obj, key) -> bool:
# print("the keys are", obj.keys())
......@@ -31,3 +35,16 @@ def check_valid_date(cookies) -> bool:
return False
def set_cookies(session: Session, cookies: List):
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
def set_headers(session: Session, headers: List):
for header in headers:
session.headers.update(header)
def html_to_text(html_data: str):
to_text = html2text.HTML2Text()
return to_text.handle(html_data)
\ No newline at end of file
import click
from typing import List
from bbcli.utils.utils import html_to_text
def print_announcements(announcements: List):
for course in announcements:
print_course_announcements(course['course_announcements'], course['course_name'])
def print_course_announcements(course_announcements: List, course_name: str = None):
for announcement in course_announcements:
if 'body' in announcement:
announcement_id = announcement['id']
title = announcement['title']
body = html_to_text(announcement['body'])
created = announcement['created'].split('T')[0]
click.echo('----------------------------------------------------------------------\n')
if course_name:
click.echo(f'{course_name}\n')
click.echo('{:<15} {:<15}'.format('Id: ', announcement_id))
click.echo('{:<15} {:<15}'.format('Title: ', title))
click.echo('{:<15} {:<15}'.format('Date: ', created))
click.echo('\n{:<15}\n'.format(body))
import click
def print_courses(courses):
click.echo('\n{:<12} {:<5}\n'.format('Id', 'Course Name'))
for course in courses:
course_id = course['id']
name = course['name']
click.echo('{:<12} {:<5}'.format(course_id, name))
click.echo('\n\n')
def print_course(course):
primary_id = course['id']
course_id = course['courseId']
name = course['name']
click.echo('\n{:<12} {:<12}'.format('Id:', primary_id))
click.echo('{:<12} {:<12}'.format('Course Id:', course_id))
click.echo('{:<12} {:<12}\n'.format('Name:', name))
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment