From b0fa7a9cb1a1b3cb7e4d56d97416423be2353aab Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holger@nahrstaedt.de>
Date: Tue, 17 Apr 2018 15:06:27 +0200
Subject: [PATCH] Follower and following added to cli

cli
* follower and following added
witness
* is_active added
Witnesses
* printAsTable improved
unit tests
* follower and following added to test_cli
---
 README.rst             |   6 +++
 beem/cli.py            | 120 ++++++++++++++++++++++++++++++++++++++++-
 beem/witness.py        |  32 +++++++----
 docs/cli.rst           |  13 +++--
 tests/beem/test_cli.py |  10 ++++
 5 files changed, 166 insertions(+), 15 deletions(-)

diff --git a/README.rst b/README.rst
index 24da62d4..1fd20aef 100644
--- a/README.rst
+++ b/README.rst
@@ -128,6 +128,12 @@ Documentation is available at http://beem.readthedocs.io/en/latest/
 
 Changelog
 =========
+0.19.22
+-------
+* beempy (command line tool) improved and all missing functions which are available in steempy are added
+* new functions to beempy added: witnesses, walletinfo, openorders, orderbook and claimreward
+* unit tests for cli added
+
 0.19.21
 -------
 * Transactionbuilder and Wallet improved
diff --git a/beem/cli.py b/beem/cli.py
index 44296250..11b45854 100644
--- a/beem/cli.py
+++ b/beem/cli.py
@@ -514,6 +514,124 @@ def interest(account):
     print(t)
 
 
+@cli.command()
+@click.argument('account', nargs=-1, required=False)
+def follower(account):
+    """ Get information about followers
+    """
+    stm = shared_steem_instance()
+    if not account:
+        if "default_account" in stm.config:
+            account = [stm.config["default_account"]]
+    for a in account:
+        t = PrettyTable([
+            "Key", "value"
+        ])
+        t.align = "r"
+        a = Account(a, steem_instance=stm)
+        print("\nFollowers statistics for @%s (please wait...)" % a.name)
+        follower_count = a.get_follow_count()["follower_count"]
+        t.add_row(["follower_count", str(follower_count)])
+        followers = a.get_followers(False)
+        own_mvest = []
+        eff_sp = []
+        rep = []
+        last_vote_h = []
+        last_post_d = []
+        no_vote = 0
+        no_post = 0
+        for f in followers:
+            rep.append(f.rep)
+            own_mvest.append(f.balances["available"][2].amount / 1e6)
+            eff_sp.append(f.get_steem_power())
+            utc = pytz.timezone('UTC')
+            last_vote = utc.localize(datetime.utcnow()) - formatTimeString(f["last_vote_time"])
+            if last_vote.days >= 365:
+                no_vote += 1
+            else:
+                last_vote_h.append(last_vote.total_seconds() / 60 / 60)
+            last_post = utc.localize(datetime.utcnow()) - formatTimeString(f["last_root_post"])
+            if last_post.days >= 365:
+                no_post += 1
+            else:
+                last_post_d.append(last_post.total_seconds() / 60 / 60 / 24)
+
+        t.add_row(["Summed MVest value", "%.2f" % sum(own_mvest)])
+        if (len(rep) > 0):
+            t.add_row(["Mean Rep.", "%.2f" % (sum(rep) / len(rep))])
+            t.add_row(["Max Rep.", "%.2f" % (max(rep))])
+        if (len(eff_sp) > 0):
+            t.add_row(["Summed eff. SP", "%.2f" % sum(eff_sp)])
+            t.add_row(["Mean eff. SP", "%.2f" % (sum(eff_sp) / len(eff_sp))])
+            t.add_row(["Max eff. SP", "%.2f" % max(eff_sp)])
+        if (len(last_vote_h) > 0):
+            t.add_row(["Mean last vote diff in hours", "%.2f" % (sum(last_vote_h) / len(last_vote_h))])
+        if len(last_post_d) > 0:
+            t.add_row(["Mean last post diff in days", "%.2f" % (sum(last_post_d) / len(last_post_d))])
+        t.add_row(["Followers without vote in 365 days", no_vote])
+        t.add_row(["Followers without post in 365 days", no_post])
+        print(t)
+
+
+@cli.command()
+@click.argument('account', nargs=-1, required=False)
+def following(account):
+    """ Get information about following
+    """
+    stm = shared_steem_instance()
+    if not account:
+        if "default_account" in stm.config:
+            account = [stm.config["default_account"]]
+    for a in account:
+        t = PrettyTable([
+            "Key", "value"
+        ])
+        t.align = "r"
+        a = Account(a, steem_instance=stm)
+        print("\nFollowing statistics for @%s (please wait...)" % a.name)
+        following_count = a.get_follow_count()["following_count"]
+        t.add_row(["following_count", str(following_count)])
+        following = a.get_following(False)
+        own_mvest = []
+        eff_sp = []
+        rep = []
+        last_vote_h = []
+        last_post_d = []
+        no_vote = 0
+        no_post = 0
+        for f in following:
+            rep.append(f.rep)
+            own_mvest.append(f.balances["available"][2].amount / 1e6)
+            eff_sp.append(f.get_steem_power())
+            utc = pytz.timezone('UTC')
+            last_vote = utc.localize(datetime.utcnow()) - formatTimeString(f["last_vote_time"])
+            if last_vote.days >= 365:
+                no_vote += 1
+            else:
+                last_vote_h.append(last_vote.total_seconds() / 60 / 60)
+            last_post = utc.localize(datetime.utcnow()) - formatTimeString(f["last_root_post"])
+            if last_post.days >= 365:
+                no_post += 1
+            else:
+                last_post_d.append(last_post.total_seconds() / 60 / 60 / 24)
+
+        t.add_row(["Summed MVest value", "%.2f" % sum(own_mvest)])
+        if (len(rep) > 0):
+            t.add_row(["Mean Rep.", "%.2f" % (sum(rep) / len(rep))])
+            t.add_row(["Max Rep.", "%.2f" % (max(rep))])
+        if (len(eff_sp) > 0):
+            t.add_row(["Summed eff. SP", "%.2f" % sum(eff_sp)])
+            t.add_row(["Mean eff. SP", "%.2f" % (sum(eff_sp) / len(eff_sp))])
+            t.add_row(["Max eff. SP", "%.2f" % max(eff_sp)])
+        if (len(last_vote_h) > 0):
+            t.add_row(["Mean last vote diff in hours", "%.2f" % (sum(last_vote_h) / len(last_vote_h))])
+        if len(last_post_d) > 0:
+            t.add_row(["Mean last post diff in days", "%.2f" % (sum(last_post_d) / len(last_post_d))])
+        t.add_row(["Following without vote in 365 days", no_vote])
+        t.add_row(["Following without post in 365 days", no_post])
+        print(t)
+
+
 @cli.command()
 @click.argument('account', nargs=1, required=False)
 def permissions(account):
@@ -1091,7 +1209,7 @@ def witnesscreate(witness, signing_key, maximum_block_size, account_creation_fee
 
 
 @cli.command()
-@click.option('--account', '-a', help='List the voted witnesses of your account')
+@click.argument('account', nargs=1, required=False)
 @click.option('--limit', help='How many witnesses should be shown', default=100)
 def witnesses(account, limit):
     """ List witnesses
diff --git a/beem/witness.py b/beem/witness.py
index 6cf977a8..49b67dc3 100644
--- a/beem/witness.py
+++ b/beem/witness.py
@@ -15,6 +15,7 @@ from datetime import datetime, timedelta
 from beembase import transactions, operations
 from beemgraphenebase.account import PrivateKey, PublicKey
 import pytz
+from prettytable import PrettyTable
 
 
 class Witness(BlockchainObject):
@@ -60,7 +61,7 @@ class Witness(BlockchainObject):
         else:
             witness = self.steem.rpc.get_witness_by_account(self.identifier)
         if not witness:
-            raise WitnessDoesNotExistsException
+            raise WitnessDoesNotExistsException(self.identifier)
         super(Witness, self).__init__(witness, id_item="owner", steem_instance=self.steem)
         self.identifier = self["owner"]
 
@@ -68,6 +69,10 @@ class Witness(BlockchainObject):
     def account(self):
         return Account(self["owner"], steem_instance=self.steem)
 
+    @property
+    def is_active(self):
+        return len(self['signing_key']) > 3 and self['signing_key'][3:] != '1111111111111111111111111111111114T1Anm'
+
     def feed_publish(self,
                      base,
                      quote="1.000 STEEM",
@@ -136,6 +141,8 @@ class Witness(BlockchainObject):
 class WitnessesObject(list):
     def printAsTable(self, sort_key="votes", reverse=True):
         utc = pytz.timezone('UTC')
+        t = PrettyTable(["name", "Votes [PV]", "disabled", "missed blocks", "feed base", "feed quote", "feed update", "fee", "size", "interest", "version"])
+        t.align = "l"
         if sort_key == 'base':
             sortedList = sorted(self, key=lambda self: self['sbd_exchange_rate']['base'], reverse=reverse)
         elif sort_key == 'quote':
@@ -153,15 +160,22 @@ class WitnessesObject(list):
         else:
             sortedList = sorted(self, key=lambda self: self[sort_key], reverse=reverse)
         for witness in sortedList:
-            outstr = ''
-            outstr += witness['owner'][:15].ljust(15) + " \t " + str(round(int(witness['votes']) / 1e15, 2)).ljust(5) + " PV - " + str(witness['total_missed']).ljust(5)
-            outstr += " missed - feed:" + str(Amount(witness['sbd_exchange_rate']['base'], steem_instance=self.steem)) + "/"
-            outstr += str(Amount(witness['sbd_exchange_rate']['quote'], steem_instance=self.steem))
             td = utc.localize(datetime.now()) - formatTimeString(witness['last_sbd_exchange_update'])
-            outstr += " " + str(td.days) + " days " + str(td.seconds // 3600) + ":" + str((td.seconds // 60) % 60) + " \t "
-            outstr += str(witness['props']['account_creation_fee']) + " " + str(witness['props']['maximum_block_size']) + " Blocks "
-            outstr += str(witness['props']['sbd_interest_rate']) + " \t " + witness['running_version']
-            print(outstr)
+            disabled = ""
+            if not witness.is_active:
+                disabled = "yes"
+            t.add_row([witness['owner'],
+                       str(round(int(witness['votes']) / 1e15, 2)),
+                       disabled,
+                       str(witness['total_missed']),
+                       str(Amount(witness['sbd_exchange_rate']['base'], steem_instance=self.steem)),
+                       str(Amount(witness['sbd_exchange_rate']['quote'], steem_instance=self.steem)),
+                       str(td.days) + " days " + str(td.seconds // 3600) + ":" + str((td.seconds // 60) % 60),
+                       str(witness['props']['account_creation_fee']),
+                       str(witness['props']['maximum_block_size']),
+                       str(witness['props']['sbd_interest_rate'] / 100) + " %",
+                       witness['running_version']])
+        print(t)
 
 
 class Witnesses(WitnessesObject):
diff --git a/docs/cli.rst b/docs/cli.rst
index 5d4f1fc4..67e643d7 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -98,7 +98,7 @@ You can see all available commands with ``beempy --help``
 ::
 
     ~ % beempy --help
-   Usage: beempy [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
+   Usage: cli.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
    
    Options:
      -n, --node TEXT        URL for public Steem API (e.g.
@@ -107,10 +107,6 @@ You can see all available commands with ``beempy --help``
      -d, --no-broadcast     Do not broadcast
      -p, --no-wallet        Do not load the wallet
      -x, --unsigned         Nothing will be signed
-     --blocking             Wait for broadcasted transactions to be included in a
-                            block and return full transaction
-     --bundle               Do not broadcast transactions right away, but allow
-                            to bundle operations
      -e, --expires INTEGER  Delay in seconds until transactions are supposed to
                             expire(defaults to 60)
      -v, --verbose INTEGER  Verbosity
@@ -122,17 +118,22 @@ You can see all available commands with ``beempy --help``
      allow                   Allow an account/key to interact with your...
      approvewitness          Approve a witnesses
      balance                 Shows balance
+     broadcast               broadcast a signed transaction
      buy                     Buy STEEM or SBD from the internal market...
      cancel                  Cancel order in the internal market
      changewalletpassphrase  Change wallet password
+     claimreward             Claim reward balances By default, this will...
      config                  Shows local configuration
      convert                 Convert STEEMDollars to Steem (takes a week...
      createwallet            Create new wallet with password
      delkey                  Delete key from the wallet PUB is the public...
+     delprofile              Delete a variable in an account's profile
      disallow                Remove allowance an account/key to interact...
      disapprovewitness       Disapprove a witnesses
      downvote                Downvote a post/comment POST is...
      follow                  Follow another account
+     follower                Get information about followers
+     following               Get information about following
      importaccount           Import an account using a passphrase
      info                    Show basic blockchain info General...
      interest                Get information about interest payment
@@ -148,6 +149,8 @@ You can see all available commands with ``beempy --help``
      resteem                 Resteem an existing post
      sell                    Sell STEEM or SBD from the internal market...
      set                     Set default_account, default_vote_weight or...
+     setprofile              Set a variable in an account's profile
+     sign                    Sign a provided transaction with available...
      transfer                Transfer SBD/STEEM
      unfollow                Unfollow another account
      updatememokey           Update an account's memo key
diff --git a/tests/beem/test_cli.py b/tests/beem/test_cli.py
index 3c50bc06..36c0d51d 100644
--- a/tests/beem/test_cli.py
+++ b/tests/beem/test_cli.py
@@ -172,6 +172,16 @@ class Testcases(unittest.TestCase):
         result = runner.invoke(cli, ['permissions', 'test'])
         self.assertEqual(result.exit_code, 0)
 
+    def test_follower(self):
+        runner = CliRunner()
+        result = runner.invoke(cli, ['follower', 'beem2'])
+        self.assertEqual(result.exit_code, 0)
+
+    def test_following(self):
+        runner = CliRunner()
+        result = runner.invoke(cli, ['following', 'beem'])
+        self.assertEqual(result.exit_code, 0)
+
     def test_allow_disallow(self):
         runner = CliRunner()
         result = runner.invoke(cli, ['-d', 'allow', 'beem1', '--account beem', '--permission posting'], input="test\n")
-- 
GitLab