Example python plugin (#825)

* Add example python plugin
* Fix log incorrectly detecting as progress level
This commit is contained in:
WithoutPants
2020-10-12 08:20:20 +11:00
committed by GitHub
parent ade109d9e4
commit 4f9af6ba27
5 changed files with 319 additions and 0 deletions

View File

@@ -52,6 +52,7 @@ var (
}
ProgressLevel = Level{
char: 'p',
Name: "progress",
}
NoneLevel = Level{
Name: "none",

View File

@@ -0,0 +1,44 @@
import sys
# Log messages sent from a plugin instance are transmitted via stderr and are
# encoded with a prefix consisting of special character SOH, then the log
# level (one of t, d, i, w, e, or p - corresponding to trace, debug, info,
# warning, error and progress levels respectively), then special character
# STX.
#
# The LogTrace, LogDebug, LogInfo, LogWarning, and LogError methods, and their equivalent
# formatted methods are intended for use by plugin instances to transmit log
# messages. The LogProgress method is also intended for sending progress data.
#
def __prefix(levelChar):
startLevelChar = b'\x01'
endLevelChar = b'\x02'
ret = startLevelChar + levelChar + endLevelChar
return ret.decode()
def __log(levelChar, s):
if levelChar == "":
return
print(__prefix(levelChar) + s + "\n", file=sys.stderr, flush=True)
def LogTrace(s):
__log(b't', s)
def LogDebug(s):
__log(b'd', s)
def LogInfo(s):
__log(b'i', s)
def LogWarning(s):
__log(b'w', s)
def LogError(s):
__log(b'e', s)
def LogProgress(p):
progress = min(max(0, p), 1)
__log(b'p', str(progress))

View File

@@ -0,0 +1,123 @@
import json
import sys
import time
import log
from stash_interface import StashInterface
# raw plugins may accept the plugin input from stdin, or they can elect
# to ignore it entirely. In this case it optionally reads from the
# command-line parameters.
def main():
input = None
if len(sys.argv) < 2:
input = readJSONInput()
log.LogDebug("Raw input: %s" % json.dumps(input))
else:
log.LogDebug("Using command line inputs")
mode = sys.argv[1]
log.LogDebug("Command line inputs: {}".format(sys.argv[1:]))
input = {}
input['args'] = {
"mode": mode
}
# just some hard-coded values
input['server_connection'] = {
"Scheme": "http",
"Port": 9999,
}
output = {}
run(input, output)
out = json.dumps(output)
print(out + "\n")
def readJSONInput():
input = sys.stdin.read()
return json.loads(input)
def run(input, output):
modeArg = input['args']["mode"]
try:
if modeArg == "" or modeArg == "add":
client = StashInterface(input["server_connection"])
addTag(client)
elif modeArg == "remove":
client = StashInterface(input["server_connection"])
removeTag(client)
elif modeArg == "long":
doLongTask()
elif modeArg == "indef":
doIndefiniteTask()
except Exception as e:
raise
#output["error"] = str(e)
#return
output["output"] = "ok"
def doLongTask():
total = 100
upTo = 0
log.LogInfo("Doing long task")
while upTo < total:
time.sleep(1)
log.LogProgress(float(upTo) / float(total))
upTo = upTo + 1
def doIndefiniteTask():
log.LogWarning("Sleeping indefinitely")
while True:
time.sleep(1)
def addTag(client):
tagName = "Hawwwwt"
tagID = client.findTagIdWithName(tagName)
if tagID == None:
tagID = client.createTagWithName(tagName)
scene = client.findRandomSceneId()
if scene == None:
raise Exception("no scenes to add tag to")
tagIds = []
for t in scene["tags"]:
tagIds.append(t["id"])
# remove first to ensure we don't re-add the same id
try:
tagIds.remove(tagID)
except ValueError:
pass
tagIds.append(tagID)
input = {
"id": scene["id"],
"tag_ids": tagIds
}
log.LogInfo("Adding tag to scene {}".format(scene["id"]))
client.updateScene(input)
def removeTag(client):
tagName = "Hawwwwt"
tagID = client.findTagIdWithName(tagName)
if tagID == None:
log.LogInfo("Tag does not exist. Nothing to remove")
return
log.LogInfo("Destroying tag")
client.destroyTag(tagID)
main()

View File

@@ -0,0 +1,29 @@
# example plugin config
name: Hawwwwt Tagger (Raw Python edition)
description: Python Hawwwwt tagging utility (using raw interface).
version: 1.0
url: http://www.github.com/stashapp/stash
exec:
- python
- "{pluginDir}/pyplugin.py"
interface: raw
tasks:
- name: Add hawwwwt tag to random scene
description: Creates a "Hawwwwt" tag if not present and adds to a random scene.
defaultArgs:
mode: add
- name: Remove hawwwwt tag from system
description: Removes the "Hawwwwt" tag from all scenes and deletes the tag.
defaultArgs:
mode: remove
- name: Indefinite task
description: Sleeps indefinitely - interruptable
# we'll try command-line argument for this one
execArgs:
- indef
- "{pluginDir}"
- name: Long task
description: Sleeps for 100 seconds - interruptable
defaultArgs:
mode: long

View File

@@ -0,0 +1,122 @@
import requests
class StashInterface:
port = ""
url = ""
headers = {
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json",
"Accept": "application/json",
"Connection": "keep-alive",
"DNT": "1"
}
def __init__(self, conn):
self.port = conn['Port']
scheme = conn['Scheme']
self.url = scheme + "://localhost:" + str(self.port) + "/graphql"
# TODO - cookies
def __callGraphQL(self, query, variables = None):
json = {}
json['query'] = query
if variables != None:
json['variables'] = variables
# handle cookies
response = requests.post(self.url, json=json, headers=self.headers)
if response.status_code == 200:
result = response.json()
if result.get("error", None):
for error in result["error"]["errors"]:
raise Exception("GraphQL error: {}".format(error))
if result.get("data", None):
return result.get("data")
else:
raise Exception("GraphQL query failed:{} - {}. Query: {}. Variables: {}".format(response.status_code, response.content, query, variables))
def findTagIdWithName(self, name):
query = """
query {
allTags {
id
name
}
}
"""
result = self.__callGraphQL(query)
for tag in result["allTags"]:
if tag["name"] == name:
return tag["id"]
return None
def createTagWithName(self, name):
query = """
mutation tagCreate($input:TagCreateInput!) {
tagCreate(input: $input){
id
}
}
"""
variables = {'input': {
'name': name
}}
result = self.__callGraphQL(query, variables)
return result["tagCreate"]["id"]
def destroyTag(self, id):
query = """
mutation tagDestroy($input: TagDestroyInput!) {
tagDestroy(input: $input)
}
"""
variables = {'input': {
'id': id
}}
self.__callGraphQL(query, variables)
def findRandomSceneId(self):
query = """
query findScenes($filter: FindFilterType!) {
findScenes(filter: $filter) {
count
scenes {
id
tags {
id
}
}
}
}
"""
variables = {'filter': {
'per_page': 1,
'sort': 'random'
}}
result = self.__callGraphQL(query, variables)
if result["findScenes"]["count"] == 0:
return None
return result["findScenes"]["scenes"][0]
def updateScene(self, sceneData):
query = """
mutation sceneUpdate($input:SceneUpdateInput!) {
sceneUpdate(input: $input) {
id
}
}
"""
variables = {'input': sceneData}
self.__callGraphQL(query, variables)