url.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  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. import os
  30. import time
  31. import urllib.request
  32. import threading
  33. import json
  34. from subprocess import *
  35. from gi.repository import Gtk
  36. from gi.repository import Gdk
  37. from gi.repository import GLib
  38. from gi.repository import Pango
  39. from gi.repository import GdkPixbuf
  40. from PIL import Image, ImageSequence
  41. from flbry import markdown
  42. from flbry import data_view
  43. from flbry import ui
  44. from flbry import fetch
  45. from flbry import claim_search
  46. from flbry import comments
  47. from flbry import follow
  48. from flbry import publish
  49. from flbry import analytics
  50. from flbry import livestreams
  51. from flbry import suggest
  52. from flbry import odysee
  53. from flbry import oscalls
  54. def resolve(w, win, url):
  55. # This function will draw a widget of resolved url
  56. #####################################################
  57. # RESOLVING PART ( NO GTK ) #
  58. #####################################################
  59. out = fetch.lbrynet("resolve",
  60. {"urls":[url]}
  61. )
  62. out = out[url]
  63. # Saving the resolved thing into the win for later use
  64. win.resolved = out
  65. # out = check_output(["flbry/lbrynet",
  66. # "resolve", url])
  67. # raw_data = out.decode("utf-8")
  68. # # Now we want to parse the json
  69. # try:
  70. # out = json.loads(out)
  71. # out = out[url]
  72. # except:
  73. # print("Resolve Failed")
  74. # return False
  75. try:
  76. is_channel = "value_type" in out and out["value_type"] == "channel"
  77. percentage = downloaded(out["claim_id"])
  78. except:
  79. # If this fails we activate search
  80. return ["search", False, False, w, win, url, False]
  81. # make sure the url path starts with lbry://
  82. if not url.startswith("lbry://"):
  83. url = "lbry://"+url
  84. win.url.set_text(url)
  85. if is_channel:
  86. stream_data = livestreams.get_data(out["claim_id"])
  87. else:
  88. try:
  89. stream_data = livestreams.get_data(out["signing_channel"]["claim_id"])
  90. except:
  91. stream_data = {}
  92. # Aditional data
  93. out["address_is_mine"] = fetch.lbrynet("address_is_mine", {"address":out["address"]})
  94. out["supports_preview"] = fetch.lbrynet("support_abandon", {"claim_id":out["claim_id"],
  95. "preview":True})
  96. return [out, is_channel, percentage, w, win, url, stream_data]
  97. def render_resolve(data):
  98. out, is_channel, percentage, w, win, url, stream_data = data
  99. # It could be failed
  100. if out == "search":
  101. return ui.load(win, claim_search.find, claim_search.render, win, win.url.get_text(), [], 1, {"order_by":""})
  102. #####################################################
  103. # DRAWING PART ( GTK ) #
  104. #####################################################
  105. try:
  106. price = out["value"]["fee"]["amount"]
  107. except:
  108. price = 0
  109. try:
  110. currency = out["value"]["fee"]["currency"]
  111. except:
  112. currency = "LBC"
  113. box = Gtk.HBox()
  114. outbox = Gtk.VBox()
  115. #### THUMBNAIL ###
  116. thumb = ""
  117. try:
  118. thumb = out["value"]["thumbnail"]["url"]
  119. thumb_url = ui.image_save_name(thumb)
  120. def thumb_open(w):
  121. oscalls.Open(thumb_url)
  122. thumb_button = Gtk.Button()
  123. thumb_button.set_tooltip_text(thumb)
  124. if is_channel:
  125. thumb_image = ui.load(win, ui.net_image_calculation, ui.net_image_render, thumb, 150, "", True)
  126. else:
  127. thumb_image = ui.load(win, ui.net_image_calculation, ui.net_image_render, thumb, 400, "", True)
  128. thumb_button.add(thumb_image)
  129. thumb_button.set_relief(Gtk.ReliefStyle.NONE)
  130. thumb_button.connect("clicked", thumb_open)
  131. box.pack_start(thumb_button, False, False, False)
  132. except:
  133. pass
  134. outbox.pack_start(box, False, False, False)
  135. from_right_to_thumbnail = Gtk.VBox()
  136. box.pack_end(from_right_to_thumbnail, True, True, False)
  137. # If channel load banner
  138. if is_channel:
  139. if "cover" in out["value"]:
  140. # This is a hack to set the scroller at the center
  141. # of the cover image
  142. def render_cover(calc):
  143. # So this fucntion pretends to be the net_image_render
  144. r = ui.net_image_render(calc) # It does call it though
  145. # But we need the size of the image and we need to do
  146. # something after the image is loaded.
  147. def wait():
  148. # This will execute after a delay
  149. v = channel_scroll.get_vadjustment()
  150. h = channel_scroll.get_hadjustment()
  151. # TODO: Check that the math is correct. It seems
  152. # to move the scrolls a bit too far.
  153. v.set_value( r.get_pixbuf().get_height() / 2 - 75)
  154. h.set_value( r.get_pixbuf().get_width() / 2 - 100)
  155. def start_waiting():
  156. # This is the delay thread
  157. time.sleep(0.1)
  158. GLib.idle_add(wait) # Making sure to use GLib so not
  159. # to get Segmentation Fault
  160. load_thread = threading.Thread(target=start_waiting)
  161. load_thread.start()
  162. # And we need to give the renderer the image itself, so it
  163. # could be added into the UI.
  164. return r # This happens before we scroll to the center
  165. # So think about it:
  166. # 1. ui.load loads the ui.net_image_calclation which downloads the cover
  167. # 2. This function runs. Activating ui.net_image_render getting the Gtk.Image
  168. # 3. We setup the thread that waits for 0.1 seconds
  169. # 4. Meanwhile ui.load recieves the ui.image and window updates with it
  170. # 5. 0.1 seconds later: we calculate the width and height of the image
  171. # and set the scroll adjustments accordilgly.
  172. # Simple isn't it?
  173. channel_banner = ui.load(win, ui.net_image_calculation, render_cover, out["value"]["cover"]["url"], False, "", True)
  174. channel_scroll = Gtk.ScrolledWindow()
  175. channel_scroll.set_size_request(400,150)
  176. channel_scroll.add_with_viewport(channel_banner)
  177. from_right_to_thumbnail.pack_start(channel_scroll, False, False, False)
  178. else:
  179. try:
  180. channel_scroll.destroy()
  181. except:
  182. pass
  183. #### NAME / CHANNEL ####
  184. the_packing_box = from_right_to_thumbnail
  185. if is_channel:
  186. the_packing_box = outbox
  187. name_channel_box = Gtk.VBox()
  188. the_packing_box.pack_start(name_channel_box, False, False, False)
  189. # Channel
  190. if "signing_channel" in out:
  191. chbox = Gtk.HBox()
  192. name_channel_box.pack_start(chbox, False, False, 0)
  193. chbox.pack_start(ui.go_to_channel(win, out["signing_channel"]),True,True,False)
  194. fbutton = follow.button(win, out["signing_channel"]["permanent_url"])
  195. chbox.pack_start(fbutton, False, False, 0 )
  196. elif is_channel:
  197. fbutton = follow.button(win, out["permanent_url"])
  198. name_channel_box.pack_start(fbutton, False, False, False)
  199. # name it self
  200. title = out["name"]
  201. try:
  202. title = out["value"]["title"]
  203. except:
  204. pass
  205. title_label = Gtk.Label()
  206. title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
  207. title_label.set_line_wrap(True)
  208. title_label.set_markup('<span size="x-large"> '+title+'</span> ')
  209. title_label.set_selectable(True)
  210. title_label.set_css_name("")
  211. name_channel_box.pack_start(title_label, False, False, False)
  212. ################# TOOL BAR ##################
  213. print("streamdata ++++++++++++++++++++++++++++++++++++ ", stream_data)
  214. toolbox = Gtk.HBox()
  215. the_packing_box.pack_start(toolbox, False, False,False)
  216. # The channel might be live from a different publication. I want to give a button to resolve it.
  217. active_id = stream_data.get("data",{}).get("ActiveClaim", {}).get("ClaimID", "")
  218. if stream_data.get("data",{}).get("Live", False) and active_id != out["claim_id"]:
  219. live_elsewhere_box = Gtk.HBox()
  220. the_packing_box.pack_start(Gtk.HSeparator(), False, False, 5)
  221. the_packing_box.pack_start(live_elsewhere_box, False, False,False)
  222. live_elsewhere_box.pack_start(Gtk.VSeparator(), 0,0,10)
  223. live_elsewhere_box.pack_start(ui.icon(win, "dialog-warning"), 0,0,0)
  224. live_elsewhere_box.pack_start(Gtk.Label(" Currently Streaming! "), 0,0,0)
  225. def go_to_livestream(widget):
  226. resolve_url = fetch.lbrynet("claim_search", {"claim_id":active_id}).get("items", [{}])[0].get("canonical_url","")
  227. win.resolve_tab = "new_tab"
  228. win.url.set_text(resolve_url)
  229. win.url.activate()
  230. go_button = Gtk.Button()
  231. go_button.set_relief(Gtk.ReliefStyle.NONE)
  232. go_box = Gtk.HBox()
  233. go_button.add(go_box)
  234. go_box.pack_start(ui.icon(win, "go-jump"), 0,0,0)
  235. go_box.pack_start(Gtk.Label(" Go To Livestream "),0,0,0)
  236. go_button.connect("clicked", go_to_livestream)
  237. live_elsewhere_box.pack_start(go_button, 0,0,0)
  238. if not is_channel:
  239. # LIVESTREAM LAUNCH
  240. if stream_data.get("data",{}).get("Live", False) and active_id == out["claim_id"]:
  241. def live_launch_action(w):
  242. suggest.record_tags_to_suggestions(out.get("value", {}).get("tags", []))
  243. Popen([win.settings["live_stream_player"], win.settings["librarian_instance"]+"/live/content/"+out["signing_channel"]["claim_id"]+"/master.m3u8"])
  244. live_launch_button = Gtk.Button()
  245. live_launch_button.connect("clicked", live_launch_action)
  246. live_launch_button.set_relief(Gtk.ReliefStyle.NONE)
  247. live_launch_box = Gtk.HBox()
  248. live_launch_button.add(live_launch_box)
  249. live_launch_icon = ui.icon(win, "media-playback-start")
  250. live_launch_box.pack_start(live_launch_icon, False, False, False)
  251. live_launch_box.pack_start(Gtk.Label(" Watch Livestream "), False, False, False)
  252. toolbox.pack_start(live_launch_button, False,False,False)
  253. else:
  254. def download_action(w):
  255. suggest.record_tags_to_suggestions(out.get("value", {}).get("tags", []))
  256. start_downloading(url)
  257. download_button = Gtk.Button()
  258. download_button.connect("clicked", download_action)
  259. download_button.set_relief(Gtk.ReliefStyle.NONE)
  260. download_box = Gtk.HBox()
  261. download_button.add(download_box)
  262. download_icon = ui.icon(win, "go-down")
  263. download_box.pack_start(download_icon, False, False, False)
  264. #filesize
  265. labeltext = " Download"
  266. if price:
  267. labeltext = " Buy for "+str(price)+" "+currency
  268. try:
  269. filesize = out["value"]["source"]["size"]
  270. labeltext = labeltext + " ("+csize(filesize)+")"
  271. except:
  272. filesize = 0
  273. download_box.pack_start(Gtk.Label(labeltext), False, False, False)
  274. toolbox.pack_start(download_button, False,False,False)
  275. download_bar = Gtk.ProgressBar()
  276. the_packing_box.pack_start(download_bar, False, False,False)
  277. def delete_action(w):
  278. filename = get_downloaded_file(out["claim_id"])
  279. delete_file(out["claim_id"])
  280. try:
  281. os.remove(filename)
  282. except:
  283. pass
  284. delete_button = Gtk.Button()
  285. delete_button.connect("clicked", delete_action)
  286. delete_button.set_relief(Gtk.ReliefStyle.NONE)
  287. delete_box = Gtk.HBox()
  288. delete_button.add(delete_box)
  289. delete_icon = ui.icon(win,"edit-delete")
  290. delete_box.pack_start(delete_icon, False, False, False)
  291. delete_box.pack_start(Gtk.Label(" Delete "), False, False, False)
  292. toolbox.pack_start(delete_button, False,False,False)
  293. def launch_action(w):
  294. oscalls.Open(get_downloaded_file(out["claim_id"]))
  295. launch_button = Gtk.Button()
  296. launch_button.connect("clicked", launch_action)
  297. launch_button.set_relief(Gtk.ReliefStyle.NONE)
  298. launch_box = Gtk.HBox()
  299. launch_button.add(launch_box)
  300. launch_icon = ui.icon(win, "media-playback-start")
  301. launch_box.pack_start(launch_icon, False, False, False)
  302. launch_box.pack_start(Gtk.Label(" Launch "), False, False, False)
  303. toolbox.pack_start(launch_button, False,False,False)
  304. win.download_buttons[out["claim_id"]] = True
  305. t = threading.Thread(target=downloading_check_thread, args=(win, out["claim_id"],download_button, delete_button, launch_button, download_bar))
  306. t.setDaemon(True)
  307. t.start()
  308. def kill_daemon(w):
  309. win.download_buttons[out["claim_id"]] = False
  310. download_button.connect("destroy", kill_daemon)
  311. #################### REPOST BUTTON #######################
  312. try:
  313. toolbox.pack_start(Gtk.HSeparator(), False,False,5)
  314. def repost_action(w):
  315. # If name is empty, show message and let user input a new
  316. # value without hiding the popup
  317. if repost_name_entry.get_text() == "":
  318. ui.simple_message_box("Name is empty",
  319. "Name cannot be empty. Please enter something and try again.")
  320. return
  321. print("name", repost_name_entry.get_text())
  322. print("bid", repost_bid_entry.get_value())
  323. print("channel", win.channel["name"])
  324. print("channel_id", win.channel["claim_id"])
  325. repost_out = fetch.lbrynet("stream_repost",
  326. {"name":repost_name_entry.get_text(),
  327. "bid":str(float(repost_bid_entry.get_value())),
  328. "claim_id":out["claim_id"],
  329. "channel_id":win.channel["claim_id"]})
  330. print(repost_out)
  331. if "error" in repost_out:
  332. ui.notify(win, "Error while reposting", str(repost_out["error"]))
  333. else:
  334. ui.notify(win, "Reposted succesfully.", "lbry://"+win.channel["name"]+"/"+repost_name_entry.get_text())
  335. repost_menu.hide()
  336. repost_menu = Gtk.Popover()
  337. # Make the re-post popup show at the bottom of the button
  338. repost_menu.set_position(Gtk.PositionType.BOTTOM)
  339. repost_menu_box = Gtk.VBox()
  340. repost_menu.add(repost_menu_box)
  341. # Repost requires 3 entries of data:
  342. # Name:
  343. def on_url(w):
  344. w.set_text(publish.lbryname(w.get_text(), force=False))
  345. repost_name_box = Gtk.HBox()
  346. repost_menu_box.pack_start(repost_name_box, 0,0,0)
  347. channel_name = win.channel.get("name", "")
  348. repost_name_box_label = Gtk.Label(" lbry://")
  349. repost_name_box.pack_start(repost_name_box_label, 0,0,0)
  350. repost_name_entry = Gtk.Entry()
  351. repost_name_entry.connect("changed", on_url)
  352. repost_name_box.pack_start(repost_name_entry, 1,1,0)
  353. # Bid:
  354. repost_bid_box = Gtk.HBox()
  355. repost_menu_box.pack_start(repost_bid_box, 0,0,0)
  356. bid_adjust = Gtk.Adjustment(0.01,
  357. lower=0.0001,
  358. upper=1000000000,
  359. step_increment=0.1)
  360. repost_bid_entry = Gtk.SpinButton(adjustment=bid_adjust,
  361. digits=4)
  362. repost_bid_box.pack_start(Gtk.Label(" Bid: "), False, False, 0)
  363. repost_bid_box.pack_end(repost_bid_entry, False, False, 0)
  364. # Repost button
  365. do_repost_button = Gtk.Button()
  366. do_repost_button.connect("clicked", repost_action)
  367. do_repost_button.set_relief(Gtk.ReliefStyle.NONE)
  368. do_repost_box = Gtk.HBox()
  369. do_repost_box.pack_start(ui.icon(win, "media-playlist-repeat"), 0,0,0)
  370. do_repost_box.pack_start(Gtk.Label(" Do Re-Post "), False, False, False)
  371. do_repost_button.add(do_repost_box)
  372. repost_menu_box.pack_start(do_repost_button, 0,0,0)
  373. repost_menu_box.show_all()
  374. repost_button = Gtk.Button()
  375. repost_menu.set_relative_to(repost_button)
  376. repost_button.set_relief(Gtk.ReliefStyle.NONE)
  377. repost_box = Gtk.HBox()
  378. repost_button.add(repost_box)
  379. repost_icon = ui.icon(win, "media-playlist-repeat")
  380. repost_box.pack_start(repost_icon, False, False, False)
  381. try:
  382. reposted_times = out["meta"]["reposted"]
  383. if not reposted_times:
  384. 1/0 # kill switch to go straig to except LOL. I'm hacking. What
  385. # do you want from me.
  386. repost_label = " Re-Post ( "+str(reposted_times)+" ) "
  387. except:
  388. repost_label = " Re-Post "
  389. repost_box.pack_start(Gtk.Label(repost_label), False, False, False)
  390. def repost_button_action(button, event):
  391. # If:
  392. # - mouse clicked or
  393. # - key pressed
  394. # - either space (32)
  395. # - or enter (65293)
  396. if isinstance(event, Gdk.EventButton) or ( isinstance(event, Gdk.EventKey) and (event.keyval == 32 or event.keyval == 65293) ):
  397. channel_name = win.channel.get("name", "")
  398. # Show current channel name on label (not editable by user)
  399. repost_name_box_label.set_text(" lbry://"+channel_name+"/")
  400. # Show name of Re-Post (editable by user)
  401. repost_name_entry.set_text(out["normalized_name"])
  402. # Show Re-Post popup
  403. repost_menu.popup()
  404. # Capture mouse events on Re-Post button
  405. repost_button.connect("button-release-event", repost_button_action)
  406. # Capture key presses on Re-Post button (enter, space)
  407. repost_button.connect("key-release-event", repost_button_action)
  408. toolbox.pack_start(repost_button, False,False,False)
  409. except Exception as e:
  410. print("Re-Post error: ", e)
  411. ############# SUPPORT BUTTON ############
  412. support_menu = Gtk.Popover()
  413. support_menu_box = Gtk.VBox()
  414. support_menu.add(support_menu_box)
  415. # Bid:
  416. support_bid_box = Gtk.HBox()
  417. support_menu_box.pack_start(support_bid_box, 0,0,0)
  418. support_bid_adjust = Gtk.Adjustment(0.01,
  419. lower=0.0001,
  420. upper=1000000000,
  421. step_increment=0.1)
  422. support_bid_entry = Gtk.SpinButton(adjustment=support_bid_adjust,
  423. digits=4)
  424. support_bid_box.pack_start(Gtk.Label(" Amount: "), False, False, 0)
  425. support_bid_box.pack_end(support_bid_entry, False, False, 0)
  426. # Do button
  427. def support_action(w):
  428. print("support amount", support_bid_entry.get_value())
  429. print("channel", win.channel["name"])
  430. print("channel_id", win.channel["claim_id"])
  431. support_out = fetch.lbrynet("support_create",
  432. {"amount":str(float(support_bid_entry.get_value())),
  433. "claim_id":out["claim_id"],
  434. "channel_id":win.channel["claim_id"],
  435. "tip":True})
  436. print(support_out)
  437. if "error" in support_out:
  438. ui.notify(win, "Error while reposting", str(support_out["error"]))
  439. else:
  440. ui.notify(win, "Supported succesfully.")
  441. support_menu.hide()
  442. do_support_button = Gtk.Button()
  443. do_support_button.connect("clicked", support_action)
  444. do_support_button.set_relief(Gtk.ReliefStyle.NONE)
  445. do_support_box = Gtk.HBox()
  446. do_support_box.pack_start(ui.icon(win, "emblem-favorite"), 0,0,0)
  447. do_support_box.pack_start(Gtk.Label(" Do Support "), False, False, False)
  448. do_support_button.add(do_support_box)
  449. support_menu_box.pack_start(do_support_button, 0,0,0)
  450. support_menu_box.show_all()
  451. support_button = Gtk.MenuButton(popover=support_menu)
  452. support_button.set_relief(Gtk.ReliefStyle.NONE)
  453. support_box = Gtk.HBox()
  454. support_button.add(support_box)
  455. support_icon = ui.icon(win, "emblem-favorite")
  456. support_box.pack_start(support_icon, False, False, False)
  457. try:
  458. rounded = round(float(out["meta"]["effective_amount"]), 2)
  459. if not rounded:
  460. rounded = out["meta"]["effective_amount"]
  461. support_label = " Support ( "+str(rounded)+" ) "
  462. except Exception as e:
  463. print(e)
  464. support_label = " Support "
  465. support_box.pack_start(Gtk.Label(support_label), False, False, False)
  466. toolbox.pack_start(support_button, False,False,False)
  467. ############# ADITIONAL MENU #############
  468. aditional_menu = Gtk.Popover()
  469. aditional_menu_box = Gtk.VBox()
  470. aditional_menu.add(aditional_menu_box)
  471. aditional_menu_button = Gtk.MenuButton(popover=aditional_menu)
  472. aditional_menu_button.add(ui.icon(win, "view-more"))
  473. aditional_menu_button.set_relief(Gtk.ReliefStyle.NONE)
  474. toolbox.pack_end(aditional_menu_button, False,False,False)
  475. # If the publication is mine.
  476. ## Unlock Tips ##
  477. unlock_menu = Gtk.Popover()
  478. unlock_menu_box = Gtk.VBox()
  479. unlock_menu.add(unlock_menu_box)
  480. unlock_button = Gtk.MenuButton(popover=unlock_menu)
  481. unlock_button.set_relief(Gtk.ReliefStyle.NONE)
  482. unlock_bbox = Gtk.HBox()
  483. unlock_button.add(unlock_bbox)
  484. unlock_bbox.pack_start(ui.icon(win, "insert-object-symbolic"), 0,0,0)
  485. unlock_bbox.pack_start(Gtk.Label("Unlock Tips"), 0,0,5)
  486. aditional_menu_box.pack_start(unlock_button, 0,0,5)
  487. unlockable = float(out["supports_preview"].get("total_output", 0))
  488. unlock_menu_box.pack_start(Gtk.Label(" You can unlock up to: "+str(unlockable)+" LBC "), 0,0,5)
  489. unlock_counter_box = Gtk.HBox()
  490. unlock_menu_box.pack_start(unlock_counter_box, 0,0,5)
  491. unlock_amount_adjust = Gtk.Adjustment(0.01,
  492. lower=0,
  493. upper=unlockable,
  494. step_increment=0.1)
  495. unlock_amount_entry = Gtk.SpinButton(adjustment=unlock_amount_adjust,
  496. digits=4)
  497. unlock_counter_box.pack_start(Gtk.Label(" Unlock:"), 0,0,5)
  498. unlock_counter_box.pack_end(unlock_amount_entry, 0,0,5)
  499. def do_unlock_action(w):
  500. result = fetch.lbrynet("support_abandon", {"claim_id":out["claim_id"],
  501. "keep": str(unlockable - unlock_amount_entry.get_value())})
  502. print("\n\nUNLOCKING RESULT:\n\n", result, "\n\n")
  503. unlock_menu.hide()
  504. aditional_menu.hide()
  505. do_unlock_button = Gtk.Button()
  506. do_unlock_button.set_relief(Gtk.ReliefStyle.NONE)
  507. do_unlock_button.connect("clicked", do_unlock_action)
  508. do_unlock_box = Gtk.HBox()
  509. do_unlock_button.add(do_unlock_box)
  510. do_unlock_box.pack_start(ui.icon(win, "insert-object-symbolic"), 0,0,0)
  511. do_unlock_box.pack_start(Gtk.Label(" Do Unlock "), 0,0,0)
  512. unlock_menu_box.pack_start(do_unlock_button, 0,0,5)
  513. unlock_menu_box.show_all()
  514. ## Share ##
  515. share_menu = Gtk.Popover()
  516. share_menu_box = Gtk.VBox()
  517. share_menu_box.set_size_request(600, -1)
  518. share_menu.add(share_menu_box)
  519. share_button = Gtk.MenuButton(popover=share_menu)
  520. share_button.set_relief(Gtk.ReliefStyle.NONE)
  521. share_bbox = Gtk.HBox()
  522. share_button.add(share_bbox)
  523. share_bbox.pack_start(ui.icon(win, "emblem-shared-symbolic"), 0,0,0)
  524. share_bbox.pack_start(Gtk.Label("Share"), 0,0,5)
  525. aditional_menu_box.pack_start(share_button, 0,0,5)
  526. # LBRY share #
  527. def do_share_lbry_copy(w):
  528. share_lbry_entry.grab_focus()
  529. clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
  530. clipboard.set_text(share_lbry_entry.get_text(), -1)
  531. clipboard.store()
  532. share_lbry_box = Gtk.HBox()
  533. share_menu_box.pack_start(share_lbry_box, 0,0,5)
  534. share_lbry_entry = Gtk.Entry(text=url)
  535. share_lbry_entry.set_editable(False)
  536. share_lbry_box.pack_start(Gtk.Label(" LBRY:"), 0,0,5)
  537. share_lbry_box.pack_start(share_lbry_entry, 1,1,5)
  538. share_lbry_copy_button = Gtk.Button()
  539. share_lbry_copy_button.connect("clicked", do_share_lbry_copy)
  540. share_lbry_copy_button.add(ui.icon(win, "edit-copy"))
  541. share_lbry_copy_button.set_relief(Gtk.ReliefStyle.NONE)
  542. share_lbry_box.pack_end(share_lbry_copy_button, 0,0,5)
  543. # Librarian share #
  544. def do_share_librarian_copy(w):
  545. share_librarian_entry.grab_focus()
  546. clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
  547. clipboard.set_text(share_librarian_entry.get_text(), -1)
  548. clipboard.store()
  549. share_librarian_box = Gtk.HBox()
  550. share_menu_box.pack_start(share_librarian_box, 0,0,5)
  551. share_librarian_entry = Gtk.Entry(text=publish.librarian_url(url, win.settings["librarian_instance"]))
  552. share_librarian_entry.set_editable(False)
  553. share_librarian_box.pack_start(Gtk.Label(" Librarian:"), 0,0,5)
  554. share_librarian_box.pack_start(share_librarian_entry, 1,1,5)
  555. share_librarian_copy_button = Gtk.Button()
  556. share_librarian_copy_button.connect("clicked", do_share_librarian_copy)
  557. share_librarian_copy_button.add(ui.icon(win, "edit-copy"))
  558. share_librarian_copy_button.set_relief(Gtk.ReliefStyle.NONE)
  559. share_librarian_box.pack_end(share_librarian_copy_button, 0,0,5)
  560. share_menu_box.show_all()
  561. aditional_menu_box.show_all()
  562. ########### BOTTOM NOTEBOOK ##############
  563. notebook = Gtk.Notebook()
  564. notebook.set_scrollable(True)
  565. outbox.pack_start(notebook, True, True,False)
  566. # If article read article
  567. try:
  568. if out["value"]["source"]["media_type"] == "text/markdown" and price == 0:
  569. suggest.record_tags_to_suggestions(out.get("value", {}).get("tags", []))
  570. # We download it first
  571. playout = fetch.lbrynet("get", {"uri":url, "save_file":True})
  572. # playout = check_output(["flbry/lbrynet",
  573. # "get", url])
  574. # # Parsing the Json
  575. # playout = json.loads(playout)
  576. md_text = open(playout['download_path'])
  577. md_text = md_text.read()
  578. # Markdown covenreted
  579. md_scrl = Gtk.ScrolledWindow()
  580. md_view = Gtk.TextView()
  581. md_view.set_wrap_mode(Gtk.WrapMode.WORD )
  582. md_buffer = md_view.get_buffer()
  583. md_view.set_editable(False)
  584. md_scrl.add(md_view)
  585. md_buffer.set_text(md_text)
  586. markdown.convert(win, md_view)
  587. detailsbox = Gtk.HBox()
  588. detailsbox.pack_start(ui.icon(win, "text-x-generic"), False, False, False)
  589. detailsbox.pack_start(Gtk.Label(" Read Article "), True, True, True)
  590. detailsbox.show_all()
  591. notebook.append_page(md_scrl, detailsbox)
  592. # Markdown source
  593. md_scrl = Gtk.ScrolledWindow()
  594. md_view = Gtk.TextView()
  595. #md_view.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0.2,0.2,0.2, 1))
  596. #md_view.override_color(Gtk.StateType.NORMAL, Gdk.RGBA(0.9,0.9,0.9, 1))
  597. md_view.override_font(Pango.FontDescription("Monospace"))
  598. md_view.set_wrap_mode(Gtk.WrapMode.WORD )
  599. md_buffer = md_view.get_buffer()
  600. md_view.set_editable(False)
  601. md_scrl.add(md_view)
  602. md_buffer.set_text(md_text)
  603. detailsbox = Gtk.HBox()
  604. detailsbox.pack_start(ui.icon(win, "text-x-preview"), False, False, False)
  605. detailsbox.pack_start(Gtk.Label(" Source of Article "), True, True, True)
  606. detailsbox.show_all()
  607. notebook.append_page(md_scrl, detailsbox)
  608. except Exception as e:
  609. print("FUCKING ERROR")
  610. print(e)
  611. print()
  612. # Channel Uploads / Publications
  613. if is_channel:
  614. uploads_box = ui.load(win, claim_search.find, claim_search.render, win, "", [out["claim_id"]], )
  615. detailsbox = Gtk.HBox()
  616. detailsbox.pack_start(ui.icon(win, "folder-remote"), False, False, False)
  617. detailsbox.pack_start(Gtk.Label(" Publications "), True, True, True)
  618. detailsbox.show_all()
  619. notebook.append_page(uploads_box, detailsbox)
  620. ##### DESCRIPTION ####
  621. try:
  622. description_scrl = Gtk.ScrolledWindow()
  623. description_scrl.set_size_request(500,200)
  624. description_field = Gtk.TextView()
  625. description_field.set_wrap_mode(Gtk.WrapMode.WORD )
  626. description_field.set_editable(False)
  627. description_buffer = description_field.get_buffer()
  628. description_buffer.set_text(out["value"]["description"])
  629. markdown.convert(win, description_field)
  630. description_box = Gtk.VBox()
  631. detailsbox = Gtk.HBox()
  632. detailsbox.pack_start(ui.icon(win, "text-x-generic"), False, False, False)
  633. detailsbox.pack_start(Gtk.Label(" Description "), True, True, True)
  634. detailsbox.show_all()
  635. notebook.append_page(description_scrl, detailsbox)
  636. description_box.pack_start(description_scrl, False, False, False)
  637. description_scrl.add(description_field)
  638. except:
  639. pass
  640. ######## COMMENTS ########
  641. com_box = Gtk.VPaned()
  642. com_box.set_position(250)
  643. com_scrl = Gtk.ScrolledWindow()
  644. com_scrl.set_vexpand(0)
  645. #com_scrl.set_hexpand(0)
  646. comments_widget = ui.load(win, comments.list_comments, comments.render_comments, win, out )
  647. com_scrl.add(comments_widget)
  648. detailsbox = Gtk.HBox()
  649. detailsbox.pack_start(ui.icon(win, "document-send"), False, False, False)
  650. detailsbox.pack_start(Gtk.Label(" Comments "), True, True, True)
  651. detailsbox.show_all()
  652. com_box.add1(comments.comment_input(win, out["claim_id"]))
  653. com_box.add2(com_scrl)
  654. notebook.append_page(com_box, detailsbox)
  655. ##### Details #######
  656. # Almost like Raw Data but shows only the important stuff
  657. details = {"LBRY URL: ":url,
  658. "Price: ":str(price)+" "+str(currency)}
  659. try:
  660. details["Claim ID: "] = out["claim_id"]
  661. except:
  662. pass
  663. try:
  664. details["Upload Bid: "] = out["amount"]+" LBC"
  665. except:
  666. pass
  667. try:
  668. details["Support: "] = out["meta"]["support_amount"]+" LBC"
  669. except:
  670. pass
  671. try:
  672. if is_channel:
  673. details["Odysee Subscribers: "] = odysee.get_odysee_subs(win, out["claim_id"])[0]
  674. else:
  675. details["Odysee Views: "] = odysee.get_odysee_views(win, out["claim_id"])[0]
  676. except:
  677. pass
  678. try:
  679. details["Filename: "] = out["value"]["source"]["name"]
  680. except:
  681. pass
  682. try:
  683. details["File Size:"] = csize(filesize)
  684. except:
  685. pass
  686. try:
  687. details["License: "] = out["value"]["license"]
  688. except:
  689. pass
  690. try:
  691. details["Released at: "] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(out["value"]["release_time"])))
  692. except:
  693. pass
  694. try:
  695. #print(out["value"]["tags"])
  696. details["Tags: "] = out["value"]["tags"]
  697. except:
  698. pass
  699. det_scrl = Gtk.ScrolledWindow()
  700. det_view = data_view.data_widget(details)
  701. det_scrl.add(det_view)
  702. detailsbox = Gtk.HBox()
  703. detailsbox.pack_start(ui.icon(win, "dialog-information"), False, False, False)
  704. detailsbox.pack_start(Gtk.Label(" Details "), True, True, True)
  705. detailsbox.show_all()
  706. notebook.append_page(det_scrl, detailsbox)
  707. ######### ANALYTICS GRAPH #######
  708. plot_chart = fetch.lbrynet("txo_plot", { "days_back":1000, # Fetch 100 days of txo
  709. "exclude_internal_transfers":True, # Without crap
  710. "is_not_my_input":True, # Not from me ( as in support only )
  711. "claim_id":out["claim_id"]
  712. })
  713. if plot_chart:
  714. chart_box = Gtk.VBox()
  715. detailsbox = Gtk.HBox()
  716. detailsbox.pack_start(ui.icon(win, "text-csv"), False, False, False)
  717. detailsbox.pack_start(Gtk.Label(" Analytics "), True, True, True)
  718. detailsbox.show_all()
  719. notebook.append_page(chart_box, detailsbox)
  720. graph_data = {"items":[],
  721. "zoom":[0,0],
  722. "allow_negative":False
  723. }
  724. for i in plot_chart:
  725. a = {}
  726. a["amount"] = i["total"]
  727. a["timestamp"] = int(time.mktime(time.strptime(i["day"],"%Y-%m-%d")))
  728. graph_data["items"].append(a)
  729. the_graph = analytics.graph(win, graph_data, "Analytics")
  730. chart_box.pack_start(the_graph,1,1,1)
  731. try:
  732. t = title
  733. if is_channel:
  734. t = ""
  735. uploads_box = ui.load(win, claim_search.find, claim_search.render, win, t, [], 1, {"any_tags":out["value"]["tags"] , "claim_type":out["value_type"]} )
  736. detailsbox = Gtk.HBox()
  737. detailsbox.pack_start(ui.icon(win, "folder-remote"), False, False, False)
  738. detailsbox.pack_start(Gtk.Label(" Similar "), True, True, True)
  739. detailsbox.show_all()
  740. notebook.append_page(uploads_box, detailsbox)
  741. except:
  742. pass
  743. ##### Raw Data #######
  744. raw_scrl = Gtk.ScrolledWindow()
  745. raw_view = data_view.data_widget(out)
  746. raw_scrl.add(raw_view)
  747. detailsbox = Gtk.HBox()
  748. detailsbox.pack_start(ui.icon(win, "dialog-warning"), False, False, False)
  749. detailsbox.pack_start(Gtk.Label(" Extra Details "), True, True, True)
  750. detailsbox.show_all()
  751. notebook.append_page(raw_scrl, detailsbox)
  752. #outbox.show_all()
  753. return outbox
  754. def downloaded(claim_id):
  755. # Returns a fraction ( from 0 to 1 ) of the download
  756. # percentage. If it's a 0, we can use it to display
  757. # the download button.
  758. #out = check_output(["flbry/lbrynet",
  759. # "file", "list", "--claim_id="+claim_id])
  760. out = fetch.lbrynet("file_list", {"claim_id":claim_id})
  761. #print(out, '\n\n')
  762. try:
  763. #out = json.loads(out)
  764. out = out["items"][0]
  765. if out["status"] == "finished":
  766. return 1
  767. else:
  768. return out["written_bytes"] / out["total_bytes"]
  769. except:
  770. return 0
  771. def get_downloaded_file(claim_id):
  772. #out = check_output(["flbry/lbrynet",
  773. # "file", "list", "--claim_id="+claim_id])
  774. out = fetch.lbrynet("file_list", {"claim_id":claim_id})
  775. try:
  776. #out = json.loads(out)
  777. out = out["items"][0]
  778. return out["download_path"]
  779. except:
  780. return ""
  781. def delete_file(claim_id):
  782. #check_output(["flbry/lbrynet",
  783. # "file", "delete", "--claim_id="+claim_id])
  784. fetch.lbrynet("file_delete", {"claim_id":claim_id})
  785. def start_downloading(url):
  786. out = fetch.lbrynet("get", {"uri":url, "save_file":True})
  787. def downloading_check_thread(win, claim_id,
  788. download_button,
  789. delete_button,
  790. launch_button,
  791. progress_bar):
  792. # This is a thread that will toggle buttons on/off
  793. # based on a curretly downloading file.
  794. def update(fraction):
  795. if not fraction: # if it's 0
  796. download_button.set_visible(True)
  797. delete_button.set_visible(False)
  798. launch_button.set_visible(False)
  799. progress_bar.set_visible(False)
  800. else:
  801. download_button.set_visible(False)
  802. delete_button.set_visible(True)
  803. launch_button.set_visible(True)
  804. progress_bar.set_visible(True)
  805. progress_bar.set_fraction(fraction)
  806. if fraction == 1:
  807. progress_bar.set_visible(False)
  808. while True:
  809. fraction = downloaded(claim_id)
  810. GLib.idle_add(update, fraction)
  811. time.sleep(2) # The new algorithm is too fast LOL
  812. if not win.download_buttons[claim_id]:
  813. return
  814. def csize(x):
  815. x = float(x)
  816. l = ["B","KB", "MB", "GB", "TB"]
  817. for i in range(5):
  818. if x > 1024:
  819. x = x / 1024
  820. else:
  821. return str(round(x, 2))+" "+l[i]
  822. return str(round(x, 2))+" "+l[i]