Initial commit
This commit is contained in:
commit
611d7ad39f
5 changed files with 283 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
config.ini
|
0
README.md
Normal file
0
README.md
Normal file
12
config.ini
Normal file
12
config.ini
Normal file
|
@ -0,0 +1,12 @@
|
|||
[taiga]
|
||||
url = https://taiga.example.com
|
||||
username = taigauser@example.com
|
||||
password = mysupersecretpassword
|
||||
|
||||
project_slug = 3d-printing
|
||||
|
||||
userstory_use_custom_field = True
|
||||
userstory_custom_field_name = Platform link
|
||||
|
||||
initial_task_status = New
|
||||
|
268
importer.py
Normal file
268
importer.py
Normal file
|
@ -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))
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
python-taiga
|
||||
requests
|
Loading…
Reference in a new issue