Reestablished working state
This commit is contained in:
@@ -7,3 +7,5 @@ COPY requirements.txt .
|
|||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ENTRYPOINT python initialize.py
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
from plugins import rmass
|
||||||
|
from core import buildOperation
|
||||||
|
|
||||||
|
outputs = buildOperation(rmass.operations.checkavailable)
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -4,8 +4,8 @@ from .request import buildRequest
|
|||||||
|
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
|
|
||||||
def executeRequest(definition, outputs):
|
def executeRequest(action, outputs):
|
||||||
request = buildRequest(definition, outputs)
|
request, outputs = buildRequest(action, outputs)
|
||||||
|
|
||||||
response = session.request(
|
response = session.request(
|
||||||
method=request["method"],
|
method=request["method"],
|
||||||
@@ -23,4 +23,4 @@ def executeRequest(definition, outputs):
|
|||||||
# sessioncookies[cookie_name] = response[cookie_key],
|
# sessioncookies[cookie_name] = response[cookie_key],
|
||||||
|
|
||||||
|
|
||||||
return response
|
return response, outputs
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ def buildRequest(definition, outputs):
|
|||||||
|
|
||||||
request["headers"] = headers
|
request["headers"] = headers
|
||||||
|
|
||||||
return request
|
return request, outputs
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,24 +2,17 @@ from core.apirequest import executeRequest
|
|||||||
from core.runtime.savedata import loadSave, buildSave
|
from core.runtime.savedata import loadSave, buildSave
|
||||||
from core.runtime.outputs import buildOutput
|
from core.runtime.outputs import buildOutput
|
||||||
from core.runtime.logs import buildLog
|
from core.runtime.logs import buildLog
|
||||||
|
from core.runtime.runtimecontract import buildRuntimeAttr
|
||||||
|
|
||||||
def buildAction (action, outputs):
|
def buildAction (action, outputs):
|
||||||
|
|
||||||
if hasattr(action, "loaddata"):
|
if hasattr(action, "loaddata"):
|
||||||
outputs = loadSave(outputs, action)
|
outputs = loadSave(outputs, action)
|
||||||
|
|
||||||
response = executeRequest(action, outputs)
|
response, outputs = executeRequest(action, outputs)
|
||||||
|
|
||||||
if hasattr(action, "outputs"):
|
#print("DEBUG:", outputs)
|
||||||
outputs = buildOutput(response, action, outputs)
|
|
||||||
|
|
||||||
if hasattr(action, "savedata"):
|
outputs, response = buildRuntimeAttr(action, outputs, response)
|
||||||
save_file = buildSave(outputs, action)
|
|
||||||
|
|
||||||
if hasattr(action, "prints"):
|
|
||||||
logs = buildLog(action, outputs)
|
|
||||||
|
|
||||||
if action.showstatus is True and response.status_code == 200:
|
|
||||||
print(f"{action.name} successful")
|
|
||||||
|
|
||||||
return outputs, response
|
return outputs, response
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import threading
|
import threading
|
||||||
from lifecycleregistry import registerLifecycle
|
from .lifecyclemanager import registerLifecycle
|
||||||
from lifecyclemanager import manageLifecycle
|
from .lifecyclemanager import manageLifecycle
|
||||||
|
|
||||||
threadlist = []
|
threadlist = []
|
||||||
threadnum = 0
|
threadnum = 0
|
||||||
@@ -27,4 +27,4 @@ def buildThread(module, lifecycles):
|
|||||||
print(threadlist)
|
print(threadlist)
|
||||||
print(lifecycles)
|
print(lifecycles)
|
||||||
|
|
||||||
return threadnum, threadname, lifecycles, threadlist
|
return thread, lifecycles
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
from core.execution.action import buildAction
|
from core.execution.action import buildAction
|
||||||
from core.execution.operation import buildOperation
|
from core.execution.operation import buildOperation
|
||||||
|
from core.runtime import manageFailure
|
||||||
|
|
||||||
def executeContract(module):
|
def executeContract(module):
|
||||||
|
#try:
|
||||||
if module.kind == "operation":
|
if module.kind == "operation":
|
||||||
outputs = buildOperation(module)
|
outputs = buildOperation(module)
|
||||||
|
|
||||||
elif module.kind == "action":
|
elif module.kind == "action":
|
||||||
outputs = buildAction(module)
|
outputs = buildAction(module)
|
||||||
|
|
||||||
|
#except Exception as error:
|
||||||
|
#outputs = manageFailure(error, module)
|
||||||
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|||||||
Binary file not shown.
@@ -1,8 +1,11 @@
|
|||||||
import time
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
from core.execution.operation import buildOperation
|
from core.execution.operation import buildOperation
|
||||||
from core.execution.action import buildAction
|
from core.execution.action import buildAction
|
||||||
from core import executeContract
|
from core.execution.executioncontract import executeContract
|
||||||
|
|
||||||
|
sleeptime = int(os.environ.get("REFRESH_INTERVAL", 180))
|
||||||
|
|
||||||
def manageLifecycle(module, lifecycles): #lifecycles, state):
|
def manageLifecycle(module, lifecycles): #lifecycles, state):
|
||||||
#print(lifecycles[module.name]["mode"])
|
#print(lifecycles[module.name]["mode"])
|
||||||
@@ -15,7 +18,7 @@ def manageLifecycle(module, lifecycles): #lifecycles, state):
|
|||||||
|
|
||||||
#print(lifecycles)
|
#print(lifecycles)
|
||||||
|
|
||||||
time.sleep(module.sleeptime)
|
time.sleep(sleeptime)
|
||||||
|
|
||||||
elif lifecycles[module.name]["mode"] == "oneshot":
|
elif lifecycles[module.name]["mode"] == "oneshot":
|
||||||
print(lifecycles)
|
print(lifecycles)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,33 +9,19 @@ from .skipstep import skipStep
|
|||||||
|
|
||||||
def buildOperation(definition):
|
def buildOperation(definition):
|
||||||
actions = definition.actions
|
actions = definition.actions
|
||||||
|
#print("DEBUG", actions)
|
||||||
# responses = []
|
responses = []
|
||||||
outputs = {}
|
outputs = {}
|
||||||
|
|
||||||
for action in actions:
|
for action in actions:
|
||||||
try:
|
#try:
|
||||||
if hasattr(definition, "skipstep"):
|
if hasattr(definition, "skipstep"):
|
||||||
if skipStep(definition, action, outputs):
|
if skipStep(definition, action, outputs):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
outputs, response = buildAction(action, outputs)
|
outputs, response = buildAction(action, outputs)
|
||||||
# responses.append(response)
|
responses.append(response)
|
||||||
|
|
||||||
except Exception as error:
|
#except Exception as error:
|
||||||
outputs = manageFailure(error, action, outputs)
|
#outputs = manageFailure(error, action, outputs)
|
||||||
|
return outputs
|
||||||
# if hasattr(action, "cookies"):
|
|
||||||
# for cookiename, cookie, in cookies:
|
|
||||||
|
|
||||||
# session.cookies.set(
|
|
||||||
# cookiename,
|
|
||||||
# outputs[cookie],
|
|
||||||
# domain=domainname
|
|
||||||
# )
|
|
||||||
|
|
||||||
# print(
|
|
||||||
# {outputs[cookie]},
|
|
||||||
# domain,
|
|
||||||
# cookiename,
|
|
||||||
# )
|
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ def skipStep(definition, action, outputs):
|
|||||||
if action in definition.skipstep:
|
if action in definition.skipstep:
|
||||||
condition = definition.skipstep[action]
|
condition = definition.skipstep[action]
|
||||||
|
|
||||||
|
#if outputs.get([condition["output"]]) is None:
|
||||||
|
#return False
|
||||||
|
#current_value = 1
|
||||||
|
#else:
|
||||||
current_value = outputs[condition["output"]]
|
current_value = outputs[condition["output"]]
|
||||||
|
#current_value = 1
|
||||||
|
|
||||||
if condition["operator"] == ">=":
|
if condition["operator"] == ">=":
|
||||||
if current_value >= condition["value"]:
|
if current_value >= condition["value"]:
|
||||||
print(f"Skipping {action.name}")
|
print(f"Skipping {action.name}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except TypeError, KeyError:
|
except KeyError:
|
||||||
|
#outputs = manageFailure(error)
|
||||||
return False
|
return False
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-4
@@ -1,11 +1,17 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
def buildLog (action, outputs):
|
def buildLog (action, outputs):
|
||||||
# try:
|
#if response.status_code == 200:
|
||||||
|
#print(f"{action.name} successful")
|
||||||
|
try:
|
||||||
for print_name, print_key in action.prints.items():
|
for print_name, print_key in action.prints.items():
|
||||||
print(
|
print(
|
||||||
f"{print_name}:",
|
f"{print_name}:",
|
||||||
f"{outputs[print_key]}",
|
f"{outputs[print_key]}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# except KeyError:
|
|
||||||
#outputs[print_key] = "N/A"
|
#else:
|
||||||
# return
|
#print(f"{action.name} failed")
|
||||||
|
except: KeyError, TypeError
|
||||||
|
return
|
||||||
|
|||||||
@@ -7,12 +7,21 @@ def manageFailure(error, action, outputs):
|
|||||||
errors = action.recoverPolicy
|
errors = action.recoverPolicy
|
||||||
error_type = type(error)
|
error_type = type(error)
|
||||||
|
|
||||||
print(f"{type(error).__name__} detected during {action.name}. Applying recovery policy.")
|
#print(f"{type(error).__name__} detected during {action.name}. Applying recovery policy.")
|
||||||
|
|
||||||
if error_type in errors:
|
if error_type in errors:
|
||||||
repairs = errors[error_type]
|
repairs = errors[error_type]
|
||||||
|
|
||||||
for repair in repairs:
|
for repair in repairs:
|
||||||
|
if repair == False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif repair is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"{type(error).__name__} detected during {action.name}. Applying recovery policy.")
|
||||||
|
|
||||||
outputs, response = buildAction(repair, outputs)
|
outputs, response = buildAction(repair, outputs)
|
||||||
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|||||||
@@ -17,5 +17,3 @@ def buildOutput(response, action, outputs):
|
|||||||
|
|
||||||
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from core.runtime import buildOutput, buildSave, buildLog
|
||||||
|
|
||||||
|
def buildRuntimeAttr(action, outputs, response):
|
||||||
|
#print("BEFORE buildOutput:", type(outputs))
|
||||||
|
if hasattr(action, "outputs"):
|
||||||
|
outputs = buildOutput(response, action, outputs)
|
||||||
|
|
||||||
|
#print("AFTER buildOutput:", type(outputs))
|
||||||
|
#print("DEBUG buildSave")
|
||||||
|
#print("outputs type:", type(outputs))
|
||||||
|
#print("action type:", type(action))
|
||||||
|
#print("outputs keys:", list(outputs) if isinstance(outputs, dict) else "NOT A DICT")
|
||||||
|
|
||||||
|
if hasattr(action, "savedata"):
|
||||||
|
buildSave(outputs, action)
|
||||||
|
|
||||||
|
if hasattr(action, "prints"):
|
||||||
|
buildLog(action, outputs)
|
||||||
|
|
||||||
|
#if hasattr(action, "conversion"):
|
||||||
|
#conversions = action.conversion.items()
|
||||||
|
#for key in action.conversion.items():
|
||||||
|
#print(f"{action.conversion.items[key]}")
|
||||||
|
#convertedoutput = conversions[key]
|
||||||
|
#outputs[conversions[key]] = convertedoutput
|
||||||
|
|
||||||
|
if action.showstatus is True and response.status_code == 200:
|
||||||
|
print(f"{action.name} successful")
|
||||||
|
|
||||||
|
return outputs, response
|
||||||
@@ -9,9 +9,9 @@ def buildSave(outputs, action):
|
|||||||
with open(f"{action.savepath}", "w") as save_file:
|
with open(f"{action.savepath}", "w") as save_file:
|
||||||
json.dump(save_data, save_file)
|
json.dump(save_data, save_file)
|
||||||
|
|
||||||
print(f"{save_name} saved by {action.name}")
|
#print(f"{save_name} saved by {action.name}")
|
||||||
|
print(f"Data saved by {action.name} to {action.savepath}")
|
||||||
return save_file
|
return outputs
|
||||||
|
|
||||||
def loadSave(outputs, action):
|
def loadSave(outputs, action):
|
||||||
with open(f"{action.savepath}", "r") as save_file:
|
with open(f"{action.savepath}", "r") as save_file:
|
||||||
@@ -19,6 +19,7 @@ def loadSave(outputs, action):
|
|||||||
|
|
||||||
for load_name in action.loaddata:
|
for load_name in action.loaddata:
|
||||||
outputs[load_name] = load_data[load_name]
|
outputs[load_name] = load_data[load_name]
|
||||||
print(f"{load_name} loaded by {action.name}")
|
#print(f"{load_name} loaded by {action.name}")
|
||||||
|
|
||||||
|
print(f"Data loaded by {action.name} from {action.savepath}")
|
||||||
return outputs
|
return outputs
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
import importlib
|
import importlib
|
||||||
from lifecyclemanager import manageLifecycle
|
from core.execution.lifecyclemanager import manageLifecycle
|
||||||
from .dispatchstate import buildState
|
from .dispatchstate import buildState
|
||||||
|
|
||||||
def buildDispatch(request):
|
def buildDispatch(request):
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
def buildResponse(module, thread, mode):
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"status": "success",
|
||||||
|
"message": f"{module.name} dispatched",
|
||||||
|
"thread": f"{thread}",
|
||||||
|
"mode": f"{mode}",
|
||||||
|
}
|
||||||
|
|
||||||
|
#responsedata = json.dumps(response).encode("utf-8")
|
||||||
|
|
||||||
|
return response
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from .createdispatch import buildDispatch
|
from .createdispatch import buildDispatch
|
||||||
from .dispatchstate import buildState
|
from .dispatchstate import buildState
|
||||||
from core.execution.createthread import buildThread
|
from core.execution.createthread import buildThread
|
||||||
|
from .createresponse import buildResponse
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@@ -14,9 +15,6 @@ def dispatchRequest(request, lifecycles):
|
|||||||
return lifecycles
|
return lifecycles
|
||||||
|
|
||||||
else:
|
else:
|
||||||
threadnum, threadname, lifecycles, threadlist = buildThread(module, lifecycles)
|
thread, lifecycles = buildThread(module, lifecycles)
|
||||||
|
|
||||||
#print("Active Threads")
|
return thread, lifecycles, module, mode
|
||||||
#print(threadlist)
|
|
||||||
|
|
||||||
return threadnum, threadname, lifecycles, threadlist
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from core import registerLifecycle
|
from core.execution.lifecyclemanager import registerLifecycle
|
||||||
|
|
||||||
def filterMode(mode):
|
def filterMode(mode):
|
||||||
if mode in ("continuous", "oneshot", "stop"):
|
if mode in ("continuous", "oneshot", "stop"):
|
||||||
|
|||||||
+17
-5
@@ -1,19 +1,25 @@
|
|||||||
import socket
|
import socket
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .dispatcher import dispatchRequest
|
||||||
|
from .createresponse import buildResponse
|
||||||
|
|
||||||
|
port = int(os.environ.get("SERVER_PORT", 19001))
|
||||||
|
|
||||||
def startServer():
|
def startServer():
|
||||||
from .dispatcher import dispatchRequest
|
|
||||||
|
|
||||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
server.bind(("127.0.0.1", 19002))
|
server.bind(("0.0.0.0", port))
|
||||||
|
|
||||||
lifecycles = {}
|
lifecycles = {}
|
||||||
|
|
||||||
server.listen()
|
server.listen()
|
||||||
|
logging.info(f"Server listening on port {port}")
|
||||||
while True:
|
while True:
|
||||||
connection, address = server.accept()
|
connection, address = server.accept()
|
||||||
|
logging.info("Accepting connections")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = connection.recv(4096)
|
data = connection.recv(4096)
|
||||||
@@ -23,7 +29,13 @@ def startServer():
|
|||||||
# if not data:
|
# if not data:
|
||||||
# break
|
# break
|
||||||
|
|
||||||
dispatch = dispatchRequest(request, lifecycles)
|
thread, lifecycles, module, mode = dispatchRequest(request, lifecycles)
|
||||||
|
|
||||||
|
response = buildResponse(module, thread, mode)
|
||||||
|
|
||||||
|
connection.sendall(
|
||||||
|
json.dumps(response).encode("utf-8"),
|
||||||
|
)
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
from core.server.server import startServer
|
from core.server.server import startServer
|
||||||
|
import login
|
||||||
|
|
||||||
|
#login
|
||||||
startServer()
|
startServer()
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,6 +13,10 @@ outputs = {
|
|||||||
"validTo": "validTo",
|
"validTo": "validTo",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conversion = {
|
||||||
|
"hrs": "validTo // 60",
|
||||||
|
}
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": {
|
"Authorization": {
|
||||||
"output": "access_token",
|
"output": "access_token",
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ savedata = [
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
]
|
]
|
||||||
|
|
||||||
savepath = "/home/david/tokens.json"
|
savepath = os.environ["TOKEN_STORE"]
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from . import getcsrf, login
|
from . import getcsrf, login
|
||||||
|
import os
|
||||||
|
|
||||||
name = "Token refresh"
|
name = "Token refresh"
|
||||||
|
|
||||||
|
kind = "action"
|
||||||
|
|
||||||
showstatus = True
|
showstatus = True
|
||||||
|
|
||||||
method = "GET"
|
method = "GET"
|
||||||
@@ -15,7 +18,7 @@ headers = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
domainname="rentmasseur.com"
|
#domainname="rentmasseur.com"
|
||||||
|
|
||||||
cookies = {
|
cookies = {
|
||||||
"accessToken": {
|
"accessToken": {
|
||||||
@@ -36,7 +39,8 @@ savedata = [
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
]
|
]
|
||||||
|
|
||||||
savepath = "/home/david/tokens.json"
|
#savepath = "/rmmanager/tokens.json"
|
||||||
|
savepath = os.environ["TOKEN_STORE"]
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
"access_token": "accessToken",
|
"access_token": "accessToken",
|
||||||
|
|||||||
@@ -45,3 +45,8 @@ outputs = {
|
|||||||
prints = {
|
prints = {
|
||||||
"Available until": "available_until"
|
"Available until": "available_until"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recoverPolicy = {
|
||||||
|
KeyError:[False],
|
||||||
|
TypeError: [],
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@ actions = [
|
|||||||
setavailable,
|
setavailable,
|
||||||
]
|
]
|
||||||
|
|
||||||
sleeptime = 10
|
#sleeptime = 10
|
||||||
|
|
||||||
skipstep = {
|
skipstep = {
|
||||||
setavailable: {
|
setavailable: {
|
||||||
@@ -21,3 +21,5 @@ skipstep = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
role = "activate"
|
role = "activate"
|
||||||
|
|
||||||
|
sleeptime = 180
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3OTE2NzQzMzAsIm5iZiI6MTc5MTY3NDE1MCwic3ViIjoiMjM4MzAyIiwidXNlcm5hbWUiOiJYeHhvdGlxIiwidXNlclR5cGUiOjEsInN0YWdlTmFtZSI6Ilh4eG90aXEiLCJhcHByb3ZlZCI6MSwibG9jYXRpb25JZCI6NDcyLCJzZWFyY2hDaXR5IjoiYXRsYW50YSIsImNvdW50cnlTaG9ydCI6IlVTIiwic2Vzc2lvbkNvdW50cnlDb250aW5lbnQiOiJOQSIsImlzRnJvemVuIjowLCJsb2NhdGlvbkxhYmVsIjoiQXRsYW50YSwgR0EiLCJpc3MiOiJodHRwczovL3JlbnRtYXNzZXVyLmNvbSIsImlhdCI6MTc5MTY3NDIxMH0.HcqR4crgZkqdaSKpY3EiC8aejvlgQppKZbV_El8MeCPzoRSHssJcDtj5dmDXjP3VhjseIbgPvCqDxKC2jb3Qdg", "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3OTQyNjYyMTAsIm5iZiI6MTc5MTY3NDE1MCwianRpIjoiZTJRY3FUYnNBSjRpZWNSQ0JEWUdXY2pvYkxDREkrS05mRUwzQUU1OEk3az0iLCJpc3MiOiJodHRwczovL3JlbnRtYXNzZXVyLmNvbSIsImlhdCI6MTc5MTY3NDIxMCwic3ViIjoiMjM4MzAyIn0.ExbPr-h99AVPpB0RTZs_3h15rY1clue95tGb0Cq0qrqkhaWloB8G6v7vmCFqJcpVyW1YHLrw45zOjPv9uZ25oA"}
|
||||||
Reference in New Issue
Block a user