|
|
|
@ -0,0 +1,268 @@ |
|
|
|
|
import os |
|
|
|
|
import uuid |
|
|
|
|
import tempfile |
|
|
|
|
import requests |
|
|
|
|
import configparser |
|
|
|
|
import urllib.parse |
|
|
|
|
from taiga import TaigaAPI |
|
|
|
|
|
|
|
|
|
# Config parsing |
|
|
|
|
CONFIG=configparser.ConfigParser() |
|
|
|
|
CONFIG.read("config.ini") |
|
|
|
|
|
|
|
|
|
# Static variables for scrape requests to printables.com |
|
|
|
|
UUID=str(uuid.uuid4()) |
|
|
|
|
ENDPOINT_URL="https://api.printables.com/graphql/" |
|
|
|
|
HEADERS = { |
|
|
|
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0", |
|
|
|
|
"Accept": "application/json, text/plain, */*", |
|
|
|
|
"Accept-Language": "en", |
|
|
|
|
"Content-Type": "application/json", |
|
|
|
|
"Referer": "https://www.printables.com/", |
|
|
|
|
"operation": "PrintProfile", |
|
|
|
|
"apollographql-client-version": "v2.46.2", |
|
|
|
|
"Client-Uid": UUID, |
|
|
|
|
"Origin": "https://www.printables.com", |
|
|
|
|
"Sec-Fetch-Dest": "empty", |
|
|
|
|
"Sec-Fetch-Mode": "cors", |
|
|
|
|
"Sec-Fetch-Site": "same-site", |
|
|
|
|
"Authorization": "", |
|
|
|
|
"Connection": "keep-alive" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
def get_printables_print_data(model_id): |
|
|
|
|
PARAMS = '{"operationName":"PrintProfile","variables":{"id":"' + str(model_id) + '''"},"query":"query PrintProfile($id: ID!) { |
|
|
|
|
print(id: $id) { |
|
|
|
|
...PrintDetailFragment |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fragment PrintDetailFragment on PrintType { |
|
|
|
|
id |
|
|
|
|
slug |
|
|
|
|
name |
|
|
|
|
authorship |
|
|
|
|
description |
|
|
|
|
hasModel |
|
|
|
|
summary |
|
|
|
|
printDuration |
|
|
|
|
numPieces |
|
|
|
|
weight |
|
|
|
|
nozzleDiameters |
|
|
|
|
usedMaterial |
|
|
|
|
layerHeights |
|
|
|
|
materials { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
filesCount |
|
|
|
|
pdfFilePath |
|
|
|
|
userGcodeCount |
|
|
|
|
printer { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
images { |
|
|
|
|
...ImageSimpleFragment |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
tags { |
|
|
|
|
name |
|
|
|
|
id |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
thingiverseLink |
|
|
|
|
filesType |
|
|
|
|
foundInUserGcodes |
|
|
|
|
remixParents { |
|
|
|
|
...remixParentDetail |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
gcodes { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
filePath |
|
|
|
|
fileSize |
|
|
|
|
filePreviewPath |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
stls { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
filePath |
|
|
|
|
fileSize |
|
|
|
|
filePreviewPath |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
slas { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
filePath |
|
|
|
|
fileSize |
|
|
|
|
filePreviewPath |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
...LatestCompetitionResult |
|
|
|
|
competitions { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
slug |
|
|
|
|
description |
|
|
|
|
isOpened |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
competitionResults { |
|
|
|
|
placement |
|
|
|
|
competition { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
slug |
|
|
|
|
printsCount |
|
|
|
|
openedFrom |
|
|
|
|
openedTo |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fragment ImageSimpleFragment on PrintImageType { |
|
|
|
|
id |
|
|
|
|
filePath |
|
|
|
|
rotation |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fragment remixParentDetail on PrintRemixType { |
|
|
|
|
id |
|
|
|
|
parentPrintId |
|
|
|
|
parentPrintName |
|
|
|
|
parentPrintAuthor { |
|
|
|
|
id |
|
|
|
|
slug |
|
|
|
|
publicUsername |
|
|
|
|
company |
|
|
|
|
verified |
|
|
|
|
handle |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
parentPrint { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
slug |
|
|
|
|
datePublished |
|
|
|
|
images { |
|
|
|
|
...ImageSimpleFragment |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
license { |
|
|
|
|
id |
|
|
|
|
name |
|
|
|
|
disallowRemixing |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
eduProject { |
|
|
|
|
id |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
url |
|
|
|
|
urlAuthor |
|
|
|
|
urlImage |
|
|
|
|
urlTitle |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fragment LatestCompetitionResult on PrintType { |
|
|
|
|
latestCompetitionResult { |
|
|
|
|
placement |
|
|
|
|
competitionId |
|
|
|
|
__typename |
|
|
|
|
} |
|
|
|
|
__typename |
|
|
|
|
}"}''' |
|
|
|
|
PARAMS=PARAMS.replace("\n", "\\n") |
|
|
|
|
|
|
|
|
|
req = requests.post(ENDPOINT_URL, headers=HEADERS, data=PARAMS) |
|
|
|
|
print_data = req.json()["data"]["print"] |
|
|
|
|
return print_data |
|
|
|
|
|
|
|
|
|
def get_modelid_from_url(url): |
|
|
|
|
parsed_url = urllib.parse.urlparse(url) |
|
|
|
|
path = parsed_url.path |
|
|
|
|
path_components = path.split("/") |
|
|
|
|
path_components = list(filter(lambda x: x != "", path_components)) #filter empty elements |
|
|
|
|
if path_components[0] != "model": |
|
|
|
|
raise Exception("Only direct links to models are supported") |
|
|
|
|
model_slug = path_components[1] |
|
|
|
|
model_id = model_slug.split("-")[0] |
|
|
|
|
return model_id |
|
|
|
|
|
|
|
|
|
link = input("Please paste link: ") |
|
|
|
|
model_id = get_modelid_from_url(link) |
|
|
|
|
print_data = get_printables_print_data(model_id) |
|
|
|
|
api = TaigaAPI(host=CONFIG["taiga"]["url"]) |
|
|
|
|
api.auth( |
|
|
|
|
username=CONFIG["taiga"]["username"], |
|
|
|
|
password=CONFIG["taiga"]["password"] |
|
|
|
|
) |
|
|
|
|
proj = api.projects.get_by_slug(CONFIG["taiga"]["project_slug"]) |
|
|
|
|
|
|
|
|
|
# Create userstory for printable |
|
|
|
|
story = proj.add_user_story( |
|
|
|
|
print_data["name"], |
|
|
|
|
description = print_data["description"], |
|
|
|
|
tags = list(map(lambda x: x.get("name"), print_data["tags"]))) |
|
|
|
|
|
|
|
|
|
# Set custom field for platform link if enabled and configured |
|
|
|
|
if bool(CONFIG["taiga"]["userstory_use_custom_field"]): |
|
|
|
|
us_attributes = list(map(lambda x: {"id": x.id, "name": x.name}, api.user_story_attributes.list())) |
|
|
|
|
attribute_id = list(filter(lambda x: CONFIG["taiga"]["userstory_custom_field_name"] in x["name"], us_attributes))[0]["id"] |
|
|
|
|
story.set_attribute(attribute_id, link) |
|
|
|
|
|
|
|
|
|
# Find id for desired status of newly tasks |
|
|
|
|
task_statuses = list(map(lambda x: {"id": x.id, "name": x.name}, api.task_statuses.list())) |
|
|
|
|
task_status_id = list(filter(lambda x: CONFIG["taiga"]["initial_task_status"] == x["name"], task_statuses))[0]["id"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tmpdir = tempfile.TemporaryDirectory() #workdir for downloads |
|
|
|
|
|
|
|
|
|
imagepath = print_data["images"][0]["filePath"] |
|
|
|
|
imageurl = urllib.parse.urljoin("https://media.printables.com", imagepath) |
|
|
|
|
local_file = os.path.join(tmpdir.name, os.path.basename(imagepath)) |
|
|
|
|
print("Downloading first image of printable to {}…".format(local_file), end="") |
|
|
|
|
with requests.get(imageurl, stream=True) as r: |
|
|
|
|
r.raise_for_status() |
|
|
|
|
with open(local_file, "wb") as f: |
|
|
|
|
for chunk in r.iter_content(chunk_size=8192): |
|
|
|
|
f.write(chunk) |
|
|
|
|
print(" done.") |
|
|
|
|
|
|
|
|
|
story.attach(local_file) |
|
|
|
|
|
|
|
|
|
stls = list(filter(lambda x: x["filePath"].endswith(".stl"), print_data["stls"])) |
|
|
|
|
stl_files = list(map(lambda x: {"name": x["name"], "filePath": x["filePath"]},stls)) |
|
|
|
|
for stl_file in stl_files: |
|
|
|
|
stlpath = stl_file["filePath"] |
|
|
|
|
filename = os.path.basename(stlpath) |
|
|
|
|
print("Creating task for file {}…".format(filename), end="") |
|
|
|
|
|
|
|
|
|
task = story.add_task(filename, task_status_id) |
|
|
|
|
|
|
|
|
|
stlurl = urllib.parse.urljoin("https://files.printables.com", stlpath) |
|
|
|
|
local_file = os.path.join(tmpdir.name, filename) |
|
|
|
|
with requests.get(stlurl, stream=True) as r: |
|
|
|
|
r.raise_for_status() |
|
|
|
|
with open(local_file, "wb") as f: |
|
|
|
|
for chunk in r.iter_content(chunk_size=8192): |
|
|
|
|
f.write(chunk) |
|
|
|
|
task.attach(local_file) |
|
|
|
|
print(" done.") |
|
|
|
|
|
|
|
|
|
newstory_url= urllib.parse.urljoin(CONFIG["taiga"]["url"], os.path.join("project", CONFIG["taiga"]["project_slug"], "us", str(story.ref))) |
|
|
|
|
print("New story created at: {}".format(newstory_url)) |