plugin.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. #####################################################################
  2. # #
  3. # THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
  4. # LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
  5. # FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
  6. # WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
  7. # IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
  8. # OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
  9. # #
  10. # ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
  11. # (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
  12. # YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
  13. # THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
  14. # VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
  15. # WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
  16. # #
  17. # THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
  18. # NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
  19. # THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
  20. # SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
  21. # THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
  22. # #
  23. # THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
  24. # FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
  25. # THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
  26. # TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
  27. # #
  28. #####################################################################
  29. # This file will be dealing with handling extensions aka plugins.
  30. import inspect
  31. import hashlib
  32. import json
  33. import sys
  34. import os
  35. import urllib.request
  36. from flbry import settings
  37. from flbry.variables import *
  38. from flbry import markdown
  39. from flbry import publish
  40. def run(args=[], command=False, address=None, execute=True, run_only=None):
  41. # This function will pass arguments to the plugin function.
  42. # The plugin will edit those arguments and return them back.
  43. # This is when we return those arguments back to the function
  44. # from where the plugin.run() was called.
  45. # To make it so anybody could extend any part of the software
  46. # without adding new paths to things inside the software code,
  47. # we are going to use an address of the function.
  48. # So for example from 'flbry' you've imported 'markdown' and
  49. # now it runs the Open() function. If inside that function we
  50. # will add plugin.run(), the resulting function address will be
  51. # 'flbry.markdwon.Open'
  52. # This is how we get the address:
  53. if not address:
  54. address = inspect.stack()[1].filename
  55. address = address.replace(os.getcwd(), "").replace(".py", "")
  56. address = address.replace("/",".")[1:]
  57. address = address +"."+ inspect.stack()[1].function
  58. # Now we want to get files from the plugins folder.
  59. plugins_folder = settings.get_settings_folder("flbry/plugins/")
  60. sys.path.append(settings.get_settings_folder())
  61. # Before we import we need to make sure that __init__.py exists
  62. # this is an empty file that will tell python that this folder is
  63. # indeed a module.
  64. if not os.path.exists(plugins_folder+"__init__.py"):
  65. t = open(plugins_folder+"__init__.py", "w")
  66. t.close()
  67. # Now let's import the files from plugins
  68. for p in os.listdir(plugins_folder):
  69. m = p[:-3]
  70. # If only a certain plugin is to run
  71. if run_only and m != run_only:
  72. continue
  73. # Ignore the __init__.py
  74. if m == "__init__":
  75. continue
  76. if p.endswith(".py") and enabled(m):
  77. exec("from plugins import "+m)
  78. # Let's get the plugin_data of the plugin
  79. try:
  80. plugin_data = eval(m+".plugin_data")
  81. except:
  82. continue
  83. # Now let's update the data of the plugin_data
  84. # into the info of the plugin settings.
  85. plugin_info = {}
  86. for i in plugin_data:
  87. if i != "functions":
  88. plugin_info[i] = plugin_data[i]
  89. enabled(m, plugin_info=plugin_info)
  90. # Now that we have it let's compare it to the
  91. # address of the function from which it's running
  92. for func in plugin_data["functions"]:
  93. if address in func and not command and not func["command"] and execute:
  94. args = func[address](args)
  95. elif address in func and command and command.startswith(func["command"]) and execute:
  96. args = func[address](command, args)
  97. # Adding functions into completer
  98. if address in func and func["command"]:
  99. complete([func["command"]], add=True)
  100. # If we run only one plugin and the plugin is disabled.
  101. elif run_only and run_only == m:
  102. center("Plugin '"+m+"' is disabled!", "bdrd")
  103. return args
  104. def check_settings_exists():
  105. # Insures that ~/.local/share/flbry plugins.json exits
  106. if not os.path.exists(settings.get_settings_folder()+"plugins.json"):
  107. with open(settings.get_settings_folder()+"plugins.json", 'w') as f:
  108. json.dump({}, f, indent=4, sort_keys=True)
  109. def enabled(plugin_name, full_report=False, flip=False, plugin_info={}):
  110. # This function will check whether the plugin is enabled in
  111. # plugin settings.
  112. # Firs let's make sure that the file exist
  113. check_settings_exists()
  114. # Then let's open the file
  115. with open(settings.get_settings_folder()+"plugins.json") as f:
  116. data = json.load(f)
  117. # Adding a missing plugin
  118. default = {"active":False,
  119. "info":{"title":plugin_name,
  120. "author":None,
  121. "license":None,
  122. "flbry":"terminal",
  123. "description":None}}
  124. if plugin_name not in data:
  125. data[plugin_name] = default
  126. # Overwriting
  127. if flip:
  128. data[plugin_name]["active"] = not data[plugin_name]["active"]
  129. # Updating info
  130. if plugin_info:
  131. data[plugin_name]["info"] = plugin_info
  132. # Saving plugins file
  133. with open(settings.get_settings_folder()+"plugins.json", 'w') as f:
  134. json.dump(data, f, indent=4, sort_keys=True)
  135. # Returning data
  136. if full_report:
  137. return data[plugin_name]
  138. else:
  139. return data[plugin_name]["active"]
  140. def manager(search=""):
  141. # This function gives a settings promt to set various
  142. # settings on plugins.
  143. to_text = True
  144. plugins_commands = [
  145. "help",
  146. "read",
  147. "set",
  148. "publish",
  149. "description"
  150. ]
  151. while True:
  152. complete(plugins_commands)
  153. print()
  154. check_settings_exists()
  155. with open(settings.get_settings_folder()+"plugins.json") as f:
  156. data = json.load(f)
  157. data = run(data) # Updating the plugins
  158. d = {"categories":["Active", "Title", "Author", "License"],
  159. "size":[1, 3, 2, 2],
  160. "data":[]}
  161. for plugin in data:
  162. # Make sure you can get the nessesary data
  163. # even if it's somewhat corrupted.
  164. active = "[ ]"
  165. title = plugin
  166. description = ""
  167. LICENSE = ""
  168. author = ""
  169. try:
  170. active = data[plugin]["active"]
  171. except:
  172. pass
  173. try:
  174. if data[plugin]["info"]["title"]:
  175. title = data[plugin]["info"]["title"]
  176. except:
  177. pass
  178. try:
  179. if data[plugin]["info"]["description"]:
  180. description = data[plugin]["info"]["description"]
  181. except:
  182. pass
  183. try:
  184. if data[plugin]["info"]["license"]:
  185. LICENSE = data[plugin]["info"]["license"]
  186. except:
  187. pass
  188. try:
  189. if data[plugin]["info"]["author"]:
  190. author = data[plugin]["info"]["author"]
  191. except:
  192. pass
  193. # Make so the search works.
  194. if search and \
  195. search.lower() not in title.lower() \
  196. and search.lower() not in author.lower() \
  197. and search.lower() not in description.lower() \
  198. and search.lower() not in LICENSE.lower():
  199. continue
  200. # Let's add the plugin into the data
  201. d["data"].append([active, title, author, LICENSE])
  202. table(d)
  203. center("")
  204. # Now let's start the madness
  205. print()
  206. c = input(typing_dots("Type 'help' for more info.", to_text))
  207. to_text = False
  208. if not c:
  209. break
  210. try:
  211. c = int(c)
  212. enabled(list(data.keys())[c], flip=True)
  213. continue
  214. except:
  215. pass
  216. if c.startswith("description"):
  217. cn = get_cn(c, "Which plugin?")
  218. try:
  219. description = list(data.values())[cn]["info"]["description"]
  220. savedes = open("/tmp/fastlbrylastdescription.md", "w")
  221. savedes.write(description)
  222. savedes.close()
  223. markdown.draw("/tmp/fastlbrylastdescription.md", "Description")
  224. except:
  225. center("This plugin has no description.")
  226. elif c.startswith("read"):
  227. cn = get_cn(c, "Which plugin?")
  228. try:
  229. plugin_name = list(data.keys())[cn]
  230. plugin_file = settings.get_settings_folder("flbry/plugins/")+plugin_name+".py"
  231. markdown.draw(plugin_file, plugin_name+" Source Code", False)
  232. except:
  233. center("Plugin is deleted or corrupted", "bdrd")
  234. elif c.startswith("set"):
  235. cn = get_cn(c, "Which plugin?")
  236. run([None], address="settings", run_only=list(data.keys())[cn])
  237. elif c.startswith("publish"):
  238. cn = get_cn(c, "Which plugin?")
  239. try:
  240. plugin_name = list(data.keys())[cn]
  241. plugin_file = settings.get_settings_folder("flbry/plugins/")+plugin_name+".py"
  242. publish_plugin(plugin_file, list(data.values())[cn]["info"])
  243. except:
  244. center("Plugin is deleted or corrupted", "bdrd")
  245. elif c == "help":
  246. markdown.draw("help/plugins.md", "Plugins Help")
  247. def publish_plugin(filename, info):
  248. """Helps the user publish a plugin"""
  249. print()
  250. # Check if the plugin has a license in the info and get the name and link to it
  251. # Getting the link only works if the license it a SPDX Lincense Identifier: https://spdx.org/licenses/
  252. if "license" in info:
  253. l = spdx_license(info["license"])
  254. if "link" in l:
  255. info["license_url"] = l["link"]
  256. info["license_name"] = l["name"]
  257. info.pop("license")
  258. info["file"] = filename
  259. if not "version" in info:
  260. info["version"] = 1.0
  261. if not "fee" in info:
  262. info["fee"] = 0
  263. to_text = True
  264. publish_commands = [
  265. "help",
  266. "file",
  267. "link",
  268. "title",
  269. "author",
  270. "license",
  271. "description",
  272. "publish",
  273. "fastlbry",
  274. "version",
  275. "fee",
  276. "publish_file"
  277. ]
  278. while True:
  279. complete(publish_commands)
  280. if "license_name" in info:
  281. d_license = info["license_name"]
  282. elif "license" in info:
  283. d_license = info["license"]
  284. else:
  285. d_license = "[no license]"
  286. d = {"categories":[ "Title", "Version", "Fee", "Author", "License"],
  287. "size":[ 3, 1, 1, 2, 2],
  288. "data":[[
  289. info["title"],
  290. info["version"],
  291. str(info["fee"]),
  292. info["author"],
  293. d_license
  294. ]]}
  295. table(d, False)
  296. if not "description" in info:
  297. info["description"] = ""
  298. d = {"categories": ["Description"],
  299. "size": [1],
  300. "data": [[info["description"]]]}
  301. table(d, False)
  302. d = {"categories": ["File or Link", "FastLBRY Variant"],
  303. "size": [3, 1],
  304. "data": [[info["file"], info["flbry"]]]}
  305. table(d, False)
  306. center("")
  307. c = input(typing_dots("Type 'help' for more info.", to_text, give_space=True))
  308. to_text = False
  309. if not c:
  310. break
  311. elif c == "publish_file":
  312. try:
  313. info["file"] = publish.configure(info["file"])['outputs'][0]['permanent_url']
  314. except Exception as e:
  315. center("Error: "+str(e), "bdrd")
  316. elif c.startswith(("file", "link")):
  317. if " " in c:
  318. info["file"] = c[c.find(" ")+1:]
  319. else:
  320. info["file"] = input(typing_dots("File or URL", give_space=True, to_add_dots=True))
  321. elif c.startswith("title"):
  322. if " " in c:
  323. info["title"] = c[c.find(" ")+1:]
  324. else:
  325. info["title"] = input(typing_dots("Plugin title", give_space=True, to_add_dots=True))
  326. elif c.startswith("author"):
  327. if " " in c:
  328. info["author"] = c[c.find(" ")+1:]
  329. else:
  330. info["author"] = input(typing_dots("Author", give_space=True, to_add_dots=True))
  331. elif c == "license":
  332. info.pop("license", None)
  333. info["license_name"], info["license_url"] = choose_license()
  334. elif c.startswith("description"):
  335. c = c + ' '
  336. a = c[c.find(" "):]
  337. if len(a) > 1:
  338. info["description"] = file_or_editor(a, "Type the description here. Don't forget to save. Then return to FastLBRY.")
  339. else:
  340. info["description"] = input(typing_dots("Description", give_space=True, to_add_dots=True))
  341. elif c.startswith("version"):
  342. if " " in c:
  343. info["version"] = c[c.find(" ")+1:]
  344. else:
  345. info["version"] = input(typing_dots("Version number", give_space=True, to_add_dots=True))
  346. elif c.startswith("fee"):
  347. if " " in c:
  348. info["fee"] = c[c.find(" ")+1:]
  349. else:
  350. info["fee"] = input(typing_dots("Fee", give_space=True, to_add_dots=True))
  351. elif c.startswith("fastlbry"):
  352. if " " in c:
  353. info["flbry"] = c[c.find(" ")+1:]
  354. else:
  355. complete(["terminal", "gtk", "all"])
  356. info["flbry"] = input(typing_dots("FastLBRY variant", give_space=True, to_add_dots=True))
  357. elif c == "publish":
  358. try:
  359. x = publish_plugin_blob(info["file"], info)
  360. if x:
  361. return
  362. except Exception as e:
  363. center("Error publishing plugin: "+str(e), "bdrd")
  364. elif c == "help":
  365. markdown.draw("help/publish_plugins.md", "Plugin Publishing Help")
  366. def publish_plugin_blob(filename, info):
  367. """Creates and publishes blobs for FastLBRY plugins"""
  368. filename = os.path.expanduser(filename)
  369. # Try to open the file and if that fails treat it as a URL
  370. try:
  371. pf = open(filename, "rb")
  372. except FileNotFoundError:
  373. if filename.startswith("lbry://"):
  374. filename = filename.replace("lbry://", "https://spee.ch/")
  375. try:
  376. pf = urllib.request.urlopen(filename)
  377. if not info["fee"] in [0, "0"]:
  378. center("Plugin file is a link, fee will be ignored", "bdrd")
  379. except urllib.error.URLError:
  380. center("File is not a valid file or URL", "bdrd")
  381. return
  382. # We need a hash of the file for security reasons
  383. pf = pf.read()
  384. sha512 = hashlib.sha512(pf).hexdigest()
  385. info["sha512"] = sha512
  386. # Try to upload the plugin to LBRY
  387. info["file"] = publish.speech_upload(info["file"], name=sha512, fee=info["fee"])
  388. # Saving it to json for publishing
  389. with open('/tmp/flbrypublishpluginblob.json', 'w') as f:
  390. json.dump(info, f, indent=4, sort_keys=True)
  391. data = {"name":info["title"],
  392. "bid":0.001,
  393. "file_path":'/tmp/flbrypublishpluginblob.json',
  394. "title":info["title"],
  395. "license":info["license_name"],
  396. "thumbnail_url":"",
  397. "channel_id":"",
  398. "channel_name":"",
  399. "fee_amount":0,
  400. "tags":["FastLBRY-terminal-plugin-blob-json-file"]
  401. }
  402. if "description" in info:
  403. data["description"] = info["description"]
  404. if "license_url" in info:
  405. data["license_url"] = info["license_url"],
  406. publish.configure('/tmp/flbrypublishpluginblob.json', data)
  407. return True