channel.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. #####################################################################
  2. # #
  3. # THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
  4. # LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
  5. # FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
  6. # WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
  7. # IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
  8. # OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
  9. # #
  10. # ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
  11. # (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
  12. # YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
  13. # THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
  14. # VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
  15. # WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
  16. # #
  17. # THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
  18. # NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
  19. # THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
  20. # SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
  21. # THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
  22. # #
  23. # THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
  24. # FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
  25. # THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
  26. # TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
  27. # #
  28. #####################################################################
  29. # This file will perform a simple search on the LBRY network.
  30. from subprocess import *
  31. import os
  32. import json
  33. from flbry import url
  34. from flbry import following
  35. from flbry import wallet
  36. from flbry import markdown
  37. from flbry import publish
  38. from flbry import settings
  39. from flbry import search
  40. from flbry.variables import *
  41. def simple(args=""):
  42. """Lists the publications of the given channel. If no channel is given it will prompt for this channel name"""
  43. # The user might write the search argument right in the same
  44. # line as the work search.
  45. #
  46. # : channel blenderdumbass
  47. #
  48. # Or they can type nothing. And be confused of what happened.
  49. # So I want to provide a catcher here. If they type nothing it
  50. # will ask them to provide a search query.
  51. if not args:
  52. args = input(typing_dots("Channel url"))
  53. if not args.startswith("@") and not args.startswith("lbry://@"):
  54. args = "@"+args
  55. # So we want to request a query to the SDK to search what ever
  56. # the user wants. The problem is it can be a very large output.
  57. # For example the "blender dumbass" query returns 1000 claims
  58. # on the LBRY network. And people will wait for a very long time
  59. # on something that might have a million claims.
  60. # So instead we are going to request only the first 20 and let
  61. # the user load more.
  62. w, h = tsize()
  63. page_size = h - 5
  64. page = 1
  65. while True:
  66. # Printing the search query and page number
  67. center("CHANNEL: "+args+" PAGE:"+str(page))
  68. out = check_output([flbry_globals["lbrynet"],
  69. "claim", "search", '--channel='+args,
  70. '--page='+str(page),
  71. '--page_size='+str(page_size),
  72. "--no_totals",
  73. '--order_by=release_time'])
  74. # Now we want to parse the json
  75. try:
  76. out = json.loads(out)
  77. except:
  78. center("Connect to LBRY first.", "bdrd")
  79. return
  80. try:
  81. data_print = {"categories":["Type", "Title"],
  82. "size":[1,5],
  83. "data":[]}
  84. # List what we found
  85. for n, i in enumerate(out["items"]):
  86. title = "---!Failed Loading Title---"
  87. ftype = "claim"
  88. try:
  89. try:
  90. title = i["value"]["title"]
  91. except:
  92. title = i['name']
  93. try:
  94. ftype = what[i["value"]["stream_type"]]
  95. except:
  96. ftype = what[i["value_type"]]
  97. except:
  98. pass
  99. data_print["data"].append([ftype, title])
  100. table(data_print)
  101. # Tell the user that they might want to load more
  102. center("---type 'more' to load more---")
  103. page = page +1
  104. # Error messages
  105. except Exception as e:
  106. if "code" in out:
  107. center("Error code: "+out["code"], "bdrd")
  108. if "message" in out:
  109. center("Error: "+out["message"], "bdrd")
  110. else:
  111. center("Error: "+e, "bdrd")
  112. return
  113. channel_commands = [
  114. "rss",
  115. "follow",
  116. "unfollow",
  117. "more",
  118. "boost",
  119. "tip",
  120. "search"
  121. ]
  122. complete(channel_commands)
  123. try:
  124. signing_channel = out["items"][0]["signing_channel"]
  125. except:
  126. center("Channel '"+args+"' is not found.", "bdrd")
  127. return
  128. # Making sure that we stop every time a new page is reached
  129. while True:
  130. c = input(typing_dots())
  131. reshow = False
  132. if c == "rss":
  133. rss = signing_channel["short_url"]
  134. rss = rss.replace("#", ":")
  135. rss = rss.split("lbry://", 1)[1]
  136. center("https://odysee.com/$/rss/"+rss)
  137. elif c == "follow":
  138. channel = signing_channel["permanent_url"]
  139. try:
  140. name = signing_channel["value"]["title"]
  141. except:
  142. name = signing_channel["signing_channel"]["normalized_name"]
  143. following.follow_channel(channel, name)
  144. elif c == "unfollow":
  145. channel = signing_channel["permanent_url"]
  146. try:
  147. name = signing_channel["value"]["title"]
  148. except:
  149. name = signing_channel["normalized_name"]
  150. following.unfollow_channel(channel, name)
  151. elif c.startswith("boost"):
  152. if " " in c:
  153. wallet.support(signing_channel["claim_id"], amount=c[c.find(" ")+1:])
  154. else:
  155. wallet.support(signing_channel["claim_id"])
  156. reshow = True
  157. elif c.startswith("tip"):
  158. if " " in c:
  159. wallet.support(signing_channel["claim_id"], amount=c[c.find(" ")+1:], tip=True)
  160. else:
  161. wallet.support(signing_channel["claim_id"], tip=True)
  162. reshow = True
  163. elif c.startswith("search"):
  164. channel = signing_channel["permanent_url"]
  165. if " " in c:
  166. search.simple(c[c.find(" ")+1:], channel)
  167. else:
  168. search.simple(channel=channel)
  169. reshow = True
  170. else:
  171. break
  172. complete(channel_commands)
  173. if reshow:
  174. table(data_print)
  175. center("---type 'more' to load more---")
  176. while True:
  177. # Making sure that we stop every time a new page is reached
  178. if c == "more":
  179. break
  180. try:
  181. c = int(c)
  182. except:
  183. return
  184. url.get(out["items"][c]["canonical_url"])
  185. # Print the list again
  186. table(data_print)
  187. center("---type 'more' to load more---")
  188. c = input(typing_dots())
  189. def select(message="", claim_id=False, anonymous=False):
  190. # This fucntion will give users to select one of their channels.
  191. center(message)
  192. out = check_output([flbry_globals["lbrynet"],
  193. "channel", "list"])
  194. # Now we want to parse the json
  195. try:
  196. out = json.loads(out)
  197. except:
  198. center("Connect to LBRY first.", "bdrd")
  199. return
  200. d = {"categories":["lbry url", "title"],
  201. "size":[1,2],
  202. "data":[]}
  203. for n, i in enumerate(out["items"]):
  204. name = "[no name]"
  205. title = "[no title]"
  206. try:
  207. name = i["name"]
  208. title = i["value"]["title"]
  209. except:
  210. pass
  211. d["data"].append([name, title])
  212. if anonymous:
  213. d["data"].append(["[anonymous]", "[no title]"])
  214. table(d)
  215. center("select a channel by typing it's number")
  216. select = input(typing_dots())
  217. if not select:
  218. select = "0"
  219. try:
  220. select = int(select)
  221. if select > len(out["items"])-1 and anonymous:
  222. if claim_id:
  223. return None, None
  224. return None
  225. if claim_id:
  226. return out["items"][select]["name"], out["items"][select]["claim_id"]
  227. return out["items"][select]["name"]
  228. except:
  229. raise()
  230. if claim_id:
  231. return out["items"][0]["name"], out["items"][0]["claim_id"]
  232. return out["items"][0]["name"]
  233. def show_data(data):
  234. # This will show the data for create()
  235. # If a value is not set, we set it to a default value
  236. if not "title" in data:
  237. title = "[no title]"
  238. else:
  239. title = data["title"]
  240. if not "description" in data:
  241. description = "[no description]"
  242. else:
  243. description = data["description"]
  244. d = {"categories": ["Name", "Bid", "Title", "Description"],
  245. "size": [2,1,3,6],
  246. "data": [[data["name"], data["bid"], title, description]]}
  247. table(d, False)
  248. if not "email" in data:
  249. email = "[no email]"
  250. else:
  251. email = data["email"]
  252. if not "website_url" in data:
  253. web_url = "[no website]"
  254. else:
  255. web_url = data["website_url"]
  256. d = {"categories": ["Email", "Website URL"],
  257. "size": [1,1],
  258. "data": [[email, web_url]]}
  259. table(d, False)
  260. if not "thumbnail_url" in data:
  261. thumb_url = "[no thumbnail]"
  262. else:
  263. thumb_url = data["thumbnail_url"]
  264. if not "cover_url" in data:
  265. cover_url = "[no cover image]"
  266. else:
  267. cover_url = data["cover_url"]
  268. d = {"categories": ["Thumbnail URL", "Cover Image URL"],
  269. "size": [1,1],
  270. "data": [[thumb_url, cover_url]]}
  271. table(d, False)
  272. if not "tags" in data:
  273. tags = "[no tags]"
  274. else:
  275. tags = data["tags"]
  276. d = {"categories":["Tags"],
  277. "size": [1],
  278. "data": [[tags]]}
  279. table(d, False)
  280. if not "languages" in data:
  281. langs = "[no languages]"
  282. else:
  283. langs = data["languages"]
  284. d = {"categories": ["Languages"],
  285. "size": [1],
  286. "data": [[langs]]}
  287. table(d, False)
  288. center("--- for commands type 'help' ---")
  289. def create(name=""):
  290. """Creates a new channel on the LBRY network"""
  291. # Get the name for the channel, since it's required.
  292. # If the user just presses enter without typing anything, it will prompt again.
  293. while not name:
  294. name = input(typing_dots("Name"))
  295. if not name.startswith("@"):
  296. name = "@" + name
  297. # This is the data dictionary we will use
  298. data = {
  299. "bid": 0.001,
  300. "name": name
  301. }
  302. complete([
  303. "name",
  304. "bid",
  305. "title",
  306. "description",
  307. "email",
  308. "website",
  309. "thumbnail",
  310. "cover",
  311. "tags",
  312. "languages",
  313. "help",
  314. "save",
  315. "load",
  316. "create"
  317. ])
  318. editor = settings.get("default_editor")
  319. while True:
  320. show_data(data)
  321. c = input(typing_dots())
  322. if not c:
  323. return
  324. elif c.startswith("name"):
  325. if " " in c:
  326. name = c[c.find(" ")+1:]
  327. else:
  328. name = input(typing_dots("Name"))
  329. if not name.startswith("@"):
  330. name = "@" + name
  331. data["name"] = name
  332. elif c.startswith("bid"):
  333. # Get the bid
  334. if " " in c:
  335. bid = c[c.find(" ")+1:]
  336. else:
  337. bid = input(typing_dots("Bid"))
  338. # Try to convert it a float
  339. try:
  340. bid = float(bid)
  341. except:
  342. pass
  343. # while bid is not a float, repeat until it is
  344. while type(bid) != type(10.0):
  345. center("Bid is not a number, try again", "bdrd")
  346. bid = input(typing_dots("Bid"))
  347. try:
  348. bid = float(bid)
  349. except:
  350. pass
  351. data["bid"] = bid
  352. elif c.startswith("title"):
  353. if " " in c:
  354. title = c[c.find(" ")+1:]
  355. else:
  356. title = input(typing_dots("Title"))
  357. data["title"] = title
  358. elif c.startswith("description"):
  359. description = "Type the description here. Don't forget to save. Then return to FastLBRY."
  360. c = c + ' '
  361. a = c[c.find(" "):]
  362. if len(a) > 1:
  363. description = file_or_editor(a, description)
  364. else:
  365. if editor:
  366. description = file_or_editor(a, description, editor)
  367. else:
  368. description = input(typing_dots("Description"))
  369. data["description"] = description
  370. elif c.startswith("email"):
  371. if " " in c:
  372. email = c[c.find(" ")+1:]
  373. else:
  374. email = input(typing_dots("Email"))
  375. data["email"] = email
  376. elif c.startswith("website"):
  377. if " " in c:
  378. web_url = c[c.find(" ")+1:]
  379. else:
  380. web_url = input(typing_dots("Website URL"))
  381. data["website_url"] = web_url
  382. elif c.startswith("thumbnail"):
  383. if " " in c:
  384. thumb_url = c[c.find(" ")+1:]
  385. else:
  386. thumb_url = input(typing_dots("Thumbnail URL"))
  387. data["thumbnail_url"] = thumb_url
  388. elif c.startswith("cover"):
  389. if " " in c:
  390. cover_url = c[c.find(" ")+1:]
  391. else:
  392. cover_url = input(typing_dots("Cover URL"))
  393. data["cover_url"] = cover_url
  394. elif c.startswith("tags"):
  395. if " " in c:
  396. tags = c[c.find(" ")+1:]
  397. else:
  398. tags = input(typing_dots("Enter the tags, separated by commas", give_space=True))
  399. tags = tags.split(",")
  400. # Stip each tag, so if the user types "tag1, tag2, tag3"
  401. # Resulting list would be: ["tag1", "tag2", "tag3"]
  402. for n, tag in enumerate(tags):
  403. tags[n] = tag.strip()
  404. data["tags"] = tags
  405. elif c.startswith("languages"):
  406. if " " in c:
  407. langs = c[c.find(" ")+1:]
  408. else:
  409. langs = input(typing_dots("Enter the languages, separated by commas", give_space=True))
  410. langs = langs.split(",")
  411. for n, lang in enumerate(langs):
  412. langs[n] = lang.strip()
  413. data["languages"] = langs
  414. elif c == "help":
  415. markdown.draw("help/create-channel.md", "Create Channel Help")
  416. elif c.startswith("save"):
  417. if " " in c:
  418. pn = c[c.find(" ")+1:]
  419. else:
  420. pn = input(typing_dots("Preset Name"))
  421. # Create the preset folder is it's not there
  422. try:
  423. os.makedirs(settings.get_settings_folder()+"presets/channel")
  424. except:
  425. pass
  426. # Write the json file
  427. with open(settings.get_settings_folder()+"presets/channel/"+pn+'.json', 'w') as f:
  428. json.dump(data, f, indent=4, sort_keys=True)
  429. elif c.startswith("load"):
  430. if " " in c:
  431. pn = c[c.find(" ")+1:]
  432. else:
  433. pn = input(typing_dots("Preset Name"))
  434. # loading the json file
  435. try:
  436. name = data["name"]
  437. with open(settings.get_settings_folder()+"presets/channel/"+pn+'.json') as f:
  438. data = json.load(f)
  439. data["name"] = name
  440. except:
  441. center("There is no '"+pn+"' preset!", "bdrd")
  442. elif c == "create":
  443. command = [flbry_globals["lbrynet"], "channel", "create", "--name="+data["name"], "--bid="+str(data["bid"])]
  444. for i in ["title", "description", "email", "website_url", "thumbnail_urL", "cover_url"]:
  445. if i in data:
  446. command.append("--"+i+"="+str(data[i]))
  447. for i in ["tags", "languages"]:
  448. if i in data:
  449. for j in data[i]:
  450. command.append("--"+i+"="+str(j))
  451. out = check_output(command)
  452. out = json.loads(out)
  453. if "message" in out:
  454. center("Error creating channel: "+out["message"], "bdrd")
  455. else:
  456. center("Successfully created "+name, "bdgr")
  457. return
  458. def sign(data: str = "", channel: str = "", message: str = "Channel to sign data with:", hexdata: str = ""):
  459. """
  460. Sign a string or hexdata and return the signatures
  461. Keyword arguments:
  462. data -- a string to sign
  463. channel -- channel name to sign with (e.g. "@example"). Will prompt for one if not given.
  464. message -- message to give when selecting a channel. Please pass this if not passing channel.
  465. hexdata -- direct hexadecimal data to sign
  466. """
  467. if (not data and not hexdata) or (data and hexdata):
  468. raise ValueError("Must give either data or hexdata")
  469. elif data:
  470. hexdata = data.encode().hex()
  471. if not channel:
  472. channel = select(message)
  473. if not channel.startswith("@"):
  474. channel = "@" + channel
  475. try:
  476. sigs = check_output([flbry_globals["lbrynet"],
  477. "channel", "sign",
  478. "--channel_name=" + channel,
  479. "--hexdata=" + hexdata])
  480. sigs = json.loads(sigs)
  481. return sigs
  482. except:
  483. center("Connect to LBRY first.", "bdrd")
  484. return