158 کامیت‌ها 6d065ead33 ... 583fd7f2c1

نویسنده SHA1 پیام تاریخ
  zortazert 583fd7f2c1 Some windows support and emacs fart cleanup 2 سال پیش
  jyamihud 1f676ea2d6 Views and Subs 2 سال پیش
  jyamihud f86ffad91d Odysee notifications v 0.0.-1 2 سال پیش
  jyamihud 59ed1dc60b Fixed a Publish bug 2 سال پیش
  jyamihud c40293f3a9 Tuned the suggestions algorithm a bit 2 سال پیش
  jyamihud fef95e9f7c Setting to choose the default tab 2 سال پیش
  jyamihud efc672bdf8 Suggestion filtering a lot better. Removing irrelevant tags from history. 2 سال پیش
  jyamihud 3c9260410d Suggest livestreams too 2 سال پیش
  jyamihud f8f47b8a18 Basic offline suggestion algorithm 2 سال پیش
  jyamihud 998ce63c55 Fixed a little bug that happened with the latest article of mine 2 سال پیش
  jyamihud 52c5396977 Expanded livestreams instead of constant 2 سال پیش
  jyamihud 2e22a1f453 Showing if any of the following channels is currently streaming live 2 سال پیش
  jyamihud b1d4c6ec18 View All Livestreams 2 سال پیش
  jyamihud ac58e09af7 A bit prettier GO TO LIVESTREAM button 2 سال پیش
  jyamihud 18128d8b76 Livestreams are now easier to come across 2 سال پیش
  jyamihud 80fe92889a LIVESTREAMS PLAYABLE version 0.1 2 سال پیش
  jyamihud 22ae0bdd52 Better sizing for the comment writting section 2 سال پیش
  jyamihud db7d4a609e Support Comments 2 سال پیش
  jyamihud 7c731e8a2d Extendable comment writing vetrically 2 سال پیش
  jyamihud 14d6344cf7 Count my reactions 2 سال پیش
  jyamihud ff914ed1aa Pretend to be the "creator" LOL... Why the hell not. 2 سال پیش
  jyamihud 8428c24434 Working comment reactions 2 سال پیش
  jyamihud b9ed41ccbc Made search by default use "creation_height" instead of "release_time" which corresponds to the actual release time better. 2 سال پیش
  jyamihud 8e66c0b8fc When locked, and trying to send a comment, it will not disappear. 2 سال پیش
  jyamihud 32ac01195e Search options for types 2 سال پیش
  jyamihud 29b9610341 Order By option 2 سال پیش
  jyamihud 0984cf2894 A bit more useful menu 2 سال پیش
  jyamihud 6b89e56e47 primitive search options 2 سال پیش
  jyamihud 4295671241 non-resolvable links show up 2 سال پیش
  jyamihud 9db7babdb3 Load similar / Started work on search filters for users 2 سال پیش
  jyamihud 9eb739af6d Fixed a bug with tags at publish 2 سال پیش
  jyamihud e4fc56cb85 Links inside articles now totally work 2 سال پیش
  jyamihud dac9d76eba Resizing comments in bugged threads 2 سال پیش
  jyamihud 0cbfb7343d Fixed a rendering issue with code parts in markdown 2 سال پیش
  jyamihud f5fbdaff7f Support button shows effective amount 2 سال پیش
  jyamihud 2c6f003552 zooming in graph 2 سال پیش
  jyamihud 4d9f6ea88e Graph now shows data on mouse over 2 سال پیش
  jyamihud 23a3020e1a Fixed graph orientation stupidity 2 سال پیش
  jyamihud f4b82b9999 Graph: positive values are now bezier curves. 2 سال پیش
  jyamihud 15f6e6322e Made graph a bit more helpful ( by showing values ) 2 سال پیش
  jyamihud f71977b568 Analytics of single publications 2 سال پیش
  jyamihud 3e3c8de79d Pie Chart of Disk Usage in Downloads 2 سال پیش
  jyamihud 26fcfef265 Meanwhile solution for the graph issue 2 سال پیش
  jyamihud 0e899c9765 Upadted the wallet graph to more accurate data / Made support button 2 سال پیش
  jyamihud a568fa0729 Repost 2 سال پیش
  jyamihud 4fd4dce063 Reactive graph / Shows only last 30 days. 2 سال پیش
  jyamihud 734c9c4268 I WAS LUCKY TO CATCH THIS BUG OMG!!! 2 سال پیش
  jyamihud fe84704fe4 Graph starts loading from the last part 2 سال پیش
  jyamihud 820aab0063 Graph v 0.0 aplha 2 سال پیش
  jyamihud a8406d7def Colors a bit less shnikes 2 سال پیش
  jyamihud 488bf15bd0 A simple pie chart for the wallet balance 2 سال پیش
  jyamihud 2d52badc18 Lock Unlock wallet!!! 2 سال پیش
  jyamihud 74c56ceeb2 Fixed image issue on lbry://@blender-organizer#5/21.0615#1 2 سال پیش
  jyamihud ce202bb061 Not perfect, but quite useble dowbloads 2 سال پیش
  jyamihud 6da1c7645e Now you can delete junk 2 سال پیش
  jyamihud 482fc38df0 A lot 2 سال پیش
  jyamihud 129f9b7703 Markdown for reading code snippets - fixed 2 سال پیش
  jyamihud ead55b9933 Screenshot update 2 سال پیش
  jyamihud 23b7c4a5b2 Fixed a few minor things about dragging out things. Added an ability to drag out channels. 2 سال پیش
  jyamihud 885a7a087e Added Uploads 2 سال پیش
  jyamihud a5b55e1495 Download buttons Tabs issue FIXED! 2 سال پیش
  jyamihud 728ab3d3bf Comments Tabs issues FIXED!!! 2 سال پیش
  jyamihud 6e14b413ab Connect / Disconnect support for tabs 2 سال پیش
  jyamihud ccc5994446 Tabs! 2 سال پیش
  jyamihud 3226f5babb Tabs? Aplha... 2 سال پیش
  jyamihud 58d74f7550 Renders stickers and emojies from Odysee 2 سال پیش
  jyamihud 3694521a48 Open replies within replies 2 سال پیش
  jyamihud c2c96073dc Tags move to the end after adding 2 سال پیش
  jyamihud 655c2d0d6f Added tags into Details 2 سال پیش
  jyamihud 043cf70fc5 Tags filtering system 2 سال پیش
  jyamihud 592d47d630 Fixed an issue with comment promotion 2 سال پیش
  jyamihud 595e93e00d Now you can follow / unfollow channels 2 سال پیش
  jyamihud 894a1e3073 Auto scrolling the cover images of channels 2 سال پیش
  jyamihud bd3ddaa3bd Making sure that markdown in comments renders small enough images 2 سال پیش
  jyamihud d38dc4a546 Markdown editor ( for comments ) 2 سال پیش
  jyamihud b1b9b717dc comments markdown ( WHAT THE FUCK! ) 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 82dcfa032f Merge branch 'master' of gammalanded/FastLBRY-GTK into master 2 سال پیش
  gammalanded notabug 96d6220859 Added new Blender Legacy icons and introduced new icon naming scheme, changed icon function accordingly. 2 سال پیش
  jyamihud 5f2b032330 Prettier main menu 2 سال پیش
  jyamihud 98953ea246 Fixed: Comments were not updating when there was no comments at all 2 سال پیش
  jyamihud b57e62a4f7 Trending and Article Trending 2 سال پیش
  jyamihud b610b25438 Replies targets are now expandable and collapasable 2 سال پیش
  jyamihud cac79fab1c Fixed search ( hopefully ) 2 سال پیش
  jyamihud 133856d4d7 A few good to have changes in publish, plus a whole new + button in the main toolbar 2 سال پیش
  jyamihud aa549bdddf Removed unnececary 'print()' from ui when loading images 2 سال پیش
  jyamihud da9c0ab06d Made the main menu a bit better 2 سال پیش
  jyamihud e81d7631d1 updated readme 2 سال پیش
  jyamihud f0b439ea96 Upload works finally 2 سال پیش
  jyamihud 22b50de169 Presets fully working 2 سال پیش
  jyamihud 03d5624173 Publish returns a good json ( not publishes yet ) 2 سال پیش
  jyamihud 5bb371a3f5 Layout of Publish 2 سال پیش
  jyamihud 9503099f3b Promoter feature 2 سال پیش
  jyamihud 630ea5feff Drag and Drop 2 سال پیش
  jyamihud ed0697a202 Made replies work 2 سال پیش
  jyamihud 0253862583 Made it show the title of the publication in the notification 2 سال پیش
  jyamihud 6bcd875053 A bit less broken listening button 2 سال پیش
  jyamihud 58d3bc8af5 Trying to make comment listen function / nofitication 2 سال پیش
  jyamihud 803553f72b Now it should do only one thread 2 سال پیش
  jyamihud b52ea4506e Comments update automatically 2 سال پیش
  jyamihud 1157aaffca Made images load a bit smarter ( without leaving a trail of million images in /tmp ) 2 سال پیش
  jyamihud 8182cdc192 Optimized image loading / Made it remember you preferred channel 2 سال پیش
  jyamihud e8b9797a5f Blender icon theme submitted manually by https://notabug.org/gammalanded 2 سال پیش
  jyamihud bfcc80d985 Smart Search 2 سال پیش
  jyamihud f29ecbdb5e Readme, logo too big 2 سال پیش
  jyamihud d8b1cff53d Updated readme ( added screenshot ) 2 سال پیش
  jyamihud 7d26ead70c Now you can comment on LBRY 2 سال پیش
  jyamihud ec323c8325 testing comment send thing 2 سال پیش
  jyamihud d00c1f9e79 Re-organized run.py 2 سال پیش
  jyamihud 23bbe74dcb Made channel list scrollable ( still don't know how to make it select and not select and what the fuck ) 2 سال پیش
  jyamihud 50fa4832e0 Too little publications entered a loop FIXED! 2 سال پیش
  jyamihud 5ab5d566f5 Fixed following and and comment scrolling 2 سال پیش
  jyamihud 5b29dc31e9 Trying to get GTK out of threads 2 سال پیش
  jyamihud 5666d93fd5 Seems like I fixed everything LOL 2 سال پیش
  jyamihud e1225bc663 Latest Latest SEGMENTATION FAULT STILL THERE 2 سال پیش
  jyamihud fbd1e5fa4f Well, made more progress on comments 2 سال پیش
  jyamihud f6a36243a7 Comment alpha 0.0 2 سال پیش
  jyamihud 353f634311 Added comments v1 2 سال پیش
  jyamihud 2991bd791a Made nameless channels render nicely 2 سال پیش
  jyamihud 4352268fa0 Tabs now iconified 2 سال پیش
  jyamihud 8122c71e0e Made paid publication show that they are paid 2 سال پیش
  jyamihud 52d95d2842 IDK 2 سال پیش
  jyamihud c0bdb36143 Fixed banner and added a speed restriction to download progress bar 2 سال پیش
  jyamihud f9615117ff Fixed downloading / deleting / reading articles 2 سال پیش
  jyamihud 4132f3ece7 Thus far 2 سال پیش
  jyamihud 216de762e1 Now it launches with following 2 سال پیش
  jyamihud 17cfffda64 Added a gitignore 2 سال پیش
  jyamihud c773dc1c41 Deleted pyc 2 سال پیش
  jyamihud a3fcbd0d13 Search and channel publications: please delete all the .pyc 2 سال پیش
  jyamihud 6374f19ce5 Added SDK calls from subprocess if the normal one fails 2 سال پیش
  jyamihud 16f00858c4 Forgot, sorry 2 سال پیش
  jyamihud 671c4c0d62 Chaged request to using the faster retrieveing API 2 سال پیش
  jyamihud 45d1b1585b Fixed markdown to use the new loading ui thingy 2 سال پیش
  jyamihud 647f437bc6 Merge branch 'master' of https://notabug.org/jyamihud/FastLBRY-GTK 2 سال پیش
  jyamihud 9185dffe8d Seems like a fixed the Segmentation Fault by using the GLib thing 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) a4b4c146be Merge branch 'master' of Modanung/FastLBRY-GTK into master 2 سال پیش
  Modanung fffe20241c Added PNGs 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 09055389d5 Upload files to '' 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) b5b2e1df05 Merge branch 'master' of Modanung/FastLBRY-GTK into master 2 سال پیش
  Modanung c7a5fa2723 Replaced icon with SVG 2 سال پیش
  Modanung ea2b4ad283 Typo in readme 2 سال پیش
  jyamihud 8364db1b4d Much prettier Markdown. And an option to resolve links from aritlces. 2 سال پیش
  jyamihud 7d1537bee4 Fixed issues with deleting icons.py 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 1699be6652 Cleaned some mess 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 3d40708ba2 Cleaned some mess 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) e458dfa002 Outdated 2 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) db7dae244f Upload files to '' 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 8cfcdfcab4 Download / Delete / Launch 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 0bc131b200 View Json data in a nested tree view instead of code 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 436f8c9242 Upload files to '' 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 4a2b7b01e6 Upload files to 'flbry' 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 5095cbe403 Upload files to 'flbry' 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) ff24c34ced Upload files to 'flbry' 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 20333773ca Upload files to 'flbry' 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) d3c1986637 Resolve 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) bf462f1b1b Resolve 3 سال پیش
  Jeison Yehuda Amihud (Blender Dumbass) 2679a04c2c Updated the Readme 3 سال پیش
  TrueAuraCoral 6d065ead33 Update 'README.md' 3 سال پیش
  TrueAuraCoral 0a23e3b94f Update 'README.md' 3 سال پیش
10فایلهای تغییر یافته به همراه2143 افزوده شده و 57 حذف شده
  1. 2 0
      .gitignore
  2. BIN
      FastLBRY.png
  3. 22 44
      README.md
  4. 49 0
      flbry/0
  5. 49 0
      flbry/2
  6. 743 0
      flbry/analytics.py
  7. 474 0
      flbry/claim_search.py
  8. 792 0
      flbry/comments.py
  9. 12 13
      flbry/connect.py
  10. 0 0
      flbry/data_view.py

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*.pyc
+*~ 

BIN
FastLBRY.png


+ 22 - 44
README.md

@@ -1,55 +1,33 @@
-# FastLBRY GTK
+![FastLBRY ScreenShow](screen_shot.png)
 
-This is one of the **3 programs** that will be the FastLBRY project. There are not enough choice when it comes to LBRY. You can either use the Odysee.com website. Or the LBRY Desktop that shares the [source code](https://github.com/lbryio/lbry-desktop) with Odysee, which makes it a sub-optimal solution. LBRY Desktop is more local, but it's still a [tiny browser](https://www.electronjs.org/) with a website on it. 
+# FastLBRY-GTK
 
-If you look on what's running on Odysee.com, you will discover a bloated mess of [compiled JavaScript](https://www.gnu.org/philosophy/javascript-trap.html) on top of other bloated mess of JavaScript. Pages barely contain HTML5 code. I've [talked to them about this](https://github.com/lbryio/lbry-desktop/issues/6197) and all they could say was:
+FastLBRY is a project for accessing the [LBRY Network](http://lbry.com/), but instead of using their bloated Electron Based apps, we are using GTK.
 
-> Thank you for the issue and write up. This is not something we plan to do anytime soon since we rely on the open-source videojs framework heavily. We may consider it in the future.
+There is a [Terminal](https://notabug.org/jyamihud/FastLBRY-terminal) version of FastLBRY and in future we are planning to build an [HTML5](https://notabug.org/jyamihud/FastLBRY-HTML) self hostable client. *For now there is [Librarian](https://codeberg.org/librarian/librarian).*
 
-Fine. You won't do that. How about me? Maybe I can do that. It's Free / Libre Software we are talking about. You don't need a permission to start implementing things.
+# License
 
-Here is the plan for this project:
+The project is under the [GNU General Public License Version 3](https://notabug.org/jyamihud/FastLBRY-GTK/src/master/LICENSE) or any later version. One part ( the LBRY SDK executable ) is under the Expat License, also known as the MIT. The source code and the license of the SDK you can find [here](https://github.com/lbryio/lbry-sdk).
 
- - **FastLBRY Terminal**. A fully featured, terminal application to interact with LBRY. It will allow watching videos, download files, view and send comments, upload new files. This stage is half finished. 
- - **FastLBRY GTK**. A fully featured graphical application. It will have all the same features as the FastLBRY Terminal but with a GUI that a basic user might understand. It will require a bit of UI design work. I don't want it to resemble YouTube. LBRY is way deeper then just a clone of YouTube. I want the design to showcase the LBRY network correctly.
- - **FastLBRY HTML5**. A fully featured server application. Released on the AGPL this time. That people could install to make instances of LBRY on the web. But instead of it being this bloated mess of JavaScript. It will be a straight forward HTML5 site. With JS maybe only in the most minimal form. And only for secondary features ( like live notifications for comments ). So it will not break core features if the user decides to block all JS.
+# Terminal
 
-# WE NEED HACKERS!!! WE NEED YOU!!!
-
-This project requires a lot of work to be done. The good thing is. It's based on python. Without using too many complex features. We do use the [lbrynet SDK](https://github.com/lbryio/lbry-sdk) for this for now. Maybe we can implement the entire SDK in code later on.
-
-If you know nothing about programming, this is a good way to start. If you know something about programming, this is a good opportunity to contribute. To learn things needed to hack on this project and to help it be where it needs to be you can use the following resources.
-
- - [Python Basics](https://pythonbasics.org/) to learn the basic syntax of python. You can use this as a handbook when ever you don't understand a line. Or when ever you need a way to implement a thing.
- - [LBRY SDK API](https://lbry.tech/api/sdk) to learn all kinds of possible things that you can do with the SDK. Alternatively you can go to the folder `flbry` ( using `cd flbry` ) and then use the `--help` feature to see various commands of the SDK. `./lbrynet --help`. This way I learned enough to hack this bare bones version so far.
- - [DuckDuckGo](https://duckduckgo.com/) a search engine that can help you find answers for things that are not documented in any of the previous places. Sometimes you may need to go to the second page.
- - [The Matrix Chat](https://app.element.io/#/room/#FastLBRY:matrix.org) (`#FastLBRY:matrix.org`) where you can hang out with already a quite substantial amount of users / hackers or FastLBRY. I'm also there. So if you have any question what so ever, just post it there.
-
-**VIDEO TUTORIAL ON HOW TO HACK IT**
+Keep in mind that this project will share a lot of code with the [FastLBRY Terminal](https://notabug.org/jyamihud/FastLBRY-terminal) and some features that are not yet available here, might be already available there and vice versa. I do not guarantee that all features will be mirrored between the two. Some things are just impossible in the Terminal version. And some are harder to do in GTK.
+    
+# To do list
 
-[![thumb](https://spee.ch/c/caef832cc7a1c169.png)](https://odysee.com/@blenderdumbass:f/hacking-on-fast-lbry:9)
+This is a to-do list for the GTK version only.
 
-*Click the image to watch on odysee or...* [Direct Spee.ch link](https://spee.ch/@blenderdumbass:f/hacking-on-fast-lbry:9)
+ - [x] Hackable working GTK window.
+ - [x] Connect / Disconnect LBRY SDK
+ - [x] Search Publications 
+ - [x] Download / Launch / Watch Publications 
+ - [x] Publish
+ - [ ] Plugin system [Issue #5](https://notabug.org/jyamihud/FastLBRY-GTK/issues/5)
  
-Now open the `run.py` file in your [preferred editor](https://www.gnu.org/software/emacs/) and start hacking.
+ For more things to do, look at the [user submitted issues.](https://notabug.org/jyamihud/FastLBRY-GTK/issues)
+ 
+# Matrix Chat Room
+ 
+ To chat with the developers and users of FastLBRY ( all versions ) you can use the [Matrix Chat](https://matrix.org/clients/) room `FastLBRY:matrix.org`. **Keep in mind** that all **offtopic** messages are done in a separate room `#BlenderDumbassChat:matrix.org`.
  
-# To do list
-
-To get an idea of what to work on, there is a checklist of things. Alternatively. You can look into the [Issues page](https://notabug.org/jyamihud/FastLBRY-terminal/issues) for various issues found by the users. That you can help fixing as well.
-
-**Basic Features:**
-
-
-
-# Licensing
-
-The project is (c) J.Y.Amihud and Other Contributors 2021, under the [GNU General Public License Version 3](https://notabug.org/jyamihud/FastLBRY-terminal/src/master/LICENSE.md) or **any later version**. This choice will protect this project from proprietaryzation.
-
-Unfortunately, the LBRY SDK is (c) LBRY Inc. and is under the [Expat License](https://www.gnu.org/licenses/license-list.html#Expat) ( also known as the MIT license. ) [Click here to read their license.](https://raw.githubusercontent.com/lbryio/lbry-sdk/master/LICENSE) Which wasn't our choice. You can complaint about it on the [LBRY SDK repository](https://github.com/lbryio/lbry-sdk).
-
-# User help
-
-If you have a question or an issue please tell us about it. We are not magical wizards. We can't read your mind. And we don't have telemetry. So it's on you to tell us about errors and other annoyances. You can use the:
-
- - [Issue tracker](https://notabug.org/jyamihud/FastLBRY-terminal/issues)
- - [Matrix Chat](https://app.element.io/#/room/#FastLBRY:matrix.org) (`#FastLBRY:matrix.org`)

+ 49 - 0
flbry/0

@@ -0,0 +1,49 @@
+{
+  "code": -32500,
+  "data": {
+    "args": [],
+    "command": "claim_search",
+    "kwargs": {
+      "all_languages": [],
+      "all_locations": [],
+      "all_tags": [],
+      "any_languages": [],
+      "any_locations": [],
+      "any_tags": [],
+      "channel_ids": [],
+      "claim_ids": [],
+      "fee_amount": "",
+      "has_channel_signature": false,
+      "has_no_source": false,
+      "has_source": false,
+      "include_is_my_output": false,
+      "include_purchase_receipt": false,
+      "invalid_channel_signature": false,
+      "is_controlling": false,
+      "media_types": [],
+      "no_totals": false,
+      "not_channel_ids": [],
+      "not_languages": [],
+      "not_locations": [],
+      "not_tags": [],
+      "order_by": [],
+      "remove_duplicates": false,
+      "stream_types": [],
+      "valid_channel_signature": false
+    },
+    "name": "RPCError",
+    "traceback": [
+      "Traceback (most recent call last):",
+      "  File \"lbry/extras/daemon/daemon.py\", line 693, in _process_rpc_call",
+      "  File \"lbry/extras/daemon/daemon.py\", line 2494, in jsonrpc_claim_search",
+      "  File \"lbry/wallet/ledger.py\", line 889, in claim_search",
+      "  File \"lbry/wallet/ledger.py\", line 772, in _inflate_outputs",
+      "  File \"lbry/wallet/network.py\", line 89, in send_request",
+      "  File \"lbry/wallet/network.py\", line 79, in send_request",
+      "  File \"lbry/wallet/rpc/session.py\", line 484, in send_request",
+      "lbry.wallet.rpc.jsonrpc.RPCError: (-32603, 'internal server error')",
+      ""
+    ]
+  },
+  "message": "(-32603, 'internal server error')"
+}

+ 49 - 0
flbry/2

@@ -0,0 +1,49 @@
+{
+  "code": -32500,
+  "data": {
+    "args": [],
+    "command": "claim_search",
+    "kwargs": {
+      "all_languages": [],
+      "all_locations": [],
+      "all_tags": [],
+      "any_languages": [],
+      "any_locations": [],
+      "any_tags": [],
+      "channel_ids": [],
+      "claim_ids": [],
+      "fee_amount": "",
+      "has_channel_signature": false,
+      "has_no_source": false,
+      "has_source": false,
+      "include_is_my_output": false,
+      "include_purchase_receipt": false,
+      "invalid_channel_signature": false,
+      "is_controlling": false,
+      "media_types": [],
+      "no_totals": false,
+      "not_channel_ids": [],
+      "not_languages": [],
+      "not_locations": [],
+      "not_tags": [],
+      "order_by": [],
+      "remove_duplicates": false,
+      "stream_types": [],
+      "valid_channel_signature": false
+    },
+    "name": "RPCError",
+    "traceback": [
+      "Traceback (most recent call last):",
+      "  File \"lbry/extras/daemon/daemon.py\", line 693, in _process_rpc_call",
+      "  File \"lbry/extras/daemon/daemon.py\", line 2494, in jsonrpc_claim_search",
+      "  File \"lbry/wallet/ledger.py\", line 889, in claim_search",
+      "  File \"lbry/wallet/ledger.py\", line 772, in _inflate_outputs",
+      "  File \"lbry/wallet/network.py\", line 89, in send_request",
+      "  File \"lbry/wallet/network.py\", line 79, in send_request",
+      "  File \"lbry/wallet/rpc/session.py\", line 484, in send_request",
+      "lbry.wallet.rpc.jsonrpc.RPCError: (-32603, 'internal server error')",
+      ""
+    ]
+  },
+  "message": "(-32603, 'internal server error')"
+}

+ 743 - 0
flbry/analytics.py

@@ -0,0 +1,743 @@
+#####################################################################
+#                                                                   #
+#  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.  #
+#                                                                   #
+#####################################################################
+
+# Since LBRY wallet is just a long stream of increasing and decreasing
+# numbers. I call this the analytics.
+
+import os
+import math
+import json
+import time
+import random
+import colorsys
+import threading
+
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GLib
+from gi.repository import cairo
+
+from flbry import fetch
+from flbry import settings
+
+
+
+
+
+def make_colors(amount=4, color=(0.1,0.5,0.8)):
+
+    # TODO: Figure out how to got the themes prefered active color
+    # and use it here. 
+    
+    colors = []
+    for i in range(amount):
+
+        nc = colorsys.rgb_to_hsv(*color)
+        nc = list(nc)
+        
+        if i != 0:
+            nc[1] =  0
+            nc[2] = (i)*(1/amount)
+            
+        colors.append(colorsys.hsv_to_rgb(*nc))
+
+    return colors
+
+def window(win):
+
+    
+
+    
+    awin = Gtk.Window()
+    awin.set_title("FastLBRY GTK: Wallet / Analytics")
+    awin.set_size_request(500, 500)  
+
+    # Let's get...
+    raw_balance = fetch.lbrynet("wallet_balance")
+
+    # ... and format the balance information.
+    balance = {
+        "Spendable":raw_balance["available"],
+        "Claim Bids":raw_balance["reserved_subtotals"]["claims"],
+        "Supports":raw_balance["reserved_subtotals"]["supports"],
+        "Tips":raw_balance["reserved_subtotals"]["tips"],
+    }
+
+
+    box = Gtk.VBox()
+    awin.add(box)
+
+    # Pie chart for balance
+    def conv(t):
+        return str(t) + " LBC"
+    box.pack_start(pie_chart(win, balance, "Totals", converter=conv), 0, 0 , 0)
+
+    minus_30_days = 60*60*24*30*-1
+    
+    transactions = {"items":[],       # The list of items
+                    "zoom":[0,0], #minus_30_days,0], # The range of the selection ( by timestamp )
+                    "allow_negative":True
+    }
+    
+    def getting_all_transactions(data, win):
+        
+        # This is a small function funnin in a separate thread, loading
+        # all transactions so the graph could draw them.
+
+        # TODO: this commented code works perfectly fine to get the kind of
+        # data that I want. But it's hell'a heavy on my computer. Last time
+        # I tried, it froze. So I need to figure out something about this.
+
+        # cash_file = settings.get_settings_folder()+"GTK-transactions-graph.json"
+        
+        # try:
+        #     with open(cash_file) as json_file: 
+        #         data["items"] = json.load(json_file)
+        # except Exception as e:
+        #     print(e)
+
+            
+            
+        # def add(out, addpage):
+        #     ret = False
+        #     added = 0
+        #     count = 0
+        #     for i in out["items"]:
+
+        #         # Removing unnesesary data
+                
+        #         a = i.copy()
+        #         # a["amount"] = i["amount"]
+        #         # a["timestamp"] = i["timestamp"]
+        #         if a["timestamp"] == None: # Too recent
+        #             a["timestamp"] = int(time.time())
+        #         # a["txid"] = i["txid"]
+        #         # a["name"] = i.get("name", "")
+
+        #         if a not in data["items"]:
+        #             added += 1
+        #             data["items"].append(a)
+        #         elif not addpage:
+        #             count += 1
+        #             ret = True
+
+        #     return ret
+                
+
+        # Fetching
+
+        
+        
+        # out = fetch.lbrynet("transaction_list", { "page_size":50})
+        # all_of_them = out["total_items"]
+        # addpage = 0
+        # add(out, 0)
+        # for i in range(out["total_pages"]):
+        #     out = fetch.lbrynet("transaction_list", {"page":addpage+i+2,
+        #                                      #"exclude_internal_transfers": True,
+        #                                      #"no_totals":True,
+        #                                      "page_size":50})
+            
+        #     if add(out, addpage):
+        #         addpage = int((len(data["items"]))/50) - 2
+                      
+
+        #     # Save to cash
+        #     with open(cash_file, 'w') as fp:
+        #          json.dump(data["items"], fp)
+
+        #     if not win.keep_loading_the_wallet_graph or all_of_them == len(data["items"]):
+        #         return
+
+        # THE MEANWHILE SOLUTION
+
+         out = fetch.lbrynet("txo_plot", { "days_back":1000, # Fetch 100 days of txo
+                                           "exclude_internal_transfers":True, # Without crap
+                                           "is_not_my_input":True, # Not from me ( as in support only )
+         })
+
+
+         
+         for i in out:
+
+             a = {}
+             a["amount"] = i["total"]
+             a["timestamp"] = int(time.mktime(time.strptime(i["day"],"%Y-%m-%d")))
+
+
+             data["items"].append(a)
+        
+
+        
+    win.keep_loading_the_wallet_graph = True
+    t = threading.Thread(target=getting_all_transactions, args=(transactions, win))
+    t.setDaemon(True)
+    t.start()
+    def kill_graph(w):
+        win.keep_loading_the_wallet_graph = False
+    awin.connect("destroy", kill_graph)
+        
+    # Graph with the history of all transactions
+    the_graph = graph(win, transactions, "Totals")
+    
+    box.pack_start(the_graph, 1, 1 , 0)
+    
+    
+    
+    awin.show_all()
+
+def box_of_color(color):
+
+    def on_draw(widget, cr, data):
+        
+        width = widget.get_allocated_width()
+        height = widget.get_allocated_height()
+        
+        cr.set_source_rgb(*color)
+        cr.rectangle(0, 0, width, height)
+        cr.fill()
+    
+    area = Gtk.DrawingArea()
+    area.set_size_request(40, 40)
+    area.connect("draw", on_draw, color)
+    return area
+    
+def pie_chart_draw(d, main_layer, win, data, colors):
+
+    
+    # Need to know how big is our chart
+    w = d.get_allocated_width()
+    h = d.get_allocated_height()
+
+    # We want our circle to fit, so we find which one is
+    # smaller.
+    smaller = min(w, h)
+
+    
+
+    last = 0
+    whole = float(math.pi*2)
+
+    sum_of_data = 0
+    
+    for i in data:
+        sum_of_data += float(data[i])
+    
+    for n, i in enumerate(data):
+        
+        this = whole* ( float(data[i]) / sum_of_data )
+        
+        main_layer.move_to(w/2, h/2)
+        main_layer.arc(w/2, h/2, smaller/2, last, last+this)
+        main_layer.close_path()
+        main_layer.set_source_rgb(*colors[n%len(colors)])
+        main_layer.fill()
+        
+        last = last+this
+
+def pie_chart(win, data, title="", converter=False):
+
+    ret = Gtk.HBox(True)
+
+    colors = make_colors(len(data))
+    
+    da = Gtk.DrawingArea()
+    da.connect("draw", pie_chart_draw, win, data, colors)
+
+    ret.pack_start(da, 1,1,5)
+
+    lbox = Gtk.VBox()
+    ret.pack_start(lbox, 1,1,5)
+
+    sum_of_data = 0
+    
+    for i in data:
+        sum_of_data += float(data[i])
+
+    if converter:
+       sum_of_data = converter(sum_of_data) 
+        
+    lbox.pack_start(Gtk.Label("  Total : "+str(sum_of_data)+"  "), 0,0,1)
+    
+    for n, i in enumerate(data):
+        ibox = Gtk.HBox()
+        lbox.pack_start(ibox, 0,0,3)
+
+        ibox.pack_start(box_of_color(colors[n%len(colors)]), 0,0,0)
+        show_size = data[i]
+        if converter:
+            show_size = converter(show_size) 
+        ibox.pack_start(Gtk.Label("  "+i+": "+str(show_size)+"  "), 0,0,1)
+                        
+        
+    
+    return ret
+
+def graph_draw(d, main_layer, win, data):
+
+        
+    data["items"] = sorted(data["items"], key=lambda k: k["timestamp"])
+    
+    # Need to know how big is our graph
+    w = d.get_allocated_width()
+    h = d.get_allocated_height()
+
+    if data.get("allow_negative", False):
+        zero_at = h / 2
+    else:
+        zero_at = h
+
+    # The mouse position of a given frame
+    mx = d.get_pointer()[0]
+    my = d.get_pointer()[1]
+
+    # Test of the mouse position
+    # main_layer.move_to(0,0)
+    # main_layer.line_to(mx, my)
+    # main_layer.stroke()
+
+    
+    len_day = 60*60*24 # Seconds in a day
+    len_hour = 60*60   # Seconds in an hour
+    len_minute = 60    # Seconds in a minute
+    
+    # Here we are getting the latest and the earliest
+    # timestamp, so we could calculate the step of the
+    # graph. ( So the data will be readable )
+
+    latest = 0
+    try:
+        earliest = data["items"][0]["timestamp"]
+    except:
+        earliest = 0
+
+    for i in data["items"]:
+        if i.get("timestamp", 0) > latest:
+            latest = i.get("timestamp", 0)
+        if i.get("timestamp", 0) < earliest:
+            earliest = i.get("timestamp", 0)
+
+    # Now let's look at our zoom value
+
+    for n, e in enumerate([earliest, latest]):
+
+        if data["zoom"][n] == 0:
+            data["zoom"][n] = e
+    
+    earliest, latest = data["zoom"]
+
+    # Now I want to make a scale of dates from left
+    # to right.
+
+    main_layer.select_font_face("Monospace")
+    main_layer.set_font_size(10)
+    
+    full_date = "%Y-%m-%d %H:%M:%S"
+    only_date = "%Y-%m-%d"
+
+    if latest - earliest > 10 * len_day:
+        show_format = only_date
+        count = int( w / (len("xxxx-xx-xx")*6+12) )
+    else:
+        show_format = full_date
+        count = int( w / (len("xxxx-xx-xx xx:xx:xx")*6+12) )
+
+
+    # Now I want to show the current date / time for
+    # the area where the user is hovering.
+
+    suglen = len("xxxx-xx-xx xx:xx:xx")*6+12
+    
+    thexm = mx-suglen/2
+    if thexm < 2:
+        thexm = 2
+    elif thexm > w - suglen - 2:
+        thexm = w - suglen - 2
+
+    try:
+        res_date = int( ( latest - earliest ) / w * mx + earliest )
+        show_date = time.strftime(full_date, time.gmtime(res_date))
+    except:
+        show_date = "0000-00-00"
+    
+    main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
+    main_layer.rectangle(2+thexm,2,len(show_date)*6+2, 14)
+    main_layer.fill()
+
+    main_layer.move_to(3+thexm,12)
+    main_layer.set_source_rgba(1,1,1,1)
+
+    main_layer.show_text(str(show_date))
+
+    
+    # main_layer.set_source_rgba(0.7,0.7,0.7,1)
+    # main_layer.move_to( mx, 20 )
+    # main_layer.line_to( mx, h )
+    # main_layer.stroke()
+
+    # main_layer.set_dash([10,10])
+    # main_layer.set_source_rgba(0.2,0.2,0.2,1)
+    # main_layer.move_to( mx, 20 )
+    # main_layer.line_to( mx, h )
+    # main_layer.stroke()
+    # main_layer.set_dash([1])
+
+    # And the rest of the dates
+        
+    for date in range(count):
+
+        try:
+            res_date = int( ( latest - earliest ) / count * date + earliest )
+            show_date = time.strftime(show_format, time.gmtime(res_date))
+        except:
+            show_date = "0000-00-00"
+
+        thex = w / count * date
+
+        
+
+        # If not in range of the mouse ( so I could show the current day
+        # for that specific area ).
+        if int(thex) not in range(int(thexm-suglen/2), int(thexm+suglen)):
+        
+            main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
+            main_layer.rectangle(2+thex,2,len(show_date)*6+2, 14)
+            main_layer.fill()
+
+            main_layer.move_to(3+thex,12)
+            main_layer.set_source_rgba(1,1,1,1)
+
+            main_layer.show_text(str(show_date))
+
+
+    
+
+            
+    # A step is how often will there be a data point
+    # of the graph. Step of one minute, means every
+    # point on the graph will consist all the data
+    # happened in this minute. 
+
+    
+    
+    step = (latest - earliest) / (w / 20) # A second
+    
+    
+    # Now we need the smallest and biggest value in a
+    # given step
+
+    values = []
+    times =  []
+    
+    pstep = earliest
+    s = 0
+    
+    for n, i in enumerate(data["items"]):
+
+        if i.get("timestamp", 0) < earliest:
+            continue
+
+        
+        s += float(i.get("amount", i.get("value", 0)))
+
+        if i.get("timestamp", 0) > pstep + step-1:
+
+            pstep = i.get("timestamp", n)
+            values.append(s)
+            times.append(pstep)
+            s = 0
+
+        if i.get("timestamp", 0) > latest:
+            break
+
+
+    # If there is only on value
+
+
+    if len(values) == 1:
+        # We want to add a few move on both ends
+        step = 10
+        values = [0, values[0], 0]
+        times = [times[0]-step, times[0], times[0]+step]
+        latest = times[-1]
+        earliest = times[0]
+        
+        
+    
+    # Finding the farthest point from the center
+    # center being the 0 (zero)
+    try:
+        biggest = max(values)
+        if min(values) * -1 > biggest:
+            biggest = min(values) * -1 # Multuply by -1 reverses the - to a +
+    except Exception as e:
+        biggest = 0
+            
+    # Now let's draw it
+
+    main_layer.set_line_cap(cairo.LineCap.ROUND)
+    
+    # POSITIVE VALUE
+    
+
+    main_layer.move_to(0, zero_at)
+
+    prex = 0
+    prey = zero_at
+
+    toxes = []
+    toyes = []
+    
+    for n, i in enumerate(values):
+        
+        tox = w / (latest - earliest) * (times[n]-earliest)
+        toy = ( zero_at ) - ( ( zero_at ) / biggest * i ) *0.9
+        toy = min(toy, zero_at)
+
+        toxes.append(tox)
+        toyes.append(toy)
+        
+        main_layer.curve_to(
+                            tox - (tox - prex)/2,
+                            prey,
+                            
+                            prex + (tox - prex)/2,
+                            toy,
+
+                            tox,
+                            toy)
+
+        prex = tox
+        prey = toy
+
+    main_layer.line_to( w, zero_at)
+    main_layer.set_source_rgba(0.2,0.8,0.2,0.5)
+    main_layer.fill_preserve()
+    main_layer.set_source_rgba(0.2,0.8,0.2,1)
+    main_layer.stroke()
+
+    # NEGATIVE VALUE
+
+    # TODO: to make negative values appear in the graph, we have to have
+    # negative values. Meanwhile I will comment it, to save time while rendering
+    # the graph. NOTE: The code was originally a copy of the code above,
+    # the positive values, but I improved the positive values since then.
+    
+    # main_layer.move_to(0, zero_at)
+    
+    # for n, i in enumerate(values):
+        
+    #     tox = w / (latest - earliest) * (latest - times[n]) 
+    #     toy = ( zero_at ) - ( ( zero_at ) / biggest * i ) *0.9
+    #     toy = max(toy, zero_at)
+    #     main_layer.line_to(tox, toy)
+
+    # main_layer.line_to( w, zero_at)
+    # main_layer.set_source_rgba(0.8,0.2,0.2,0.5)
+    # main_layer.fill_preserve()
+    # main_layer.set_source_rgba(0.8,0.2,0.2,1)
+    # main_layer.stroke()
+
+    # Reference line
+
+    main_layer.set_source_rgba(0.7,0.7,0.7,1)
+    main_layer.move_to( 0, zero_at )
+    main_layer.line_to( w, zero_at )
+    main_layer.stroke()
+
+    main_layer.set_dash([10,10])
+    main_layer.set_source_rgba(0.2,0.2,0.2,1)
+    main_layer.move_to( 0, zero_at )
+    main_layer.line_to( w, zero_at )
+    main_layer.stroke()
+
+    main_layer.set_dash([1])
+
+    # MOUSE OVER SELECTOR
+
+    def closest(l, v):
+        distances = []
+        for i in l:
+            distances.append(max(i-v, v-i))
+        try:
+            return l[distances.index(min(distances))]
+        except:
+            return 0
+
+
+    selectx = closest(toxes, mx)
+    if selectx:
+        selecty = toyes[toxes.index(selectx)]
+
+
+        # Litte circle
+        
+        main_layer.arc(selectx, selecty, 8, 0, math.pi*2)
+        main_layer.set_source_rgba(0.2,0.8,0.2,1)
+        main_layer.fill()
+
+        # Line from that circle downwards
+        
+        main_layer.move_to(selectx, selecty)
+        main_layer.line_to(selectx, zero_at)
+        main_layer.stroke()
+
+        # Data about this time frame
+
+        to_data = times[toxes.index(selectx)]
+        from_data = to_data - step
+
+        try:
+            from_data = time.strftime(show_format, time.gmtime(from_data))
+        except:
+            from_data = "0000-00-00"
+        try:
+            to_data = time.strftime(show_format, time.gmtime(to_data))
+        except:
+            to_data = "0000-00-00"
+
+        
+            
+        # Counting the largest thing
+        plist = ["From: "+from_data,
+                 "To: "+to_data,
+                 "Total: "+str(round(values[toxes.index(selectx)], 2))+" LBC" ]
+        
+        leng = 0
+        for thing in plist:
+            if len(str(thing))*6+2 > leng:
+                leng = len(str(thing))*6+2
+
+        if selectx > w/2:
+            recx = selectx - leng - 10
+        else:
+            recx = selectx + 10
+
+        if selecty + len(plist)*15 > h:
+            recy = selecty - len(plist)*15
+        else:
+            recy = selecty
+            
+        main_layer.set_source_rgba(0.1,0.1,0.1,0.7)
+        main_layer.rectangle(recx, recy, leng, len(plist)*15)
+        main_layer.fill()
+
+        for n, thing in enumerate(plist):
+            main_layer.move_to(recx+2, recy+12+(15*n))
+            main_layer.set_source_rgba(1,1,1,1)
+            main_layer.show_text(thing)
+    
+    # Now let's get the values ( to the side of the graph )
+
+    for i in range(int(h/20)):
+
+        # TODO: This has to be tuned a bit. It's not perfect. But it's
+        # very close.
+        
+        they = i*20+20
+
+        try:
+            value_is = round( biggest / zero_at * (zero_at - they), 2)
+        except Exception as e:
+            print("what", e)
+            value_is = 0
+        
+        show_value = str(value_is) + " LBC"
+        
+        main_layer.set_source_rgba(0.1,0.1,0.1,0.5)
+        main_layer.rectangle(2, 2+they,len(show_value)*6+4, 14)
+        main_layer.fill()
+        
+        main_layer.move_to(3,12+they)
+        main_layer.set_source_rgba(1,1,1,1)
+        
+        main_layer.show_text(show_value)
+
+    # Render a little pressed selector
+
+    if "pressed" in data:
+
+        for i in [data["pressed"], mx]:
+
+            main_layer.set_source_rgba(0.7,0.7,0.7,1)
+            main_layer.move_to( i, 0 )
+            main_layer.line_to( i, h )
+            main_layer.stroke()
+
+            main_layer.set_dash([10,10])
+            main_layer.set_source_rgba(0.2,0.2,0.2,1)
+            main_layer.move_to( i, 0 )
+            main_layer.line_to( i, h )
+            main_layer.stroke()
+
+            main_layer.set_dash([1])
+        
+    # Keep redrawing the graph
+    d.queue_draw()
+    
+def graph_button_press(w, e, data, da):
+
+    data["pressed"] = e.x
+    
+    print(data["zoom"])
+    print(e.x, e.y)
+
+def graph_button_release(w, e, data, da):
+
+    if "pressed" in data:
+        x = data["pressed"]
+
+        # If there was no motion
+        if x-2 < e.x < x+2:
+            data["zoom"] = [0,0]
+
+        else:
+            w = da.get_allocated_width()
+            zoom0 = data["zoom"][0] + ((data["zoom"][1] - data["zoom"][0]) / w * min(x, e.x))
+            zoom1 = data["zoom"][0] + ((data["zoom"][1] - data["zoom"][0]) / w * max(x, e.x))
+
+            data["zoom"] = [zoom0, zoom1]
+
+        print(data["zoom"])
+    del data["pressed"]
+
+def graph(win, data, title=""):
+
+    
+    
+    event_box = Gtk.EventBox()
+    da = Gtk.DrawingArea()
+    da.set_size_request(100,100)
+    da.connect("draw", graph_draw, win, data)
+    event_box.connect("button-press-event", graph_button_press, data, da)
+    event_box.connect("button-release-event", graph_button_release, data, da)
+    event_box.add(da)
+
+
+    return event_box

+ 474 - 0
flbry/claim_search.py

@@ -0,0 +1,474 @@
+#####################################################################
+#                                                                   #
+#  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.  #
+#                                                                   #
+#####################################################################
+
+# This is the seach mechanism
+
+import threading
+from flbry import ui
+from flbry import fetch
+from gi.repository import Gtk
+from gi.repository import GLib
+
+def find(win, arg="", channel_ids=[], page=1, r={}, flowbox=False):
+    
+
+    # This is very dumb. I did a mess with the inputs of this command
+    # now I'm trying to add a new feature, without rewriting a whole
+    # lot. And it requires form me to give it a weird kind of thing.
+    sr = r.copy()
+    
+    if "method" in r:
+        method = r["method"]
+        del r["method"]
+        c = {}
+        c["page"] = page
+    else:
+        method = "claim_search"
+
+ 
+        c = {}
+        c["text"] = arg
+    
+        c["channel_ids"] = channel_ids
+        c["page"] = page
+    
+    
+
+        c["order_by"] = "creation_height" #{"order_by":"release_time"}
+        c["remove_duplicates"] = True
+
+        # TAGS TO EXCLUDE ( E.G. 'mature', 'porn' )
+
+        tags = win.settings["filter_tags"] # It's set in the settings
+        c["not_tags"] = tags
+
+    # Any overwrites ?
+    
+    for i in r:
+        if r[i]:
+            c[i] = r[i]
+        elif c[i]:
+            del c[i]
+    
+    channel_load = True
+    if len(channel_ids) == 1:
+        channel_load = False
+    out = fetch.lbrynet(method, c)
+
+    
+    same = [win, arg, channel_ids, page+1, sr]
+
+    if flowbox:
+        def add_to_flowbox(flowbox, out):
+            for i in out["items"]:
+                flowbox.add(ui.search_item(win, i, channel_load))
+            if len(out["items"]) > 1:
+                lastthing(win, same, flowbox)
+            flowbox.show_all()
+        GLib.idle_add(add_to_flowbox, flowbox, out)
+    else:
+        return [win, out, channel_load, same, sr]
+
+def render(out):
+
+    win, out, channel_load, same, r = out
+
+    overlay = Gtk.Overlay()
+    
+    scrolled = Gtk.ScrolledWindow()
+    scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+
+    flowbox = Gtk.FlowBox()
+    flowbox.set_valign(Gtk.Align.START)
+    flowbox.set_max_children_per_line(30)
+    flowbox.set_selection_mode(Gtk.SelectionMode.NONE)
+
+
+
+    scrolled.add(flowbox)
+    
+    box = Gtk.VBox()
+    for i in out["items"]:
+        flowbox.add(ui.search_item(win, i, channel_load))
+    if len(out["items"]) > 1:
+        lastthing(win, same, flowbox)
+
+    overlay.add(scrolled)
+
+    ############ SEARCH MENU ##############
+
+    settings_menu = Gtk.Popover()
+    settings_scroll = Gtk.ScrolledWindow()
+    settings_menu.add(settings_scroll)
+    settings_scroll.set_size_request(400,400)
+    menu_box = Gtk.VBox()
+    settings_scroll.add(menu_box)
+    
+    menu_box.pack_start(Gtk.Label("  Search Options:  "), 0,0,5)
+
+    #### SEARCH TERMS  ###
+
+    # name - the LBRY url of publication.
+
+    collapsable = Gtk.Expander(label="  By Text Query:  ")
+    menu_box.pack_start(collapsable, 0,0,5)
+
+    term_box = Gtk.VBox()
+    collapsable.add(term_box)
+
+    hbox = Gtk.HBox()
+    term_box.pack_start(hbox, 0,0,5)
+    hbox.pack_start(Gtk.Label("  LBRY url:  "), 0,0,0)
+
+    
+    lbry_name_entry = Gtk.Entry()
+    if same[-1].get("name"):
+        lbry_name_entry.set_text(same[-1]["name"])
+    hbox.pack_end(lbry_name_entry, 1,1,0)
+
+    
+    # text - the text in title
+
+    hbox = Gtk.HBox()
+    term_box.pack_start(hbox, 0,0,5)
+    hbox.pack_start(Gtk.Label("  Search by Text:  "), 0,0,0)
+    
+    text_entry = Gtk.Entry()
+    if same[1]:
+        text_entry.set_text(same[1])
+    elif same[-1].get("text"):
+        text_entry.set_text(same[-1]["text"])
+    hbox.pack_end(text_entry, 1,1,0)
+    
+    # claim_id - search partial of full claim_id
+
+    collapsable = Gtk.Expander(label="  By Claim ID:  ")
+    menu_box.pack_start(collapsable, 0,0,5)
+
+    term_box = Gtk.VBox()
+    collapsable.add(term_box)
+
+    hbox = Gtk.HBox()
+    term_box.pack_start(hbox, 0,0,5)
+    hbox.pack_start(Gtk.Label("  Claim ID:  "), 0,0,0)
+    
+    claim_id_entry = Gtk.Entry()
+    if same[-1].get("claim_id"):
+        claim_id_entry.set_text(same[-1]["claim_id"])
+    hbox.pack_start(claim_id_entry, 1,1,0)
+
+    # claim_ids - search partial of full claim_id
+
+    term_box.pack_start(Gtk.Label("  Many Full Claim IDs  "), 0,0,5)
+
+    claim_ids_list = []
+    if same[-1].get("claim_ids"):
+        claim_ids_list = same[-1].get("claim_ids")
+    
+    claim_ids_editor = ui.tags_editor(win, claim_ids_list)
+    term_box.pack_start(claim_ids_editor, 0,0,0)
+
+    ## TAGS ##
+
+    collapsable = Gtk.Expander(label="  Tags:  ")
+    menu_box.pack_start(collapsable, 0,0,5)
+
+    term_box = Gtk.VBox()
+    collapsable.add(term_box)
+
+    term_box.pack_start(Gtk.Label("  Any of:  "), 0,0,5)
+
+    any_tags_list = []
+    if same[-1].get("any_tags"):
+        any_tags_list = same[-1].get("any_tags")
+    
+    tags_editor = ui.tags_editor(win, any_tags_list)
+    term_box.pack_start(tags_editor, 0,0,0)
+
+    term_box.pack_start(Gtk.Label("  All of:  "), 0,0,5)
+
+    all_tags_list = []
+    if same[-1].get("all_tags"):
+        all_tags_list = same[-1].get("all_tags")
+    
+    tags_editor = ui.tags_editor(win, all_tags_list)
+    term_box.pack_start(tags_editor, 0,0,0)
+
+    term_box.pack_start(Gtk.Label("  Excluding:  "), 0,0,5)
+
+    not_tags_list = []
+    if same[-1].get("not_tags"):
+        not_tags_list = same[-1].get("not_tags")
+        
+    
+    tags_editor = ui.tags_editor(win, not_tags_list)
+    term_box.pack_start(tags_editor, 0,0,0)
+
+    ## CHANNELS ##
+
+    collapsable = Gtk.Expander(label="  Channels:  ")
+    menu_box.pack_start(collapsable, 0,0,5)
+
+    term_box = Gtk.VBox()
+    collapsable.add(term_box)
+    
+    hbox = Gtk.HBox()
+    term_box.pack_start(hbox, 0,0,5)
+    hbox.pack_start(Gtk.Label("  Channel Name:  "), 0,0,0)
+    
+    channel_entry = Gtk.Entry()
+    if same[-1].get("channel"):
+        channel_entry.set_text(same[-1]["channel"])
+    hbox.pack_start(channel_entry, 1,1,0)    
+    
+    term_box.pack_start(Gtk.Label("  Anything from (channel's claim IDs):  "), 0,0,5)
+    
+    any_channels_list = []
+    if same[2]:
+        any_channels_list = same[2]
+    elif same[-1].get("channel_ids"):
+        any_channels_list = same[-1]["channel_ids"]
+    
+    tags_editor = ui.tags_editor(win, any_channels_list)
+    term_box.pack_start(tags_editor, 0,0,0)
+
+    term_box.pack_start(Gtk.Label("   Excluding (channel's claim IDs):  "), 0,0,5)
+
+    not_channels_list = []
+    if same[-1].get("not_channel_ids"):
+        not_channels_list = same[-1]["not_channel_ids"]
+        
+    tags_editor = ui.tags_editor(win, not_channels_list)
+    term_box.pack_start(tags_editor, 0,0,0)
+    
+    ## ORDER BY ##
+
+    collapsable = Gtk.Expander(label="  Order:  ")
+    menu_box.pack_start(collapsable, 0,0,5)
+
+    term_box = Gtk.VBox()
+    collapsable.add(term_box)
+
+    order_by_rules = {
+        "Name":"name",
+        "Release Time":"release_time",
+        "Trending":"trending_mixed",
+        "Trending (Global)":"trending_global",
+        "Trending (Group)":"trending_group",
+        "Trending (Local)":"trending_local",
+        "Bid":"amount",
+        "Support":"support_amount",
+        "Support plus Bid":"effective_amount",
+        "Block Height":"height",
+        "Start Block":"activation_height"
+        
+    }
+    
+    order_by = Gtk.ComboBoxText()
+    
+
+    was_set = False
+    for n, i in enumerate(order_by_rules):
+        order_by.append_text(i)
+        if order_by_rules[i] in str(same[-1].get("order_by")):
+            order_by.set_active(n)
+            was_set = True
+
+    if not was_set:
+        order_by.set_active(1)
+
+    term_box.pack_start(order_by, 0,0,5)
+    
+    hbox = Gtk.HBox()
+    term_box.pack_start(hbox, 0,0,5)
+    hbox.pack_start(Gtk.Label("  Reverse:  "), 0,0,0)
+    order_reverse = Gtk.Switch()
+    if "^" in str(same[-1].get("order_by")):
+        order_reverse.set_active(True)
+    hbox.pack_end(order_reverse, 0,0,0)
+    
+    ## ORDER BY ##
+
+    collapsable = Gtk.Expander(label="  Type:  ")
+    menu_box.pack_start(collapsable, 0,0,5)
+
+    term_box = Gtk.VBox()
+    collapsable.add(term_box)
+
+    claim_types = {
+        "Every Claim Type":"",
+        "Channel":"channel",
+        "File":"stream",
+        "Re-Post":"repost",
+        "Collection":"collection"
+    }
+    claim_type = Gtk.ComboBoxText()
+    was_set = False
+    for n, i in enumerate(claim_types):
+        claim_type.append_text(i)
+        if claim_types[i] in str(same[-1].get("claim_type")):
+           claim_type.set_active(n)
+           was_set = True
+
+    if not was_set:
+        claim_type.set_active(0)
+
+    term_box.pack_start(claim_type, 0,0,5)
+
+    ## FILETYPES ##
+    
+    term_box.pack_start(Gtk.Label("  Filetypes:  "), 0,0,5)
+    
+    stream_types = []
+    if same[-1].get("stream_types"):
+        stream_types = same[-1]["stream_types"]
+        
+    stream_types_editor = ui.tags_editor(win, stream_types)
+    stream_types_editor.set_tooltip_text("Types of a file. For example: video, audio, image, document, binary.")
+
+    term_box.pack_start(stream_types_editor, 0,0,5)
+
+    ## MIME-TYPES ##
+    
+    term_box.pack_start(Gtk.Label("  Mime-Types:  "), 0,0,5)
+    
+    media_types = []
+    if same[-1].get("media_types"):
+        media_types = same[-1]["media_types"]
+        
+    media_types_editor = ui.tags_editor(win, media_types)
+    media_types_editor.set_tooltip_text("Precise Formats. For example: image/png, audio/ogg, application/x-ext-blend")
+    
+    term_box.pack_start(media_types_editor, 0,0,5)
+    
+
+    
+    ####### !!! RE _ SEARCH !!! #######
+
+    def do_re_search(w):
+
+        request = {}
+
+        ## TEXT ##
+        if lbry_name_entry.get_text():
+            request["name"] = lbry_name_entry.get_text()
+        if text_entry.get_text():
+            request["text"] = text_entry.get_text()
+        ## CLAIM ID ##
+        if claim_id_entry.get_text():
+            request["claim_id"] = claim_id_entry.get_text()
+        if claim_ids_list:
+            request["claim_ids"] = claim_ids_list
+        ## TAGS ##
+        if any_tags_list:
+            request["any_tags"] = any_tags_list
+        if all_tags_list:
+            request["all_tags"] = all_tags_list
+        if any_tags_list:
+            request["not_tags"] = not_tags_list
+        ## CHANNELS ##
+        if channel_entry.get_text():
+            request["channel"] = channel_entry.get_text()
+        if any_channels_list:
+            request["channel_ids"] = any_channels_list
+        if not_channels_list:
+            request["not_channel_ids"] = not_channels_list
+        ## Order by ##
+        prefix = ""
+        if order_reverse.get_active():
+            prefix = "^"
+        request["order_by"] = prefix+list(order_by_rules.values())[order_by.get_active()]
+
+        ## CLAIM TYPE ##
+        if list(claim_types.values())[claim_type.get_active()]:
+            request["claim_type"] = list(claim_types.values())[claim_type.get_active()]
+            # Some logic related to what the user will percieve
+            if request["channel_ids"] and not request.get("claim_ids") and request["claim_type"] == "channel":
+                request["claim_ids"] = request["channel_ids"]
+                request["channel_ids"] = []
+        ## STREAM TYPE ##
+        if stream_types:
+            request["stream_types"] = stream_types
+        ## MIME TYPES ##
+        if media_types:
+            request["media_types"] = media_types
+
+            
+        box = overlay.get_parent()
+        overlay.destroy()
+        resolve = ui.load(win, find, render, win, "", [], 1, request)
+        box.pack_start(resolve, 1,1,0)
+        box.show_all()
+        
+    re_search = Gtk.Button()
+    re_search.connect("clicked", do_re_search)
+    re_search.set_relief(Gtk.ReliefStyle.NONE)
+    b = Gtk.HBox()
+    b.pack_start(ui.icon(win, "system-search"),0,0,0)
+    b.pack_start(Gtk.Label("  Re-Search  "),0,0,0)
+    re_search.add(b)
+
+    
+    menu_box.pack_end(re_search, 0,0,0)
+
+    
+
+
+    
+    settings_scroll.show_all()
+    
+    b = Gtk.MenuButton(popover=settings_menu)
+    b.add(ui.icon(win, "preferences-system"))
+    b.set_halign(Gtk.Align.END)
+    b.set_valign(Gtk.Align.END)
+    overlay.add_overlay(b)
+        
+    return overlay
+
+def lastthing(win, same, flowbox):
+    # THis is a hack. I use a spinner to get a draw event
+    # when the user scrolls far enough, it will execute it
+    # to load more stuff
+    spinner_more = ui.icon(win, "loading", "gif")
+    flowbox.add(spinner_more)
+    def draw_event(w, e):
+        
+        print("EVENT MOTHERFUCKER")
+        w.destroy()
+        same.append(flowbox)
+
+        print("SAME:", same)
+
+        load_thread = threading.Thread(target=find, args=[*same])
+        load_thread.setDaemon(True)
+        load_thread.start()
+
+    spinner_more.connect("draw", draw_event)
+        
+    

+ 792 - 0
flbry/comments.py

@@ -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

+ 12 - 13
flbry/connect.py

@@ -31,6 +31,7 @@
 
 import subprocess
 import json
+from flbry import fetch
 
 def start():
     retcode = subprocess.Popen(['flbry/lbrynet', 'start'], 
@@ -49,17 +50,15 @@ def stop():
 def check():
     # This output true or false
     # whether the SDK is running
-
     try:
-        out = subprocess.check_output(["flbry/lbrynet",
-                         "claim", "search", '--text="test"',
-                         '--page=1',
-                         '--page_size=1',
-                            "--no_totals",
-                            '--order_by=release_time'])
-        out = json.loads(out)
-        out["items"]
-        return True
-    except Exception as e:
-        print("ERROR = ", e)
-        return False
+        out = fetch.lbrynet("status")
+        out = out['startup_status']
+    
+        trues = 0
+        for i in out:
+            if out[i]:
+                trues += 1
+
+        return trues / len(out)
+    except:
+        return 0

+ 0 - 0
flbry/data_view.py


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است