analytics.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  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 with handle various analytics. Currently in LBRY Desktop
  30. # or Odysee.com, the analytics are very basic, while they have a very
  31. # rich SDK that allows to get all kinds of information about transactions.
  32. # This transaction data is useful to get various Analytics.
  33. from subprocess import *
  34. import json
  35. import time
  36. from flbry import url
  37. from flbry import settings
  38. from flbry import markdown
  39. from flbry import channel
  40. from flbry.variables import *
  41. def graph(data=[]):
  42. # This function will draw a graph. I wanted originally to make it a
  43. # part of variables.py, but I'm afraid it will be a huge function.
  44. # And it makes a lot of sense to put it here (in Analytics).
  45. # Concept art:
  46. # 2021-11-15 --------------------------------------> 2021-12-30
  47. #0 2 4 6 8 10 13 16 19 22 25 28 31 34 37 40 43 46 49 .... 100 104
  48. #
  49. # #
  50. # #
  51. # # # # # #
  52. # # ### # ## # # # # #
  53. # # ## ###### # ####### # ## ### ## # #
  54. # # # ### ########### ### # ######### ### ##### ### ####
  55. # #################################################### ###########
  56. # ################################################################
  57. if not data:
  58. center("No Data!", "bdrd")
  59. return {0:[]}
  60. if len(data) == 1:
  61. center("Only one entry! Cannot draw graph!", "bdrd")
  62. return {0:data}
  63. w, h = tsize()
  64. height = h - 7
  65. width = w - 16
  66. times = []
  67. values = []
  68. for i in data:
  69. times.append(i["timestamp"])
  70. try:
  71. values.append(float(i["amount"]))
  72. except:
  73. values.append(0)
  74. #for i in times:
  75. # print(i)
  76. # Finding times
  77. import time
  78. startdate = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime( min(times)) )
  79. enddate = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime( max(times)))
  80. center(startdate+" "+("-"*(width-(len(startdate)+len(enddate))-6))+"> "+enddate)
  81. # Times minus the amount of the first entry
  82. ctimes = []
  83. for i in times:
  84. ctimes.append(i-min(times))
  85. # LET'S START DRAWING
  86. p = []
  87. for i in ctimes:
  88. pix = round((width-1)/max(ctimes)*i)
  89. p.append(pix)
  90. ap = []
  91. ret_data = {}
  92. for i in range(width):
  93. count = 0
  94. for n, d in enumerate(p):
  95. if d == i:
  96. count = count + values[n]
  97. if d not in ret_data:
  98. ret_data[d] = []
  99. ret_data[d].append(data[n])
  100. ap.append(count)
  101. choice = " ░▒▓█"
  102. if settings.get("graph_force_ASCII"):
  103. choice = " .~8#"
  104. for i in reversed(range(height)):
  105. va = max(ap) * ((i+1)/(height-1))
  106. s = clr["bdma"]+" "+clr["tbwh"]+wdth(va, 5)+" "
  107. for b in ap:
  108. x = ((height-1)/max(ap)*b)
  109. c = max(min(round((x - i)*4), 4), 0)
  110. y = choice[c]
  111. s = s + clr["bdbu"]+clr["tbwh"]+y
  112. print(" "+s+clr["bdma"]+" "+clr["norm"])
  113. center(" ")
  114. # Here I want to print a sideways ruler
  115. ruler_sideways(width-1, 6)
  116. center(" ")
  117. return ret_data
  118. def graph_loop(items):
  119. to_make = True
  120. while True:
  121. # parts of the graph's data indexed to the column on the graph
  122. parts = graph(items)
  123. # commands
  124. c = input(typing_dots("Type 'help' for graph commands.", to_make))
  125. to_make = False
  126. if not c:
  127. break
  128. # Showing minimum data ( similar to raw, but with a lot less stuff )
  129. elif c == "numbers":
  130. data_print = {"categories": ["Time", "Amount"],
  131. "size":[2,1],
  132. "data":[]}
  133. for i in parts:
  134. for b in parts[i]:
  135. try:
  136. amount = b["amount"]
  137. except:
  138. amount = "[NO AMOUNT]"
  139. try:
  140. import time
  141. timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(b["timestamp"]) )
  142. except:
  143. timestamp = "[NO TIME DATA]"
  144. data_print["data"].append([timestamp, amount])
  145. print()
  146. table(data_print, False)
  147. center("")
  148. input()
  149. print()
  150. # Zooming in the Graph
  151. elif c.startswith("zoom"):
  152. try:
  153. if " " in c:
  154. n = c[c.find(" ")+1:]
  155. else:
  156. n = input(typing_dots("Number if a range?"))
  157. # if it's a range
  158. if " " in n:
  159. nr = n.split()
  160. nrange = []
  161. for i in nr:
  162. nrange.append(int(i))
  163. zoom_data = []
  164. for i in range(nrange[0], nrange[-1]+1):
  165. if i in parts:
  166. for d in parts[i]:
  167. zoom_data.append(d)
  168. graph_loop(zoom_data)
  169. else:
  170. try:
  171. graph_loop(parts[int(n)])
  172. except:
  173. graph_loop([])
  174. except Exception as e:
  175. center("Error: "+str(e), "bdrd")
  176. # Printing the Raw data into the terminal
  177. elif c == "raw":
  178. size = [1,4]
  179. for i in range(len(parts[0][0].keys())-2):
  180. size.append(2)
  181. categories = list(parts[0][0].keys())
  182. data_print = {"categories": categories,
  183. "size":size,
  184. "data":[]}
  185. for i in parts:
  186. for b in parts[i]:
  187. if list(b.keys()) != categories:
  188. print()
  189. table(data_print, False)
  190. center("")
  191. categories = list(b.keys())
  192. size = [1,4]
  193. for i in range(len(b.keys())-2):
  194. size.append(2)
  195. data_print = {"categories": categories,
  196. "size":size,
  197. "data":[]}
  198. ap = list(b.values())
  199. if len(ap) < len(size):
  200. for dif in range(len(size) - len(ap)):
  201. ap.append(None)
  202. data_print["data"].append(ap)
  203. print()
  204. table(data_print, False)
  205. center("")
  206. input()
  207. print()
  208. # Output a CSV file.
  209. elif c == "csv":
  210. def to_csv_string(data):
  211. ret = ""
  212. for n, i in enumerate(data):
  213. if n == 0:
  214. comma = ""
  215. else:
  216. comma = ","
  217. if type(i) in [int,float,bool,str]:
  218. ret = ret + comma + '"'+str(i).replace('"', '""')+'"'
  219. else:
  220. ret = ret + comma + '"[COMPLEX DATA]"'
  221. return ret
  222. text = to_csv_string(parts[0][0].keys())
  223. keys = text
  224. for i in parts:
  225. for b in parts[i]:
  226. if to_csv_string(b.keys()) != keys:
  227. keys = to_csv_string(b.keys())
  228. text = text + "\n\n"+ keys
  229. text = text + "\n" + to_csv_string(b.values())
  230. saving = open("/tmp/fast_lbry_csv_tempfile.csv", 'w')
  231. saving.write(text)
  232. saving.close()
  233. Popen(["xdg-open",
  234. "/tmp/fast_lbry_csv_tempfile.csv"],
  235. stdout=DEVNULL,
  236. stderr=STDOUT)
  237. # Saving graph
  238. elif c == "save":
  239. itemsjson = []
  240. for i in parts:
  241. for b in parts[i]:
  242. itemsjson.append(b)
  243. filename = settings.get_settings_folder(flbry="flbry/graphs")
  244. from datetime import datetime
  245. now = datetime.now()
  246. filename = filename + "/" + str(now.strftime("%Y-%m-%d_%H:%M:%S")) + ".json"
  247. note = input(typing_dots("Note?"))
  248. savedata = {"note":note, "items":itemsjson}
  249. with open(filename, 'w') as f:
  250. json.dump(savedata, f, indent=4)
  251. center("Saved to :"+ filename)
  252. input()
  253. elif c == "help":
  254. markdown.draw("help/graph.md", "Graph Help")
  255. def get_data(claim_id="", total=0, mode="sales"):
  256. # This function will actually load data from
  257. # a given claim_id
  258. if not total:
  259. command = [flbry_globals["lbrynet"],
  260. "txo", "list",
  261. "--exclude_internal_transfers",
  262. "--is_not_my_input",
  263. "--page_size=1"]
  264. if mode == "sales":
  265. command.append("--type=purchase")
  266. if claim_id:
  267. command.append("--claim_id="+claim_id)
  268. txo = check_output(command)
  269. try:
  270. txo = json.loads(txo)
  271. except:
  272. center("Connect to LBRY first.")
  273. return
  274. total = txo["total_items"]
  275. cpage = 1
  276. items = []
  277. total_total = total
  278. print()
  279. while total > 0:
  280. progress_bar(total_total-total, total_total, "Getting "+mode+" data...")
  281. command = [flbry_globals["lbrynet"],
  282. "txo", "list",
  283. "--page_size=50",
  284. "--exclude_internal_transfers",
  285. "--is_not_my_input",
  286. "--page="+str(cpage)]
  287. if mode == "sales":
  288. command.append("--type=purchase")
  289. if claim_id:
  290. command.append("--claim_id="+claim_id)
  291. txo = check_output(command)
  292. try:
  293. txo = json.loads(txo)
  294. except:
  295. center("Connect to LBRY first.")
  296. return
  297. cpage = cpage + 1
  298. for i in txo["items"]:
  299. items.append(i)
  300. total = total - 50
  301. progress_bar(total_total, total_total, "Done.")
  302. print()
  303. return items
  304. def sales(mode="sales"):
  305. # This function will show sales of non-gratis publications.
  306. # First let's get the list of our channels
  307. out = check_output([flbry_globals["lbrynet"],
  308. "channel", "list"])
  309. try:
  310. out = json.loads(out)
  311. except:
  312. center("Connect to LBRY first.")
  313. return
  314. channels = []
  315. for i in out["items"]:
  316. channels.append(i["claim_id"])
  317. page = 1
  318. cached = 0 # Page that was loaded last
  319. while True:
  320. w, h = tsize()
  321. page_size = h - 5
  322. command = [flbry_globals["lbrynet"],
  323. "claim", "search",
  324. "--remove_duplicates",
  325. '--order_by=release_time',
  326. '--page='+str(page),
  327. '--page_size='+str(page_size)]
  328. if mode == "sales":
  329. command.append('--fee_amount=>0')
  330. for i in channels:
  331. command.append("--channel_ids="+i)
  332. if page != cached:
  333. list_of_publications = check_output(command)
  334. try:
  335. list_of_publications = json.loads(list_of_publications)
  336. except:
  337. center("Connect to LBRY first.")
  338. return
  339. if mode == "sales":
  340. data_print = {"categories":["Publication", "Price", "Sold Copies"],
  341. "size":[5,1,1],
  342. "data":[]}
  343. else:
  344. data_print = {"categories":["Publication", "Supported Times"],
  345. "size":[5,2],
  346. "data":[]}
  347. print()
  348. for n, i in enumerate(list_of_publications["items"]):
  349. name = i["name"]
  350. try:
  351. name = i["value"]["title"]
  352. except:
  353. pass
  354. #print( name )
  355. price = 0
  356. try:
  357. price = i["value"]["fee"]["amount"]
  358. except:
  359. pass
  360. #print(price)
  361. progress_bar(n+1, len(list_of_publications["items"]), "Fetching: "+name)
  362. # Now lets get the amount of entries in txo
  363. command = [flbry_globals["lbrynet"],
  364. "txo", "list",
  365. "--claim_id="+i["claim_id"],
  366. "--exclude_internal_transfers",
  367. "--is_not_my_input",
  368. "--page_size=1"]
  369. if mode == "sales":
  370. command.append("--type=purchase")
  371. txo = check_output(command)
  372. try:
  373. txo = json.loads(txo)
  374. except:
  375. center("Connect to LBRY first.")
  376. return
  377. sold = 0
  378. try:
  379. sold = txo["total_items"]
  380. except:
  381. pass
  382. #print(sold)
  383. if mode == "sales":
  384. data_print["data"].append([name, price, sold])
  385. else:
  386. data_print["data"].append([name, sold])
  387. print()
  388. table(data_print)
  389. cached = page
  390. center("---type 'more' to load more---")
  391. # Now the 'more' and such.
  392. c = input(typing_dots())
  393. if not c:
  394. break
  395. # TODO: Please test that this even works.
  396. if c == "more":
  397. page = page + 1
  398. else:
  399. try:
  400. c = int(c)
  401. total = data_print["data"][c][-1]
  402. i = list_of_publications["items"][c]
  403. try:
  404. items = get_data(i["claim_id"], total, mode)
  405. graph_loop(items)
  406. except Exception as e:
  407. print(e)
  408. print()
  409. except:
  410. pass
  411. def load_graph_from_file():
  412. # This function will load cached graphs back into the terminal.
  413. folder = settings.get_settings_folder(flbry="flbry/graphs")
  414. while True:
  415. data_print = {"categories": ["Note", "Size", "Saving Time"],
  416. "size":[4,1,2],
  417. "data":[]}
  418. graphs = []
  419. for graph in os.listdir(folder):
  420. if graph.endswith(".json"):
  421. date = graph.replace(".json", "").replace("_", " ")
  422. with open(folder+"/"+graph) as f:
  423. json_data= json.load(f)
  424. try:
  425. note = json_data["note"]
  426. except:
  427. note = ""
  428. try:
  429. items = json_data["items"]
  430. except:
  431. items = []
  432. graphs.append(items)
  433. data_print["data"].append([note,len(items), date])
  434. print()
  435. table(data_print)
  436. center("")
  437. print()
  438. c = input(typing_dots("Select graph number."))
  439. if not c:
  440. break
  441. try:
  442. graph_loop(graphs[int(c)])
  443. except:
  444. center("Something's wrong!", "bdrd")