run.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. # Preparing the executable of lbrynet to be running. If we don't that
  30. # there will be a nasty error of permission being denied.
  31. import os
  32. os.system("chmod u+x flbry/lbrynet")
  33. # I'm not trying to make it pretty. I'm trying to make it snappy.
  34. # It's FastLBRY not SlowLBRY.
  35. import gi
  36. import threading
  37. gi.require_version('Gtk', '3.0')
  38. from gi.repository import Gtk
  39. from gi.repository import Gdk
  40. from gi.repository import GLib
  41. # It's gonna take a couple of things to make the button run
  42. from flbry import connect
  43. from flbry import parse
  44. from flbry import settings
  45. from flbry import url
  46. from flbry import ui
  47. from flbry import claim_search
  48. from flbry import fetch
  49. from flbry import publish
  50. settings.make_sure_file_exists()
  51. ##########################################################################
  52. # #
  53. # THE MAIN WINDOW OF FASTLBRY GTK #
  54. # #
  55. ##########################################################################
  56. win = Gtk.Window() # Make a GTK window object
  57. win.connect("destroy", Gtk.main_quit) # If you close the window, program quits
  58. win.set_title("FastLBRY GTK") # Title
  59. win.set_default_icon_from_file("icon.png") # Icon
  60. win.set_size_request(800, 900) # Minimum size
  61. ##########################################################################
  62. # #
  63. # GLOBAL VARIABLES HACK ( adding things to 'win' ) #
  64. # #
  65. ##########################################################################
  66. # This hack is very simple. Our main Gtk.Window() is a class to which we
  67. # can add variables later on. Like the example you see below.
  68. # This way, giving some function only 'win' and nothing else, will give
  69. # that other function the entire list of global variable ( added this way ).
  70. win.settings = settings.load() # Settings
  71. win.SDK_running = connect.check() # Is the SDK running ( from 0 to 1 to draw progress bar)
  72. win.resolved = {} # Last resolved urls
  73. win.commenting = {} # Data for while chatting in comments
  74. ##########################################################################
  75. # #
  76. # TOP PANNEL #
  77. # #
  78. ##########################################################################
  79. # Gtk.HeaderBar() is like a box but it draws itself in the headerbar insead
  80. # of in the window itself.
  81. pannel = Gtk.HeaderBar()
  82. pannel.set_show_close_button(True)
  83. win.set_titlebar(pannel) # Dumbasses wrote GTK
  84. # Untill the user connected to the SDK, search and other functions should
  85. # not work. So we split the pannel into two parts. The other one is a
  86. # normal box.
  87. restofpannel = Gtk.HBox() # H for Horrizontal
  88. pannel.pack_end(restofpannel)
  89. ############## CONNECT BUTTON ################
  90. # Connect button will also be a disconnect button
  91. # Icons for both
  92. icon_connect = ui.icon(win, "network-wired")
  93. icon_disconnect = ui.icon(win, "network-wired-disconnected")
  94. # Boxes for both
  95. connect_box = Gtk.HBox()
  96. disconnect_box = Gtk.HBox()
  97. # Connect box
  98. connect_box.pack_start(icon_connect, False, False, False)
  99. connect_box.set_tooltip_text("Connect to LBRY Network")
  100. connect_box.pack_start(Gtk.Label("Connect "), False, False, False)
  101. # Disconnect box
  102. disconnect_box.pack_start(icon_disconnect, False, False, False)
  103. disconnect_box.set_tooltip_text("Disconnect from LBRY Network")
  104. disconnect_box.pack_start(Gtk.Label("Disconnect "), False, False, False)
  105. # By whether SDK was running while the software was launched
  106. # we present the correct button to the user and set the rest-of-pannel
  107. # accordingly.
  108. if win.SDK_running:
  109. title = disconnect_box
  110. restofpannel.set_sensitive(True)
  111. else:
  112. title = connect_box
  113. restofpannel.set_sensitive(False)
  114. connect_disconncet_button = Gtk.Button()
  115. connect_disconncet_button.add(title)
  116. connect_disconncet_button.set_relief(Gtk.ReliefStyle.NONE)
  117. pannel.pack_start(connect_disconncet_button)#, False, False, False)
  118. ############### CONNECT / DISCONNECT BUTTONS FUNCTION ################
  119. # The button should do something.
  120. def connect_disconnect_function(w):
  121. w.set_sensitive(False)
  122. for i in dynamic_box.get_children():
  123. i.destroy()
  124. progress_connect = Gtk.ProgressBar(show_text=True)
  125. dynamic_box.pack_start(progress_connect, True, True, 30)
  126. win.show_all()
  127. def do_sdk(w, pb):
  128. wasrunning = win.SDK_running
  129. if win.SDK_running == 1:
  130. connect.stop()
  131. else:
  132. connect.start()
  133. def update_pb(pb):
  134. pb.set_fraction(win.SDK_running)
  135. pb.set_text("Connecting: "+str(int(round(win.SDK_running*100)))+"%")
  136. def update_buttons(w):
  137. for i in dynamic_box.get_children():
  138. i.destroy()
  139. w.get_child().destroy()
  140. if win.SDK_running == 1:
  141. w.add(disconnect_box)
  142. restofpannel.set_sensitive(True)
  143. else:
  144. w.add(connect_box)
  145. restofpannel.set_sensitive(False)
  146. w.show_all()
  147. w.set_sensitive(True)
  148. if win.SDK_running == 1:
  149. load_following()
  150. load_channel(win)
  151. ui.notify(win, "Connected to LBRY")
  152. if wasrunning == 0:
  153. while True:
  154. win.SDK_running = connect.check()
  155. if win.SDK_running == 1:
  156. break
  157. GLib.idle_add(update_pb, pb)
  158. GLib.idle_add(update_buttons, w)
  159. load_thread = threading.Thread(target=do_sdk, args=(w, progress_connect))
  160. load_thread.setDaemon(True)
  161. load_thread.start()
  162. connect_disconncet_button.connect("clicked", connect_disconnect_function)
  163. ##########################################################################
  164. # #
  165. # PUBLISH BUTTON #
  166. # #
  167. ##########################################################################
  168. restofpannel.pack_start(Gtk.VSeparator(), True, True, 20)
  169. publ = Gtk.Button()
  170. publ.set_tooltip_text("Publish to LBRY")
  171. publ.set_relief(Gtk.ReliefStyle.NONE)
  172. publ.add(ui.icon(win, "list-add"))
  173. restofpannel.pack_start(publ, False, False, False)
  174. def do_publish(w):
  175. publish.window(win)
  176. publ.connect("clicked", do_publish)
  177. restofpannel.pack_start(Gtk.VSeparator(), True, True, 20)
  178. ##########################################################################
  179. # #
  180. # SEARCH / URL BAR #
  181. # #
  182. ##########################################################################
  183. # Note that I add this directly into the 'win' to make it a global variable.
  184. # This let's me to start any url from anywhere in the software.
  185. win.url = Gtk.Entry() # Gtk.SearchEntry() also looks good, but nah
  186. win.url.set_size_request(400,40)
  187. restofpannel.pack_start(win.url, True, True, False)
  188. ####### SEARCH FUNCTION #######
  189. def search(w):
  190. # DRAG AND DROP FOR PUBLISH
  191. if win.url.get_text().startswith("file://") or os.path.exists(win.url.get_text()):
  192. # TODO: Some filenames include weird characters like %20 for spacebar and
  193. # stuff like that. We need to filter that out.
  194. filepath = win.url.get_text().replace("file://", "")
  195. publish.window(win, {"file_path":filepath})
  196. win.url.set_text("")
  197. return
  198. for i in dynamic_box.get_children():
  199. i.destroy()
  200. ## IF WE ARE NOT FORCING IT TO SEACH, IT WILL TRY TO RESOLVE THE LBRY URL FIRST ##
  201. if not force_search.get_active() or win.url.get_text().startswith("lbry://"):
  202. win.url.set_text(parse.bar(win.url.get_text()))
  203. resolve = ui.load(win, url.resolve, url.render_resolve, w, win, win.url.get_text())
  204. dynamic_box.pack_start(resolve, True, True, True)
  205. win.show_all()
  206. else:
  207. resolve = ui.load(win, claim_search.find, claim_search.render, win, win.url.get_text(), [], 1, {"order_by":""})
  208. dynamic_box.pack_start(resolve, True, True, True)
  209. win.show_all()
  210. # Button to activate seach for those who don't know that you can press Enter
  211. search_button = Gtk.Button()
  212. search_icon = ui.icon(win, "system-search")
  213. search_button.add(search_icon)
  214. search_button.set_relief(Gtk.ReliefStyle.NONE)
  215. search_button.connect("clicked", search)
  216. win.url.connect("activate", search)
  217. restofpannel.pack_start(search_button, False, False, False)
  218. ##########################################################################
  219. # #
  220. # HAMBURGER MENU #
  221. # #
  222. ##########################################################################
  223. # Popover is the new GTK menu thingy that looks like a comic book dialog
  224. # box. Which is what we want here. Because we want it to look nice I suppose.
  225. hamburger = Gtk.Popover()
  226. hambutton = Gtk.MenuButton(popover=hamburger)
  227. hambutton_icon = ui.icon(win, "system-users")
  228. hambutton.add(hambutton_icon)
  229. hambutton.set_relief(Gtk.ReliefStyle.NONE)
  230. pannel.pack_start(hambutton)
  231. # Let's now pack the hamburger menu with items
  232. hambox = Gtk.HBox()
  233. # For the elements inside the box we need a scrolled window, to fit a lot of
  234. # them there.
  235. hamscrl = Gtk.ScrolledWindow()
  236. hamscrl.set_size_request(200,200)
  237. hamburger.add(hambox)
  238. hambox.pack_start(hamscrl, False, False, False)
  239. # "hamchannelbox" will be empty untill the user connects and it loads his channel
  240. # list
  241. hamchannelbox = Gtk.VBox()
  242. hamscrl.add_with_viewport(hamchannelbox)
  243. #hambox.pack_start(hamchannelbox, False, False, False)
  244. ###### CHANNELS SELECTOR ########
  245. win.channel = False
  246. def load_channel(win):
  247. def change_channel(w, win, channel ):
  248. win.channel = channel
  249. win.settings["channel"] = win.channel["claim_id"]
  250. settings.save(win.settings)
  251. try:
  252. hambutton.get_child().destroy()
  253. try:
  254. newicon = ui.load(win, ui.net_image_calculation, ui.net_image_render, channel["value"]["thumbnail"]["url"], 40, "", False )
  255. except:
  256. newicon = ui.icon(win, "system-users")
  257. hambutton.add(newicon)
  258. pannel.show_all()
  259. except Exception as e:
  260. print("What?", e)
  261. out = fetch.lbrynet("channel_list")
  262. win.my_channels = out
  263. # We are chooseing first only if there is no channel set in the settings
  264. try:
  265. first = out["items"][0]
  266. win.channel = first
  267. except:
  268. pass
  269. if "channel" in win.settings:
  270. try:
  271. for i in out["items"]:
  272. if i["claim_id"] == win.settings["channel"]:
  273. first = i
  274. break
  275. except:
  276. pass
  277. try:
  278. change_channel(False, win, first)
  279. except:
  280. pass
  281. try:
  282. go.set_sensitive(True)
  283. for i in out["items"]:
  284. channel_button = ui.go_to_channel(win, i, resolve=False)
  285. channel_button.connect("clicked", change_channel, win, i)
  286. hamchannelbox.pack_start(channel_button, False, False, False)
  287. hamchannelbox.show_all()
  288. second_raw.show_all()
  289. except Exception as e:
  290. print("CHANNEL ERROR:", e)
  291. second_raw = Gtk.VBox()
  292. hambox.pack_start(Gtk.VSeparator(), False, False, 5)
  293. hambox.pack_start(second_raw, False, False, False)
  294. ####### Go to channel button #####
  295. def go_to_channel(w):
  296. channel_url = win.channel["name"]
  297. try:
  298. channel_url = channel_url + "#" + win.channel["claim_id"]
  299. except:
  300. pass
  301. win.url.set_text(channel_url)
  302. win.url.activate()
  303. go = Gtk.Button()
  304. b = Gtk.HBox()
  305. b.pack_start(ui.icon(win, "folder-remote"), False, False, False)
  306. b.pack_start(Gtk.Label(" Go To Channel "), True, True, False)
  307. go.add(b)
  308. go.set_sensitive(False)
  309. go.set_relief(Gtk.ReliefStyle.NONE)
  310. go.connect("clicked", go_to_channel)
  311. second_raw.pack_start(go, False, False, False)
  312. second_raw.pack_start(Gtk.HSeparator(), False, False,5)
  313. ######## FOLLOWING BUTTON ###########
  314. def load_following( w=False):
  315. for i in dynamic_box.get_children():
  316. i.destroy()
  317. try:
  318. out = fetch.lbrynet("preference_get")
  319. subs_raw = out["shared"]["value"]["subscriptions"]
  320. subs_raw = fetch.lbrynet("resolve", {"urls":subs_raw})
  321. subs = []
  322. for i in subs_raw:
  323. try:
  324. subs.append(subs_raw[i]["claim_id"])
  325. except:
  326. pass
  327. except Exception as e:
  328. print("\n\nERROR:", e, "\n\n")
  329. subs = []
  330. resolve = ui.load(win, claim_search.find, claim_search.render, win, "", subs)
  331. dynamic_box.pack_start(resolve, True, True, True)
  332. dynamic_box.show_all()
  333. following = Gtk.Button()
  334. b = Gtk.HBox()
  335. b.pack_start(ui.icon(win, "emblem-favorite"), False, False, False)
  336. b.pack_start(Gtk.Label(" Following "), True, True, False)
  337. following.add(b)
  338. following.set_relief(Gtk.ReliefStyle.NONE)
  339. following.connect("clicked", load_following)
  340. second_raw.pack_start(following, False, False,False)
  341. ######## TRENDING BUTTONS ###########
  342. def load_trending(w=False, articles=False):
  343. for i in dynamic_box.get_children():
  344. i.destroy()
  345. send = {"order_by":"trending_mixed"}
  346. if articles:
  347. send["media_types"] = ["text/markdown"]
  348. resolve = ui.load(win, claim_search.find, claim_search.render, win, "", [], 1, send)
  349. dynamic_box.pack_start(resolve, True, True, True)
  350. dynamic_box.show_all()
  351. following = Gtk.Button()
  352. b = Gtk.HBox()
  353. b.pack_start(ui.icon(win, "applications-internet"), False, False, False)
  354. b.pack_start(Gtk.Label(" Trending "), True, True, False)
  355. following.add(b)
  356. following.set_relief(Gtk.ReliefStyle.NONE)
  357. following.connect("clicked", load_trending)
  358. second_raw.pack_start(following, False, False,False)
  359. following = Gtk.Button()
  360. b = Gtk.HBox()
  361. b.pack_start(ui.icon(win, "text-x-generic"), False, False, False)
  362. b.pack_start(Gtk.Label(" Articles "), True, True, False)
  363. following.add(b)
  364. following.set_relief(Gtk.ReliefStyle.NONE)
  365. following.connect("clicked", load_trending, True)
  366. second_raw.pack_start(following, False, False,False)
  367. ######## SETTINGS BUTTON ############
  368. second_raw.pack_start(Gtk.HSeparator(), False, False,5)
  369. settings_button = Gtk.Button()
  370. b = Gtk.HBox()
  371. b.pack_start(ui.icon(win, "preferences-system"), False, False, False)
  372. b.pack_start(Gtk.Label(" Settings "), True, True, False)
  373. settings_button.add(b)
  374. settings_button.set_relief(Gtk.ReliefStyle.NONE)
  375. settings_button.connect("clicked", settings.dialogue, win)
  376. second_raw.pack_start(settings_button, False, False, False)
  377. second_raw.pack_start(Gtk.HSeparator(), False, False,5)
  378. ########## FORCE SEARCH BUTTON #######
  379. force_search_box = Gtk.HBox()
  380. force_search = Gtk.Switch()
  381. force_search_box.pack_end(force_search, False, False, 0)
  382. force_search_box.pack_start(ui.icon(win, "system-search"), False, False, 0)
  383. force_search_box.pack_start(Gtk.Label(" Force Search "), True, True, 0)
  384. force_search.set_tooltip_text("If not activated it will try to resolve the claim first.")
  385. second_raw.pack_start(force_search_box, False, False, False)
  386. hambox.show_all()
  387. ##########################################################################
  388. # #
  389. # DYNAMIC BOX ( REST ) #
  390. # #
  391. ##########################################################################
  392. # Dynamic box is a box contents of which will change depending on the "page"
  393. # that the user is loading.
  394. box = Gtk.VBox() # I'm making a top level box just in case
  395. win.add(box)
  396. dynamic_box = Gtk.VBox() # And here our dynamic box
  397. scrl = Gtk.ScrolledWindow()
  398. box.pack_start(dynamic_box, True, True, False)
  399. # Just something to run if we are connected
  400. if win.SDK_running == 1:
  401. load_following()
  402. load_channel(win)
  403. else:
  404. dynamic_box.pack_start(Gtk.Label("LBRY is not connected. Please connect."), True, True, 20)
  405. ##########################################################################
  406. # #
  407. # DRAG AND DROP #
  408. # #
  409. ##########################################################################
  410. def on_drop(widget, drag_context, x, y, data, info, time):
  411. win.url.set_text(data.get_text())
  412. win.url.activate()
  413. enforce_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(4), 129)
  414. box.drag_dest_set(Gtk.DestDefaults.ALL, [enforce_target], Gdk.DragAction.COPY)
  415. box.connect("drag-data-received", on_drop)
  416. # Starting Everything
  417. win.show_all()
  418. Gtk.main()
  419. #### Clearing the cache of images ###
  420. ui.clean_image_cache()