|
@@ -0,0 +1,792 @@
|
|
|
+#####################################################################
|
|
|
+# #
|
|
|
+# THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
|
|
|
+# LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
|
|
|
+# FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
|
|
|
+# WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
|
|
|
+# IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
|
|
|
+# OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
|
|
|
+# #
|
|
|
+# ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
|
|
|
+# (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
|
|
|
+# YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
|
|
|
+# THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
|
|
|
+# VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
|
|
|
+# WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
|
|
|
+# #
|
|
|
+# THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
|
|
|
+# NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
|
|
|
+# THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
|
|
|
+# SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
|
|
|
+# THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
|
|
|
+# #
|
|
|
+# THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
|
|
|
+# FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
|
|
|
+# THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
|
|
|
+# TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
|
|
|
+# #
|
|
|
+#####################################################################
|
|
|
+
|
|
|
+import time
|
|
|
+import json
|
|
|
+import threading
|
|
|
+import urllib.request
|
|
|
+from gi.repository import Gtk
|
|
|
+from gi.repository import Gdk
|
|
|
+from gi.repository import GLib
|
|
|
+from gi.repository import Pango
|
|
|
+from gi.repository import GdkPixbuf
|
|
|
+
|
|
|
+from flbry import markdown
|
|
|
+from flbry import ui
|
|
|
+from flbry import fetch
|
|
|
+from flbry import settings
|
|
|
+
|
|
|
+# TODO: Get rid of this string and use win.settings["comment_api"] instead
|
|
|
+
|
|
|
+comments_api = "https://comments.odysee.com/api/v2"
|
|
|
+
|
|
|
+# This is going into the end of comments to promote FastLBRY GTK
|
|
|
+BUTTON_GTK_PROMOTE = "\n\n[![](https://player.odycdn.com/api/v4/streams/free/button_GTK/d025c8ec2cdb5122a85b374b7bc453ba11d9409d/6526ef)](https://notabug.org/jyamihud/FastLBRY-GTK)"
|
|
|
+
|
|
|
+def list_comments( win, data, page=1, megabox=False):
|
|
|
+
|
|
|
+ claim_id = data["claim_id"]
|
|
|
+
|
|
|
+ # gets list of comment
|
|
|
+
|
|
|
+ params = {
|
|
|
+ "claim_id": claim_id,
|
|
|
+ "page": page,
|
|
|
+# "page_size": page_size,
|
|
|
+ "sort_by": 0,
|
|
|
+ "top_level": False,
|
|
|
+ }
|
|
|
+
|
|
|
+ time.sleep(1) # too fast
|
|
|
+
|
|
|
+ out = comment_request("comment.List", params)
|
|
|
+ out = get_reactions(win, out)
|
|
|
+
|
|
|
+ return [win, out, data, megabox]
|
|
|
+
|
|
|
+def render_single_comment(win, box, i):
|
|
|
+
|
|
|
+ claim_id = i["claim_id"]
|
|
|
+
|
|
|
+ items = win.commenting[claim_id]["data"]["result"]["items"]
|
|
|
+
|
|
|
+ def send_reaction(w, reaction):
|
|
|
+
|
|
|
+ remove = not w.get_active()
|
|
|
+ print("remove", remove)
|
|
|
+
|
|
|
+ sigs = sign(win.channel["name"], win.channel["name"])
|
|
|
+
|
|
|
+ params = {
|
|
|
+ "channel_name": win.channel["name"],
|
|
|
+ "channel_id": win.channel["claim_id"],
|
|
|
+ "comment_ids": i.get("comment_id"),
|
|
|
+ "type": reaction,
|
|
|
+ **sigs
|
|
|
+ }
|
|
|
+ if remove:
|
|
|
+ params["remove"] = True
|
|
|
+
|
|
|
+
|
|
|
+ out = comment_request("reaction.React", params)
|
|
|
+ print(out)
|
|
|
+
|
|
|
+ # Like / Dislike ratio
|
|
|
+
|
|
|
+ reactionbox = Gtk.HBox()
|
|
|
+
|
|
|
+ like_dislike_ratio_box = Gtk.VBox()
|
|
|
+
|
|
|
+ like_dislike_box = Gtk.HBox()
|
|
|
+ print(i)
|
|
|
+ likes = i.get("reactions", {}).get("like", 0)+i.get("my_reactions", {}).get("like", 0)
|
|
|
+
|
|
|
+ like = Gtk.ToggleButton(" 👍 "+str(likes)+" ")
|
|
|
+ like.set_active(i.get("my_reactions", {}).get("like", 0))
|
|
|
+ like.connect("clicked", send_reaction, "like")
|
|
|
+ like.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ like_dislike_box.pack_start(like, False, False, 0)
|
|
|
+
|
|
|
+ dislikes = i.get("reactions", {}).get("dislike", 0)+i.get("my_reactions", {}).get("dislike", 0)
|
|
|
+
|
|
|
+ dislike = Gtk.ToggleButton(" 👎 "+str(dislikes)+" ")
|
|
|
+ dislike.set_active(i.get("my_reactions", {}).get("dislike", 0))
|
|
|
+ dislike.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ dislike.connect("clicked", send_reaction, "dislike")
|
|
|
+ like_dislike_box.pack_start(dislike, False, False, 0)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ like_dislike_ratio_box.pack_start(like_dislike_box, False, False, False)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ try:
|
|
|
+ frac = likes / (likes + dislikes)
|
|
|
+ except:
|
|
|
+ frac = 0
|
|
|
+ ratio_bar = Gtk.ProgressBar()
|
|
|
+ ratio_bar.set_fraction(frac)
|
|
|
+ like_dislike_ratio_box.pack_start(ratio_bar, True, True,0)
|
|
|
+
|
|
|
+ reactionbox.pack_start(like_dislike_ratio_box, False, False, False)
|
|
|
+
|
|
|
+ #creator_like
|
|
|
+
|
|
|
+ creator_likes = i.get("reactions", {}).get("creator_like", 0)+i.get("my_reactions", {}).get("creator_like", 0)
|
|
|
+
|
|
|
+ creator_like = Gtk.ToggleButton(" ❤ "+str(creator_likes)+" ")
|
|
|
+ creator_like.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ creator_like.set_active(i.get("my_reactions", {}).get("creator_like", 0))
|
|
|
+ creator_like.connect("clicked", send_reaction, "creator_like")
|
|
|
+ reactionbox.pack_start(creator_like, False, False, 0)
|
|
|
+
|
|
|
+ bones = i.get("reactions", {}).get("bones", 0)+i.get("my_reactions", {}).get("bones", 0)
|
|
|
+
|
|
|
+ bone = Gtk.ToggleButton(" 🍖 "+str(bones)+" ")
|
|
|
+ bone.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ bone.set_active(i.get("my_reactions", {}).get("bones", 0))
|
|
|
+ bone.connect("clicked", send_reaction, "bones")
|
|
|
+ reactionbox.pack_start(bone, False, False, 0)
|
|
|
+
|
|
|
+ frozen_toms = i.get("reactions", {}).get("frozen_tom", 0)+i.get("my_reactions", {}).get("frozen_tom", 0)
|
|
|
+
|
|
|
+ frozen_tom = Gtk.ToggleButton(" 🍦 "+str(frozen_toms)+" ")
|
|
|
+ frozen_tom.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ frozen_tom.set_active(i.get("my_reactions", {}).get("frozen_tom", 0))
|
|
|
+ frozen_tom.connect("clicked", send_reaction, "frozen_tom")
|
|
|
+ reactionbox.pack_start(frozen_tom, False, False, 0)
|
|
|
+
|
|
|
+ mind_blowns = i.get("reactions", {}).get("mind_blown", 0)+i.get("my_reactions", {}).get("mind_blown", 0)
|
|
|
+
|
|
|
+ mind_blown = Gtk.ToggleButton(" 🤯 "+str(mind_blowns)+" ")
|
|
|
+ mind_blown.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ mind_blown.connect("clicked", send_reaction, "mind_blown")
|
|
|
+ mind_blown.set_active(i.get("my_reactions", {}).get("mind_blown", 0))
|
|
|
+ reactionbox.pack_start(mind_blown, False, False, 0)
|
|
|
+
|
|
|
+
|
|
|
+ ########### REPLY ############
|
|
|
+
|
|
|
+ def reply_set(w):
|
|
|
+ for ch in win.commenting[claim_id]["reply"]["box"].get_children():
|
|
|
+ ch.destroy()
|
|
|
+ win.commenting[claim_id]["reply"]["to"] = i["comment_id"]
|
|
|
+
|
|
|
+ reply_frame = Gtk.Frame(label=" Replying to: ")
|
|
|
+ reply_frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
|
|
|
+ reply_box = Gtk.HBox()
|
|
|
+ reply_frame.add(reply_box)
|
|
|
+
|
|
|
+ chbox = Gtk.HBox()
|
|
|
+ channel_button = ui.load(win, resolve_channel, render_channel, win, i["channel_url"])
|
|
|
+ chbox.pack_start(channel_button, False, False, 0)
|
|
|
+ reply_box.pack_start(chbox, False, False, 4)
|
|
|
+ comment_label = Gtk.Label(i["comment"].split("\n")[0][:100]+" ...")
|
|
|
+ comment_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
|
|
|
+ comment_label.set_line_wrap(True)
|
|
|
+ reply_box.pack_start(comment_label, False, False, 10)
|
|
|
+
|
|
|
+
|
|
|
+ win.commenting[claim_id]["reply"]["box"].pack_start(reply_frame, True, True, 5)
|
|
|
+
|
|
|
+ ##### DELETE REPLY ####
|
|
|
+ def delete_set(w):
|
|
|
+ for ch in win.commenting[claim_id]["reply"]["box"].get_children():
|
|
|
+ ch.destroy()
|
|
|
+ win.commenting[claim_id]["reply"]["to"] = ""
|
|
|
+
|
|
|
+ delete = Gtk.Button()
|
|
|
+ delete.connect("clicked", delete_set)
|
|
|
+ delete.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ delete.add(ui.icon(win, "edit-delete"))
|
|
|
+ win.commenting[claim_id]["reply"]["box"].pack_end(delete, False, False, 0)
|
|
|
+
|
|
|
+
|
|
|
+ win.commenting[claim_id]["reply"]["box"].show_all()
|
|
|
+ reply = Gtk.Button(" Reply ")
|
|
|
+ reply.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ reply.connect("clicked", reply_set)
|
|
|
+ reactionbox.pack_end(reply, False, False, False)
|
|
|
+
|
|
|
+ box.pack_end(reactionbox, False, False, 0)
|
|
|
+
|
|
|
+
|
|
|
+ comment_text = i["comment"]
|
|
|
+ supporter = False
|
|
|
+ if BUTTON_GTK_PROMOTE in comment_text:
|
|
|
+ supporter = True
|
|
|
+ comment_text = comment_text.replace(BUTTON_GTK_PROMOTE, "")
|
|
|
+
|
|
|
+
|
|
|
+ def force_size(w, e):
|
|
|
+ w.queue_resize()
|
|
|
+
|
|
|
+ commentbox = Gtk.Frame()
|
|
|
+ commentbox.set_border_width(5)
|
|
|
+ comment_field = Gtk.TextView()
|
|
|
+ #comment_field.connect("draw", force_size)
|
|
|
+ comment_field.set_wrap_mode(Gtk.WrapMode.WORD_CHAR )
|
|
|
+ comment_field.set_editable(False)
|
|
|
+ comment_field.set_hexpand(False)
|
|
|
+ comment_buffer = comment_field.get_buffer()
|
|
|
+ comment_buffer.set_text(comment_text)
|
|
|
+ markdown.convert(win, comment_field, imwidth=200)
|
|
|
+ commentbox.add(comment_field)
|
|
|
+ box.pack_end(commentbox, True, True, 0)
|
|
|
+
|
|
|
+
|
|
|
+ # comment_label = Gtk.Label(comment_text)
|
|
|
+ # comment_label.set_selectable(True)
|
|
|
+ # comment_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
|
|
|
+ # comment_label.set_line_wrap(True)
|
|
|
+ # comment_label.set_max_width_chars(20)
|
|
|
+ # box.pack_end(comment_label, True, True, 10)
|
|
|
+
|
|
|
+ def resolve_channel(win, url):
|
|
|
+ out = fetch.lbrynet("resolve", {"urls":url})
|
|
|
+ out = out[url]
|
|
|
+ return [win, out]
|
|
|
+ def render_channel(out):
|
|
|
+ win, out = out
|
|
|
+ return ui.go_to_channel(win, out)
|
|
|
+
|
|
|
+ # Sometimes it's a reply
|
|
|
+
|
|
|
+ if "parent_id" in i:
|
|
|
+ for b in items:
|
|
|
+ if b["comment_id"] == i["parent_id"]:
|
|
|
+ expand = Gtk.Expander(label=" In Reply to: ")
|
|
|
+ reply_frame = Gtk.Frame()
|
|
|
+ reply_frame.set_border_width(10)
|
|
|
+ expand.add(reply_frame)
|
|
|
+ reply_frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
|
|
|
+ reply_box = Gtk.VBox()
|
|
|
+ reply_frame.add(reply_box)
|
|
|
+
|
|
|
+ render_single_comment(win, reply_box, b)
|
|
|
+
|
|
|
+ box.pack_end(expand, True, True, 10)
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+ # each channel will need to be resolved on fly
|
|
|
+ if "channel_url" in i:
|
|
|
+ chbox = Gtk.HBox()
|
|
|
+ channel_button = ui.load(win, resolve_channel, render_channel, win, i["channel_url"])
|
|
|
+ chbox.pack_start(channel_button, False, False, 0)
|
|
|
+
|
|
|
+ if "support_amount" in i and i["support_amount"]:
|
|
|
+ chbox.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
|
|
|
+ chbox.pack_start(Gtk.Label(" "+str(i["support_amount"])+" LBC"), False, False, 0)
|
|
|
+
|
|
|
+ if supporter:
|
|
|
+ chbox.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
|
|
|
+ chbox.pack_start(Gtk.Label(" Promoter of FastLBRY "), False, False, 0)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ box.pack_end(chbox, False, False, 4)
|
|
|
+
|
|
|
+
|
|
|
+ box.pack_end(Gtk.HSeparator(), False, False, 0)
|
|
|
+
|
|
|
+def render_comments(out, megabox=False):
|
|
|
+
|
|
|
+ win, out, data, megabox = out
|
|
|
+
|
|
|
+ if not megabox:
|
|
|
+ megabox = Gtk.VBox()
|
|
|
+ megabox.show_all()
|
|
|
+
|
|
|
+ # renders a list of comments
|
|
|
+
|
|
|
+
|
|
|
+ box = Gtk.VBox()
|
|
|
+ box.set_hexpand(False)
|
|
|
+
|
|
|
+ # I want to setup a little daemon to update the comments
|
|
|
+ # automatically
|
|
|
+
|
|
|
+ claim_id = data["claim_id"]
|
|
|
+ try:
|
|
|
+ the_title = data["value"]["title"]
|
|
|
+ except:
|
|
|
+ the_title = data["name"]
|
|
|
+
|
|
|
+ if out["result"]["page"] == 1: # If it's the first page
|
|
|
+
|
|
|
+
|
|
|
+ win.commenting[claim_id]["box"] = box
|
|
|
+ win.commenting[claim_id]["data"] = out
|
|
|
+
|
|
|
+ win.check_comment_daemon_keep_alive = True
|
|
|
+ load_thread = threading.Thread(target=comment_daemon, args=[win, data, the_title])
|
|
|
+ load_thread.setDaemon(True)
|
|
|
+ load_thread.start()
|
|
|
+ def kill_daemon(w):
|
|
|
+ win.check_comment_daemon_keep_alive = False
|
|
|
+ box.connect("destroy", kill_daemon)
|
|
|
+
|
|
|
+
|
|
|
+ try:
|
|
|
+ items = out["result"]["items"]
|
|
|
+ for i in items:
|
|
|
+ if i not in win.commenting[claim_id]["data"]["result"]["items"]:
|
|
|
+ win.commenting[claim_id]["data"]["result"]["items"].append(i)
|
|
|
+ except:
|
|
|
+ items = []
|
|
|
+ for i in reversed(items):
|
|
|
+
|
|
|
+ #print("\n\n:::::::::::::::::\n\n", i)
|
|
|
+ render_single_comment(win, box, i)
|
|
|
+
|
|
|
+ box.show_all()
|
|
|
+
|
|
|
+ # The top level box containing about 50 comments
|
|
|
+ megabox.pack_start(box, False, False, 0)
|
|
|
+ lastthing(win, out, data, megabox)
|
|
|
+ megabox.show_all()
|
|
|
+
|
|
|
+
|
|
|
+ return megabox
|
|
|
+
|
|
|
+def comment_daemon(win, data, the_title=""):
|
|
|
+
|
|
|
+ # This function will be a daemon for the current comment system to
|
|
|
+ # load all new comments that happen while the user it on the page.
|
|
|
+
|
|
|
+ claim_id = data["claim_id"]
|
|
|
+
|
|
|
+ while win.commenting[claim_id]["keep_alive"]:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ time.sleep(2) # Update every two seconds ( I don't want it to crash )
|
|
|
+
|
|
|
+ n = win.commenting[claim_id]["data"]
|
|
|
+
|
|
|
+ try:
|
|
|
+ claim_id = n["result"]["items"][0]["claim_id"]
|
|
|
+ except:
|
|
|
+ n["result"]["items"] = []
|
|
|
+ continue
|
|
|
+
|
|
|
+ win, out, data, megabox = list_comments(win, data)
|
|
|
+
|
|
|
+ ##### LOGIC OF ADDING MISSING COMMENTS INTO THE BOX ####
|
|
|
+
|
|
|
+ for i in reversed(out["result"]["items"]):
|
|
|
+ if i not in n["result"]["items"]:
|
|
|
+ ids = []
|
|
|
+ for b in n["result"]["items"]:
|
|
|
+ ids.append(b["comment_id"])
|
|
|
+
|
|
|
+ ######## FOUND A NEW COMMENT #######
|
|
|
+
|
|
|
+ if i["comment_id"] not in ids:
|
|
|
+
|
|
|
+ try:
|
|
|
+ channel = i["channel_name"]
|
|
|
+ except:
|
|
|
+ channel = "[anonymous]"
|
|
|
+ text = i["comment"]
|
|
|
+ print("COMMENT FROM ", channel, "IS", text)
|
|
|
+ force = win.resolved["claim_id"] != claim_id
|
|
|
+ ui.notify(win, channel+" Commented on "+the_title, text, force=force)
|
|
|
+
|
|
|
+ win.commenting[claim_id]["data"]["result"]["items"].append(i)
|
|
|
+ print(i["comment_id"])
|
|
|
+ box = win.commenting[claim_id]["box"]
|
|
|
+ def render(win, box, i):
|
|
|
+ render_single_comment(win, box, i)
|
|
|
+ box.show_all()
|
|
|
+ GLib.idle_add(render, win, box, i)
|
|
|
+
|
|
|
+ def br(w):
|
|
|
+ if not win.commenting[claim_id]["listen"]:
|
|
|
+ win.commenting[claim_id]["keep_alive"] = False
|
|
|
+ win.commenting[claim_id]["box"].connect("destroy", br)
|
|
|
+
|
|
|
+
|
|
|
+def lastthing(win, out, data, megabox):
|
|
|
+
|
|
|
+ # This will hack itself to load more comments when you reached the end.
|
|
|
+
|
|
|
+ spinner_more = ui.icon(win, "loading", "gif")
|
|
|
+
|
|
|
+ megabox.pack_start(spinner_more, False, False, 0)
|
|
|
+
|
|
|
+
|
|
|
+ def draw_event(w, e):
|
|
|
+ print("EVENT")
|
|
|
+ w.destroy()
|
|
|
+
|
|
|
+ try:
|
|
|
+ claim_id = out["result"]["items"][0]["claim_id"]
|
|
|
+ page = out["result"]["page"] + 1
|
|
|
+ ui.load(win, list_comments, render_comments, win, data, page, megabox, wait=False )
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ print("ERROR IS:", e)
|
|
|
+
|
|
|
+
|
|
|
+ spinner_more.connect("draw", draw_event)
|
|
|
+
|
|
|
+
|
|
|
+def get_reactions(win, out):
|
|
|
+
|
|
|
+ # Adds reactions list to the out
|
|
|
+
|
|
|
+ try:
|
|
|
+ claim_id = out["result"]["items"][0]["claim_id"]
|
|
|
+ ids = ""
|
|
|
+ for i in out["result"]["items"]:
|
|
|
+ ids = ids + i["comment_id"] + ","
|
|
|
+ ids = ids[:-1]
|
|
|
+ except:
|
|
|
+ return out
|
|
|
+ sigs = sign(win.channel["name"], win.channel["name"])
|
|
|
+ rec = {
|
|
|
+ "channel_name":win.channel["name"],
|
|
|
+ "channel_id":win.channel["claim_id"],
|
|
|
+ "claim_id":claim_id,
|
|
|
+ "comment_ids":ids,
|
|
|
+ **sigs
|
|
|
+ }
|
|
|
+ rec = comment_request("reaction.List", rec, addition="?m=reaction.List")
|
|
|
+
|
|
|
+ for i in out["result"]["items"]:
|
|
|
+ try:
|
|
|
+ i["reactions"] = rec["result"]["others_reactions"][i["comment_id"]]
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ try:
|
|
|
+ i["my_reactions"] = rec["result"]["my_reactions"][i["comment_id"]]
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+ return out
|
|
|
+
|
|
|
+def comment_request(method: str, params: dict, addition=""):
|
|
|
+ """
|
|
|
+ Sends a request to the comment API
|
|
|
+ """
|
|
|
+ data = {
|
|
|
+ "method": method,
|
|
|
+ "id": 1,
|
|
|
+ "jsonrpc":"2.0",
|
|
|
+ "params": params
|
|
|
+ }
|
|
|
+ data = json.dumps(data).encode()
|
|
|
+
|
|
|
+ headers = {
|
|
|
+ "Content-Type": "application/json"
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ try:
|
|
|
+ req = urllib.request.Request(comments_api, data, headers)
|
|
|
+ res = urllib.request.urlopen(req)
|
|
|
+ out = res.read().decode()
|
|
|
+ return json.loads(out)
|
|
|
+ except Exception as e:
|
|
|
+ print("COMMENT ERROR:", e)
|
|
|
+ return
|
|
|
+
|
|
|
+def comment_input(win, claim_id):
|
|
|
+
|
|
|
+
|
|
|
+ # claim_id = win.resolved["claim_id"]
|
|
|
+
|
|
|
+ # This is the input for new comments.
|
|
|
+
|
|
|
+ vbox = Gtk.VBox()
|
|
|
+ pannel = Gtk.HBox()
|
|
|
+ vbox.pack_start(pannel, False, False, False)
|
|
|
+
|
|
|
+ ################## LISTEN BUTTON ###############
|
|
|
+
|
|
|
+ try:
|
|
|
+ give_listen = win.commenting[claim_id]["listen"]
|
|
|
+ except:
|
|
|
+ give_listen = False
|
|
|
+
|
|
|
+ win.commenting[claim_id] = {"box": False,
|
|
|
+ "data":{},
|
|
|
+ "listen":give_listen,
|
|
|
+ "keep_alive":True}
|
|
|
+
|
|
|
+ if win.settings["notifications"]:
|
|
|
+
|
|
|
+ lbox = Gtk.HBox()
|
|
|
+ lbox.pack_start(Gtk.Label(" Listen "), False, False, False)
|
|
|
+ listen = Gtk.Switch()
|
|
|
+
|
|
|
+ def lclick(w, u):
|
|
|
+ win.commenting[claim_id]["listen"] = listen.get_active()
|
|
|
+ try:
|
|
|
+ listen.set_active(win.commenting[claim_id]["listen"])
|
|
|
+ except Exception as e:
|
|
|
+ print("Switch is:", e)
|
|
|
+ listen.connect("notify::active", lclick)
|
|
|
+ lbox.pack_start(listen, False, False, False)
|
|
|
+ pannel.pack_start(lbox, False, False, False)
|
|
|
+
|
|
|
+
|
|
|
+ if win.channel:
|
|
|
+
|
|
|
+ ############# BOX FOR REPLIES ##############
|
|
|
+
|
|
|
+ # There will be a hidden box for replies. And when
|
|
|
+ # the user presses the 'reply' button on any
|
|
|
+ # of the current comments, it will fill up the box.
|
|
|
+
|
|
|
+ win.commenting[claim_id]["reply"] = {"box":Gtk.HBox(),
|
|
|
+ "to":""}
|
|
|
+ vbox.pack_start( win.commenting[claim_id]["reply"]["box"], False, False, False)
|
|
|
+
|
|
|
+
|
|
|
+ ############### SEND BUTTON ###################
|
|
|
+
|
|
|
+ def send_do(w):
|
|
|
+
|
|
|
+ tb = view.get_buffer()
|
|
|
+ message = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True)
|
|
|
+ #tb.set_text("")
|
|
|
+ send_comment(win, message, claim_id, tb, support=support_bid_entry)
|
|
|
+
|
|
|
+ send = Gtk.Button()
|
|
|
+ send.connect("clicked", send_do)
|
|
|
+ send.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ sendbox = Gtk.HBox()
|
|
|
+ sendbox.pack_start(ui.icon(win, "document-send"), False, False, 0)
|
|
|
+ sendbox.pack_start(Gtk.Label(" Send "), False, False, 0)
|
|
|
+ pannel.pack_end(send, False, False, False)
|
|
|
+ send.add(sendbox)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ ############## TEXT INPUT ##############
|
|
|
+
|
|
|
+ inputbox = Gtk.HPaned()
|
|
|
+ inputbox.set_position(500)
|
|
|
+ vbox.pack_start(inputbox, True, True, True)
|
|
|
+
|
|
|
+ frame = Gtk.Frame()
|
|
|
+ scrl = Gtk.ScrolledWindow()
|
|
|
+ view = Gtk.TextView()
|
|
|
+ view.override_font(Pango.FontDescription("Monospace"))
|
|
|
+ view.set_wrap_mode(Gtk.WrapMode.WORD)
|
|
|
+ scrl.set_size_request(100,100)
|
|
|
+ scrl.add(view)
|
|
|
+ #view.set_size_request(100,100)
|
|
|
+ frame.add(scrl)
|
|
|
+ inputbox.add1(frame)
|
|
|
+
|
|
|
+ ############## MARKDOWN PREVIEW ##############
|
|
|
+
|
|
|
+ commentbox = Gtk.Frame()
|
|
|
+ scrl2 = Gtk.ScrolledWindow()
|
|
|
+ comment_field = Gtk.TextView()
|
|
|
+ comment_field.set_wrap_mode(Gtk.WrapMode.WORD )
|
|
|
+ comment_field.set_editable(False)
|
|
|
+ comment_field.set_hexpand(False)
|
|
|
+ comment_buffer = comment_field.get_buffer()
|
|
|
+ scrl2.add(comment_field)
|
|
|
+ commentbox.add(scrl2)
|
|
|
+ inputbox.add2(commentbox)
|
|
|
+
|
|
|
+ def on_changed(w):
|
|
|
+
|
|
|
+ tb = view.get_buffer()
|
|
|
+ message = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True)
|
|
|
+ comment_buffer.set_text(message)
|
|
|
+ markdown.convert(win, comment_field, 200)
|
|
|
+ comment_field.show_all()
|
|
|
+
|
|
|
+ # Scrolling to the end
|
|
|
+ # TODO: Make it scroll to the character
|
|
|
+ adj = scrl2.get_vadjustment()
|
|
|
+ adj.set_value( adj.get_upper() )
|
|
|
+
|
|
|
+ view.get_buffer().connect("changed", on_changed)
|
|
|
+
|
|
|
+ ################# MARKDOWN CONTROLLS #################
|
|
|
+
|
|
|
+ pannel.pack_start(Gtk.VSeparator(), False, False, 10)
|
|
|
+
|
|
|
+ def do_mark(w, r, l):
|
|
|
+
|
|
|
+ tb = view.get_buffer()
|
|
|
+ s, e = tb.get_selection_bounds()
|
|
|
+ tb.insert(s, r)
|
|
|
+ s, e = tb.get_selection_bounds()
|
|
|
+ tb.insert(e, l)
|
|
|
+
|
|
|
+ print(r, l)
|
|
|
+
|
|
|
+ bold = Gtk.Button()
|
|
|
+ bold.connect("clicked", do_mark, "**", "**")
|
|
|
+ bold.add(ui.icon(win, "format-text-bold"))
|
|
|
+ bold.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ pannel.pack_start(bold, False, False, False)
|
|
|
+
|
|
|
+ italic = Gtk.Button()
|
|
|
+ italic.connect("clicked", do_mark, "*", "*")
|
|
|
+ italic.add(ui.icon(win, "format-text-italic"))
|
|
|
+ italic.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ pannel.pack_start(italic, False, False, False)
|
|
|
+
|
|
|
+ code = Gtk.Button("</>")
|
|
|
+ code.connect("clicked", do_mark, "`", "`")
|
|
|
+ code.set_relief(Gtk.ReliefStyle.NONE)
|
|
|
+ pannel.pack_start(code, False, False, False)
|
|
|
+
|
|
|
+ pannel.pack_start(Gtk.VSeparator(), False, False, 10)
|
|
|
+
|
|
|
+ #################### PROMOTE BUTTON ######################
|
|
|
+
|
|
|
+ pannel.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
|
|
|
+ pannel.pack_start(Gtk.Label(" Promote FastLBRY: "), False, False, 0)
|
|
|
+ switch = Gtk.Switch()
|
|
|
+ switch.set_tooltip_text("Adds a little link to FastLBRY visible in all other LBRY apps.")
|
|
|
+ pannel.pack_start(switch, False, False, 0)
|
|
|
+ switch.set_active(win.settings["promote_fast_lbry_in_comments"])
|
|
|
+
|
|
|
+ def on_promote(w, e):
|
|
|
+ win.settings["promote_fast_lbry_in_comments"] = switch.get_active()
|
|
|
+ settings.save(win.settings)
|
|
|
+
|
|
|
+ switch.connect("notify::active", on_promote)
|
|
|
+
|
|
|
+
|
|
|
+ pannel.pack_start(Gtk.VSeparator(), False, False, 10)
|
|
|
+
|
|
|
+ ################# SUPPORT AMOUNT ##########################
|
|
|
+
|
|
|
+ pannel.pack_start(ui.icon(win, "emblem-favorite"), False, False, 0)
|
|
|
+ pannel.pack_start(Gtk.Label(" Support Amount: "), False, False, 0)
|
|
|
+
|
|
|
+ support_bid_adjust = Gtk.Adjustment(0,
|
|
|
+ lower=0,
|
|
|
+ upper=1000000000,
|
|
|
+ step_increment=0.1)
|
|
|
+ support_bid_entry = Gtk.SpinButton(adjustment=support_bid_adjust,
|
|
|
+ digits=4)
|
|
|
+ pannel.pack_start(support_bid_entry, False, False, 0)
|
|
|
+
|
|
|
+ pannel.pack_start(Gtk.VSeparator(), False, False, 10)
|
|
|
+
|
|
|
+
|
|
|
+ else:
|
|
|
+ vbox.pack_start(Gtk.Label("To comment, Make a channel"), False, False, 30)
|
|
|
+
|
|
|
+ return vbox
|
|
|
+
|
|
|
+def send_comment(win, message, claim_id, tb, support=0):
|
|
|
+
|
|
|
+
|
|
|
+ support = support.get_value()
|
|
|
+ if support:
|
|
|
+ support_out = fetch.lbrynet("support_create",
|
|
|
+ {"amount":str(float(round(support, 6))),
|
|
|
+ "claim_id":claim_id,
|
|
|
+ "channel_id":win.channel["claim_id"],
|
|
|
+ "tip":True})
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ # if the user wants to promote FastLBRY
|
|
|
+ if win.settings["promote_fast_lbry_in_comments"]:
|
|
|
+ message = message + BUTTON_GTK_PROMOTE # See the beginning of the file
|
|
|
+
|
|
|
+ # claim_id = win.resolved["claim_id"]
|
|
|
+ channel_name = win.channel["name"]
|
|
|
+ channel_id = win.channel["claim_id"]
|
|
|
+
|
|
|
+ sigs = sign(message, channel_name)
|
|
|
+ if not sigs:
|
|
|
+ return
|
|
|
+
|
|
|
+ params = {
|
|
|
+ "channel_id": channel_id,
|
|
|
+ "channel_name": channel_name,
|
|
|
+ "claim_id": claim_id,
|
|
|
+ "comment": message,
|
|
|
+ **sigs
|
|
|
+ }
|
|
|
+ if support:
|
|
|
+ params["support_tx_id"] = support_out["txid"]
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if win.commenting[claim_id]["reply"]["to"]:
|
|
|
+ params["parent_id"] = win.commenting[claim_id]["reply"]["to"]
|
|
|
+
|
|
|
+ # Make sure to clean that up
|
|
|
+ for ch in win.commenting[claim_id]["reply"]["box"].get_children():
|
|
|
+ ch.destroy()
|
|
|
+ win.commenting[claim_id]["reply"]["to"] = ""
|
|
|
+
|
|
|
+
|
|
|
+ out = comment_request("comment.Create", params)
|
|
|
+ i = out["result"] # This will error out if comment is not sent
|
|
|
+ tb.set_text("") # Only if the comment is sent, we clean the text
|
|
|
+ # RENDERING THE NEWLY DONW COMMENT INTO THE PAGE
|
|
|
+ # TODO: It duplicates it when the comment is actually loaded
|
|
|
+ win.commenting[claim_id]["data"]["result"]["items"].append(i)
|
|
|
+ print(i["comment_id"])
|
|
|
+ box = win.commenting[claim_id]["box"]
|
|
|
+ def render(win, box, i):
|
|
|
+ render_single_comment(win, box, i)
|
|
|
+ box.show_all()
|
|
|
+ GLib.idle_add(render, win, box, i)
|
|
|
+
|
|
|
+def sign(data: str = "", channel: str = "", message: str = "Channel to sign data with:", hexdata: str = ""):
|
|
|
+ """
|
|
|
+ Sign a string or hexdata and return the signatures
|
|
|
+
|
|
|
+ Keyword arguments:
|
|
|
+ data -- a string to sign
|
|
|
+ channel -- channel name to sign with (e.g. "@example"). Will prompt for one if not given.
|
|
|
+ message -- message to give when selecting a channel. Please pass this if not passing channel.
|
|
|
+ hexdata -- direct hexadecimal data to sign
|
|
|
+ """
|
|
|
+ if (not data and not hexdata) or (data and hexdata):
|
|
|
+ raise ValueError("Must give either data or hexdata")
|
|
|
+ elif data:
|
|
|
+ hexdata = data.encode().hex()
|
|
|
+
|
|
|
+ if not channel:
|
|
|
+ channel = select(message)
|
|
|
+
|
|
|
+ if not channel.startswith("@"):
|
|
|
+ channel = "@" + channel
|
|
|
+
|
|
|
+ try:
|
|
|
+ sigs = fetch.lbrynet("channel_sign", {"channel_name":channel, "hexdata":hexdata})
|
|
|
+
|
|
|
+ # sigs = check_output([flbry_globals["lbrynet"],
|
|
|
+ # "channel", "sign",
|
|
|
+ # "--channel_name=" + channel,
|
|
|
+ # "--hexdata=" + hexdata])
|
|
|
+ # sigs = json.loads(sigs)
|
|
|
+
|
|
|
+ return sigs
|
|
|
+ except:
|
|
|
+ print("Stupid Error")
|
|
|
+ return
|