14 Commits 563c79e6be ... c7775d4ec9

Auteur SHA1 Bericht Datum
  jyamihud c7775d4ec9 Inbox now removes duplicate comments. It's doing it using Comment ID for now. 2 jaren geleden
  jyamihud 87dd5811e5 Added Tristan as developer / Adjusted his pull request a bit to make it seamless. 2 jaren geleden
  Jeison Yehuda Amihud (Blender Dumbass) 10ba18575a Merge branch 'master' of MyBeansAreBaked/FastLBRY-terminal into master 2 jaren geleden
  Tristan 9f8adde68c Remove error message on kill. 2 jaren geleden
  Jeison Yehuda Amihud (Blender Dumbass) c910199762 Update 'README.md' 2 jaren geleden
  jyamihud 04e6630edd Removed SELF from analytics. Now analytics are a bit more accurate. 2 jaren geleden
  jyamihud 67f87e35f9 Graph saving / Total analytics / Total sales 2 jaren geleden
  jyamihud 25965ade20 Access Analytics and Sales graphs directly from URL 2 jaren geleden
  jyamihud a9de8fce14 Fixed some themeing issues 2 jaren geleden
  jyamihud 1b95f0d526 FastLBRY theme (Alpha) for the new logo / More contrast in the UI 2 jaren geleden
  jyamihud 3fd93087bf Updated Icon 2 jaren geleden
  jyamihud 34eb8697cd Graph help / CSV output 2 jaren geleden
  jyamihud 849ac74064 Raw data preview in graphs. Type 'raw' within a graph. 2 jaren geleden
  jyamihud eca45938ef Zoom in the graph / graph ruler 2 jaren geleden
10 gewijzigde bestanden met toevoegingen van 428 en 42 verwijderingen
  1. 1 1
      README.md
  2. 5 1
      devs.json
  3. 298 30
      flbry/analytics.py
  4. 10 5
      flbry/comments.py
  5. 23 2
      flbry/url.py
  6. 46 3
      flbry/variables.py
  7. 25 0
      help/graph.md
  8. 12 0
      help/main.md
  9. 8 0
      help/url.md
  10. 0 0
      icon.blend

+ 1 - 1
README.md

@@ -105,7 +105,7 @@ To get an idea of what needs to be done, here is a checklist of things. Alternat
 
  - [x] Make UI fit to the terminal size.
  - [ ] Changing the `lbrynet` binary to a full python implementation. [Issue #3](https://notabug.org/jyamihud/FastLBRY-terminal/issues/3)
- - [ ] Analytics [Issue #19](https://notabug.org/jyamihud/FastLBRY-terminal/issues/19)
+ - [x] Analytics
  - [ ] Multi-Language support [Issue #21](https://notabug.org/jyamihud/FastLBRY-terminal/issues/21), [Issue #32](https://notabug.org/jyamihud/FastLBRY-terminal/issues/32)
 
 # Licensing

+ 5 - 1
devs.json

@@ -1,12 +1,16 @@
 {
     "blenderdumbass@gmail.com": {
-        "commits": 50,
+        "commits": 49,
         "lbry": "lbry://@FastLBRY:f"
     },
     "eklektisk@eklektiskiscoding.xyz": {
         "commits": 1,
         "lbry": "lbry://@Eklektisk:0c790605ec8c917f3209920ab132b8fa953756e7"
     },
+    "tristan@tristans.cloud": {
+        "commits": 1,
+        "lbry": "lbry://@MyBeansAreBaked:b"
+    },
     "vertbyqb@tuta.io": {
         "commits": 49,
         "lbry": "lbry://@vertbyqb:8"

+ 298 - 30
flbry/analytics.py

@@ -51,6 +51,7 @@ def graph(data=[]):
     # Concept art:
 
     # 2021-11-15  --------------------------------------> 2021-12-30
+    #0 2 4 6 8 10 13 16 19 22 25 28 31 34 37 40 43 46 49 .... 100 104
     #
     #           #
     #           #
@@ -63,16 +64,16 @@ def graph(data=[]):
 
     if not data:
         center("No Data!", "bdrd")
-        return
+        return {0:[]}
     if len(data) == 1:
         center("Only one entry! Cannot draw graph!", "bdrd")
-        return
+        return {0:data}
 
     w, h = tsize()
 
-    height = h - 5
+    height = h - 7
     width = w - 16
-
+    
     times = []
     values = []
 
@@ -92,6 +93,8 @@ def graph(data=[]):
 
     center(startdate+" "+("-"*(width-(len(startdate)+len(enddate))-6))+"> "+enddate)
 
+    
+    # Times minus the amount of the first entry
     ctimes = []
     for i in times:
         ctimes.append(i-min(times))
@@ -106,11 +109,15 @@ def graph(data=[]):
         p.append(pix)
 
     ap = []
+    ret_data = {}
     for i in range(width):
         count = 0
         for n, d in enumerate(p):
             if d == i:
                 count = count + values[n]
+                if d not in ret_data:
+                    ret_data[d] = []
+                ret_data[d].append(data[n])
         ap.append(count)
 
     choice = " ░▒▓█"
@@ -121,7 +128,7 @@ def graph(data=[]):
 
         va = max(ap) * ((i+1)/(height-1))
 
-        s = clr["bdma"]+" "+wdth(va, 5)+" "
+        s = clr["bdma"]+" "+clr["tbwh"]+wdth(va, 5)+" "
 
 
         for b in ap:
@@ -130,14 +137,242 @@ def graph(data=[]):
             c = max(min(round((x - i)*4), 4), 0)
 
             y = choice[c]
-            s = s +  clr["bdbu"]+clr["tdcy"]+y
+            s = s +  clr["bdbu"]+clr["tbwh"]+y
 
 
         print("    "+s+clr["bdma"]+" "+clr["norm"])
 
     center(" ")
+    # Here I want to print a sideways ruler
+    ruler_sideways(width-1, 6)
+    center(" ")
+
+    return ret_data
+
+def graph_loop(items):
+
+    to_make = True
+    while True:
+        # parts of the graph's data indexed to the column on the graph
+        parts = graph(items)
+    
+        # commands
+        c = input(typing_dots("Type 'help' for graph commands.", to_make))
+        to_make = False
+
+        if not c:
+            break
+
+        # Showing minimum data ( similar to raw, but with a lot less stuff )
+        elif c == "numbers":
+            data_print = {"categories": ["Time", "Amount"],
+                          "size":[2,1],
+                          "data":[]}
+            for i in parts:
+                for b in parts[i]:
+                    try:
+                        amount = b["amount"]
+                    except:
+                        amount = "[NO AMOUNT]"
+                    try:
+                        import time
+                        timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(b["timestamp"])  )
+                    except:
+                        timestamp = "[NO TIME DATA]"
+                    data_print["data"].append([timestamp, amount])
 
+            print()
+            table(data_print, False)
+            center("")
+            input()
+            print()
+        
+        # Zooming in the Graph
+        elif c.startswith("zoom"):
+            try:
+                if " " in c:
+                    n = c[c.find(" ")+1:]
+                else:
+                    n = input(typing_dots("Number if a range?"))
+
+                # if it's a range
+                if " " in n:
+                    nr = n.split()
+                    nrange = []
+                    
+                    for i in nr:
+                        nrange.append(int(i))
+
+                    zoom_data = []    
+                    for i in range(nrange[0], nrange[-1]+1):
+                        if i in parts:
+                            for d in parts[i]:
+                                zoom_data.append(d)
+                        
+                    graph_loop(zoom_data)
+                else:
+                    try:
+                        graph_loop(parts[int(n)])
+                    except:
+                        graph_loop([])
+            except Exception as e:
+                center("Error: "+str(e), "bdrd")
+
+        # Printing the Raw data into the terminal
+        elif c == "raw":
+
+            size = [1,4]
+            for i in range(len(parts[0][0].keys())-2):
+                size.append(2)
 
+            categories = list(parts[0][0].keys())
+            
+            data_print = {"categories": categories,
+                          "size":size,
+                          "data":[]}
+            for i in parts:
+                for b in parts[i]:
+                    if list(b.keys()) != categories:
+                        
+                        print()
+                        table(data_print, False)
+                        center("")
+                        
+                        categories = list(b.keys())
+                        size = [1,4]
+                        for i in range(len(b.keys())-2):
+                            size.append(2)
+                        data_print = {"categories": categories,
+                          "size":size,
+                          "data":[]}
+                        
+                    ap = list(b.values())
+                    if len(ap) < len(size):
+                        for dif in range(len(size) - len(ap)):
+                            ap.append(None)
+                    data_print["data"].append(ap)
+            print()
+            table(data_print, False)
+            center("")
+            input()
+            print()
+
+        # Output a CSV file.
+        elif c == "csv":
+
+            def to_csv_string(data):
+                ret = ""
+                for n, i in enumerate(data):
+                    if n == 0:
+                        comma = ""
+                    else:
+                        comma = ","
+                    if type(i) in [int,float,bool,str]:
+                        ret = ret + comma + '"'+str(i).replace('"', '""')+'"'
+                    else:
+                        ret = ret + comma + '"[COMPLEX DATA]"'
+                return ret
+            
+            text = to_csv_string(parts[0][0].keys())
+            keys = text
+            for i in parts:
+                for b in parts[i]:
+                    if to_csv_string(b.keys()) != keys:
+                        keys = to_csv_string(b.keys())
+                        text = text + "\n\n"+ keys
+                    text = text + "\n" + to_csv_string(b.values())
+
+            saving = open("/tmp/fast_lbry_csv_tempfile.csv", 'w')
+            saving.write(text)
+            saving.close()
+            Popen(["xdg-open",
+                        "/tmp/fast_lbry_csv_tempfile.csv"],
+                                stdout=DEVNULL,
+                                stderr=STDOUT)
+        # Saving graph    
+        elif c == "save":
+            itemsjson = []
+            for i in parts:
+                for b in parts[i]:
+                    itemsjson.append(b)
+            filename = settings.get_settings_folder(flbry="flbry/graphs")
+            from datetime import datetime
+            now = datetime.now()
+            filename = filename + "/" + str(now.strftime("%Y-%m-%d_%H:%M:%S")) + ".json"
+
+            note = input(typing_dots("Note?"))
+            savedata = {"note":note, "items":itemsjson}
+
+            with open(filename, 'w') as f:
+                json.dump(savedata, f, indent=4)
+            
+            center("Saved to :"+ filename)
+            input()
+
+            
+        elif c == "help":
+            
+            markdown.draw("help/graph.md", "Graph Help")
+                    
+def get_data(claim_id="", total=0, mode="sales"):
+
+    # This function will actually load data from
+    # a given claim_id
+
+    if not total:
+        command = [lbrynet_binary["b"],
+                        "txo", "list",
+                   "--exclude_internal_transfers",
+                   "--is_not_my_input",
+                        "--page_size=1"]
+        if mode == "sales":
+            command.append("--type=purchase")
+        if claim_id:
+            command.append("--claim_id="+claim_id)
+        txo = check_output(command)
+        try:
+            txo = json.loads(txo)
+        except:
+            center("Connect to LBRY first.")
+            return
+        total = txo["total_items"]
+    
+    cpage = 1
+    items = []
+    total_total = total
+    print()
+    while total > 0:
+
+        progress_bar(total_total-total, total_total, "Getting "+mode+" data...")
+
+        
+        command = [lbrynet_binary["b"],
+                        "txo", "list",
+                   "--page_size=50",
+                   "--exclude_internal_transfers",
+                   "--is_not_my_input",
+                   "--page="+str(cpage)]
+        if mode == "sales":
+            command.append("--type=purchase")
+        if claim_id:
+            command.append("--claim_id="+claim_id)
+        txo = check_output(command)
+        try:
+            txo = json.loads(txo)
+        except:
+            center("Connect to LBRY first.")
+            return
+        cpage = cpage + 1
+        for i in txo["items"]:
+            items.append(i)
+        total = total - 50
+
+    progress_bar(total_total, total_total, "Done.")
+
+    print()
+    return items
+
+        
 def sales(mode="sales"):
 
     # This function will show sales of non-gratis publications.
@@ -219,6 +454,8 @@ def sales(mode="sales"):
                 command = [lbrynet_binary["b"],
                                 "txo", "list",
                                 "--claim_id="+i["claim_id"],
+                           "--exclude_internal_transfers",
+                           "--is_not_my_input",
                                 "--page_size=1"]
                 if mode == "sales":
                     command.append("--type=purchase")
@@ -260,30 +497,61 @@ def sales(mode="sales"):
                 c = int(c)
                 total = data_print["data"][c][-1]
                 i = list_of_publications["items"][c]
-                cpage = 1
-                items = []
-                while total > 0:
-                    command = [lbrynet_binary["b"],
-                                    "txo", "list",
-                                    "--claim_id="+i["claim_id"],
-                               "--page_size=50",
-                               "--page="+str(cpage)]
-                    if mode == "sales":
-                        command.append("--type=purchase")
-                    txo = check_output(command)
-                    try:
-                        txo = json.loads(txo)
-                    except:
-                        center("Connect to LBRY first.")
-                        return
-                    cpage = cpage + 1
-                    for i in txo["items"]:
-                        items.append(i)
-                    total = total - 50
-
-                graph(items)
-                input()
-                print()
+                try:
+                    
+                    items = get_data(i["claim_id"], total, mode)
+                    graph_loop(items)
+                except Exception as e:
+                    print(e)
 
+                print()
+                
             except:
                 pass
+
+def load_graph_from_file():
+
+    # This function will load cached graphs back into the terminal.
+
+    folder = settings.get_settings_folder(flbry="flbry/graphs")
+    while True:
+        data_print = {"categories": ["Note", "Size", "Saving Time"],
+                      "size":[4,1,2],
+                      "data":[]}
+        graphs = []
+        for graph in os.listdir(folder):
+            if graph.endswith(".json"):
+                date = graph.replace(".json", "").replace("_", " ")
+
+                with open(folder+"/"+graph) as f:
+                    json_data= json.load(f)
+
+                try:
+                    note = json_data["note"]
+                except:
+                    note = ""
+
+                try:
+                    items = json_data["items"]
+                except:
+                    items = []
+                graphs.append(items)
+                data_print["data"].append([note,len(items), date])
+
+        print()
+        table(data_print)
+        center("")
+        print()
+
+        c = input(typing_dots("Select graph number."))
+
+        if not c:
+            break
+
+        try:
+            graph_loop(graphs[int(c)])
+        except:
+            center("Something's wrong!", "bdrd")
+            
+            
+    

+ 10 - 5
flbry/comments.py

@@ -473,11 +473,7 @@ def inbox(opt=10):
                     except:
                         i["publication_title"] =  publication["name"]
 
-                    # TODO: It could get doubles into the cache when some
-                    #       body replies to a comment. Since i will now not
-                    #       equal the almost identical item in comments_cache.
-                    #       Please come up with some smarter way to get rid of
-                    #       doubles.
+                    
                     if i not in comments_cache:
                         comments_cache.append(i)
 
@@ -487,6 +483,15 @@ def inbox(opt=10):
     # Let's sort the comments based on the time they were sent
     comments_cache = sorted(comments_cache, key=lambda k: k['timestamp'], reverse=True)
 
+    # Let's remove duplicate comments
+    tmp = []
+    tmp_ids = []
+    for comment in comments_cache:
+        if comment["comment_id"] not in tmp_ids:
+            tmp_ids.append(comment["comment_id"])
+            tmp.append(comment)
+    comments_cache = tmp
+    
     with open(settings.get_settings_folder()+'inbox.json', 'w') as fp:
             json.dump(comments_cache, fp , indent=4)
 

+ 23 - 2
flbry/url.py

@@ -44,6 +44,7 @@ from flbry import settings
 from flbry import wallet
 from flbry import plugin
 from flbry import channel
+from flbry import analytics
 import urllib.parse
 
 def print_url_info(url, out):
@@ -310,7 +311,9 @@ def get(url="", do_search=True):
         "unfollow",
         "boost",
         "tip",
-        "repost"
+        "repost",
+        "analytics",
+        "sales"
     ]
 
     complete(url_commands)
@@ -348,7 +351,7 @@ def get(url="", do_search=True):
         elif c.startswith("open"):
 
             # If open has an argument (like `open mpv`) it opens it in that
-            # Next it trys to open it with the default opener
+            # Next it tries to open it with the default opener
             # If neither of those find an opener, it prompts the user.
             if len(c) > 5:
                     p = c[5:]
@@ -590,6 +593,24 @@ def get(url="", do_search=True):
             else:
                 center("Successfully reposted to "+ch, "bdgr")
 
+        elif c == "sales":
+            items = analytics.get_data(out["claim_id"])
+            try:
+                analytics.graph_loop(items)
+            except Exception as e:
+                print(e)
+
+            print()
+
+        elif c == "analytics":
+            items = analytics.get_data(out["claim_id"], mode="analytics")
+            try:
+                analytics.graph_loop(items)
+            except Exception as e:
+                print(e)
+
+            print()
+                
         elif c:
             out = plugin.run(out, command=c)
 

+ 46 - 3
flbry/variables.py

@@ -238,6 +238,7 @@ def typing_dots(text="", to_text=True, to_add_dots=False,  give_space=False):
     # since this function call adds 1 to the stack we need
     # to decrease the number by one
 
+    depth -= 1
     if not text or not to_add_dots:
         depth -= 1
 
@@ -252,7 +253,7 @@ def typing_dots(text="", to_text=True, to_add_dots=False,  give_space=False):
 
     w, h = tsize()
     if text and to_text:
-        side_string = " < "+text+" "
+        side_string = clr["tbwh"]+" < "+text+" "
 
         put_at = w-len(side_string)-1
 
@@ -417,9 +418,9 @@ def table(data, number=True):
         nb = ""
         if number:
             nb = clr["tbwh"]+wdth(b,4)
-        s = "    "+clr["bdma"]+" "+nb+clr["norm"]+clr["b"+d+"bu"]+clr["tbwh"]
+        s = "    "+clr["bdma"]+" "+nb+clr["norm"]+clr["b"+d+"bu"]#+clr["tbwh"]
         for n, item in enumerate(i):
-            s = s +clr["b"+d+"bu"]+ wdth(item, size[n]-1)+clr["bdma"]+" "
+            s = s +clr["b"+d+"bu"]+ clr["tbwh"]+wdth(item, size[n]-1)+clr["bdma"]+" "
         print(s+clr["norm"])
 
 def center(line, c="bdma", blink=False):
@@ -736,3 +737,45 @@ def try_getting_git_commit():
         # Git isn't installed
         center("Error getting commit hash: "+str(e), "bdrd")
         pass
+    
+def ruler_sideways(width, offset=0):
+
+    w, h = tsize() # For reference, the 'width' value will be
+                   # The actual width of the ruler. 'w' is used
+                   # For rendering.
+
+    
+    # This one prints a sideways ruler giving each next column
+    # a line number, skiping one space between the numbers like:
+    
+    # 0 2 4 6 8 10 13 16 19 21
+
+    # First raw of the printout will be a simple rythmic pattern
+    # similar to : |---------|---------|--------|--------|---------|
+
+    pattern = "    "+clr["bdma"]+(" "*offset)+" "+clr["tbwh"]
+    for i in range(width+1):
+        a = " "
+        if i % 10 == 0:
+            a = "▒"
+        elif i % 2 == 0:
+            a = "░"
+        pattern = pattern + a
+
+    print(pattern+" "+clr["norm"])
+    
+    ret = " "*offset
+    skipto = 0
+    
+    for i in range(width):
+        if i == skipto:
+            ret = ret + str(i) + " "
+            skipto = i + len(str(i)) + 1
+
+    ret = wdth(ret,w-10-len(str(width))-1)
+    ret = ret + " " + str(width)
+            
+            
+    print("    "+clr["bdma"],
+          clr["bold"]+clr["tbwh"]+ret,
+          clr["norm"])

+ 25 - 0
help/graph.md

@@ -0,0 +1,25 @@
+This is a help for all kinds of graphs of FastLBRY terminal.
+
+**numbers**
+
+Show stripped down, relevant data in a table, rather than a graph.
+
+**save**
+
+Save a graph raw data into a json. You can load it back from the main menu using **load_graph**.
+
+**raw**
+
+Will show raw data that the graph received. ( The format of the data may be different in different places. )
+
+**csv**
+
+Opens a *csv* formatted raw data in a default application. ( Note that it makes a temporary file, to save it securely use "Save As" in your default application. )
+
+**zoom**
+
+Opens a graph on only a part of the current data. Under the graph there is a ruler made for this purpose. You may input a number like: **zoom 15** to open only data in the 15th slot. Or you can use a range of slots like: **zoom 12 57** which will give you a graph on the data between the slots 12 and 57. Of course you can use whatever numbers you desire. As long as they are in the ruler's range. Note that the rules skips a few numbers for visibility. It doesn't mean that you can't use those skipped number. You can. They are skipped only for visibility.
+
+**help** 
+
+Opens this help text.

+ 12 - 0
help/main.md

@@ -147,3 +147,15 @@ Will let you access beautiful graphs of support for all of your publications. LB
 **sales**
 
 Similar to **analytics** but showing only sales of non-gratis publications.
+
+**total_analytics**
+
+Show a graph with the entire support history. Might load for a long time.
+
+**total_sales**
+
+Show a graph with the entire history of sales.
+
+**load_graph**
+
+Load a graph from memory, those saved using **save** in the graph itself.

+ 8 - 0
help/url.md

@@ -96,3 +96,11 @@ Boost a publication with LBC. This is refundable.
 **tip**
 
 Tip a publication with LBC. This is nonrefundable.
+
+**analytics**
+
+Show analytics graph of this publication. Only works with your own publications.
+
+**sales**
+
+Show sales graph of this publication. Only works with your own publications.

+ 0 - 0
icon.blend


Some files were not shown because too many files changed in this diff