url.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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 fetch an LBRY URL directly and print out various
  30. # options that the user may do with the publication.
  31. from subprocess import *
  32. import json
  33. import os
  34. from flbry.variables import *
  35. from flbry import markdown
  36. from flbry import channel
  37. from flbry import search
  38. from flbry import comments
  39. import urllib.request
  40. from flbry import following
  41. from flbry import settings
  42. from flbry import wallet
  43. from flbry import plugin
  44. from flbry import channel
  45. import urllib.parse
  46. def get(url="", do_search=True):
  47. # The user might type the word url and nothing else.
  48. if not url:
  49. url = input(" LBRY url :: ")
  50. # Decode the URL to turn percent encoding into normal characters
  51. # Example: "%C3%A9" turns into "é"
  52. url = urllib.parse.unquote(url)
  53. ##### Converting an HTTPS domain ( of any website ) into lbry url ###
  54. # Any variation of this:
  55. # https://odysee.com/@blenderdumbass:f/hacking-fastlbry-live-with-you:6
  56. # customlbry.com/@blenderdumbass:f/hacking-fastlbry-live-with-you:6
  57. # Should become this:
  58. # lbry://@blenderdumbass:f/hacking-fastlbry-live-with-you:6
  59. if "/" in url and not url.startswith("@") and not url.startswith("lbry://"):
  60. if "@" in url:
  61. url = url[url.find("@"):]
  62. else:
  63. url = url[text.rfind("/")-1:]
  64. url = "lbry://"+url
  65. # Then let's fetch the url from our beloved SDK.
  66. out = check_output(["flbry/lbrynet",
  67. "resolve", url])
  68. # Now we want to parse the json
  69. try:
  70. out = json.loads(out)
  71. except:
  72. center("Connect to LBRY first.", "bdrd")
  73. return
  74. out = plugin.run(out) # in case there are plugins that want
  75. # to modify resolved data.
  76. out = out[url]
  77. ### REPOST ####
  78. # Sometimes a user wants to select a repost. This will not
  79. # load anything of a value. A repost is an empty blob that
  80. # links to another blob. So I want to automatically load
  81. # the actuall publication it self here.
  82. if "value_type" in out and out["value_type"] == "repost":
  83. get(out["reposted_claim"]["canonical_url"])
  84. return
  85. #### FORCE SEARCH ###
  86. # Sometimes user might type something that is not a url
  87. # in this case ["value_type"] will not be loaded. And in
  88. # this case we can load search instead.
  89. if "value_type" not in out and do_search:
  90. search.simple(url)
  91. return out
  92. elif "value_type" not in out:
  93. return out
  94. # Now that we know that don't search for it. We can make
  95. # one thing less broken. Sometimes a user might type a
  96. # urls that's going to be resolved but that doesn't have
  97. # the lbry:// in the beginning of it. Like typing
  98. # @blenderdumbass instead of lbry://blenderdumbass
  99. # I want to add the lbry:// to it anyway. So non of the
  100. # stuff later will break.
  101. if not url.startswith("lbry://") and not url.startswith("@"):
  102. url = "lbry://" + url
  103. # Now let's print some useful information
  104. ##### NAME URL INFORMATION #####
  105. center("Publication Information")
  106. d = {"categories":["lbry url", "title"],
  107. "size":[1,1],
  108. "data":[[url]]}
  109. try:
  110. # This prints out the title
  111. d["data"][0].append(out["value"]["title"])
  112. except:
  113. d["data"][0] = [url]
  114. d["data"][0].append("[no title]")
  115. table(d, False)
  116. #### LICENSE ####
  117. try:
  118. d = {"categories":["License"],
  119. "size":[1],
  120. "data":[[out["value"]["license"]]]}
  121. except:
  122. d = {"categories":["License"],
  123. "size":[1],
  124. "data":[["[failed to load license]"]]}
  125. table(d, False)
  126. #### TAGS #####
  127. d = {"categories":[],
  128. "size":[],
  129. "data":[[]]}
  130. try:
  131. for tag in out["value"]["tags"]:
  132. d["categories"].append(" ")
  133. d["size"].append(1)
  134. d["data"][0].append(tag)
  135. except:
  136. d = {"categories":[" "],
  137. "size":[1],
  138. "data":[["[no tags found]"]]}
  139. table(d, False)
  140. #### FILE INFO #####
  141. d = {"categories":["Value Type", "File Type", "File Size", "Duration"],
  142. "size":[1,1,1, 1],
  143. "data":[[]]}
  144. try:
  145. d["data"][0].append(what[out["value_type"]])
  146. except:
  147. d["data"][0].append("[no value type]")
  148. try:
  149. d["data"][0].append(out["value"]["source"]["media_type"])
  150. except:
  151. d["data"][0].append("[no file type]")
  152. try:
  153. d["data"][0].append(csize(out["value"]["source"]["size"]))
  154. except:
  155. d["data"][0].append("[no file size]")
  156. try:
  157. d["data"][0].append(timestring(float(out["value"]["video"]["duration"])))
  158. except:
  159. d["data"][0].append("[no duration]")
  160. table(d, False)
  161. ##### CHANNEL INFORMATION ####
  162. center("Channel Information")
  163. d = {"categories":["lbry url", "title"],
  164. "size":[1,1],
  165. "data":[[]]}
  166. try:
  167. # This prints out the title
  168. d["data"][0].append(out["signing_channel"]["name"])
  169. title = "[no title]"
  170. try:
  171. title = out["signing_channel"]["value"]["title"]
  172. except:
  173. pass
  174. d["data"][0].append(title)
  175. except:
  176. d["data"][0] = []
  177. d["data"][0].append("[no url]")
  178. d["data"][0].append("[anonymous publisher]")
  179. table(d, False)
  180. #### LBC INFORMATION ####
  181. center("LBRY Coin ( LBC ) Information")
  182. d = {"categories":["combined", "at upload", "support"],
  183. "size":[1,1,1],
  184. "data":[[]]}
  185. try:
  186. fullamount = float(out["amount"]) + float(out["meta"]["support_amount"])
  187. # This prints out the title
  188. d["data"][0].append(fullamount)
  189. d["data"][0].append(out["amount"])
  190. d["data"][0].append(out["meta"]["support_amount"])
  191. except:
  192. d["data"][0] = []
  193. d["data"][0].append("[no data]")
  194. d["data"][0].append("[no data]")
  195. d["data"][0].append("[no data]")
  196. table(d, False)
  197. #### PRICE ####
  198. try:
  199. # Print the prince of this publication in LBC
  200. center("PRICE: "+out["value"]["fee"]["amount"]+" "+out["value"]["fee"]["currency"], "bdrd", blink=True)
  201. except:
  202. pass
  203. # Some things are too big to output like this in the terminal
  204. # so for them I want the user to type a command.
  205. center("--- for publication commands list type 'help' --- ")
  206. # So we are going to start a new while loop here. IK crazy.
  207. # this one will handle all the commands associated with the
  208. # currently selected publication.
  209. # Completer thingy
  210. url_commands = [
  211. "help",
  212. "link",
  213. "id",
  214. "web",
  215. "description",
  216. "read",
  217. "channel",
  218. "comments",
  219. "reply",
  220. "open",
  221. "play",
  222. "music",
  223. "save",
  224. "rss",
  225. "thumbnail",
  226. "follow",
  227. "unfollow",
  228. "boost",
  229. "tip",
  230. "repost"
  231. ]
  232. complete(url_commands)
  233. settings_cache = settings.get_all_settings()
  234. while True:
  235. plugin.run(execute=False)
  236. c = input(typing_dots())
  237. if not c:
  238. break
  239. elif c == "help":
  240. markdown.draw("help/url.md", "Publication Help")
  241. elif c == "web":
  242. d = {"categories":["Name", "URL", "Maintainer", "Interface"],
  243. "size":[1,2,1,1],
  244. "data":web_instances}
  245. table(d)
  246. center("")
  247. # Choose an instance
  248. which = input(typing_dots())
  249. try:
  250. print(" "+url.replace("lbry://", web_instances[int(which)][1]))
  251. except:
  252. print(" "+url.replace("lbry://", web_instances[0][1]))
  253. elif c == "link":
  254. print(" "+url)
  255. elif c == "id":
  256. print(" "+out["claim_id"])
  257. elif c.startswith("open"):
  258. # If open has an argument (like `open mpv`) it opens it in that
  259. # Next it trys to open it with the default opener
  260. # If neither of those find an opener, it prompts the user.
  261. if len(c) > 5:
  262. p = c[5:]
  263. elif settings_cache["default_opener"]:
  264. p = settings_cache["default_opener"]
  265. else:
  266. p = input(" Open in : ")
  267. Popen([p,
  268. url.replace("lbry://", "https://spee.ch/").replace("#", ":").replace("(", "%28").replace(")", "%29")],
  269. stdout=DEVNULL,
  270. stderr=STDOUT)
  271. elif c == "description":
  272. #print(out["value"]["description"])
  273. # Here I want to print out the description of the publication.
  274. # but since, they are most likely in the markdown format I
  275. # need to implement a simple markdown parser. Oh wait.... I
  276. # have one. For the article read function. How about using it
  277. # here?
  278. # First we need to save the description into a file. Let's use
  279. # /tmp/ since there files are automatically cleaned up by the
  280. # system.
  281. try:
  282. savedes = open("/tmp/fastlbrylastdescription.md", "w")
  283. savedes.write(out["value"]["description"])
  284. savedes.close()
  285. except:
  286. savedes = open("/tmp/fastlbrylastdescription.md", "w")
  287. savedes.write("This file has no description.")
  288. savedes.close()
  289. # Now let's just simply load the markdown on this file.
  290. markdown.draw("/tmp/fastlbrylastdescription.md", "Description")
  291. elif c.startswith("play"):
  292. # Then we want to tell the SDK to start downloading.
  293. playout = check_output(["flbry/lbrynet",
  294. "get", url])
  295. # Parsing the Json
  296. playout = json.loads(playout)
  297. # Same thing as in open
  298. if len(c) > 5:
  299. p = c[5:]
  300. elif settings_cache["player"]:
  301. p = settings_cache["player"]
  302. else:
  303. p = input(" Play in : ")
  304. # Then we want to launch the player
  305. Popen([p,
  306. playout['download_path']],
  307. stdout=DEVNULL,
  308. stderr=STDOUT)
  309. elif c.startswith("music"):
  310. # Then we want to tell the SDK to start downloading.
  311. playout = check_output(["flbry/lbrynet",
  312. "get", url])
  313. # Parsing the Json
  314. playout = json.loads(playout)
  315. # And again for music player
  316. if len(c) > 6:
  317. p = c[6:]
  318. elif settings_cache["music_player"]:
  319. p = settings_cache["music_player"]
  320. else:
  321. p = input(" Play in : ")
  322. # Then we want to launch the player
  323. Popen([p,
  324. playout['download_path']],
  325. stdout=DEVNULL,
  326. stderr=STDOUT)
  327. elif c == "save":
  328. # Then we want to tell the SDK to start downloading.
  329. playout = check_output(["flbry/lbrynet",
  330. "get", url])
  331. # Parsing the Json
  332. playout = json.loads(playout)
  333. print(" Saved to :", playout['download_path'])
  334. elif c == "read":
  335. # Then we want to tell the SDK to start downloading.
  336. playout = check_output(["flbry/lbrynet",
  337. "get", url])
  338. # Parsing the Json
  339. playout = json.loads(playout)
  340. # Present the article to the user.
  341. markdown.draw(playout['download_path'], out["value"]["title"])
  342. elif c == "channel":
  343. # This a weird one. If the publication is a channel we
  344. # want it to list the publications by that channel.
  345. # If a publication is a publication. We want it to list
  346. # publications by the channel that made the publication.
  347. if out["value_type"] == "channel":
  348. channel.simple(url)
  349. else:
  350. try:
  351. channel.simple(out["signing_channel"]["canonical_url"].replace("lbry://",""))
  352. except:
  353. center("Publication is anonymous", "bdrd")
  354. elif c == "comments":
  355. comments.list(out["claim_id"], url)
  356. elif c.startswith("reply"):
  357. c = c + ' '
  358. comments.post(out["claim_id"], c[c.find(" "):])
  359. elif c == "rss":
  360. if out["value_type"] == "channel":
  361. url = out["short_url"].replace("#", ":")
  362. if url.startswith("lbry://"):
  363. url = url.split("lbry://", 1)[1]
  364. print(" https://odysee.com/$/rss/"+url)
  365. else:
  366. try:
  367. url = out["signing_channel"]["short_url"].replace("#", ":")
  368. url = url.split("lbry://", 1)[1]
  369. print(" https://odysee.com/$/rss/"+url)
  370. except:
  371. center("Publication is anonymous!", "bdrd")
  372. elif c == "thumbnail":
  373. try:
  374. thumb_url = out["value"]["thumbnail"]["url"]
  375. urllib.request.urlretrieve(thumb_url, "/tmp/fastlbrythumbnail")
  376. Popen(["xdg-open",
  377. "/tmp/fastlbrythumbnail"],
  378. stdout=DEVNULL,
  379. stderr=STDOUT)
  380. except:
  381. center("Publication does not have a thumbnail", "bdrd")
  382. elif c == "follow":
  383. if out["value_type"] == "channel":
  384. try:
  385. name = out["value"]["title"]
  386. except:
  387. name = out["normalized_name"]
  388. url = out["permanent_url"]
  389. following.follow_channel(url, name)
  390. else:
  391. try:
  392. try:
  393. name = out["signing_channel"]["value"]["title"]
  394. except:
  395. name = out["signing_channel"]["normalized_name"]
  396. url = out["signing_channel"]["permanent_url"]
  397. following.follow_channel(url, name)
  398. except:
  399. center("Publication is anonymous.", "bdrd")
  400. elif c == "unfollow":
  401. if out["value_type"] == "channel":
  402. try:
  403. name = out["value"]["title"]
  404. except:
  405. name = out["normalized_name"]
  406. url = out["permanent_url"]
  407. following.unfollow_channel(url, name)
  408. else:
  409. try:
  410. try:
  411. name = out["signing_channel"]["value"]["title"]
  412. except:
  413. name = out["signing_channel"]["normalized_name"]
  414. url = out["signing_channel"]["permanent_url"]
  415. following.unfollow_channel(url, name)
  416. except:
  417. center("Publication is anonymous.", "bdrd")
  418. elif c.startswith("boost"):
  419. if " " in c:
  420. wallet.support(out["claim_id"], amount=c[c.find(" ")+1:])
  421. else:
  422. wallet.support(out["claim_id"])
  423. elif c.startswith("tip"):
  424. if " " in c:
  425. wallet.support(out["claim_id"], amount=c[c.find(" ")+1:], tip=True)
  426. else:
  427. wallet.support(out["claim_id"], tip=True)
  428. elif c.startswith("repost"):
  429. name = input(" Name (enter for name of publication): ")
  430. if not name:
  431. name = out["normalized_name"]
  432. a = c.split()
  433. try:
  434. bid = a[1]
  435. except:
  436. bid = input(" Bid: ")
  437. claim_id = out["claim_id"]
  438. ch, chid = channel.select("Channel to repost to:", True)
  439. repost_out = check_output(["flbry/lbrynet", "stream", "repost", "--name="+name, "--bid="+bid, "--claim_id="+claim_id, "--channel_id="+chid])
  440. repost_out = json.loads(repost_out)
  441. if "message" in repost_out:
  442. center("Error reposting: "+repost_out["message"], "bdrd")
  443. else:
  444. center("Successfully reposted to "+ch, "bdgr")
  445. elif c:
  446. out = plugin.run(out, command=c)
  447. complete(url_commands)