comments.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  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 time
  30. import json
  31. import threading
  32. import urllib.request
  33. from gi.repository import Gtk
  34. from gi.repository import Gdk
  35. from gi.repository import GLib
  36. from gi.repository import Pango
  37. from gi.repository import GdkPixbuf
  38. from flbry import markdown
  39. from flbry import ui
  40. from flbry import fetch
  41. from flbry import settings
  42. # TODO: Get rid of this string and use win.settings["comment_api"] instead
  43. # This is going into the end of comments to promote FastLBRY GTK
  44. BUTTON_GTK_PROMOTE = "\n\n[![](https://player.odycdn.com/api/v4/streams/free/button_GTK/d025c8ec2cdb5122a85b374b7bc453ba11d9409d/6526ef)](https://notabug.org/jyamihud/FastLBRY-GTK)"
  45. BUTTON_GTK_PROMOTE_TEXT = "\n\n[Sent via FastLBRY GTK](https://notabug.org/jyamihud/FastLBRY-GTK)"
  46. def list_comments( win, data, page=1, megabox=False):
  47. claim_id = data["claim_id"]
  48. # gets list of comment
  49. params = {
  50. "claim_id": claim_id,
  51. "page": page,
  52. # "page_size": page_size,
  53. "sort_by": 0,
  54. "top_level": False,
  55. }
  56. time.sleep(1) # too fast
  57. out = comment_request(win, "comment.List", params)
  58. out = get_reactions(win, out)
  59. return [win, out, data, megabox]
  60. def render_single_comment(win, box, i):
  61. claim_id = i["claim_id"]
  62. items = win.commenting[claim_id]["data"]["result"]["items"]
  63. def send_reaction(w, reaction):
  64. remove = not w.get_active()
  65. print("remove", remove)
  66. sigs = sign(win.channel["name"], win.channel["name"])
  67. params = {
  68. "channel_name": win.channel["name"],
  69. "channel_id": win.channel["claim_id"],
  70. "comment_ids": i.get("comment_id"),
  71. "type": reaction,
  72. **sigs
  73. }
  74. if remove:
  75. params["remove"] = True
  76. out = comment_request(win, "reaction.React", params)
  77. print(out)
  78. # Like / Dislike ratio
  79. reactionbox = Gtk.HBox()
  80. like_dislike_ratio_box = Gtk.VBox()
  81. like_dislike_box = Gtk.HBox()
  82. print(i)
  83. likes = i.get("reactions", {}).get("like", 0)+i.get("my_reactions", {}).get("like", 0)
  84. like = Gtk.ToggleButton(" 👍 "+str(likes)+" ")
  85. like.set_active(i.get("my_reactions", {}).get("like", 0))
  86. like.connect("clicked", send_reaction, "like")
  87. like.set_relief(Gtk.ReliefStyle.NONE)
  88. like_dislike_box.pack_start(like, False, False, 0)
  89. dislikes = i.get("reactions", {}).get("dislike", 0)+i.get("my_reactions", {}).get("dislike", 0)
  90. dislike = Gtk.ToggleButton(" 👎 "+str(dislikes)+" ")
  91. dislike.set_active(i.get("my_reactions", {}).get("dislike", 0))
  92. dislike.set_relief(Gtk.ReliefStyle.NONE)
  93. dislike.connect("clicked", send_reaction, "dislike")
  94. like_dislike_box.pack_start(dislike, False, False, 0)
  95. like_dislike_ratio_box.pack_start(like_dislike_box, False, False, False)
  96. try:
  97. frac = likes / (likes + dislikes)
  98. except:
  99. frac = 0
  100. ratio_bar = Gtk.ProgressBar()
  101. ratio_bar.set_fraction(frac)
  102. like_dislike_ratio_box.pack_start(ratio_bar, True, True,0)
  103. reactionbox.pack_start(like_dislike_ratio_box, False, False, False)
  104. #creator_like
  105. creator_likes = i.get("reactions", {}).get("creator_like", 0)+i.get("my_reactions", {}).get("creator_like", 0)
  106. creator_like = Gtk.ToggleButton(" ❤ "+str(creator_likes)+" ")
  107. creator_like.set_relief(Gtk.ReliefStyle.NONE)
  108. creator_like.set_active(i.get("my_reactions", {}).get("creator_like", 0))
  109. creator_like.connect("clicked", send_reaction, "creator_like")
  110. reactionbox.pack_start(creator_like, False, False, 0)
  111. bones = i.get("reactions", {}).get("bones", 0)+i.get("my_reactions", {}).get("bones", 0)
  112. bone = Gtk.ToggleButton(" 🍖 "+str(bones)+" ")
  113. bone.set_relief(Gtk.ReliefStyle.NONE)
  114. bone.set_active(i.get("my_reactions", {}).get("bones", 0))
  115. bone.connect("clicked", send_reaction, "bones")
  116. reactionbox.pack_start(bone, False, False, 0)
  117. frozen_toms = i.get("reactions", {}).get("frozen_tom", 0)+i.get("my_reactions", {}).get("frozen_tom", 0)
  118. frozen_tom = Gtk.ToggleButton(" 🍦 "+str(frozen_toms)+" ")
  119. frozen_tom.set_relief(Gtk.ReliefStyle.NONE)
  120. frozen_tom.set_active(i.get("my_reactions", {}).get("frozen_tom", 0))
  121. frozen_tom.connect("clicked", send_reaction, "frozen_tom")
  122. reactionbox.pack_start(frozen_tom, False, False, 0)
  123. mind_blowns = i.get("reactions", {}).get("mind_blown", 0)+i.get("my_reactions", {}).get("mind_blown", 0)
  124. mind_blown = Gtk.ToggleButton(" 🤯 "+str(mind_blowns)+" ")
  125. mind_blown.set_relief(Gtk.ReliefStyle.NONE)
  126. mind_blown.connect("clicked", send_reaction, "mind_blown")
  127. mind_blown.set_active(i.get("my_reactions", {}).get("mind_blown", 0))
  128. reactionbox.pack_start(mind_blown, False, False, 0)
  129. ########### EDIT ############
  130. def edit_set(w):
  131. for ch in win.commenting[claim_id]["edit"]["box"].get_children():
  132. ch.destroy()
  133. win.commenting[claim_id]["edit"]["comment_id"] = i["comment_id"]
  134. edit_frame = Gtk.Frame(label=" Editing: ")
  135. edit_frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
  136. edit_box = Gtk.HBox()
  137. edit_frame.add(edit_box)
  138. chbox = Gtk.HBox()
  139. channel_button = ui.load(win, resolve_channel, render_channel, win, i["channel_url"])
  140. chbox.pack_start(channel_button, False, False, 0)
  141. edit_box.pack_start(chbox, False, False, 4)
  142. comment_label = Gtk.Label(i["comment"].split("\n")[0][:100]+" ...")
  143. comment_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
  144. comment_label.set_line_wrap(True)
  145. edit_box.pack_start(comment_label, False, False, 10)
  146. ##### DELETE EDIT BOX ####
  147. def delete_set(w):
  148. for ch in win.commenting[claim_id]["edit"]["box"].get_children():
  149. ch.destroy()
  150. win.commenting[claim_id]["edit"]["comment_id"] = ""
  151. delete = Gtk.Button()
  152. delete.connect("clicked", delete_set)
  153. delete.set_relief(Gtk.ReliefStyle.NONE)
  154. delete.set_tooltip_text("Cancel editing workflow")
  155. delete.add(ui.icon(win, "edit-delete"))
  156. win.commenting[claim_id]["edit"]["box"].pack_end(delete, False, False, 0)
  157. win.commenting["textview"].get_buffer().set_text(i["comment"])
  158. win.commenting[claim_id]["edit"]["box"].pack_start(edit_frame, True, True, 5)
  159. win.commenting[claim_id]["edit"]["box"].show_all()
  160. # Show edit button only if current comment is posted by current channel
  161. if len(items) > 0 and win.channel.get("claim_id", "_") == i.get("channel_id", ""):
  162. edit_button = Gtk.Button(" Edit ")
  163. edit_button.set_relief(Gtk.ReliefStyle.NONE)
  164. edit_button.connect("clicked", edit_set)
  165. reactionbox.pack_end(edit_button, False, False, False)
  166. ########### REPLY ############
  167. def reply_set(w):
  168. for ch in win.commenting[claim_id]["reply"]["box"].get_children():
  169. ch.destroy()
  170. win.commenting[claim_id]["reply"]["to"] = i["comment_id"]
  171. reply_frame = Gtk.Frame(label=" Replying to: ")
  172. reply_frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
  173. reply_box = Gtk.HBox()
  174. reply_frame.add(reply_box)
  175. chbox = Gtk.HBox()
  176. channel_button = ui.load(win, resolve_channel, render_channel, win, i["channel_url"])
  177. chbox.pack_start(channel_button, False, False, 0)
  178. reply_box.pack_start(chbox, False, False, 4)
  179. comment_label = Gtk.Label(i["comment"].split("\n")[0][:100]+" ...")
  180. comment_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
  181. comment_label.set_line_wrap(True)
  182. reply_box.pack_start(comment_label, False, False, 10)
  183. win.commenting[claim_id]["reply"]["box"].pack_start(reply_frame, True, True, 5)
  184. ##### DELETE REPLY ####
  185. def delete_set(w):
  186. for ch in win.commenting[claim_id]["reply"]["box"].get_children():
  187. ch.destroy()
  188. win.commenting[claim_id]["reply"]["to"] = ""
  189. delete = Gtk.Button()
  190. delete.connect("clicked", delete_set)
  191. delete.set_relief(Gtk.ReliefStyle.NONE)
  192. delete.add(ui.icon(win, "edit-delete"))
  193. win.commenting[claim_id]["reply"]["box"].pack_end(delete, False, False, 0)
  194. win.commenting[claim_id]["reply"]["box"].show_all()
  195. reply = Gtk.Button(" Reply ")
  196. reply.set_relief(Gtk.ReliefStyle.NONE)
  197. reply.connect("clicked", reply_set)
  198. reactionbox.pack_end(reply, False, False, False)
  199. box.pack_end(reactionbox, False, False, 0)
  200. comment_text = i["comment"]
  201. supporter = False
  202. if BUTTON_GTK_PROMOTE in comment_text or BUTTON_GTK_PROMOTE_TEXT in comment_text:
  203. supporter = True
  204. comment_text = comment_text.replace(BUTTON_GTK_PROMOTE, "").replace(BUTTON_GTK_PROMOTE_TEXT, "")
  205. def force_size(w, e):
  206. if w.get_allocated_width() > w.get_parent().get_parent().get_parent().get_parent().get_parent().get_allocated_width() - 10:
  207. all1 = w.get_parent().get_parent().get_parent().get_parent().get_parent().get_allocation()
  208. all2 = w.get_allocation()
  209. all1.y = all2.y
  210. w.size_allocate(all1)
  211. commentbox = Gtk.Frame()
  212. commentbox.set_border_width(5)
  213. comment_field = Gtk.TextView()
  214. if win.settings["comments_auto_resize"]:
  215. comment_field.connect("draw", force_size)
  216. comment_field.set_wrap_mode(Gtk.WrapMode.WORD_CHAR )
  217. comment_field.set_editable(False)
  218. comment_field.set_hexpand(False)
  219. comment_buffer = comment_field.get_buffer()
  220. comment_buffer.set_text(comment_text)
  221. markdown.convert(win, comment_field, imwidth=200)
  222. commentbox.add(comment_field)
  223. box.pack_end(commentbox, True, True, 0)
  224. # comment_label = Gtk.Label(comment_text)
  225. # comment_label.set_selectable(True)
  226. # comment_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
  227. # comment_label.set_line_wrap(True)
  228. # comment_label.set_max_width_chars(20)
  229. # box.pack_end(comment_label, True, True, 10)
  230. def resolve_channel(win, url):
  231. out = fetch.lbrynet("resolve", {"urls":url})
  232. out = out[url]
  233. return [win, out]
  234. def render_channel(out):
  235. win, out = out
  236. return ui.go_to_channel(win, out)
  237. # Sometimes it's a reply
  238. if "parent_id" in i:
  239. for b in items:
  240. if b["comment_id"] == i["parent_id"]:
  241. expand = Gtk.Expander(label=" In Reply to: ")
  242. reply_frame = Gtk.Frame()
  243. reply_frame.set_border_width(10)
  244. expand.add(reply_frame)
  245. reply_frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
  246. reply_box = Gtk.VBox()
  247. reply_frame.add(reply_box)
  248. render_single_comment(win, reply_box, b)
  249. box.pack_end(expand, True, True, 10)
  250. break
  251. # each channel will need to be resolved on fly
  252. if "channel_url" in i:
  253. chbox = Gtk.HBox()
  254. channel_button = ui.load(win, resolve_channel, render_channel, win, i["channel_url"])
  255. chbox.pack_start(channel_button, False, False, 0)
  256. if "support_amount" in i and i["support_amount"]:
  257. chbox.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
  258. chbox.pack_start(Gtk.Label(" "+str(i["support_amount"])+" LBC"), False, False, 0)
  259. if supporter:
  260. chbox.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
  261. chbox.pack_start(Gtk.Label(" Promoter of FastLBRY "), False, False, 0)
  262. box.pack_end(chbox, False, False, 4)
  263. box.pack_end(Gtk.HSeparator(), False, False, 0)
  264. def render_comments(out, megabox=False):
  265. win, out, data, megabox = out
  266. if not megabox:
  267. megabox = Gtk.VBox()
  268. megabox.show_all()
  269. # renders a list of comments
  270. box = Gtk.VBox()
  271. box.set_hexpand(False)
  272. # I want to setup a little daemon to update the comments
  273. # automatically
  274. claim_id = data["claim_id"]
  275. try:
  276. the_title = data["value"]["title"]
  277. except:
  278. the_title = data["name"]
  279. if out["result"]["page"] == 1: # If it's the first page
  280. win.commenting[claim_id]["box"] = box
  281. win.commenting[claim_id]["data"] = out
  282. win.check_comment_daemon_keep_alive = True
  283. load_thread = threading.Thread(target=comment_daemon, args=[win, data, the_title])
  284. load_thread.setDaemon(True)
  285. load_thread.start()
  286. def kill_daemon(w):
  287. win.check_comment_daemon_keep_alive = False
  288. box.connect("destroy", kill_daemon)
  289. try:
  290. items = out["result"]["items"]
  291. for i in items:
  292. if i not in win.commenting[claim_id]["data"]["result"]["items"]:
  293. win.commenting[claim_id]["data"]["result"]["items"].append(i)
  294. except:
  295. items = []
  296. for i in reversed(items):
  297. #print("\n\n:::::::::::::::::\n\n", i)
  298. render_single_comment(win, box, i)
  299. box.show_all()
  300. # The top level box containing about 50 comments
  301. megabox.pack_start(box, False, False, 0)
  302. lastthing(win, out, data, megabox)
  303. megabox.show_all()
  304. return megabox
  305. def comment_daemon(win, data, the_title=""):
  306. # This function will be a daemon for the current comment system to
  307. # load all new comments that happen while the user it on the page.
  308. claim_id = data["claim_id"]
  309. while win.commenting[claim_id]["keep_alive"]:
  310. time.sleep(2) # Update every two seconds ( I don't want it to crash )
  311. n = win.commenting[claim_id]["data"]
  312. try:
  313. claim_id = n["result"]["items"][0]["claim_id"]
  314. except:
  315. n["result"]["items"] = []
  316. continue
  317. win, out, data, megabox = list_comments(win, data)
  318. ##### LOGIC OF ADDING MISSING COMMENTS INTO THE BOX ####
  319. for i in reversed(out["result"]["items"]):
  320. if i not in n["result"]["items"]:
  321. ids = []
  322. for b in n["result"]["items"]:
  323. ids.append(b["comment_id"])
  324. ######## FOUND A NEW COMMENT #######
  325. if i["comment_id"] not in ids:
  326. try:
  327. channel = i["channel_name"]
  328. except:
  329. channel = "[anonymous]"
  330. text = i["comment"]
  331. print("COMMENT FROM ", channel, "IS", text)
  332. force = win.resolved["claim_id"] != claim_id
  333. ui.notify(win, channel+" Commented on "+the_title, text, force=force)
  334. win.commenting[claim_id]["data"]["result"]["items"].append(i)
  335. print(i["comment_id"])
  336. box = win.commenting[claim_id]["box"]
  337. def render(win, box, i):
  338. render_single_comment(win, box, i)
  339. box.show_all()
  340. GLib.idle_add(render, win, box, i)
  341. def br(w):
  342. if not win.commenting[claim_id]["listen"]:
  343. win.commenting[claim_id]["keep_alive"] = False
  344. win.commenting[claim_id]["box"].connect("destroy", br)
  345. def lastthing(win, out, data, megabox):
  346. # This will hack itself to load more comments when you reached the end.
  347. spinner_more = ui.icon(win, "loading", "gif")
  348. megabox.pack_start(spinner_more, False, False, 0)
  349. def draw_event(w, e):
  350. print("EVENT")
  351. w.destroy()
  352. try:
  353. claim_id = out["result"]["items"][0]["claim_id"]
  354. page = out["result"]["page"] + 1
  355. ui.load(win, list_comments, render_comments, win, data, page, megabox, wait=False )
  356. except Exception as e:
  357. print("ERROR IS:", e)
  358. spinner_more.connect("draw", draw_event)
  359. def get_reactions(win, out):
  360. # Adds reactions list to the out
  361. try:
  362. claim_id = out["result"]["items"][0]["claim_id"]
  363. ids = ""
  364. for i in out["result"]["items"]:
  365. ids = ids + i["comment_id"] + ","
  366. ids = ids[:-1]
  367. except:
  368. return out
  369. sigs = sign(win.channel["name"], win.channel["name"])
  370. rec = {
  371. "channel_name":win.channel["name"],
  372. "channel_id":win.channel["claim_id"],
  373. "claim_id":claim_id,
  374. "comment_ids":ids,
  375. **sigs
  376. }
  377. rec = comment_request(win, "reaction.List", rec, addition="?m=reaction.List")
  378. for i in out["result"]["items"]:
  379. try:
  380. i["reactions"] = rec["result"]["others_reactions"][i["comment_id"]]
  381. except:
  382. pass
  383. try:
  384. i["my_reactions"] = rec["result"]["my_reactions"][i["comment_id"]]
  385. except:
  386. pass
  387. return out
  388. def comment_request(win, method: str, params: dict, addition=""):
  389. """
  390. Sends a request to the comment API
  391. """
  392. data = {
  393. "method": method,
  394. "id": 1,
  395. "jsonrpc":"2.0",
  396. "params": params
  397. }
  398. data = json.dumps(data).encode()
  399. headers = {
  400. "Content-Type": "application/json"
  401. }
  402. try:
  403. req = urllib.request.Request(win.settings["comment_api"], data, headers)
  404. res = urllib.request.urlopen(req)
  405. out = res.read().decode()
  406. return json.loads(out)
  407. except Exception as e:
  408. print("COMMENT ERROR:", e)
  409. return
  410. def comment_input(win, claim_id):
  411. # claim_id = win.resolved["claim_id"]
  412. # This is the input for new comments.
  413. vbox = Gtk.VBox()
  414. pannel = Gtk.HBox()
  415. vbox.pack_start(pannel, False, False, False)
  416. ################## LISTEN BUTTON ###############
  417. try:
  418. give_listen = win.commenting[claim_id]["listen"]
  419. except:
  420. give_listen = False
  421. win.commenting[claim_id] = {"box": False,
  422. "data":{},
  423. "listen":give_listen,
  424. "keep_alive":True}
  425. if win.settings["notifications"]:
  426. lbox = Gtk.HBox()
  427. lbox.pack_start(Gtk.Label(" Listen "), False, False, False)
  428. listen = Gtk.Switch()
  429. def lclick(w, u):
  430. win.commenting[claim_id]["listen"] = listen.get_active()
  431. try:
  432. listen.set_active(win.commenting[claim_id]["listen"])
  433. except Exception as e:
  434. print("Switch is:", e)
  435. listen.connect("notify::active", lclick)
  436. lbox.pack_start(listen, False, False, False)
  437. pannel.pack_start(lbox, False, False, False)
  438. if win.channel:
  439. ############# BOX FOR REPLIES ##############
  440. # There will be a hidden box for replies. And when
  441. # the user presses the 'reply' button on any
  442. # of the current comments, it will fill up the box.
  443. win.commenting[claim_id]["reply"] = {"box":Gtk.HBox(),
  444. "to":""}
  445. vbox.pack_start( win.commenting[claim_id]["reply"]["box"], False, False, False)
  446. ############# BOX FOR EDITS ##############
  447. # There will be a hidden box for edits. And when
  448. # the user presses the 'edit' button on any
  449. # of the current comments, it will fill up the box.
  450. win.commenting[claim_id]["edit"] = {"box":Gtk.HBox(),
  451. "comment_id":""}
  452. vbox.pack_start( win.commenting[claim_id]["edit"]["box"], False, False, False)
  453. ############### SEND BUTTON ###################
  454. def send_do(w):
  455. tb = win.commenting["textview"].get_buffer()
  456. message = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True)
  457. #tb.set_text("")
  458. send_comment(win, message, claim_id, tb, support=support_bid_entry)
  459. send = Gtk.Button()
  460. send.connect("clicked", send_do)
  461. send.set_relief(Gtk.ReliefStyle.NONE)
  462. sendbox = Gtk.HBox()
  463. sendbox.pack_start(ui.icon(win, "document-send"), False, False, 0)
  464. sendbox.pack_start(Gtk.Label(" Send "), False, False, 0)
  465. pannel.pack_end(send, False, False, False)
  466. send.add(sendbox)
  467. ############## TEXT INPUT ##############
  468. inputbox = Gtk.HPaned()
  469. inputbox.set_position(500)
  470. vbox.pack_start(inputbox, True, True, True)
  471. frame = Gtk.Frame()
  472. scrl = Gtk.ScrolledWindow()
  473. win.commenting["textview"] = Gtk.TextView()
  474. win.commenting["textview"].override_font(Pango.FontDescription("Monospace"))
  475. win.commenting["textview"].set_wrap_mode(Gtk.WrapMode.WORD)
  476. #scrl.set_size_request(100,100)
  477. scrl.add(win.commenting["textview"])
  478. #view.set_size_request(100,100)
  479. frame.add(scrl)
  480. inputbox.add1(frame)
  481. ############## MARKDOWN PREVIEW ##############
  482. commentbox = Gtk.Frame()
  483. scrl2 = Gtk.ScrolledWindow()
  484. comment_field = Gtk.TextView()
  485. comment_field.set_wrap_mode(Gtk.WrapMode.WORD )
  486. comment_field.set_editable(False)
  487. comment_field.set_hexpand(False)
  488. comment_buffer = comment_field.get_buffer()
  489. scrl2.add(comment_field)
  490. commentbox.add(scrl2)
  491. inputbox.add2(commentbox)
  492. def on_changed(w):
  493. tb = win.commenting["textview"].get_buffer()
  494. message = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True)
  495. comment_buffer.set_text(message)
  496. markdown.convert(win, comment_field, 200)
  497. comment_field.show_all()
  498. # Scrolling to the end
  499. # TODO: Make it scroll to the character
  500. adj = scrl2.get_vadjustment()
  501. adj.set_value( adj.get_upper() )
  502. win.commenting["textview"].get_buffer().connect("changed", on_changed)
  503. ################# MARKDOWN CONTROLLS #################
  504. pannel.pack_start(Gtk.VSeparator(), False, False, 10)
  505. def do_mark(w, r, l):
  506. tb = win.commenting["textview"].get_buffer()
  507. s, e = tb.get_selection_bounds()
  508. tb.insert(s, r)
  509. s, e = tb.get_selection_bounds()
  510. tb.insert(e, l)
  511. print(r, l)
  512. bold = Gtk.Button()
  513. bold.connect("clicked", do_mark, "**", "**")
  514. bold.add(ui.icon(win, "format-text-bold"))
  515. bold.set_relief(Gtk.ReliefStyle.NONE)
  516. pannel.pack_start(bold, False, False, False)
  517. italic = Gtk.Button()
  518. italic.connect("clicked", do_mark, "*", "*")
  519. italic.add(ui.icon(win, "format-text-italic"))
  520. italic.set_relief(Gtk.ReliefStyle.NONE)
  521. pannel.pack_start(italic, False, False, False)
  522. code = Gtk.Button("</>")
  523. code.connect("clicked", do_mark, "`", "`")
  524. code.set_relief(Gtk.ReliefStyle.NONE)
  525. pannel.pack_start(code, False, False, False)
  526. pannel.pack_start(Gtk.VSeparator(), False, False, 10)
  527. #################### PROMOTE BUTTON ######################
  528. pannel.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
  529. pannel.pack_start(Gtk.Label(" Promote FastLBRY: "), False, False, 0)
  530. switch = Gtk.Switch()
  531. switch.set_tooltip_text("Adds a little link to FastLBRY visible in all other LBRY apps.")
  532. pannel.pack_start(switch, False, False, 0)
  533. switch.set_active(win.settings["promote_fast_lbry_in_comments"])
  534. def on_promote(w, e):
  535. win.settings["promote_fast_lbry_in_comments"] = switch.get_active()
  536. settings.save(win.settings)
  537. switch.connect("notify::active", on_promote)
  538. pannel.pack_start(Gtk.VSeparator(), False, False, 10)
  539. ################# SUPPORT AMOUNT ##########################
  540. pannel.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
  541. pannel.pack_start(Gtk.Label(" Support Amount: "), False, False, 0)
  542. support_bid_adjust = Gtk.Adjustment(0,
  543. lower=0,
  544. upper=1000000000,
  545. step_increment=0.1)
  546. support_bid_entry = Gtk.SpinButton(adjustment=support_bid_adjust,
  547. digits=4)
  548. pannel.pack_start(support_bid_entry, False, False, 0)
  549. pannel.pack_start(Gtk.VSeparator(), False, False, 10)
  550. else:
  551. vbox.pack_start(Gtk.Label("To comment, Make a channel"), False, False, 30)
  552. return vbox
  553. def send_comment(win, message, claim_id, tb, support=0):
  554. support = support.get_value()
  555. if support:
  556. support_out = fetch.lbrynet("support_create",
  557. {"amount":str(float(round(support, 6))),
  558. "claim_id":claim_id,
  559. "channel_id":win.channel["claim_id"],
  560. "tip":True})
  561. # if the user wants to promote FastLBRY
  562. if win.settings["promote_fast_lbry_in_comments"]:
  563. message = message + win.settings["promote_fast_lbry_text"]
  564. # claim_id = win.resolved["claim_id"]
  565. channel_name = win.channel["name"]
  566. channel_id = win.channel["claim_id"]
  567. sigs = sign(message, channel_name)
  568. if not sigs:
  569. return
  570. params = {
  571. "channel_id": channel_id,
  572. "channel_name": channel_name,
  573. "claim_id": claim_id,
  574. "comment": message,
  575. **sigs
  576. }
  577. if support:
  578. params["support_tx_id"] = support_out["txid"]
  579. if win.commenting[claim_id]["reply"]["to"]:
  580. params["parent_id"] = win.commenting[claim_id]["reply"]["to"]
  581. # Make sure to clean that up
  582. for ch in win.commenting[claim_id]["reply"]["box"].get_children():
  583. ch.destroy()
  584. win.commenting[claim_id]["reply"]["to"] = ""
  585. elif win.commenting[claim_id]["edit"]["comment_id"]:
  586. params["comment_id"] = win.commenting[claim_id]["edit"]["comment_id"]
  587. # Make sure to clean the editing info above the form
  588. for ch in win.commenting[claim_id]["edit"]["box"].get_children():
  589. ch.destroy()
  590. win.commenting[claim_id]["edit"]["comment_id"] = ""
  591. if params.get("comment_id", False):
  592. out = comment_request(win, "comment.Edit", params)
  593. else:
  594. out = comment_request(win, "comment.Create", params)
  595. i = out["result"] # This will error out if comment is not sent
  596. tb.set_text("") # Only if the comment is sent, we clean the text
  597. # RENDERING THE NEWLY DONW COMMENT INTO THE PAGE
  598. # TODO: It duplicates it when the comment is actually loaded
  599. win.commenting[claim_id]["data"]["result"]["items"].append(i)
  600. print(i["comment_id"])
  601. box = win.commenting[claim_id]["box"]
  602. def render(win, box, i):
  603. render_single_comment(win, box, i)
  604. box.show_all()
  605. GLib.idle_add(render, win, box, i)
  606. def sign(data: str = "", channel: str = "", message: str = "Channel to sign data with:", hexdata: str = ""):
  607. """
  608. Sign a string or hexdata and return the signatures
  609. Keyword arguments:
  610. data -- a string to sign
  611. channel -- channel name to sign with (e.g. "@example"). Will prompt for one if not given.
  612. message -- message to give when selecting a channel. Please pass this if not passing channel.
  613. hexdata -- direct hexadecimal data to sign
  614. """
  615. if (not data and not hexdata) or (data and hexdata):
  616. raise ValueError("Must give either data or hexdata")
  617. elif data:
  618. hexdata = data.encode().hex()
  619. if not channel:
  620. channel = select(message)
  621. if not channel.startswith("@"):
  622. channel = "@" + channel
  623. try:
  624. sigs = fetch.lbrynet("channel_sign", {"channel_name":channel, "hexdata":hexdata})
  625. # sigs = check_output([flbry_globals["lbrynet"],
  626. # "channel", "sign",
  627. # "--channel_name=" + channel,
  628. # "--hexdata=" + hexdata])
  629. # sigs = json.loads(sigs)
  630. return sigs
  631. except:
  632. print("Stupid Error")
  633. return