|
#/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) |
|
|