Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Browse & Access Upload #58

Closed
philips opened this issue Feb 7, 2025 · 5 comments
Closed

Implement Browse & Access Upload #58

philips opened this issue Feb 7, 2025 · 5 comments

Comments

@philips
Copy link
Owner

philips commented Feb 7, 2025

Similar to #48 create a feature that uses Browse & Access to upload to a Supernote.

@mmujynya
Copy link

Hi Brandon,

Not Typescript but Python that I use in PySN, but that may help:
I use the following snippet. The SN webserver never overwrites a file, but postfixes it with an auto increment value, if the file exists at destination.

Hope that helps.
Max

async def upload_file(file_path, upload_url, filename):
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
async with aiohttp.ClientSession() as session:
# Read the file data
async with aiofiles.open(file_path, 'rb') as f:
file_data = await f.read()
# Create multipart form body as bytes
body = (
f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
f'Content-Type: {mimetypes.guess_type(file_path)[0]}\r\n\r\n'
).encode() + file_data + (
f'\r\n--{boundary}\r\n'
f'Content-Disposition: form-data; name="fileinfo"\r\n'
f'Content-Type: application/json\r\n\r\n'
f'{{"name": "{filename}"}}\r\n'
f'--{boundary}--\r\n'
).encode()
headers = {
'Content-Type': f'multipart/form-data; boundary={boundary}'
}
async with session.post(upload_url, data=body, headers=headers) as response:
if response.status != 200:
print(f"*** Error: Failed to upload {file_path}, status: {response.status}")

@philips
Copy link
Owner Author

philips commented Feb 12, 2025

I appreciate that @mmujynya !

@philips
Copy link
Owner Author

philips commented Feb 12, 2025

Whatis the upload_url @mmujynya ?

@mmujynya
Copy link

Sorry Brandon, I realize now that copying and pasting from my phone made the code unreadable and that it was incomplete.
The url passed to "upload_url", as you can see towards the bottom, is the "url_export", that is a concatenation of the root_url and the SN file path (with some extra prefix if the path is a path on a microSD card)
I added the code I use to detect the microSD card as well and I hope the below is more readable(I added some comments).

############################## GLOBAL VARIABLES ################################
SUPERNOTE_FOLDERS = [
'Document', 'EXPORT', 'MyStyle',
'Note', 'SCREENSHOT', 'INBOX', 'SDcard']

############################## BASE FUNCTION FOR MICRO USD ################################
async def lanon_and_microsdname(url):
""" Checks if the Supernote is reachable through LAN on and returns the microSD card prefix, if any"""

async with aiohttp.ClientSession() as session:
    html = await fetch(session, url)
    if html is None:  # Error connecting
        return False, ''
    
    soup = BeautifulSoup(html, 'html.parser')

    # Find the script containing the data
    script_text = ''
    found = False
    for script in soup.find_all("script"):
        if 'const json =' in script.string or 'const json=' in script.string:  # Check if the script contains the JSON variable
            script_text = script.string
            found = True
            break
    if not found:
        print("No script with JSON data found.")
        return False, ''
    
    # Extract JSON data from the script
    match = re.search(r"('.*?')", script_text, re.DOTALL)
    if match:
        json_text = match.group(1)
        data = json.loads(json_text[1:-1])
    else:
        print("Failed to extract JSON data.")
        return False, ''

    # Find uri that is not in the default list
    default_list = SUPERNOTE_FOLDERS
    disk_uri_list = [x['uri'] for x in data['fileList'] if x['name'] not in default_list]
    if len(disk_uri_list) > 0:
        microsd_card = disk_uri_list[0]
        return True, microsd_card

    print(f'  > Connected to {url}')
    print()
    return True, ''

############################## BASE FUNCTION ################################
async def upload_file(file_path, upload_url, filename):

boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
async with aiohttp.ClientSession() as session:
    # Read the file data
    async with aiofiles.open(file_path, 'rb') as f:
        file_data = await f.read()

    # Create multipart form body as bytes
    body = (
        f'--{boundary}\r\n'
        f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
        f'Content-Type: {mimetypes.guess_type(file_path)[0]}\r\n\r\n'
    ).encode() + file_data + (
        f'\r\n--{boundary}\r\n'
        f'Content-Disposition: form-data; name="fileinfo"\r\n'
        f'Content-Type: application/json\r\n\r\n'
        f'{{"name": "{filename}"}}\r\n'
        f'--{boundary}--\r\n'
    ).encode()

    headers = {
        'Content-Type': f'multipart/form-data; boundary={boundary}'
    }

    async with session.post(upload_url, data=body, headers=headers) as response:
        if response.status != 200:
            print(f"*** Error: Failed to upload {file_path}, status: {response.status}")

############################# WRAPPING FUNCTION USED ####################################
async def async_upload(file_to_upload, upload_url, filename):
await upload_file(file_to_upload, upload_url, filename)

############################# EARLIER VARIABLE AFFECTATION ####################################

<... other code ...>
root_url = f'http://{an_ip}:{SN_IO_PORT}' # an_ip is the address of the SN webserver and SN_IO_PORT is 8089
lan_is_on, external_prefix = asyncio.run(lanon_and_microsdname(root_url)) # checking if webserver running at root_url and retrieving the exgternal_prefix for microsd card

############################# BRINGING ALL TOGETHER WITH A CALL TO THE WRAPPER ###################################

<... other code ...>
    elif transfer_mode == 'webserver':  # For transfers via WIFI
        try:
            source_sn_folder_path = ('/').join(source_sn_folder_list[:-1])
            url_export = f'{root_url}/{source_sn_folder_path}'
            root_folder = source_sn_folder_list[0]

            if root_folder not in SUPERNOTE_FOLDERS:   # Path to a microsd card (usually with "REMOVABLE0")
                url_export = f'{root_url}{external_prefix}'+('/').join(source_sn_folder_list[1:-1])

            if os.path.exists(file_name):
                asyncio.run(async_upload(file_name, url_export, file_basename))

        except Exception as e:
            print(e)
            print(f"**- Could not upload {f'{basename}.note'}. Is the SN access - Browse mode on?")
<... other code ...>

philips added a commit that referenced this issue Feb 12, 2025
Enable users to upload files to their Supernote via Browse and Access. #58
@philips
Copy link
Owner Author

philips commented Feb 12, 2025

fixed via d82542d

@philips philips closed this as completed Feb 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants