Skip to content

Instantly share code, notes, and snippets.

@catlan
Last active December 18, 2015 00:22
Show Gist options
  • Save catlan/542e28a273609bc7d986 to your computer and use it in GitHub Desktop.
Save catlan/542e28a273609bc7d986 to your computer and use it in GitHub Desktop.
Script to update the style in fastspring by @lukele

Install

sudo easy_install pip
sudo pip install requests
sudo pip install pyquery

Settings

Change FS_COMPANY, FS_USERNAME, and FS_PASSWORD to your login details.

Usage

zip -r FancyStyle.zip FancyStyle && python ./fastspring-style-updater.py FancyStyle FancyStyle.zip

Credits

Written by Lukas Pitschl (@lukele)

#/usr/bin/python
import logging
import requests
import sys
import os
from pyquery import PyQuery as pq
FS_API_URL = "https://springboard.fastspring.com"
FS_COMPANY = ""
FS_USERNAME = ""
FS_PASSWORD = ""
FS_SESSIONID_NAME = "jsessionid"
FS_STYLES_OVERVIEW_URL = "/site/configuration/all.xml"
FS_LOGIN_FIELD_MAP = {
"login:company": FS_COMPANY,
"login:username": FS_USERNAME,
"login:password": FS_PASSWORD
}
if len(sys.argv) < 3:
print "usage: python fastspring-style-updater.py <style> <filename.zip>"
sys.exit(1)
STYLE = sys.argv[1]
FILENAME = sys.argv[2]
if not os.path.isfile(FILENAME):
print "Error: %s doesn't exist" % (FILENAME)
sys.exit(1)
if not FS_COMPANY.strip() or not FS_USERNAME.strip() or not FS_PASSWORD.strip():
print "Make sure to configure the authentication settings."
sys.exit(1)
# logging.basicConfig()
# logging.getLogger().setLevel(logging.DEBUG)
# requests_log = logging.getLogger("requests.packages.urllib3")
# requests_log.setLevel(logging.DEBUG)
# requests_log.propagate = True
#
def apiURL(path):
if path.find(FS_API_URL) != -1:
return path
return "%s%s" % (FS_API_URL, path)
def sessionIDFromAdminIndexPage(adminIndexPage):
idx = adminIndexPage.find("%s=" % (FS_SESSIONID_NAME))
if idx == -1:
return None
rest = adminIndexPage[idx:]
idxEnd = rest.find("?")
if idxEnd == -1:
return None
return rest[0:idxEnd].replace("%s=" % (FS_SESSIONID_NAME), "")
def login(loginFieldMap):
# Load login.xml site to receive default form data and most importantly
# javax.faces.ViewState which seems to be required.
loginIndexPageRequest = requests.get(apiURL("/login.xml"))
indexPageNode = pq(loginIndexPageRequest.content)
defaultFormData = {}
for field in indexPageNode.find("form#login input"):
fieldNode = pq(field)
fieldName = pq(field).attr("name")
fieldValue = fieldNode.attr("value")
defaultFormData[fieldName] = fieldValue
defaultFormData.update(loginFieldMap)
adminIndexPage = requests.post(apiURL("/login.xml"), data=defaultFormData)
return {"sessionID": sessionIDFromAdminIndexPage(adminIndexPage.content),
"styleOverviewURL": styleOverviewURLFromAdminIndexPage(adminIndexPage.content)}
def injectSessionIDIntoURL(sessionID, URL):
idx = URL.find("?")
subURL = URL[:idx]
queryData = URL[idx:]
return apiURL("%s;%s=%s%s" % (subURL, FS_SESSIONID_NAME, sessionID, queryData))
def styleOverviewURLFromAdminIndexPage(adminIndexPage):
adminIndexPageNode = pq(adminIndexPage)
for link in adminIndexPageNode.find("a[href]"):
href = pq(link).attr("href")
if href.find(FS_STYLES_OVERVIEW_URL) != -1:
return href
return None
def findStyleEditPageURLByName(pageInfo, name):
def urlFromOnClickValue(onclickValue):
idx = onclickValue.find("'")
rest = onclickValue[idx+1:]
endIdx = rest.find("'")
return rest[0:endIdx]
overviewPage = requests.get(injectSessionIDIntoURL(pageInfo["sessionID"], pageInfo["styleOverviewURL"]))
overviewPageNode = pq(overviewPage.content)
for row in overviewPageNode.find(".panel-body table tr"):
rowNode = pq(row)
if not rowNode.attr("onclick"):
continue
styleName = rowNode.find("td.dataTableStatusColumn").next().text()
if styleName == name:
return urlFromOnClickValue(rowNode.attr("onclick"))
return None
def uploadDidSucceed(page, filename):
pageNode = pq(page)
if not pageNode.find(".windowMessageInfo").size():
return False
content = pageNode.find(".windowMessageInfo").eq(0).text()
if content.find("Received uploaded") == -1 or content.find(filename) == -1:
return False
if pageNode.find(".windowMessageInfo").size() < 2:
return False
content = pageNode.find(".windowMessageInfo").eq(1).text()
if content.find("was saved") == -1:
return False
return True
def updateStyleWithFile(style, filename):
pageInfo = login(FS_LOGIN_FIELD_MAP)
editPageURL = apiURL(findStyleEditPageURLByName(pageInfo, style))
editPage = requests.get(editPageURL)
formData = {}
editPageNode = pq(editPage.content)
formNode = editPageNode.find("#form")
fileElementName = None
for element in formNode.find(":input"):
elementNode = pq(element)
add = True
if (elementNode.is_(":checkbox") or elementNode.is_(":radio")) and not elementNode.attr("checked"):
add = False
#else:
#print "Add %s - %s checked!" % (elementNode.attr("name"), elementNode.attr("value"))
if elementNode.attr("type") == "file":
fileElementName = elementNode.attr("name")
add = False
if add:
if elementNode.attr("name") in formData and type(formData[elementNode.attr("name")]) != type([]):
currentValue = formData[elementNode.attr("name")]
formData[elementNode.attr("name")] = []
formData[elementNode.attr("name")].append(currentValue)
if elementNode.attr("name") in formData:
formData[elementNode.attr("name")].append(elementNode.attr("value"))
else:
formData[elementNode.attr("name")] = elementNode.attr("value")
#else:
#print "Not adding: %s - %s" % (elementNode.attr("name"), elementNode.attr("value"))
formData["form:saveAction"] = "form:saveAction"
# DEBUG. Change the Hide company field setting.
#del formData["form:j_idt159"][0]
# print formData
# print fileElementName
response = requests.post(editPageURL, data=formData, files={fileElementName: (filename, open(filename, "rb"))}, allow_redirects=False)
if response.status_code == 302:
response = requests.get(injectSessionIDIntoURL(pageInfo["sessionID"], response.headers["location"]))
if uploadDidSucceed(response.content, filename):
print "Successfully updated %s" % (style)
else:
print "Failed to update %s" % (style)
updateStyleWithFile(STYLE, FILENAME)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment