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 3 年之前
  Modanung fffe20241c Added PNGs 3 年之前
  Jeison Yehuda Amihud (Blender Dumbass) 09055389d5 Upload files to '' 3 年之前
  Jeison Yehuda Amihud (Blender Dumbass) b5b2e1df05 Merge branch 'master' of Modanung/FastLBRY-GTK into master 3 年之前
  Modanung c7a5fa2723 Replaced icon with SVG 3 年之前
  Modanung ea2b4ad283 Typo in readme 3 年之前
  jyamihud 8364db1b4d Much prettier Markdown. And an option to resolve links from aritlces. 3 年之前
  jyamihud 7d1537bee4 Fixed issues with deleting icons.py 3 年之前
  Jeison Yehuda Amihud (Blender Dumbass) 1699be6652 Cleaned some mess 3 年之前
  Jeison Yehuda Amihud (Blender Dumbass) 3d40708ba2 Cleaned some mess 3 年之前
  Jeison Yehuda Amihud (Blender Dumbass) e458dfa002 Outdated 3 年之前
  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. 二进制
      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
+*~ 

二进制
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


部分文件因为文件数量过多而无法显示