Skip to content

Instantly share code, notes, and snippets.

@0187773933
Created April 20, 2024 02:57
Show Gist options
  • Save 0187773933/bb2968438c14f359b5ec603984e55408 to your computer and use it in GitHub Desktop.
Save 0187773933/bb2968438c14f359b5ec603984e55408 to your computer and use it in GitHub Desktop.
Downloads Miro Board Backup
#!/usr/bin/env python3
import sys
import time
import json
import requests
from pprint import pprint
import mimetypes
from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor
from box import Box
from pathlib import Path
import concurrent.futures
import time
from ratelimit import rate_limited
# https://developers.miro.com/docs/rest-api-build-your-first-hello-world-app#step-1-create-your-app-in-miro
# https://miro.com/app/settings/user-profile/apps/#asdf
TOKEN = "asdf"
def write_json( file_path , python_object ):
with open( file_path , 'w', encoding='utf-8' ) as f:
json.dump( python_object , f , ensure_ascii=False , indent=4 )
def read_json( file_path ):
with open( file_path ) as f:
return json.load( f )
def get_file_extension( content_type ):
extension = mimetypes.guess_extension( content_type )
if extension:
return extension
# return ".bin"
return ".pdf"
def get_boards():
global TOKEN
headers = {
'accept': 'application/json, text/plain, */*',
'Authorization': f"Bearer {TOKEN}"
}
base_url = "https://api.miro.com/v2/boards"
boards_data = []
def fetch_page(url):
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
# Start with the first page
url = base_url
total = 0
while url:
data = fetch_page(url)
total = data["total"]
print( f"Downloaded [ {len(boards_data)} ] of {total}" )
for board in data['data']:
board_info = {
'name': board['name'],
'id': board['id']
}
boards_data.append(board_info)
url = data['links'].get('next', None)
print( len( boards_data ) )
time.sleep( 1 )
return boards_data
def get_item_details( board_id , item_id ):
global TOKEN
headers = {
'accept': 'application/json, text/plain, */*',
'Authorization': f"Bearer {TOKEN}"
}
url = f"https://api.miro.com/v2/boards/{board_id}/items/{item_id}"
response = requests.get( url , headers=headers )
response.raise_for_status()
json_result = response.json()
return json_result
def get_items_on_board( board_id ):
global TOKEN
headers = {
'accept': 'application/json, text/plain, */*',
'Authorization': f"Bearer {TOKEN}"
}
base_url = f"https://api.miro.com/v2/boards/{board_id}/items"
boards_data = []
cursor = False
def fetch_page(url):
if cursor:
response = requests.get( url , headers=headers , params={ "cursor" : cursor } )
else:
response = requests.get( url , headers=headers )
print( response.text )
response.raise_for_status()
return response.json()
# Start with the first page
url = base_url
total = 0
while url:
data = fetch_page(base_url)
total = data["total"]
print( f"Downloaded [ {len(boards_data)} ] of {total}" )
boards_data.extend( data['data'] )
# write_json( BoardName , boards_data )
url = data['links'].get('next', None)
if "cursor" in data:
cursor = data['cursor']
print( len( boards_data ) )
time.sleep( 6 )
return boards_data
@rate_limited( calls=1900 , period=60 )
def download_resource_with_redirect( url , id , save_path ):
global TOKEN
headers = {'Authorization': f"Bearer {TOKEN}"}
with requests.get( url , stream=True , headers=headers ) as r:
if r.status_code == 429:
print( "Rate Limited ...." )
print( r.text )
return
if r.status_code == 200:
content_type = r.headers.get( "content-type" )
file_extension = get_file_extension( content_type )
file_name = f"{id}.{file_extension}"
output_file_with_extension = save_path.joinpath( id + file_extension )
if Path(output_file_with_extension).is_file():
print(f"File already exists: {output_file_with_extension}")
return
total_size = int(r.headers.get('content-length', 0))
block_size = 1024
t = tqdm(total=total_size, unit='iB', unit_scale=True)
with open(str(output_file_with_extension), 'wb') as f:
for data in r.iter_content(block_size):
t.update(len(data))
f.write(data)
t.close()
# if total_size != 0 and t.n != total_size:
if total_size != 0 and t.n != total_size:
print("ERROR, something went wrong")
print( t.n , total_size )
# else:
# print(f"File downloaded and saved as: {output_file_with_extension}")
elif r.status_code == 307:
redirect_url = r.headers.get('location')
print(f"Received 307 Temporary Redirect. Following the redirect to: {redirect_url}")
download_resource_with_redirect(redirect_url, id , save_path)
else:
print(f"Unexpected status code: {r.status_code}, response content: {r.content}")
def find_frame_for_image( image_item , frames ):
image_x, image_y = image_item['position']['x'], image_item['position']['y']
image_width, image_height = image_item['geometry']['width'], image_item['geometry']['height']
image_origin = image_item['position']['origin']
for frame in frames:
frame_x, frame_y = frame['position']['x'], frame['position']['y']
frame_width, frame_height = frame['geometry']['width'], frame['geometry']['height']
frame_origin = frame['position']['origin']
# Adjust the image position based on its origin
if image_origin == 'center':
image_x -= image_width / 2
image_y -= image_height / 2
# Adjust the frame position based on its origin
if frame_origin == 'center':
frame_x -= frame_width / 2
frame_y -= frame_height / 2
# Check if the image is within the frame
if (
frame_x <= image_x <= frame_x + frame_width and
frame_y <= image_y <= frame_y + frame_height and
image_width <= frame_width and
image_height <= frame_height
):
return frame
return None
if __name__ == "__main__":
# 1.) Get All Boards
# boards = get_boards()
# write_json( "miro_boards.json" , boards )
# 2.) Get all items on a Board
board_name = "TMP"
board_id = "asdf=" # you can get the id from the embed url
board_data = get_items_on_board( board_id )
write_json( f"{board_name}.json" , board_data )
# 3.) Download actual Board Data ( images , documents )
board = read_json( f"{board_name}.json" )
frames = []
images = []
documents = []
embeds = []
# // cards , shapes , sticky_notes , text ( we don't care about these for now )
for x in board:
if x[ "type" ] == "frame":
frames.append( x )
elif x[ "type" ] == "image":
images.append( x )
elif x[ "type" ] == "document":
documents.append( x )
elif x[ "type" ] == "embed":
embeds.append( x )
print( f"Frames = {len( frames )}" )
print( f"Images = {len( images )}" )
print( f"Documents = {len( documents )}" )
print( f"Embeds = {len( embeds )}" )
save_path = Path.cwd().joinpath( f"{board_name}-attachments" )
save_path.mkdir( parents=True , exist_ok=True )
total_images = len( images )
for i , item in enumerate( images ):
url = item[ "data" ][ "imageUrl" ].split( "?format" )[ 0 ] + "?format=original&redirect=true"
print( f"Downloading Image [ {i+1} ] of {total_images} === {item[ 'id' ]} === {url}" )
download_resource_with_redirect( url , item[ "id" ] , save_path )
total_documents = len( documents )
for i , item in enumerate( documents ):
url = item[ "data" ][ "documentUrl" ].split( "?" )[ 0 ] + "?redirect=true"
print( f"Downloading Document [ {i+1} ] of {total_documents} === {item[ 'id' ]} === {url}" )
download_resource_with_redirect( url , item[ "id" ] , save_path )
# 4.) Reconstruct into HTML Canvas
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment