diff --git a/CHANGELOG.md b/CHANGELOG.md index 563d946..fe10f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.1] + +### Added + +- Added function to modify a row title and url by its id, this is a base for developing ideas on #8. +- Now it looks at all images available on the content added so we can get more information and AI tag every image. +- Click to open added content works for url's, texts and images now. +- When you add content, it tracks if it was added from your phone or desktop. You need to add a new text atribute called "mind_extension". You can use this to filter by elements added from phone or desktop. +### Fixed + +- Fixed error #9 +- Fixed error no output provided when url or title was none adding a url. +- Fixed error when on some websites where tagged as image not found when images were found. +- Moved utils function to a util file. + ## [2.0] ### Added @@ -54,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed server issues adding callback capabilities. Before some websites images were not added due to timeouts. +[2.0.1]: https://github.com/elblogbruno/NotionAI-MyMind/releases/tag/2.0.1 [2.0]: https://github.com/elblogbruno/NotionAI-MyMind/releases/tag/2.0 [1.9]: https://github.com/elblogbruno/NotionAI-MyMind/releases/tag/1.9 [1.8]: https://github.com/elblogbruno/NotionAI-MyMind/releases/tag/1.8 diff --git a/Python Server/app/NotionAI/NotionAI.py b/Python Server/app/NotionAI/NotionAI.py index ea7af73..941e731 100644 --- a/Python Server/app/NotionAI/NotionAI.py +++ b/Python Server/app/NotionAI/NotionAI.py @@ -1,42 +1,42 @@ from notion.client import NotionClient from notion.block import ImageBlock, TextBlock -import validators -import os - -from utils.custom_errors import OnImageNotFound, OnUrlNotValid, EmbedableContentNotFound, NoTagsFound +from utils.custom_errors import OnImageNotFound, OnUrlNotValid, NoTagsFound +import os import requests import json - import webbrowser import socket -from threading import Thread +from threading import Thread from image_tagging.image_tagging import ImageTagging -from utils.lang_utils import get_response_text +from NotionAI.utils import create_json_response, web_clipper_request, extract_image_from_content, \ + get_current_extension_name class NotionAI: def __init__(self, logging, port): self.logging = logging logging.info("Initiating NotionAI Class.") - if os.path.isfile('data.json'): + if os.path.isfile('data.json'): # If we have a data.json with credentials, we start with these credentials print("Initiating with a found config file.") logging.info("Initiating with a found config file.") self.loaded = self.run(logging) - else: + else: # Instead it is the first time running the server, so we open the server url. self.open_browser_at_start(port) def open_browser_at_start(self, port): hostname = socket.gethostname() local_ip = socket.gethostbyname(hostname) - final_url = "http://" + str(local_ip) + ":" + str(port) + final_url = "http://{0}:{1}/".format(str(local_ip), str(port)) + print("You should go to the homepage and set the credentials. The url will open in your browser now. If can't " "access a browser you can access {0}".format(final_url)) + self.logging.info("You should go to the homepage and set the credentials. The url will open in your browser " - "now. If can't " - "access a browser you can access {0}".format(final_url)) + "now. If can't access a browser you can access {0}".format(final_url)) + webbrowser.open(final_url) def run(self, logging, email=None, password=None): @@ -71,11 +71,15 @@ def run(self, logging, email=None, password=None): self.mind_id = mind_page.id - self.image_tagger = ImageTagging(data, logging) + self.image_tagger = ImageTagging(data, logging) # we initialize the image tagger with our data. cv = self.client.get_collection_view(self.data['url']) - self.collection = cv.collection + self.collection = cv.collection # collection is our database or "mind" in notion + + self.counter = 0 + + self.times_to_retry = 5 # if no image is found initially, it will retry this many times loaded = True except requests.exceptions.HTTPError: @@ -83,26 +87,30 @@ def run(self, logging, email=None, password=None): return loaded def add_url_to_database(self, url, title): - self.logging.info("Adding url to mind: {0} {1}".format(url.encode('utf8'), title.encode('utf8'))) - self.statusCode = 200 # at start we asume everything will go ok - try: - rowId = self.web_clipper_request(url, title) + if url is None or title is None: + self.statusCode = 500 + return create_json_response(self) + else: + self.logging.info("Adding url to mind: {0} {1}".format(url.encode('utf8'), title.encode('utf8'))) + self.statusCode = 200 # at start we asume everything will go ok + try: + rowId = web_clipper_request(self, url, title) - thread = Thread(target=self.add_url_thread, args=(rowId,)) - thread.daemon = True # Daemonize thread - thread.start() # Start the execution + thread = Thread(target=self.add_url_thread, args=(rowId,)) + thread.daemon = True # Daemonize thread + thread.start() # Start the execution - return self.create_json_response(rowId=rowId) + return create_json_response(self, rowId=rowId) - except OnUrlNotValid as invalidUrl: - self.logging.info(invalidUrl) - self.statusCode = 500 - return self.create_json_response() - except AttributeError as e: - self.logging.info(e) - print(e) - self.statusCode = 404 - return self.create_json_response() + except OnUrlNotValid as invalidUrl: + self.logging.info(invalidUrl) + self.statusCode = 500 + return create_json_response(self) + except AttributeError as e: + self.logging.info(e) + print(e) + self.statusCode = 404 + return create_json_response(self) def add_text_to_database(self, text, url): self.logging.info("Adding text to mind: {0} {1}".format(url.encode('utf8'), text.encode('utf8'))) @@ -110,14 +118,16 @@ def add_text_to_database(self, text, url): try: if url == "" or text == "": self.statusCode = 500 - return self.create_json_response() + return create_json_response(self) else: + row = self.collection.add_row() + self.row = row thread = Thread(target=self.add_text_thread, args=(url, text,)) thread.daemon = True # Daemonize thread thread.start() # Start the execution - return self.create_json_response() + return create_json_response(self, rowId=row.id) except requests.exceptions.HTTPError as invalidUrl: print(invalidUrl) self.logging.info(invalidUrl) @@ -128,7 +138,7 @@ def add_text_to_database(self, text, url): self.logging.info(e) print(e) self.statusCode = 404 - return self.create_json_response() + return create_json_response(self) def add_image_to_database(self, image_src, url=None, image_src_url=None): is_local = image_src_url is None and url is None @@ -136,54 +146,76 @@ def add_image_to_database(self, image_src, url=None, image_src_url=None): if is_local: self.logging.info("Adding image to mind: {0}".format(image_src.encode('utf8'))) else: - self.logging.info("Adding image to mind: {0} {1} {2}".format(url.encode('utf8'), image_src.encode('utf8'), + self.logging.info("Adding image to mind: {0} {1} {2}".format(image_src.encode('utf8'), url.encode('utf8'), image_src_url.encode('utf8'))) - self.statusCode = 200 # at start we asume everything will go ok try: + row = self.collection.add_row() + self.row = row thread = Thread(target=self.add_image_thread, args=(image_src, url, image_src_url, is_local,)) thread.daemon = True # Daemonize thread thread.start() # Start the execution - return self.create_json_response() + return create_json_response(self, rowId=row.id) except requests.exceptions.HTTPError as invalidUrl: self.logging.info(invalidUrl) self.statusCode = 500 - return self.create_json_response() + return create_json_response(self) + except AttributeError as e: + self.logging.info(e) + print(e) + self.statusCode = 404 + return create_json_response(self) + + def modify_row_by_id(self, id, title, url): + self.statusCode = 204 # at start we asume everything will go ok + try: + block = self.client.get_block(id) + + if url == "" or title == "": + self.statusCode = 500 + return create_json_response(self) + else: + block.title = title + block.url = url + return create_json_response(self) + + except OnUrlNotValid as invalidUrl: + self.logging.info(invalidUrl) + self.statusCode = 500 + return create_json_response(self) except AttributeError as e: self.logging.info(e) print(e) self.statusCode = 404 - return self.create_json_response() + return create_json_response(self) def row_callback(self, record, difference): - if len(self.row.AITagsText) == 0: + if len(self.row.AITagsText) == 0 and len(self.row.mind_extension) == 0 and len(difference[0][-1][0][1]) != 0: print("Callback from row. Here's what was changed:") - print(difference) self.page_content = difference[0][-1][0][1] try: - img_url = self.extract_image_from_content(self.page_content) + img_url_list = extract_image_from_content(self, self.page_content, record.id) try: self.row.remove_callbacks(self.row_callback) - self.add_tags_to_row(img_url, False) + self.add_tags_to_row(img_url_list, False) except NoTagsFound as e: print(e) + self.add_tags_to_row(None, False) self.logging.info(e) except ValueError as e: print(e) + self.add_tags_to_row(None, False) self.logging.info(e) except Exception as e: - print(e) + self.add_tags_to_row(None, False) self.logging.info(e) except OnImageNotFound as e: print(e) - self.row.AITagsText = "no-tags-available" - self.logging.info(e) - except EmbedableContentNotFound as e: - print(e) + self.add_tags_to_row(None, False) self.logging.info(e) else: print("This row is added already") @@ -201,9 +233,7 @@ def add_url_thread(self, rowId): self.logging.info("Thread %s: finishing", rowId) def add_text_thread(self, url, text): - row = self.collection.add_row() - self.row = row - + row = self.row self.logging.info("Add text Thread %s: starting", row.id) row.name = "Extract from " + url @@ -216,10 +246,7 @@ def add_text_thread(self, url, text): self.logging.info("Add text Thread %s: finished", row.id) def add_image_thread(self, image_src, url=None, image_src_url=None, is_local=False): - row = self.collection.add_row() - - self.row = row - + row = self.row self.logging.info("Image add Thread %s: starting", row.id) img_block = row.children.add_new(ImageBlock) @@ -227,7 +254,6 @@ def add_image_thread(self, image_src, url=None, image_src_url=None, is_local=Fal if is_local: img_block.upload_file(image_src) row.name = "Image from phone" - else: img_block.source = image_src row.name = "Image from " + str(image_src_url) @@ -235,65 +261,26 @@ def add_image_thread(self, image_src, url=None, image_src_url=None, is_local=Fal row.icon = img_block.source row.person = self.client.current_user + self.analyze_image_thread([image_src], row, is_image_local=is_local) - self.analyze_image_thread(image_src, row, is_image_local=is_local) - - def add_tags_to_row(self, img_url, is_image_local): - self.logging.info("Adding tags to image {0}".format(img_url)) - tags = self.image_tagger.get_tags(img_url, is_image_local) - self.logging.info("Tags from image {0} : {1}".format(img_url, tags)) - self.row.AITagsText = tags - - def extract_image_from_content(self, page_content): - url = " " - for element in page_content: - im = self.client.get_block(element) - block_type = im.get('type') - if block_type == 'image': - url = im.source - break - if url == " ": - raise OnImageNotFound("Thumbnail Image URL not found. Value is None", self) - else: - print(url) - self.logging.info(url) - return url - - def web_clipper_request(self, url, title): - cookies = { - 'token_v2': self.token_v2, - } - - headers = { - 'Content-Type': 'application/json', - } - if title == None: - title = url - - is_well_formed = validators.url(url) - if is_well_formed: - url_object = { - "url": url, - "title": title - } - data_dict = { - "type": "block", - "blockId": "{}".format(self.mind_id), - "property": "P#~d", - "items": [url_object], - "from": "chrome" - } - data = json.dumps(data_dict) - - self.logging.info(data) - response = requests.post('https://www.notion.so/api/v3/addWebClipperURLs', headers=headers, cookies=cookies, - data=data) - response_text = response.text - json_response = json.loads(response_text) - rowId = json_response['createdBlockIds'][0] - return rowId + def add_tags_to_row(self, img_url_list, is_image_local): + if img_url_list is None or len(img_url_list) == 0: + self.logging.info("No image was found or no tags are available.") + self.row.AITagsText = "no-tags-available" else: - raise OnUrlNotValid("Invalid url was sent", self) + self.logging.info("Found {0} images.".format(len(img_url_list))) + result = "" + for img_url in img_url_list: + self.logging.info("Adding tags to image {0}".format(img_url)) + tags = self.image_tagger.get_tags(img_url, is_image_local) + self.logging.info("Tags from image {0} : {1}".format(img_url, tags)) + result = tags + "," + result + self.row.AITagsText = result + self.row.mind_extension = get_current_extension_name(self.request_platform) + + # sets the current platform making the request, so we know if content is added from phone or desktop + def set_mind_extension(self, platform): + self.request_platform = platform def analyze_image_thread(self, image_src, row, is_image_local=False): try: @@ -311,31 +298,3 @@ def analyze_image_thread(self, image_src, row, is_image_local=False): except OnImageNotFound as e: print(e) self.logging.info(e) - except EmbedableContentNotFound as e: - print(e) - self.logging.info(e) - - def create_json_response(self, status_code=None, rowId=None): - url = "https://github.com/elblogbruno/NotionAI-MyMind#love-to-try-it" - - if status_code is None: - status_code = self.statusCode - - if rowId is not None: - rowIdExtracted = rowId.split("-") - str1 = ''.join(str(e) for e in rowIdExtracted) - url = "https://www.notion.so/" + str1 - - text_response, status_text = get_response_text(status_code) - - x = { - "status_code": status_code, - "text_response": text_response, - "status_text": status_text, - "block_url": url - } - - # convert into JSON: - json_response = json.dumps(x) - - return json_response diff --git a/Python Server/app/NotionAI/utils.py b/Python Server/app/NotionAI/utils.py new file mode 100644 index 0000000..ad43b48 --- /dev/null +++ b/Python Server/app/NotionAI/utils.py @@ -0,0 +1,128 @@ +import json +from utils.lang_utils import get_response_text +from utils.custom_errors import OnUrlNotValid, OnImageNotFound +from notion.block import ImageBlock +from time import sleep + +import requests +import validators + + +##Makes a web request to the notion web clipper API to add url's and returns the rowId +def web_clipper_request(self, url, title): + cookies = { + 'token_v2': self.token_v2, + } + + headers = { + 'Content-Type': 'application/json', + } + if title == None: + title = url + + is_well_formed = validators.url(url) + if is_well_formed: + url_object = { + "url": url, + "title": title + } + data_dict = { + "type": "block", + "blockId": "{}".format(self.mind_id), + "property": "P#~d", + "items": [url_object], + "from": "chrome" + } + data = json.dumps(data_dict) + + self.logging.info(data) + response = requests.post('https://www.notion.so/api/v3/addWebClipperURLs', headers=headers, cookies=cookies, + data=data) + response_text = response.text + json_response = json.loads(response_text) + rowId = json_response['createdBlockIds'][0] + return rowId + else: + raise OnUrlNotValid("Invalid url was sent", self) + + +##Extracts all images from content as a list of url's +def extract_image_from_content(self, page_content, row_id): + print("Will look for images on {}".format(page_content)) + list_of_img_url = [] + ##This loop looks at all the page content and finds every image on it. + for element in page_content: + im = self.client.get_block(element) + block_type = im.get('type') + if block_type == 'image': + list_of_img_url.append(im.source) + elif len(im.children) > 0: + for child in im.children: + if isinstance(child, ImageBlock): + list_of_img_url.append(child.source) + + if len(list_of_img_url) == 0 and self.counter == self.times_to_retry: + self.counter = 0 + raise OnImageNotFound("Thumbnail Image URL not found. Value is None", self) + elif len(list_of_img_url) > 0: + self.counter = 0 + self.logging.info("These images were found: " + str(list_of_img_url)) + return list_of_img_url + else: + row = self.client.get_block(row_id) + row.refresh() + content = row.get('content') + sleep(0.15) + self.counter += 1 + return self.extract_image_from_content(content, row_id) + return list_of_img_url + + +def create_json_response(self, status_code=None, rowId=None): + url = "https://github.com/elblogbruno/NotionAI-MyMind#love-to-try-it" + + block_title = "-1" + block_attached_url = "-1" + + if status_code is None: + status_code = self.statusCode + + if rowId is not None: + rowIdExtracted = rowId.split("-") + str1 = ''.join(str(e) for e in rowIdExtracted) + url = "https://www.notion.so/" + str1 + row = self.client.get_block(rowId) + block_title = row.title + block_attached_url = row.url + + text_response, status_text = get_response_text(status_code) + + if len(block_attached_url) == 0: + block_attached_url = "-1" + + if len(block_title) == 0: + block_title = "-1" + + x = { + "status_code": status_code, + "text_response": text_response, + "status_text": status_text, + "block_url": url, + "block_title": block_title, + "block_attached_url": block_attached_url, + } + + # convert into JSON: + json_response = json.dumps(x) + + return json_response + + +# based on the machine doing the request we know which extension is being used +def get_current_extension_name(platform): + if platform is None or len(platform) == 0: + return "Unknown mind extension" + elif platform in ['android', 'ipad', 'iphone', 'symbian', 'blackberry']: + return "phone-app-extension" + else: + return "browser-extension" diff --git a/Python Server/app/server.py b/Python Server/app/server.py index 52cdac6..93f1b9b 100644 --- a/Python Server/app/server.py +++ b/Python Server/app/server.py @@ -1,6 +1,6 @@ import logging -from quart import Quart, render_template, flash, request, redirect +from quart import Quart, render_template, flash, request from werkzeug.utils import secure_filename import secrets @@ -8,6 +8,7 @@ from NotionAI.NotionAI import * from utils.utils import ask_server_port, save_options, save_data, createFolder + UPLOAD_FOLDER = '../app/uploads/' ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'webp']) @@ -24,6 +25,7 @@ async def add_url_to_mind(): url = request.args.get('url') title = request.args.get('title') + notion.set_mind_extension(request.user_agent.platform) return str(notion.add_url_to_database(url, title)) @@ -31,9 +33,11 @@ async def add_url_to_mind(): async def add_text_to_mind(): url = request.args.get('url') text = request.args.get('text') + notion.set_mind_extension(request.user_agent.platform) if len(request.args) > 2: l = request.args.to_dict() text = text + " & " + str(list(l)[-1]) + return str(notion.add_text_to_database(text, url)) @@ -42,7 +46,17 @@ async def add_image_to_mind(): url = request.args.get('url') image_src = request.args.get('image_src') image_src_url = request.args.get('image_src_url') - return str(notion.add_image_to_database(url, image_src, image_src_url)) + notion.set_mind_extension(request.user_agent.platform) + return str(notion.add_image_to_database(image_src, url, image_src_url)) + + +@app.route('/modify_element_by_id') +async def modify_element_by_id(): + id = request.args.get('id') + title = request.args.get('new_title') + url = request.args.get('new_url') + notion.set_mind_extension(request.user_agent.platform) + return str(notion.modify_row_by_id(id, title, url)) def allowed_file(filename): diff --git a/Python Server/app/utils/custom_errors.py b/Python Server/app/utils/custom_errors.py index e467f4c..68428f3 100644 --- a/Python Server/app/utils/custom_errors.py +++ b/Python Server/app/utils/custom_errors.py @@ -28,22 +28,6 @@ def __str__(self): return 'OnImageUrlNotValid has been raised' -class EmbedableContentNotFound(Exception): - def __init__(self, *args): - if args: - self.message = args[0] - else: - self.message = None - args[1].statusCode = 409 - # args[1].row.remove() #removes the row beacuse it will be blank - - def __str__(self): - if self.message: - return 'EmbedableContentNotFound, {0} '.format(self.message) - else: - return 'EmbedableContentNotFound has been raised' - - class NoTagsFound(Exception): def __init__(self, *args): if args: diff --git a/Python Server/app/utils/lang_utils.py b/Python Server/app/utils/lang_utils.py index d521236..a68664e 100644 --- a/Python Server/app/utils/lang_utils.py +++ b/Python Server/app/utils/lang_utils.py @@ -5,5 +5,7 @@ def get_response_text(status_code): return "Added to your mind.", "success" elif status_code == 404: return "No Notion credentials where provided. Please add them on the server.", "error" + elif status_code == 204: + return "Title and Url was modified correctly", "success" else: return "Invalid url or text was provided.", "error" diff --git a/flutter-android-ios-app/notion_ai_my_mind/lib/overlay_view.dart b/flutter-android-ios-app/notion_ai_my_mind/lib/overlay_view.dart index 010913c..055ec19 100644 --- a/flutter-android-ios-app/notion_ai_my_mind/lib/overlay_view.dart +++ b/flutter-android-ios-app/notion_ai_my_mind/lib/overlay_view.dart @@ -1,11 +1,7 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:notion_ai_my_mind/Arguments.dart'; import 'package:notion_ai_my_mind/api/apiresponse.dart'; -import 'package:notion_ai_my_mind/main.dart'; import 'package:notion_ai_my_mind/resources/strings.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'api/api.dart';