class SteemConnect: def __init__(self, client_id="", client_secret="", callback_url="", permissions="login,offline,vote"): self.client_id = client_id self.client_secret = client_secret self.callback_url = callback_url self.permissions = permissions self.sc = None self.accesstoken = None self.msg = Msg("simplesteem.log", "~", "quiet") def steemconnect(self, accesstoken=None): ''' Initializes the SteemConnect Client class ''' if self.sc is not None: return self.sc if accesstoken is not None: self.accesstoken = accesstoken if self.accesstoken is None: self.sc = Client(client_id=self.client_id, client_secret=self.client_secret) else: self.sc = Client(access_token=self.accesstoken, client_id=self.client_id, client_secret=self.client_secret) return self.sc def get_token(self, code=None): ''' Uses a SteemConnect refresh token to retreive an access token ''' tokenobj = self.steemconnect().get_access_token(code) for t in tokenobj: if t == 'error': self.msg.error_message(str(tokenobj[t])) return False elif t == 'access_token': self.username = tokenobj['username'] self.refresh_token = tokenobj['refresh_token'] return tokenobj[t] def auth_url(self): ''' Returns the SteemConnect url used for authentication ''' return self.steemconnect().get_login_url(self.callback_url, self.permissions, "get_refresh_token=True") def vote(self, voter, author, permlink, voteweight): ''' Uses a SteemConnect accses token to vote. ''' vote = Vote(voter, author, permlink, voteweight) result = self.steemconnect().broadcast([vote.to_operation_structure()]) return result
def __init__(self, filename, path, screenmode): self.msg = Msg(filename, path, screenmode) self.total_vesting_fund_steem = None self.total_vesting_shares = None self.vote_power_reserve_rate = None self.info = None self.currentnode = 0
def __init__ (self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode)
class DB: def __init__(self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.db = None self.cursor = None self.dbresults = none def open_db(self): """ opens a database connection """ self.db = pymysql.connect("localhost", self.dbuser, self.dbpass, self.dbname) self.cursor = self.db.cursor() def get_results(self, sql, *args): """ Gets the results of an SQL statement """ self.open_db() try: self.cursor.execute(pymysql.escape_string(sql), args) self.dbresults = self.cursor.fetchall() except Exception as e: self.msg.error_message(e) self.dbresults = False self.db.rollback() return False else: return len(self.dbresults) finally: # Every call to this method from the other # classes in this package only takes place once # so we can safely close the DB every time # This can be moved to the deconstructor # if functionality demands multiple calls to # this method self.db.close() def commit(self, sql, *args): """ Commits the actions of an SQL statement to the database """ self.open_db() try: self.cursor.execute(pymysql.escape_string(sql), args) self.db.commit() except Exception as e: self.msg.error_message(e) self.db.rollback() return False else: return True finally: self.db.close() # EOF
class DB(): def __init__(self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode) def open_db(self): ''' opens a database connection ''' self.db = pymysql.connect("localhost", self.dbuser, self.dbpass, self.dbname) self.cursor = self.db.cursor() def get_results(self, sql, *args): ''' Gets the results of an SQL statement ''' self.open_db() try: self.cursor.execute(pymysql.escape_string(sql), args) self.dbresults = self.cursor.fetchall() except Exception as e: self.msg.error_message(e) self.dbresults = False self.db.rollback() return False else: return len(self.dbresults) finally: self.db.close() def commit(self, sql, *args): ''' Commits the actions of an SQL statement to the database ''' self.open_db() try: self.cursor.execute(pymysql.escape_string(sql), args) self.db.commit() except Exception as e: self.msg.error_message(e) self.db.rollback() return False else: return True finally: self.db.close()
def __init__(self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.db = None self.cursor = None self.dbresults = none
def __init__(self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.sendto = None self.inviter = None self.invitee = None self.errmsg = None
def __init__(self): self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.db = axdb.AXdb(default.dbuser, default.dbpass, default.dbname) self.steem = SimpleSteem() self.react = Reaction()
def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) try: imp.find_module('config', [os.path.dirname(os.path.realpath(__file__))]) except ImportError: makeconfig.MakeConfig().setup() from simplesteem import config try: self.mainaccount except: self.mainaccount = config.mainaccount try: self.keys except: self.keys = config.keys try: self.nodes except: self.nodes = config.nodes try: self.client_id except: self.client_id = config.client_id try: self.client_secret except: self.client_secret = config.client_secret try: self.callback_url except: self.callback_url = config.callback_url try: self.permissions except: self.permissions = config.permissions try: self.logpath except: self.logpath = config.logpath try: self.screenmode except: self.screenmode = config.screenmode self.s = None self.c = None self.msg = Msg("simplesteem.log", self.logpath, self.screenmode) self.util = util.Util() self.connect = steemconnectutil.SteemConnect(self.client_id, self.client_secret, self.callback_url, self.permissions) self.checkedaccount = None
def __init__(self, client_id="", client_secret="", callback_url="", permissions="login,offline,vote"): self.client_id = client_id self.client_secret = client_secret self.callback_url = callback_url self.permissions = permissions self.sc = None self.accesstoken = None self.msg = Msg("simplesteem.log", "~", "quiet")
def __init__(self): self.steem = SimpleSteem(client_id=default.client_id, client_secret=default.client_secret, callback_url=default.callback_url, screenmode=default.msgmode) self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.response = None self.post_one = "" self.post_two = "" self.vote_cut = 0
def __init__(self, debug=False, mode="verbose", botname="settings"): ''' Uses simplesteem and screenlogger from https://github.com/ArtoLabs/SimpleSteem https://github.com/ArtoLabs/ScreenLogger ''' bot = importlib.import_module("." + botname, "delegatorbot") self.debug = debug self.cfg = bot.Config() self.msg = Msg(self.cfg.logfilename, self.cfg.logpath, mode) self.db = BotDB(self.cfg.dbusername, self.cfg.dbpass, self.cfg.dbname, self.cfg.dbtable, self.cfg.dbposttable, self.cfg.delegatortable) self.steem = SimpleSteem(mainaccount=self.cfg.mainaccount, keys=self.cfg.keys, screenmode=mode) self.voteweight = 0 self.bidbot = None
class DB: def __init__(self, dbuser, dbpass, dbname, logfilename, logpath, msgmode): self.msg = Msg(logfilename, logpath, msgmode) self.db = pymysql.connect("localhost", dbuser, dbpass, dbname) self.cursor = self.db.cursor() self.dbresults = None def __del__(self): """ opens a database connection """ self.db.close() def get_results(self, sql, *args): """ Gets the results of an SQL statement """ try: self.cursor.execute(pymysql.escape_string(sql), args) self.dbresults = self.cursor.fetchall() except Exception as e: self.msg.error_message(e) self.dbresults = False self.db.rollback() return False else: return len(self.dbresults) def commit(self, sql, *args): """ Commits the actions of an SQL statement to the database """ try: self.cursor.execute(pymysql.escape_string(sql), args) self.db.commit() except Exception as e: self.msg.error_message(e) self.db.rollback() return False else: return True
class AXverify: def __init__(self): self.steem = SimpleSteem(client_id=default.client_id, client_secret=default.client_secret, callback_url=default.callback_url, screenmode=default.msgmode) self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.response = None self.post_one = "" self.post_two = "" self.vote_cut = 0 def vote_on_it(self, voter, author, post, weight): """ Use the tokens in the database to vote on a post. If the vote fails, renews the token and tries again. """ db = axdb.AXdb(default.dbuser, default.dbpass, default.dbname) if db.get_user_token(voter) is not False: accesstoken = db.dbresults[0][1] refreshtoken = db.dbresults[0][2] else: return False # If the vote fails then we renew the token if not self.sc_vote(voter, author, post, weight, accesstoken): print("Renewing token for " + voter) newtoken = self.renew_token(voter, refreshtoken) if newtoken is not False: return self.sc_vote(voter, author, post, weight, newtoken) else: self.msg.error_message("A NEW TOKEN COULD NOT BE CREATED") return False def sc_vote(self, voter, author, post, weight, token): """ Takes the given token and initializes SteemConnect to make a vote. Analyzes the result and prints the outcome to screen as well as returns a boolean result. """ self.steem.connect.sc = None self.steem.connect.steemconnect( token) result = self.steem.connect.vote( voter, author, post, int(weight)) try: result['error'] # We use a broad exception clause to "catch" everything # that is not an error except: # The vote was successful print(str(voter) + " has voted on " + str(post) + " " + str(weight) + "%") return True else: self.msg.error_message(str(result)) return False def renew_token(self, accountname, refreshtoken): """ If the access token has expired use the refresh token to get a new accss token. """ if self.steem.verify_key( acctname="", tokenkey=refreshtoken): db.update_token(self.steem.username, self.steem.accesstoken, self.steem.refreshtoken) return self.steem.accesstoken else: return False def get_vote_value(self, acctname, voteweight=100, votepower=0): """ Voteweight and votepower are entered as a percentage value (1 to 100) If the default is used for votepower (0) it is set by the system at the time the account last voted. """ self.steem.check_balances(acctname) if votepower == 0: votepower = self.steem.votepower self.votevalue = self.steem.current_vote_value( lastvotetime=self.steem.lastvotetime, steempower=self.steem.steempower, voteweight=int(voteweight), votepower=int(votepower)) self.voteweight = voteweight self.votepower = votepower return self.steem.rshares def verify_post(self, account1, account2): """ Gets only the most recent post, gets the timestamp and finds the age of the post in days. Is the post too old? Did account2 vote on the post already? """ identifier = self.steem.recent_post(account1) if identifier is False or identifier is None: self.msg.error_message("No post for " + account1) return False else: permlink = self.steem.util.permlink(identifier) votes = self.steem.vote_history(permlink[1], account1) for v in votes: if v['voter'] == account2: self.msg.error_message(account2 + " has aready voted on " + permlink[1]) return False return permlink[1] def eligible_posts(self, account1, account2): """ Verify the posts of both accounts """ post = self.verify_post(account1, account2) if post is False or post is None: self.msg.message(account1 + " does not have an eligible post.") return False else: self.post_one = post post = self.verify_post(account2, account1) if post is False or post is None: self.msg.message(account2 + " does not have an eligible post.") return False else: self.post_two = post return True def eligible_votes(self, account1, account2, percentage, ratio, flag): """ If the flag is raised use the full voting power to make the comparison rather than the current voting power. If ratio was not acheived return false Otherwise display approximate upvote matches """ if flag == 1: vpow = 100 else: vpow = 0 v1 = self.get_vote_value(account1, percentage, vpow) v2 = self.get_vote_value(account2, 100, vpow) v3 = ((v1 / v2) * 100) / float(ratio) v3a = round(v3, 2) exceeds = False if v3a < 1: v3 = 1 exceeds = True if v3a > 100: v3 = 100 exceeds = True self.msg.message(account2 + " needs to vote " + str(v3a) + "% in order to meet " + account1) self.vote_cut = v3 v4 = self.get_vote_value(account2, v3, vpow) v1s = self.steem.rshares_to_steem(v1) v4s = self.steem.rshares_to_steem(v4) if exceeds and flag != 2: if v3 == 1: v5 = v4 - v1 v5s = self.steem.rshares_to_steem(v5) self.response = (account2 + "'s vote of " + str(v4s) + " will be larger than " + account1 + "'s vote by: " + str(v5s)) self.msg.message(self.response) if v3 == 100: v5 = v1 - v4 v5s = self.steem.rshares_to_steem(v5) self.response = (account1 + "'s vote of " + str(v1s) + " will be larger than " + account2 + "'s vote by: " + str(v5s)) self.msg.message(self.response) return False else: self.msg.message(account1 + " will upvote $" + str(v1s) + " and " + account2 + " will upvote $" + str(v4s)) return True
class AXverify: def __init__(self): self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.steem = SimpleSteem() self.response = None def get_vote_value (self, acctname, voteweight=100, votepower=0): ''' Voteweight and votepower are entered as a percentage value (1 to 100) If the default is used for votepower (0) it is set by the system at the time the account last voted. ''' self.steem.check_balances(acctname) if votepower == 0: votepower = self.steem.votepower self.votevalue = self.steem.current_vote_value( self.steem.lastvotetime, self.steem.steempower, voteweight, votepower) self.voteweight = voteweight self.votepower = votepower return self.steem.rshares def verify_post (self, account1, account2, mode): ''' Gets only the most recent post, gets the timestamp and finds the age of the post in days. Is the post too old? Did account2 vote on the post already? ''' permlink = self.steem.get_post_info(account1) if not permlink: self.msg.error_message("No post for " + account1) return False else: votes = self.steem.vote_history(account1, permlink) for v in votes: if v['voter'] == account2: self.msg.message(account2 + " has aready voted on " + permlink) return False return True def eligible_posts (self, account1, account2, mode): ''' Verify the posts of both accounts ''' if not self.verify_post(account1, account2, mode): return False if not self.verify_post(account2, account1, mode): return False return True def eligible_votes (self, account1, account2, percentage, ratio, mode, flag): ''' If the flag is raised use the full voting power to make the comparison rather than the current voting power. If ratio was not acheived return false Otherwise display approximate upvote matches ''' if flag == 1: vpow = 100 else: vpow = 0 v1 = self.get_vote_value(account1, percentage, vpow, mode) v2 = self.get_vote_value(account2, 100, vpow, mode) v3 = ((v1 / v2) * 100) / float(ratio) v3a = round(v3, 2) exceeds = False if v3a < 1: v3 = 1 exceeds = True if v3a > 100: v3 = 100 exceeds = True self.msg.message(account2 + " needs to vote " + str(v3a) + "% in order to meet " + account1) v4 = self.get_vote_value(account2, v3, vpow, mode) v1s = self.steem.rshares_to_steem(v1) v4s = self.steem.rshares_to_steem(v4) if exceeds: if v3 == 1: v5 = v4 - v1 v5s = self.steem.rshares_to_steem(v5) self.response = (account2 + "'s vote of " + str(v4s) + " will be larger than " + account1 + "'s vote by: " + str(v5s)) self.msg.message(self.response) if v3 == 100: v5 = v1 - v4 v5s = self.steem.rshares_to_steem(v5) self.response = (account1 + "'s vote of " + str(v1s) + " will be larger than " + account2 + "'s vote by: " + str(v5s)) self.msg.message(self.response) return False else: self.msg.message(account1 + " will upvote $" + str(v1s) + " and " + account2 + " will upvote $" + str(v4s)) return True
def __init__(self): self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.steem = SimpleSteem() self.response = None
def __init__(self): self.msg = Msg()
def __init__(self, dbuser, dbpass, dbname, logfilename, logpath, msgmode): self.msg = Msg(logfilename, logpath, msgmode) self.db = pymysql.connect("localhost", dbuser, dbpass, dbname) self.cursor = self.db.cursor() self.dbresults = None
class AXdb(DB): def __init__(self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.sendto = None self.inviter = None self.invitee = None self.errmsg = None def first_time_setup(self): """ Sets up all three needed tables if they do not already exist """ if not self.get_results("SELECT * FROM users WHERE 1;"): self.commit('CREATE TABLE IF NOT EXISTS users ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'Account varchar(50), PrivateKey varchar(100), ' + 'RefreshToken varchar(400), Token varchar(400), ' + 'Time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);') if not self.get_results("SELECT * FROM axlist WHERE 1;"): self.commit('CREATE TABLE IF NOT EXISTS axlist ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'Account1 varchar(50), Account2 varchar(50), ' + 'Percentage varchar(5), Ratio varchar(5), ' + 'Duration varchar(5), MemoID varchar(100), ' + 'Status varchar(10), ' + 'Time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);') if not self.get_results("SELECT * FROM axtrans WHERE 1;"): self.commit('CREATE TABLE IF NOT EXISTS axtrans ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'TXID varchar(50), MemoFrom varchar(20), ' + 'Amount varchar(20), MemoID varchar(100), ' + 'Action varchar(20), TxTime TIMESTAMP NULL, ' + 'DiscoveryTime TIMESTAMP NOT NULL ' + 'DEFAULT CURRENT_TIMESTAMP ' + 'ON UPDATE CURRENT_TIMESTAMP);') if not self.get_results("SELECT * FROM axhistory WHERE 1;"): self.commit( 'CREATE TABLE IF NOT EXISTS axhistory ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'MemoID varchar(100), ' + 'Account1 varchar(200), Account2 varchar(200), ' + 'VoteValue1 varchar(100), VoteValue2 varchar(100), ' + 'Identifier1 varchar(400), Identifier2 varchar(400), ' + 'Time TIMESTAMP NOT NULL ' + 'DEFAULT CURRENT_TIMESTAMP ' + 'ON UPDATE CURRENT_TIMESTAMP);') def archive_exchange(self, memoid, account1, account2, ident1, ident2, vote1, vote2): """ If posts and votes are eligible and an exchange occurs it is recorded in the database. """ return self.commit( 'INSERT INTO axhistory (MemoID, Account1, ' + 'Account2, VoteValue1, VoteValue2, Identifier1, Identifier2) ' + 'VALUES (%s, %s, %s, %s, %s, %s, %s);', memoid, account1, account2, vote1, vote2, ident1, ident2) def get_exchange_archive(self, account=None): """ Returns a list of all the recent exchanges that have occured on steemax """ if account is None: if self.get_results('SELECT Account1, Account2, VoteValue1, ' + 'VoteValue2, Identifier1, Identifier2, Time ' + 'FROM axhistory WHERE 1 ORDER BY Time DESC;'): return self.dbresults else: if self.get_results( 'SELECT Account1, Account2, VoteValue1, ' + 'VoteValue2, Identifier1, Identifier2, Time ' + 'FROM axhistory WHERE %s IN (Account1, Account2) ' + 'ORDER BY Time DESC;', str(account)): return self.dbresults def get_most_recent_trans(self): """ Returns the timestamp from the most recent memo id message. """ if not self.get_results('SELECT TxTime FROM axtrans ' + 'WHERE 1 ORDER BY TxTime DESC LIMIT 1;'): return datetime.utcnow() - timedelta(days=5) else: return self.dbresults[0][0] def add_trans(self, txid, memofrom, amt, memoid, action, txtime): """ Adds a processed transaction to the axtrans database """ return self.commit( 'INSERT INTO axtrans (TXID, MemoFrom, ' + 'Amount, MemoID, Action, TxTime) ' + 'VALUES (%s, %s, %s, %s, %s, %s);', txid, memofrom, amt, memoid, action, txtime) def verify_memoid(self, acct0, memoid): """Verify that the Memo ID entered matches the account name entered. """ if not self.get_results( 'SELECT Account1, Account2, ' + 'Status FROM axlist WHERE MemoID = %s;', memoid): self.msg.error_message("The Memo ID " + memoid + " is not in database.") return False if acct0 == self.dbresults[0][0]: self.sendto = self.dbresults[0][1] self.inviter = self.dbresults[0][0] self.invitee = self.dbresults[0][1] elif acct0 == self.dbresults[0][1]: self.sendto = self.dbresults[0][0] self.inviter = self.dbresults[0][1] self.invitee = self.dbresults[0][0] else: self.msg.error_message("Account does not match Memo ID.") return False return True def cancel(self, acct, memoid): """ Either account can cancel """ return self.commit( 'DELETE FROM axlist WHERE %s IN ' + '(Account1, Account2) AND (MemoID = %s);', acct, memoid) def get_invite(self, memoid): """ Gets an invite from the database and returns it as a list """ if self.get_results( "SELECT Percentage, Ratio, Duration, " + "Status FROM axlist WHERE MemoID = %s;", memoid): return [ self.dbresults[0][0], self.dbresults[0][1], self.dbresults[0][2], self.dbresults[0][3] ] else: self.msg.error_message("No invite found for " + memoid) return [0, 0, 0, 0] def update_invite(self, percent, ratio, duration, memoid, status): """ Updates and invite during the barter process """ return self.commit( 'UPDATE axlist SET Percentage = %s, ' + 'Ratio = %s, Duration = %s, Status = %s WHERE MemoID = %s;', percent, ratio, duration, status, memoid) def update_status(self, status, memoid): """ -1 = waiting for inviter to authorize 0 = invite sent. waiting for invitee 1 = exchange authorized by both parties 2 = inviter (account1) has offered to barter 3 = invitee (account2) has offered to barter 4 = exchange has been cancelled """ return self.commit("UPDATE axlist SET Status = %s WHERE MemoID = %s;", status, memoid) def check_status(self, memoid): """ Checks the status of an invite so that steemax knows how to react to a command """ if self.get_results("SELECT Status FROM axlist WHERE MemoID = %s;", memoid): return self.dbresults[0][0] else: return False def get_user_token(self, acct): """ Gets a user's SteemConnect tokens and Private Posting Key """ if self.get_results( 'SELECT PrivateKey, Token, RefreshToken ' + 'FROM users WHERE Account = %s;', acct): return self.dbresults[0][1] else: return False def update_token(self, acct, accesstoken, refreshtoken): """ Updates a user's SteemConnect tokens """ return self.commit( "UPDATE users SET Token = %s, " + "RefreshToken = %s WHERE Account = %s;", accesstoken, refreshtoken, acct) def add_invite(self, acct1, acct2, percent, ratio, duration): """Adds the initial invite to the database and provides the unique Memo ID. """ memoid = self.generate_memoid() if acct1 == acct2: self.errmsg = ('The same account name was ' + 'entered for both accounts.') self.msg.error_message(self.errmsg) return False if self.get_results( 'SELECT * FROM axlist ' + 'WHERE %s IN (Account1, Account2) ' + 'AND (%s IN (Account1, Account2)) ' + 'AND (Status != 4);', acct1, acct2): self.errmsg = ('An exchange has already been ' + 'made between these accounts.') self.msg.error_message(self.errmsg) return False if self.commit( 'INSERT INTO axlist (Account1, Account2, ' + 'Percentage, Ratio, ' + 'Duration, MemoID, Status) ' + 'VALUES (%s, %s, %s, %s, %s, %s, %s);', acct1, acct2, percent, ratio, duration, memoid, -1): return memoid else: return False def add_user(self, acct, key, refreshtoken, accesstoken): """ Adds a user and their tokens/key to the database """ return self.commit( 'INSERT INTO users (Account, PrivateKey, ' + 'RefreshToken, Token) ' + 'VALUES (%s, %s, %s, %s);', acct, key, refreshtoken, accesstoken) def get_axlist(self, account=None, run=False): """ Gets the entire list of transactions to be processed """ if run: self.get_results('SELECT * FROM axlist WHERE Status = 1;') elif account is None: self.get_results( 'SELECT * FROM axlist WHERE Status != 4 ORDER BY Status;') else: self.get_results( 'SELECT * FROM axlist ' + 'WHERE %s IN (Account1, Account2) AND (Status != 4) ORDER BY Status;', account) return self.dbresults def generate_memoid(self, length=32): return ''.join([str(random.randint(0, 9)) for i in range(length)]) def expiration_date(self, timestamp, duration): end_date = (datetime.strptime(str(timestamp), '%Y-%m-%d %H:%M:%S') + timedelta(days=int(duration))) return (str(end_date.strftime("%B")) + " " + str(end_date.day) + ", " + str(end_date.year)) def expire(self): self.get_results( "SELECT ID, Duration, Time, Account1, Account2 FROM axlist WHERE 1;" ) for row in self.dbresults: if (datetime.strptime(str(row[2]), '%Y-%m-%d %H:%M:%S') + timedelta(days=int(row[1])) < datetime.now()): self.commit("UPDATE axlist SET Status = 4 WHERE ID = %s;", row[0]) self.msg.message(row[3] + " vs. " + row[4] + " has expired.")
class SimpleSteem: def __init__(self, **kwargs): ''' Looks for config.py if not found runs setup which prompts user for the config values one time only. ''' for key, value in kwargs.items(): setattr(self, key, value) try: imp.find_module('config', [os.path.dirname(os.path.realpath(__file__))]) except ImportError: makeconfig.MakeConfig().setup() from simplesteem import config try: self.mainaccount except: self.mainaccount = config.mainaccount try: self.keys except: self.keys = config.keys try: self.nodes except: self.nodes = config.nodes try: self.client_id except: self.client_id = config.client_id try: self.client_secret except: self.client_secret = config.client_secret try: self.callback_url except: self.callback_url = config.callback_url try: self.permissions except: self.permissions = config.permissions try: self.logpath except: self.logpath = config.logpath try: self.screenmode except: self.screenmode = config.screenmode self.s = None self.c = None self.e = None self.dex = None self.msg = Msg("simplesteem.log", self.logpath, self.screenmode) self.util = util.Util("simplesteem.log", self.logpath, self.screenmode) self.connect = steemconnectutil.SteemConnect( self.client_id, self.client_secret, self.callback_url, self.permissions) self.checkedaccount = None self.accountinfo = None self.blognumber = 0 self.reward_balance = 0 self.price_of_steem = 0 def account(self, account=None): ''' Fetches account information and stores the result in a class variable. Returns that variable if the account has not changed. ''' for num_of_retries in range(default.max_retry): if account is None: account = self.mainaccount if account == self.checkedaccount: return self.accountinfo self.checkedaccount = account try: self.accountinfo = self.steem_instance().get_account(account) except Exception as e: self.util.retry(("COULD NOT GET ACCOUNT INFO FOR " + str(account)), e, num_of_retries, default.wait_time) self.s = None self.e = e else: if self.accountinfo is None: self.msg.error_message("COULD NOT FIND ACCOUNT: " + str(account)) return False else: return self.accountinfo def steem_instance(self): ''' Returns the steem instance if it already exists otherwise uses the goodnode method to fetch a node and instantiate the Steem class. ''' if self.s: return self.s for num_of_retries in range(default.max_retry): node = self.util.goodnode(self.nodes) try: self.s = Steem(keys=self.keys, nodes=[node]) except Exception as e: self.util.retry("COULD NOT GET STEEM INSTANCE", e, num_of_retries, default.wait_time) self.s = None self.e = e else: return self.s return False def claim_rewards(self): if self.steem_instance().claim_reward_balance( account=self.mainaccount): time.sleep(5) return True else: return False def verify_key (self, acctname=None, tokenkey=None): ''' This can be used to verify either a private posting key or to verify a steemconnect refresh token and retreive the access token. ''' if (re.match( r'^[A-Za-z0-9]+$', tokenkey) and tokenkey is not None and len(tokenkey) <= 64 and len(tokenkey) >= 16): pubkey = PrivateKey(tokenkey).pubkey or 0 pubkey2 = self.account(acctname) if (str(pubkey) == str(pubkey2['posting']['key_auths'][0][0])): self.privatekey = tokenkey self.refreshtoken = None self.accesstoken = None return True else: return False elif (re.match( r'^[A-Za-z0-9\-\_\.]+$', tokenkey) and tokenkey is not None and len(tokenkey) > 64): self.privatekey = None self.accesstoken = self.connect.get_token(tokenkey) if self.accesstoken: self.username = self.connect.username self.refreshtoken = self.connect.refresh_token return True else: return False else: return False def reward_pool_balances(self): ''' Fetches and returns the 3 values needed to calculate the reward pool and other associated values such as rshares. Returns the reward balance, all recent claims and the current price of steem. ''' if self.reward_balance > 0: return self.reward_balance else: reward_fund = self.steem_instance().get_reward_fund() self.reward_balance = Amount( reward_fund["reward_balance"]).amount self.recent_claims = float(reward_fund["recent_claims"]) self.base = Amount( self.steem_instance( ).get_current_median_history_price()["base"] ).amount self.quote = Amount( self.steem_instance( ).get_current_median_history_price()["quote"] ).amount self.price_of_steem = self.base / self.quote return [self.reward_balance, self.recent_claims, self.price_of_steem] def rshares_to_steem (self, rshares): ''' Gets the reward pool balances then calculates rshares to steem ''' self.reward_pool_balances() return round( rshares * self.reward_balance / self.recent_claims * self.price_of_steem, 4) def global_props(self): ''' Retrieves the global properties used to determine rates used for calculations in converting steempower to vests etc. Stores these in the Utilities class as that is where the conversions take place, however SimpleSteem is the class that contains steem_instance, so to to preserve heirarchy and to avoid "spaghetti code", this method exists in this class. ''' if self.util.info is None: self.util.info = self.steem_instance().get_dynamic_global_properties() self.util.total_vesting_fund_steem = Amount(self.util.info["total_vesting_fund_steem"]).amount self.util.total_vesting_shares = Amount(self.util.info["total_vesting_shares"]).amount self.util.vote_power_reserve_rate = self.util.info["vote_power_reserve_rate"] return self.util.info def current_vote_value(self, **kwargs): ''' Ensures the needed variables are created and set to defaults although a variable number of variables are given. ''' try: kwargs.items() except: pass else: for key, value in kwargs.items(): setattr(self, key, value) try: self.lastvotetime except: self.lastvotetime=None try: self.steempower except: self.steempower=0 try: self.voteweight except: self.voteweight=100 try: self.votepoweroverride except: self.votepoweroverride=0 try: self.accountname except: self.accountname=None if self.accountname is None: self.accountname = self.mainaccount if self.check_balances(self.accountname) is not False: if self.voteweight > 0 and self.voteweight < 101: self.voteweight = self.util.scale_vote(self.voteweight) if self.votepoweroverride > 0 and self.votepoweroverride < 101: self.votepoweroverride = self.util.scale_vote(self.votepoweroverride) else: self.votepoweroverride = (self.votepower + self.util.calc_regenerated( self.lastvotetime)) self.vpow = round(self.votepoweroverride / 100, 2) self.global_props() self.rshares = self.util.sp_to_rshares(self.steempower, self.votepoweroverride, self.voteweight) self.votevalue = self.rshares_to_steem(self.rshares) return self.votevalue return None def check_balances(self, account=None): ''' Fetches an account balance and makes necessary conversions ''' a = self.account(account) if a is not False and a is not None: self.sbdbal = Amount(a['sbd_balance']).amount self.steembal = Amount(a['balance']).amount self.votepower = a['voting_power'] self.lastvotetime = a['last_vote_time'] vs = Amount(a['vesting_shares']).amount dvests = Amount(a['delegated_vesting_shares']).amount rvests = Amount(a['received_vesting_shares']).amount vests = (float(vs) - float(dvests)) + float(rvests) try: self.global_props() self.steempower_delegated = self.util.vests_to_sp(dvests) self.steempower_raw = self.util.vests_to_sp(vs) self.steempower = self.util.vests_to_sp(vests) except Exception as e: self.msg.error_message(e) self.e = e else: return [self.sbdbal, self.steembal, self.steempower, self.votepower, self.lastvotetime] return False def transfer_funds(self, to, amount, denom, msg): ''' Transfer SBD or STEEM to the given account ''' try: self.steem_instance().commit.transfer(to, float(amount), denom, msg, self.mainaccount) except Exception as e: self.msg.error_message(e) self.e = e return False else: return True def get_my_history(self, account=None, limit=10000): ''' Fetches the account history from most recent back ''' if not account: account = self.mainaccount try: h = self.steem_instance().get_account_history( account, -1, limit) except Exception as e: self.msg.error_message(e) self.e = e return False else: return h def post(self, title, body, permlink, tags): ''' Used for creating a main post to an account's blog. Waits 20 seconds after posting as that is the required amount of time between posting. ''' for num_of_retries in range(default.max_retry): try: self.msg.message("Attempting to post " + permlink) self.steem_instance().post(title, body, self.mainaccount, permlink, None, None, None, None, tags, None, False) except Exception as e: self.util.retry("COULD NOT POST '" + title + "'", e, num_of_retries, 10) self.s = None self.e = e else: self.msg.message("Post seems successful. Wating 60 seconds before verifying...") self.s = None time.sleep(60) checkident = self.recent_post() ident = self.util.identifier(self.mainaccount, permlink) if checkident == ident: return True else: self.util.retry('A POST JUST CREATED WAS NOT FOUND IN THE ' + 'BLOCKCHAIN {}'''.format(title), "Identifiers do not match", num_of_retries, default.wait_time) self.s = None def reply(self, permlink, msgbody): ''' Used for creating a reply to a post. Waits 20 seconds after posting as that is the required amount of time between posting. ''' for num_of_retries in range(default.max_retry): try: self.steem_instance().post("message", msgbody, self.mainaccount, None, permlink, None, None, "", None, None, False) except Exception as e: self.util.retry("COULD NOT REPLY TO " + permlink, e, num_of_retries, default.wait_time) self.s = None self.e = e else: self.msg.message("Replied to " + permlink) time.sleep(20) return True def follow(self, author): ''' Follows the given account ''' try: self.steem_instance().commit.follow(author, ['blog'], self.mainaccount) except Exception as e: self.msg.error_message(e) self.e = e return False else: return True def unfollow(self, author): ''' Unfollows the given account ''' try: self.steem_instance().commit.unfollow(author, ['blog'], self.mainaccount) except Exception as e: self.msg.error_message(e) self.e = e return False else: return True def following(self, account=None, limit=100): ''' Gets a list of all the followers of a given account. If no account is given the followers of the mainaccount are returned. ''' if not account: account = self.mainaccount followingnames = [] try: self.followed = self.steem_instance().get_following(account, '', 'blog', limit) except Exception as e: self.msg.error_message(e) self.e = e return False else: for a in self.followed: followingnames.append(a['following']) return followingnames def recent_post(self, author=None, daysback=0, flag=0): ''' Returns the most recent post from the account given. If the days back is greater than zero then the most recent post is returned for that day. For instance if daysback is set to 2 then it will be the most recent post from 2 days ago (48 hours). ''' if not author: author = self.mainaccount for num_of_retries in range(default.max_retry): try: self.blog = self.steem_instance().get_blog(author, 0, 30) except Exception as e: self.util.retry('COULD NOT GET THE ' + 'MOST RECENT POST FOR ' + '{}'.format(author), e, num_of_retries, default.wait_time) self.s = None self.e = e else: i = 0 for p in self.blog: if p != "error": if p['comment']['author'] == author: self.blognumber = i ageinminutes = self.util.minutes_back(p['comment']['created']) ageindays = (ageinminutes / 60) / 24 if (int(ageindays) == daysback): if flag == 1 and ageinminutes < 15: return None else: return self.util.identifier( p['comment']['author'], p['comment']['permlink']) else: return None i += 1 def vote_history(self, permlink, author=None): ''' Returns the raw vote history of a given post from a given account ''' if author is None: author = self.mainaccount return self.steem_instance().get_active_votes(author, permlink) def vote(self, identifier, weight=100.0): ''' Waits 5 seconds as that is the required amount of time between votes. ''' for num_of_retries in range(default.max_retry): try: self.steem_instance().vote(identifier, weight, self.mainaccount) self.msg.message("voted for " + identifier) time.sleep(5) except Exception as e: if re.search(r'You have already voted in a similar way', str(e)): self.msg.error_message('''Already voted on {}'''.format(identifier)) return "already voted" else: self.util.retry('''COULD NOT VOTE ON {}'''.format(identifier), e, num_of_retries, default.wait_time) self.s = None self.e = e else: return True def resteem(self, identifier): ''' Waits 20 seconds as that is the required amount of time between resteems ''' for num_of_retries in range(default.max_retry): try: self.steem_instance().resteem( identifier, self.mainaccount) self.msg.message("resteemed " + identifier) time.sleep(10) except Exception as e: self.util.retry('''COULD NOT RESTEEM {}'''.format(identifier), e, num_of_retries, default.wait_time) self.s = None self.e = e else: return True def dex_ticker(self): ''' Simply grabs the ticker using the steem_instance method and adds it to a class variable. ''' self.dex = Dex(self.steem_instance()) self.ticker = self.dex.get_ticker(); return self.ticker def steem_to_sbd(self, steemamt=0, price=0, account=None): ''' Uses the ticker to get the highest bid and moves the steem at that price. ''' if not account: account = self.mainaccount if self.check_balances(account): if steemamt == 0: steemamt = self.steembal elif steemamt > self.steembal: self.msg.error_message("INSUFFICIENT FUNDS. CURRENT STEEM BAL: " + str(self.steembal)) return False if price == 0: price = self.dex_ticker()['highest_bid'] try: self.dex.sell(steemamt, "STEEM", price, account=account) except Exception as e: self.msg.error_message("COULD NOT SELL STEEM FOR SBD: " + str(e)) self.e = e return False else: self.msg.message("TRANSFERED " + str(steemamt) + " STEEM TO SBD AT THE PRICE OF: $" + str(price)) return True else: return False def sbd_to_steem(self, sbd=0, price=0, account=None): ''' Uses the ticker to get the lowest ask and moves the sbd at that price. ''' if not account: account = self.mainaccount if self.check_balances(account): if sbd == 0: sbd = self.sbdbal elif sbd > self.sbdbal: self.msg.error_message("INSUFFICIENT FUNDS. CURRENT SBD BAL: " + str(self.sbdbal)) return False if price == 0: price = 1 / self.dex_ticker()['lowest_ask'] try: self.dex.sell(sbd, "SBD", price, account=account) except Exception as e: self.msg.error_message("COULD NOT SELL SBD FOR STEEM: " + str(e)) self.e = e return False else: self.msg.message("TRANSFERED " + str(sbd) + " SBD TO STEEM AT THE PRICE OF: $" + str(price)) return True else: return False def vote_witness(self, witness, account=None): ''' Uses the steem_instance method to vote on a witness. ''' if not account: account = self.mainaccount try: self.steem_instance().approve_witness(witness, account=account) except Exception as e: self.msg.error_message("COULD NOT VOTE " + witness + " AS WITNESS: " + e) self.e = e return False else: return True def unvote_witness(self, witness, account=None): ''' Uses the steem_instance method to unvote a witness. ''' if not account: account = self.mainaccount try: self.steem_instance().disapprove_witness(witness, account=account) except Exception as e: self.msg.error_message("COULD NOT UNVOTE " + witness + " AS WITNESS: " + e) self.e = e return False else: return True def voted_me_witness(self, account=None, limit=100): ''' Fetches all those a given account is following and sees if they have voted that account as witness. ''' if not account: account = self.mainaccount self.has_voted = [] self.has_not_voted = [] following = self.following(account, limit) for f in following: wv = self.account(f)['witness_votes'] voted = False for w in wv: if w == account: self.has_voted.append(f) voted = True if not voted: self.has_not_voted.append(f) return self.has_voted def muted_me(self, account=None, limit=100): ''' Fetches all those a given account is following and sees if they have muted that account. ''' self.has_muted = [] if account is None: account = self.mainaccount following = self.following(account, limit) if following is False: self.msg.error_message("COULD NOT GET FOLLOWING FOR MUTED") return False for f in following: h = self.get_my_history(f) for a in h: if a[1]['op'][0] == "custom_json": j = a[1]['op'][1]['json'] d = json.loads(j) try: d[1] except: pass else: for i in d[1]: if i == "what": if len(d[1]['what']) > 0: if d[1]['what'][0] == "ignore": if d[1]['follower'] == account: self.msg.message("MUTED BY " + f) self.has_muted.append(f) return self.has_muted def delegate(self, to, steempower): ''' Delegates based on Steem Power rather than by vests. ''' self.global_props() vests = self.util.sp_to_vests(steempower) strvests = str(vests) strvests = strvests + " VESTS" try: self.steem_instance().commit.delegate_vesting_shares(to, strvests, account=self.mainaccount) except Exception as e: self.msg.error_message("COULD NOT DELEGATE " + str(steempower) + " SP TO " + to + ": " + str(e)) self.e = e return False else: self.msg.message("DELEGATED " + str(steempower) + " STEEM POWER TO " + str(to)) return True def create_account(new_account, new_account_master_key, creator): self.steem_instance().create_account( new_account, delegation_fee_steem="3 STEEM", password=new_account_master_key, creator=creator, ) keys = {} for key_type in ['posting','active','owner','memo']: private_key = PasswordKey( new_account, new_account_master_key, key_type).get_private_key() keys[key_type] = { "public": str(private_key.pubkey), "private": str(private_key), } return keys
class AXdb(DB): def __init__ (self, dbuser, dbpass, dbname): self.dbuser = dbuser self.dbpass = dbpass self.dbname = dbname self.msg = Msg(default.logfilename, default.logpath, default.msgmode) def first_time_setup (self): if not self.get_results("SELECT * FROM users WHERE 1;"): self.commit('CREATE TABLE IF NOT EXISTS users ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'Account varchar(50), PrivateKey varchar(100), ' + 'RefreshToken varchar(400), Token varchar(400), ' + 'Time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);') if not self.get_results("SELECT * FROM axlist WHERE 1;"): self.commit('CREATE TABLE IF NOT EXISTS axlist ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'Account1 varchar(50), Account2 varchar(50), ' + 'Percentage varchar(5), Ratio varchar(5), ' + 'Duration varchar(5), MemoID varchar(100), ' + 'Status varchar(10), ' + 'Time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);') if not self.get_results("SELECT * FROM axtrans WHERE 1;"): self.commit('CREATE TABLE IF NOT EXISTS axtrans ' + '(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, ' + 'TXID varchar(50), MemoFrom varchar(20), ' + 'Amount varchar(20), MemoID varchar(100), ' + 'Action varchar(20), TxTime TIMESTAMP NULL, ' + 'DiscoveryTime TIMESTAMP NOT NULL ' + 'DEFAULT CURRENT_TIMESTAMP ' + 'ON UPDATE CURRENT_TIMESTAMP);') def get_most_recent_trans (self): ''' Returns the timestamp from the most recent memo id message. ''' if not self.get_results('SELECT TxTime FROM axtrans ' + 'WHERE 1 ORDER BY TxTime DESC LIMIT 1;'): return datetime.utcnow() - timedelta(days=5) else: return self.dbresults[0][0] def add_trans (self, txid, memofrom, amt, memoid, action, txtime): ''' Adds a processed transaction to the axtrans database ''' return self.commit('INSERT INTO axtrans (TXID, MemoFrom, ' + 'Amount, MemoID, Action, TxTime) ' + 'VALUES (%s, %s, %s, %s, %s, %s);', txid, memofrom, amt, memoid, action, txtime) def verify_memoid (self, acct0, memoid): '''Verify that the Memo ID entered matches the account name entered. ''' if not self.get_results('SELECT Account1, Account2, ' + 'Status FROM axlist WHERE MemoID = %s;', memoid): self.msg.error_message("Memo ID not in database.") return False if acct0 == self.dbresults[0][0]: self.sendto = self.dbresults[0][1] self.inviter = self.dbresults[0][0] self.invitee = self.dbresults[0][1] elif acct0 == self.dbresults[0][1]: self.sendto = self.dbresults[0][0] self.inviter = self.dbresults[0][1] self.invitee = self.dbresults[0][0] else: self.msg.error_message("Account does not match Memo ID.") return False return True def cancel (self, acct, memoid): ''' Either account can cancel ''' return self.commit('DELETE FROM axlist WHERE %s IN ' + '(Account1, Account2) AND (MemoID = %s);', acct, memoid) def get_invite (self, memoid): if self.get_results("SELECT Percentage, Ratio, Duration, " + "Status FROM axlist WHERE MemoID = %s;", memoid): return [self.dbresults[0][0], self.dbresults[0][1], self.dbresults[0][2], self.dbresults[0][3]] else: self.msg.error_message("No invite found for " + memoid) return [0, 0, 0, 0] def update_invite (self, percent, ratio, duration, memoid, status): return self.commit('UPDATE axlist SET Percentage = %s, ' + 'Ratio = %s, Duration = %s, Status = %s WHERE MemoID = %s;', percent, ratio, duration, status, memoid) def update_status (self, status, memoid): ''' -1 = waiting for inviter to authorize 0 = invite sent. waiting for invitee 1 = exchange authorized by both parties 2 = inviter (account1) has offered to barter 3 = invitee (account2) has offered to barter 4 = exchange has been cancelled ''' return self.commit("UPDATE axlist SET Status = %s WHERE MemoID = %s;", status, memoid) def check_status (self, memoid): if self.get_results("SELECT Status FROM axlist WHERE MemoID = %s;", memoid): return self.dbresults[0][0] else: return False def get_user_token (self, acct): if self.get_results("SELECT PrivateKey, Token, RefreshToken " + "FROM users WHERE Account = %s;", acct): return self.dbresults[0][1] else: return False def update_token (self, acct, accesstoken, refreshtoken): return self.commit("UPDATE users SET Token = %s, " + "RefreshToken = %s WHERE Account = %s;", accesstoken, refreshtoken, acct) def add_invite (self, acct1, acct2, percent, ratio, duration): '''Adds the initial invite to the database and provides the unique Memo ID. ''' memoid = self.generate_memoid() if acct1 == acct2: self.msg.message('The same account name was ' + 'entered for both accounts.') return False if self.get_results('SELECT * FROM axlist ' + 'WHERE %s IN (Account1, Account2) ' + 'AND (%s IN (Account1, Account2));', acct1, acct2): self.msg.message('An exchange has already been ' + 'made between these accounts.') return False if self.commit('INSERT INTO axlist (Account1, Account2, ' + 'Percentage, Ratio, ' + 'Duration, MemoID, Status) ' + 'VALUES (%s, %s, %s, %s, %s, %s, %s);', acct1, acct2, percent, ratio, duration, memoid, -1): return memoid else: return False def add_user (self, acct, key, refreshtoken, accesstoken): return self.commit('INSERT INTO users (Account, PrivateKey, ' + 'RefreshToken, Token) ' + 'VALUES (%s, %s, %s, %s);', acct, key, refreshtoken, accesstoken) def get_axlist (self): self.get_results("SELECT * FROM axlist WHERE 1;") return self.dbresults def generate_memoid (self, length=32): return ''.join([str(random.randint(0, 9)) for i in range(length)])
class AXtrans: def __init__(self): self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.db = axdb.AXdb(default.dbuser, default.dbpass, default.dbname) self.steem = SimpleSteem() self.react = Reaction() def parse_memo(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) # Ew this is dirty and came from # strangers! Must be sanitized! try: memo = self.memo.split(":") except: return False self.memoid = memo[0] or None if re.match(r'^[A-Za-z0-9\:]$', self.memoid): return False if len(memo) == 2: self.action = memo[1] or None return True elif len(memo) == 5: self.action = memo[1] or None self.percentage = memo[2] or None self.ratio = memo[3] or None self.duration = memo[4] or None return True else: return False def send(self, to="artopium", amt="0.001 SBD", msg="test"): r = amt.split(" ") if self.steem.transfer_funds(to, float(r[0]), r[1], msg): self.msg.message(("Transaction committed. Sent {} " "{} to {} with the memo: " "{}").format(r[0], r[1], to, msg)) def act(self, acct1, acct2, rstatus, sendto): if not self.db.get_user_token(self.memofrom): self.react.ignore(("{} is not a current member of " + " https://steemax.trade! Join now " + "using SteemConnect.").format(self.memofrom)) elif (self.action == "start"): self.react.start(acct1, acct2, self.memofrom, rstatus, self.memoid) elif (self.action == "cancel"): self.react.cancel(self.memofrom, self.memoid) elif (self.action == "accept"): self.react.accept(acct1, acct2, self.memofrom, rstatus, self.memoid) elif (self.action == "barter"): self.react.barter(acct1, acct2, self.memoid, self.memofrom, rstatus, self.percentage, self.ratio, self.duration) else: self.react.ignore("Invalid action.") if self.react.reaction == "refund": self.sendto = self.memofrom else: self.sendto = sendto def parse_history_record(self, record, lasttrans): if (record[1]['op'][0] == 'transfer' and datetime.strptime( record[1]['timestamp'], '%Y-%m-%dT%H:%M:%S') > lasttrans and re.match(r'^[0-9]{32}', record[1]['op'][1]['memo']) and record[1]['op'][1]['to'] == self.steem.mainaccount): return True else: return False def fetch_history(self): ''' Processes the transaction history. The memo message is parsed for a valid command and performs the directed action. ''' rt = self.db.get_most_recent_trans() for a in self.steem.get_my_history(): if self.parse_history_record(a, rt): if self.parse_memo(memofrom=a[1]['op'][1]['from'], memo=a[1]['op'][1]['memo'], amount=a[1]['op'][1]['amount'], trxid=a[1]['trx_id'], txtime=a[1]['timestamp']): if self.db.verify_memoid(self.memofrom, self.memoid): self.act(self.db.dbresults[0][0], self.db.dbresults[0][1], int(self.db.dbresults[0][2]), self.db.sendto) else: self.react.ignore("Invalid Memo ID.") self.sendto = self.memofrom else: self.react.ignore("Invalid Memo.") self.sendto = self.memofrom self.send(self.sendto, self.amount, self.react.returnmsg) self.db.add_trans(self.trxid, self.memofrom, self.amount, self.memoid, self.react.reaction, self.txtime)
class Util: def __init__(self, filename, path, screenmode): self.msg = Msg(filename, path, screenmode) self.total_vesting_fund_steem = None self.total_vesting_shares = None self.vote_power_reserve_rate = None self.info = None self.currentnode = 0 def current_node(self, listlength): newnodenumber = 0 path = os.path.dirname(os.path.abspath(__file__)) + "/node.dat" try: with open(path, 'r') as fh: try: self.currentnode = fh.read() except Exception as e: self.msg.error_message(e) finally: fh.close() except Exception as e: self.msg.error_message(e) if int(self.currentnode) >= int(listlength): self.currentnode = 0 else: newnodenumber = int(self.currentnode) + 1 with open(path, 'w+') as fh: try: fh.write(str(newnodenumber)) except Exception as e: self.msg.error_message(e) finally: fh.close() return int(self.currentnode) def goodnode(self, nodelist): ''' Goes through the provided list and returns the first server node that does not return an error. ''' l = len(nodelist) for n in range(self.current_node(l), l): self.msg.message("Trying node " + str(n) + ": " + nodelist[n]) try: req = urllib.request.Request(url=nodelist[n]) urllib.request.urlopen(req) except HTTPError as e: self.msg.error_message(e) self.currentnode = int(self.currentnode) + 1 else: self.msg.message("Using " + nodelist[n]) return nodelist[n] def identifier(self, author, permlink): ''' Converts an author's name and permlink into an identifier ''' strlink = ("@" + author + "/" + permlink) return strlink def permlink(self, identifier): ''' Deconstructs an identifier into an account name and permlink ''' temp = identifier.split("@") temp2 = temp[1].split("/") return [temp2[0], temp2[1]] def minutes_back(self, date): ''' Gives a number (integer) of days since a given date ''' elapsed = (datetime.utcnow() - datetime.strptime(date,'%Y-%m-%dT%H:%M:%S')) if elapsed.days > 0: secondsback = (elapsed.days * 24 * 60 * 60) + elapsed.seconds else: secondsback = elapsed.seconds minutesback = secondsback / 60 return int(minutesback) def scale_vote(self, value): ''' Scales a vote value between 1 and 100 to 150 to 10000 as required by Steem-Python for certain method calls ''' value = int(value) * 100 if value < 100: value = 100 if value > 10000: value = 10000 return value def calc_regenerated(self, lastvotetime): ''' Uses math formula to calculate the amount of steem power that would have been regenerated given a certain datetime object ''' delta = datetime.utcnow() - datetime.strptime(lastvotetime,'%Y-%m-%dT%H:%M:%S') td = delta.days ts = delta.seconds tt = (td * 86400) + ts return tt * 10000 / 86400 / 5 def retry(self, msg, e, retry_num, waittime): ''' Creates the retry message and waits the given default time when a method call fails or a server does not respond appropriately. ''' self.msg.error_message(msg) self.msg.error_message(e) self.msg.error_message("Attempt number " + str(retry_num) + ". Retrying in " + str(waittime) + " seconds.") time.sleep(waittime) def steem_per_mvests(self): """ Obtain STEEM/MVESTS ratio """ return (self.total_vesting_fund_steem / (self.total_vesting_shares / 1e6)) def sp_to_vests(self, sp): """ Obtain VESTS (not MVESTS!) from SP :param number sp: SP to convert """ return sp * 1e6 / self.steem_per_mvests() def vests_to_sp(self, vests): """ Obtain SP from VESTS (not MVESTS!) :param number vests: Vests to convert to SP """ return vests / 1e6 * self.steem_per_mvests() def sp_to_rshares(self, sp, voting_power=10000, vote_pct=10000): """ Obtain the r-shares :param number sp: Steem Power :param int voting_power: voting power (100% = 10000) :param int vote_pct: voting participation (100% = 10000) """ vesting_shares = int(self.sp_to_vests(sp) * 1e6) used_power = int((voting_power * vote_pct) / 10000); max_vote_denom = self.vote_power_reserve_rate * (5 * 60 * 60 * 24) / (60 * 60 * 24); used_power = int((used_power + max_vote_denom - 1) / max_vote_denom) rshares = ((vesting_shares * used_power) / 10000) return rshares
class AXtrans: def __init__(self): self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.db = axdb.AXdb(default.dbuser, default.dbpass, default.dbname) self.steem = SimpleSteem() self.react = Reaction() def parse_memo(self, **kwargs): """ Parses the memo message in a transaction for the appropriate action. """ for key, value in kwargs.items(): setattr(self, key, value) try: memo = self.memo.split(":") # A broad exception is used because any exception # should return false. except: return False self.memoid = sec.filter_token(memo[0]) if len(memo) == 2: self.action = sec.filter_account(memo[1]) return True elif len(memo) == 5: self.action = sec.filter_account(memo[1]) self.percentage = sec.filter_number(memo[2]) self.ratio = sec.filter_number(memo[3], 1000) self.duration = sec.filter_number(memo[4], 365) return True else: return False def send(self, to="artopium", amt="0.001 SBD", msg="test"): """ Sends the forwarded amount of SBD along with the reaction message """ r = amt.split(" ") if self.steem.transfer_funds(to, float(r[0]), r[1], msg): self.msg.message(("Transaction committed. Sent {} " "{} to {} with the memo: " "{}").format(r[0], r[1], to, msg)) def act(self, acct1, acct2, rstatus, sendto): """ Decides how to react baed on the action present in the memo message """ if not self.db.get_user_token(self.memofrom): self.react.ignore( ("@{} is not a current member of " + " https://steemax.trade !!Join now!! " + "using SteemConnect.").format(self.memofrom)) elif self.action == "start": self.react.start(acct1, acct2, self.memofrom, rstatus, self.memoid) elif self.action == "cancel": self.react.cancel(self.memofrom, self.memoid) elif self.action == "accept": self.react.accept(acct1, acct2, self.memofrom, rstatus, self.memoid) elif self.action == "barter": self.react.barter(acct1, acct2, self.memoid, self.memofrom, rstatus, self.percentage, self.ratio, self.duration) else: self.react.ignore("Invalid action.") if self.react.reaction == "refund": self.sendto = self.memofrom else: self.sendto = sendto def parse_history_record(self, record, lasttrans): """ Parses the blockchain record for transdactions sent to @steem-ax """ if (record[1]['op'][0] == 'transfer' and datetime.strptime( record[1]['timestamp'], '%Y-%m-%dT%H:%M:%S') > lasttrans and re.match(r'^[0-9]{32}', record[1]['op'][1]['memo']) and record[1]['op'][1]['to'] == self.steem.mainaccount): return True else: return False def fetch_history(self): """ Processes the transaction history. The memo message is parsed for a valid command and performs the directed action. """ rt = self.db.get_most_recent_trans() for a in self.steem.get_my_history(): if self.parse_history_record(a, rt): if self.parse_memo(memofrom=a[1]['op'][1]['from'], memo=a[1]['op'][1]['memo'], amount=a[1]['op'][1]['amount'], trxid=a[1]['trx_id'], txtime=a[1]['timestamp']): if self.db.verify_memoid(self.memofrom, self.memoid): self.act(self.db.dbresults[0][0], self.db.dbresults[0][1], int(self.db.dbresults[0][2]), self.db.sendto) else: self.react.ignore("Invalid Memo ID.") self.sendto = self.memofrom else: self.react.ignore("Invalid Memo.") self.sendto = self.memofrom self.send(self.sendto, self.amount, self.react.returnmsg) self.db.add_trans(self.trxid, self.memofrom, self.amount, self.memoid, self.react.reaction, self.txtime)
def __init__(self, **kwargs): ''' Looks for config.py if not found runs setup which prompts user for the config values one time only. ''' for key, value in kwargs.items(): setattr(self, key, value) try: imp.find_module('config', [os.path.dirname(os.path.realpath(__file__))]) except ImportError: makeconfig.MakeConfig().setup() from simplesteem import config try: self.mainaccount except: self.mainaccount = config.mainaccount try: self.keys except: self.keys = config.keys try: self.nodes except: self.nodes = config.nodes try: self.client_id except: self.client_id = config.client_id try: self.client_secret except: self.client_secret = config.client_secret try: self.callback_url except: self.callback_url = config.callback_url try: self.permissions except: self.permissions = config.permissions try: self.logpath except: self.logpath = config.logpath try: self.screenmode except: self.screenmode = config.screenmode self.s = None self.c = None self.e = None self.dex = None self.msg = Msg("simplesteem.log", self.logpath, self.screenmode) self.util = util.Util("simplesteem.log", self.logpath, self.screenmode) self.connect = steemconnectutil.SteemConnect( self.client_id, self.client_secret, self.callback_url, self.permissions) self.checkedaccount = None self.accountinfo = None self.blognumber = 0 self.reward_balance = 0 self.price_of_steem = 0
class SimpleSteem: def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) try: imp.find_module('config', [os.path.dirname(os.path.realpath(__file__))]) except ImportError: makeconfig.MakeConfig().setup() from simplesteem import config try: self.mainaccount except: self.mainaccount = config.mainaccount try: self.keys except: self.keys = config.keys try: self.nodes except: self.nodes = config.nodes try: self.client_id except: self.client_id = config.client_id try: self.client_secret except: self.client_secret = config.client_secret try: self.callback_url except: self.callback_url = config.callback_url try: self.permissions except: self.permissions = config.permissions try: self.logpath except: self.logpath = config.logpath try: self.screenmode except: self.screenmode = config.screenmode self.s = None self.c = None self.msg = Msg("simplesteem.log", self.logpath, self.screenmode) self.util = util.Util() self.connect = steemconnectutil.SteemConnect(self.client_id, self.client_secret, self.callback_url, self.permissions) self.checkedaccount = None def steem_instance(self): if self.s: return self.s for num_of_retries in range(default.max_retry): try: self.s = Steem(keys=self.keys, nodes=[self.util.goodnode(self.nodes)]) except Exception as e: self.util.retry("COULD NOT GET STEEM INSTANCE", e, num_of_retries, default.wait_time) else: return self.s return False def claim_rewards(self): if self.steem_instance().claim_reward_balance( account=self.mainaccount): time.sleep(5) return True else: return False def verify_key(self, acctname=None, tokenkey=None): if (re.match(r'^[A-Za-z0-9]+$', tokenkey) and tokenkey is not None and len(tokenkey) <= 64 and len(tokenkey) >= 16): pubkey = PrivateKey(tokenkey).pubkey or 0 pubkey2 = self.steem_instance().get_account(acctname) if (str(pubkey) == str(pubkey2['posting']['key_auths'][0][0])): self.privatekey = tokenkey self.refreshtoken = None self.accesstoken = None return True else: return False elif (re.match(r'^[A-Za-z0-9\-\_\.]+$', tokenkey) and tokenkey is not None and len(tokenkey) > 64): self.privatekey = None self.accesstoken = self.connect.get_token(tokenkey) if self.accesstoken: self.username = self.connect.username self.refreshtoken = self.connect.refresh_token return True else: return False else: return False def reward_pool_balances(self): try: self.reward_balance except: reward_fund = self.steem_instance().get_reward_fund() self.reward_balance = Amount(reward_fund["reward_balance"]).amount self.recent_claims = float(reward_fund["recent_claims"]) self.base = Amount( self.steem_instance().get_current_median_history_price() ["base"]).amount return [self.reward_balance, self.recent_claims, self.base] def rshares_to_steem(self, rshares): self.reward_pool_balances() return round( rshares * self.reward_balance / self.recent_claims * self.base, 4) def current_vote_value(self, *kwargs): try: kwargs.items() except: pass else: for key, value in kwargs.items(): setattr(self, key, value) try: self.lastvotetime except: self.lastvotetime = None try: self.steempower except: self.steempower = 0 try: self.voteweight except: self.voteweight = 100 try: self.votepower except: self.votepower = 0 try: self.account except: self.account = None if self.account is None: self.account = self.mainaccount if (self.lastvotetime is None or self.steempower == 0 or self.votepower == 0): self.check_balances(self.account) c = Converter() self.voteweight = self.util.scale_vote(self.voteweight) if self.votepower > 0 and self.votepower < 101: self.votepower = self.util.scale_vote(self.votepower) else: self.votepower = (self.votepower + self.util.calc_regenerated(self.lastvotetime)) self.vpow = round(self.votepower / 100, 2) self.rshares = c.sp_to_rshares(self.steempower, self.votepower, self.voteweight) self.votevalue = self.rshares_to_steem(self.rshares) return self.votevalue def check_balances(self, account=None): if account is None: account = self.mainaccount try: self.votepower except: pass else: if account == self.checkedaccount: return [ self.sbdbal, self.steembal, self.steempower, self.votepower, self.lastvotetime ] self.checkedaccount = account try: acct = self.steem_instance().get_account(account) except Exception as e: self.msg.error_message(e) return False else: c = Converter() self.sbdbal = Amount(acct['sbd_balance']).amount or 0 self.steembal = Amount(acct['balance']).amount or 0 self.votepower = acct['voting_power'] self.lastvotetime = acct['last_vote_time'] vs = Amount(acct['vesting_shares']).amount dvests = Amount(acct['delegated_vesting_shares']).amount rvests = Amount(acct['received_vesting_shares']).amount vests = (float(vs) - float(dvests)) + float(rvests) self.steempower = c.vests_to_sp(vests) or 0 time.sleep(5) return [ self.sbdbal, self.steembal, self.steempower, self.votepower, self.lastvotetime ] def transfer_funds(self, to, amount, denom, msg): try: self.steem_instance().commit.transfer(to, float(amount), denom, msg, self.mainaccount) except Exception as e: self.msg.error_message(e) return False else: return True def get_my_history(self, account=None, limit=100): if not account: account = self.mainaccount try: h = self.steem_instance().get_account_history(account, -1, limit) except Exception as e: self.msg.error_message(e) return False else: return h def post(self, title, body, permlink, tags): for num_of_retries in range(default.max_retry): try: self.steem_instance().post(title, body, self.mainaccount, permlink, None, None, None, None, tags, None, True) except Exception as e: self.util.retry("COULD NOT POST '" + title + "'", e, num_of_retries, 10) else: time.sleep(20) checkident = self.recent_post() ident = self.util.identifier(self.mainaccount, permlink) if checkident == ident: return True else: self.util.retry( '''A POST JUST CREATED WAS NOT FOUND IN THE BLOCKCHAIN {}'''.format(title), e, num_of_retries, default.wait_time) def reply(self, permlink, msgbody): for num_of_retries in range(default.max_retry): try: self.steem_instance().post("message", msgbody, self.mainaccount, None, permlink, None, None, "", None, None, False) except Exception as e: self.util.retry("COULD NOT REPLY TO " + permlink, e, num_of_retries, default.wait_time) else: self.msg.message("Replied to " + permlink) time.sleep(20) return True def follow(self, author): try: self.steem_instance().commit.follow(author, ['blog'], self.mainaccount) except Exception as e: self.msg.error_message(e) return False else: return True def unfollow(self, author): try: self.steem_instance().commit.unfollow(author, ['blog'], self.mainaccount) except Exception as e: self.msg.error_message(e) return False else: return True def following(self, account=None, limit=100): if not account: account = self.mainaccount followingnames = [] try: self.followed = self.steem_instance().get_following( account, '', 'blog', limit) except Exception as e: self.msg.error_message(e) return False else: for a in self.followed: followingnames.append(a['following']) return followingnames def recent_post(self, author=None, daysback=0): if not author: author = self.mainaccount for num_of_retries in range(default.max_retry): try: self.blog = self.steem_instance().get_blog(author, 0, 30) except Exception as e: self.util.retry( '''COULD NOT GET THE MOST RECENT POST FOR {}'''.format(author), e, num_of_retries, default.wait_time) else: for p in self.blog: age = self.util.days_back(p['comment']['created']) if age < 0: age = 0 if (p['comment']['author'] == author and age == daysback): return self.util.identifier(p['comment']['author'], p['comment']['permlink']) def vote_history(self, permlink, author=None): if not author: author = self.mainaccount return self.steem_instance().get_active_votes(author, permlink) def vote(self, identifier, weight=100.0): for num_of_retries in range(default.max_retry): try: self.steem_instance().vote(identifier, weight, self.mainaccount) self.msg.message("voted for " + identifier) time.sleep(5) except Exception as e: if re.search(r'You have already voted in a similar way', str(e)): self.msg.error_message('''Already voted on {}'''.format(identifier)) return "already voted" else: self.util.retry( '''COULD NOT VOTE ON {}'''.format(identifier), e, num_of_retries, default.wait_time) else: return True def resteem(self, identifier): for num_of_retries in range(default.max_retry): try: self.steem_instance().resteem(identifier, self.mainaccount) self.msg.message("resteemed " + identifier) time.sleep(20) except Exception as e: self.util.retry( '''COULD NOT RESTEEM {}'''.format(identifier), e, num_of_retries, default.wait_time) else: return True
#!/usr/bin/python3 import re import sys from cmd import Cmd from steemax import axe from steemax import axdb from steemax import axverify from steemax import axtrans from steemax import default from screenlogger.screenlogger import Msg msg = Msg(default.logfilename, default.logpath, "") db = axdb.AXdb(default.dbuser, default.dbpass, default.dbname) xverify = axverify.AXverify() # Entry point def run(args=None): db.first_time_setup() prompt = MyPrompt() prompt.prompt = '[steemax]# ' prompt.cmdloop('\n ** Welcome to SteemAX ** \n') class Enter: def new_account(self, acct): '''Prompts user to create an account when an account is not found ''' answer = input(
class DelegatorBot(): ''' Main class holds functions for running the bot ''' def __init__(self, debug=False, mode="verbose", botname="settings"): ''' Uses simplesteem and screenlogger from https://github.com/ArtoLabs/SimpleSteem https://github.com/ArtoLabs/ScreenLogger ''' bot = importlib.import_module("." + botname, "delegatorbot") self.debug = debug self.cfg = bot.Config() self.msg = Msg(self.cfg.logfilename, self.cfg.logpath, mode) self.db = BotDB(self.cfg.dbusername, self.cfg.dbpass, self.cfg.dbname, self.cfg.dbtable, self.cfg.dbposttable, self.cfg.delegatortable) self.steem = SimpleSteem(mainaccount=self.cfg.mainaccount, keys=self.cfg.keys, screenmode=mode) self.voteweight = 0 self.bidbot = None def get_replies_to_stop(self): ''' To have the delegation bot stop following someone and thus stop upvoting their posts they simply have to type STOP (in all caps) in a reply to the delegation bots own reply. ''' c = 0 # Get the bots history from the blockchain h = self.steem.get_my_history(limit=1000) if h is False or h is None: return False # iterate through the history for a in h: # Is it a comment (reply)? if a[1]['op'][0] == "comment": # Is it -not- from the bot? if not (a[1]['op'][1]['author'] == self.cfg.mainaccount): # Does it contain the word STOP? if re.match(r'STOP', a[1]['op'][1]['body']): self.msg.error_message(a[1]['op'][1]['author'] + " said STOP in this post: \n" + a[1]['op'][1]['permlink']) # Get the reply's identifier ident = self.steem.util.identifier( a[1]['op'][1]['author'], a[1]['op'][1]['permlink']) # Unfollow if not self.debug and self.steem.unfollow( a[1]['op'][1]['author']): # Reply to user with confirmation self.steem.reply( ident, "@" + self.cfg.mainaccount + " has stopped " + "following you.") c += 1 self.msg.message(str(c) + " stop messages received") return True def process_delegators(self): ''' The delegation bot will follow and upvote those who have delegated. Delegating less than the minimum amount will cause the bot to unfollow ''' # Create the table if it doesn't already exist self.db.initialize_delegators() # set variables followed = 0 unfollowed = 0 delegators = {} # Get blockchain global variables self.steem.global_props() # Get bot's history from the blockchain h = self.steem.get_my_history(limit=1000) if h is False or h is None: return False for i in h: # if the field is the delegate_vesting_shares # field than get the name # of the delegator and the sp delegated and # store it in the 'delegators' dictionary if i[1]['op'][0] == "delegate_vesting_shares": d = i[1]['op'][1]['delegator'] vests = float(i[1]['op'][1]['vesting_shares'].replace( " VESTS", "")) if d != self.cfg.mainaccount: delegators[d] = vests # get all known delegators from the database self.db.get_delegators() # iterate through new delegators for d, vests in delegators.items(): sp = self.steem.util.vests_to_sp(vests) found = False follow = False unfollow = False # iterate through known delegators if self.db.dbresults is not None and self.db.dbresults is not False: for row in self.db.dbresults: if d == row[1]: # new delegator is already known indicating a possible change found = True # has it been changed? if float(vests) != float(row[2]): self.db.update_delegator(d, vests) self.msg.message(d + " changed delegation to " + str(vests) + " VESTS (" + str(sp) + " SP)") # is it above or lower than minimum needed ? if sp >= self.cfg.minimum_delegation: follow = True else: unfollow = True # This is really a new delegator if found is False: # Add them to the database self.db.add_delegator(d, vests) self.msg.message("New delegation from " + d + " of " + str(vests) + " VESTS (" + str(sp) + " SP)") # Did they send more then the minimum? if sp >= self.cfg.minimum_delegation: follow = True # Follow if follow: followed += 1 if self.debug is False: self.steem.follow(d) # Unfollow elif unfollow: unfollowed += 1 if self.debug is False: self.steem.unfollow(d) self.msg.message( str(followed) + " followed, " + str(unfollowed) + " unfollowed") return True def daily_report(self): ''' A report that is generated daily showing a list of current delegators, a list of the posts that were upvoted the day before, and instructions on how to get upvotes from the bot by delegating. ''' # Create the table if it doesn't already exist self.db.initialize_bot_posts() # Get the date now = datetime.now() # Create the title for the report title = ("The @" + self.cfg.mainaccount + " Daily Report for " + str(now.strftime("%B")) + " " + str(now.day) + ", " + str(now.year)) self.msg.message("Creating " + title) # Start the body of the report body = ("# @" + self.cfg.mainaccount + " wants to upvote *YOUR* posts! \n" + "@" + self.cfg.mainaccount + ", is an upvote bot run by @" + self.cfg.owner + ". Read more " + "below about what @" + self.cfg.mainaccount + " does and how you can use it.") # Fetch the bot's current upvote value at 100% voting power my_vote_value = self.steem.current_vote_value( accountname=self.cfg.mainaccount, voteweight=100, votepoweroverride=100) # Add the vote value to the report body += ("\n\n___\n### My current upvote value at 100% is: $" + str(my_vote_value) + "\n___\n\n") # Add The list of delegators del_list = self.daily_report_delegators() if del_list is not False and del_list is not None: body += del_list # The link to delegate to the bot body += ('[Delegate now](' + self.cfg.delegationlink + ') to get an upvote like these people.') body += ( '\n\nBy delegating ' + str(self.cfg.minimum_delegation) + ' SP or more @' + self.cfg.mainaccount + ' will start following you within 45 minutes and upvoting all of your posts too!' ) # Create the permlink from the title permlink = re.sub(r' ', '-', title) permlink = re.sub(r'[^A-Za-z0-9\-]', '', permlink) permlink = permlink.lower() # Add a list of all posts that received an upvote up_list = self.daily_report_upvote_list() if up_list is not False and up_list is not None: body += up_list # Add the template footer to the report footer = self.daily_report_footer() if footer is not False and footer is not None: body += footer # Check debug status if self.debug is False: self.msg.message("Posting, please wait... ") # post the daily report to the blockchain if self.steem.post(title, body, permlink, self.cfg.post_tags): identifier = self.steem.util.identifier( self.cfg.mainaccount, permlink) # Add it to the database self.db.add_bot_post(self.cfg.post_tags[0], identifier) self.msg.message("Created report " + identifier) return identifier else: return False else: # If debug is on just print it out to the screen print(body) return True def daily_report_upvote_list(self): ''' Creates a list of everyone that has received an upvote from the bot in the last 24 hours ''' # get the number of upvotes in the last 24 hours numofposts = self.db.upvote_stats(1) if (self.db.dbresults is not None and self.db.dbresults is not False and numofposts > 0): # Create the list header upvote_list = ("\n### @" + self.cfg.mainaccount + " upvoted " + str(numofposts) + " posts yesterday. They were:<br>\n") # display each post upvoted and the percentage they got for post in self.db.dbresults: upvote_list = (upvote_list + "https://steemit.com/" + post[1] + "\nThis post was voted at " + str(post[3]) + "%\n\n") return str(upvote_list) else: self.msg.error_message("\nThe database of posts is empty\n") return False def daily_report_delegators(self): ''' Creates a list of the bot's delegators ''' # Get the number of delegators from the database numofdelegators = self.db.get_delegators() # Are there delegators? if (self.db.dbresults is not None and self.db.dbresults is not False and numofdelegators > 0): # Add the delegator count to the report delegator_list = ("\n\n## @" + self.cfg.mainaccount + " has " + str(numofdelegators) + " delegators. They are:\n\n") # Get the blockchain global variables self.steem.global_props() # Iterate through all the delegators and display how much they've delegated for d in self.db.dbresults: sp = int(self.steem.util.vests_to_sp(float(d[2]))) # Those who delegate more will have their names made bigger if sp > 1000: title_size = "##" elif sp > 500: title_size = "###" elif sp > self.cfg.minimum_delegation + 5: title_size = "####" else: title_size = "" delegator_list = (delegator_list + title_size + " @" + d[1] + " has delegated " + str(sp) + " SP\n") # Thank you! delegator_list += "## Thank You Delegators! " return delegator_list else: self.msg.error_message("\nThe database of delegators is empty\n") return False def daily_report_footer(self): ''' opens the post_template.txt file and returns it populated with values from settings.py ''' # The directory we're in dir_path = os.path.dirname(os.path.realpath(__file__)) # Does the file exist? if os.path.exists(dir_path + "/post_template.txt"): # Can we open it? with open(dir_path + "/post_template.txt", 'rb') as f: try: # split the file up into a list of lines (byte list) footer = f.read().splitlines() except: f.close() self.msg.error_message( "\nCould not open post_template.txt!\n") return False else: f.close() # Convert, format and return return ((self.make_newlines(footer)).format( self.cfg.footer_top_pic_url, self.cfg.mainaccount, self.cfg.footer_info_url, self.cfg.reply_image, self.cfg.footer_info_url, self.cfg.minimum_delegation, self.cfg.delegationlink, self.cfg.footer_delegate_button_url, self.cfg.allowed_tags, self.cfg.mainaccount, self.cfg.owner, self.cfg.footer_bottom_pic_url, self.cfg.discord_invite_url, self.cfg.website_url, self.cfg.website_name)) else: self.msg.error_message( "\nCould not find post_template.txt directory.\n") return False def make_newlines(self, byte_text_list=None): ''' Converts a list of bytes into one string and adds the newline character ''' newtext = '' # decode the byte list into a string list try: ls = [i.decode() for i in byte_text_list] except Exception as e: self.msg.error_message("Byte list error: " + str(e)) return "" # concatenate that list adding newlines if len(ls) > 0: for line in ls: newtext += str(line) + '\n' return newtext def run_bot(self): ''' Runs through the list of those it is following and upvotes the most recent post. Vote weight is determined by an algorithm (see below) Note that someone does not need to be a delegator to receive upvotes, they simply need to be followed by the bot. Of course delegating causes the bot to follow someone, but you can also have the bot follow someone manually, thus granting them upvotes without delegating. ''' # If the table does not exist create it self.db.initialize_bot_mem() # Get all the account names of all those the bot is following if self.debug: following = ["artopium"] else: following = self.steem.following(self.cfg.mainaccount) # Are we following anyone? if (following is not None and following is not False and len(following) > 0): self.msg.message("Following: " + str(len(following))) # Iterate through that list for f in following: self.msg.message('\n' + f) # Did we successfully vote? if self.debug is False: identifier = self.run_bot_upvote(f) else: identifier = "debug" if identifier is not False: # Resteem the post if self.debug is False: self.steem.resteem(identifier) # Reply to the post self.run_bot_reply(identifier, f) else: self.msg.message("No post found for " + f) else: self.msg.message("The bot is not following anyone.") return False return True def run_bot_upvote(self, followed): ''' upvotes the post if it's eligible ''' # If the table does not exist create it self.db.initialize_bot_mem() if followed is False or followed is None: return False identifier = self.steem.recent_post(followed, self.cfg.post_max_days_old, 1) # Is there a post? if identifier is False or identifier is None: self.msg.message("Identifier is None") return False # Are they already in the database? if self.db.find_bot_mem(identifier) is not False: self.msg.message("Already voted for " + identifier) return False # Do they have the right tags? blognumber is assigned # During call to get recent_post if self.verify_tags(self.steem.blognumber): # Use the algorithm to adjust vote weight, assign it to # class variable so it can be accessed by run_bot_reply self.voteweight = self.adjust_vote_weight(followed) if self.debug: print("would've voted: " + identifier) v = True else: # Vote v = self.steem.vote(identifier, self.voteweight) # Does the blockchain say we already voted? if v == "already voted": self.msg.message("Already voted for " + identifier) # Add it to the database self.db.add_bot_mem(identifier, self.voteweight) return False elif v: self.msg.message(followed + " got a vote at " + str(self.voteweight) + "%") # Add it to the database self.db.add_bot_mem(identifier, self.voteweight) return identifier else: return False else: self.msg.message(followed + " does not have the right tag.") return False def run_bot_reply(self, identifier, followed): ''' Resteems a post after the bot has voted on it ''' if identifier is None or followed is None: return False # The directory we're in dir_path = os.path.dirname(os.path.realpath(__file__)) # Does the file exist? if os.path.exists(dir_path + "/reply_template.txt"): # Can we open it? with open(dir_path + "/reply_template.txt", 'rb') as bfile: try: # split the file up into a list of lines (byte list) reply = bfile.read().splitlines() except: bfile.close() self.msg.error_message( "\nCould not open reply_template.txt!\n") return False else: dailyreport = self.db.get_recent_post() bfile.close() v = int(self.voteweight) # format the tempalate newtext = self.make_newlines(reply) msg = newtext.format(followed, v, self.cfg.mainaccount, self.cfg.footer_info_url, self.cfg.reply_image, self.cfg.footer_info_url, dailyreport, self.cfg.alert_message) if self.debug: print(identifier) print(msg) return True else: self.steem.reply(identifier, msg) return True else: self.msg.error_message( "\nCould not find reply_template.txt directory.\n") return False def adjust_vote_weight(self, account): ''' This algorithm takes an average of all the votes made in the last 3 days, then subtracts that from 3-days-worth of voting power; The remaining vote power determines the vote weight. ''' if account is None or account is False: return 0 if account == self.cfg.owner: return 100 bal = self.steem.check_balances() votepower = round( (self.steem.votepower + self.steem.util.calc_regenerated(self.steem.lastvotetime)) / 100, 2) total_vote_weight_used = 1 # make sure vote power isn't too low if votepower > self.cfg.vote_power_threshold: numofposts = self.db.upvote_stats(3) for i in range(0, numofposts - 1): # for each post made we total the weight used, not the actual num of posts total_vote_weight_used += self.db.dbresults[i][3] # then we adjust based on the total weight used in the last 3 days # 800 is 80% of 1000, which is 10 votes at 100% per day (10 x 100) print( str(numofposts) + " previous posts in the last 3 days with " + str(total_vote_weight_used) + " total vote weight.") # 3 days of vote power would is 3000 # to account for new users and surges in use the 3000 is # is multiplied by a user defined number adj = ((3000 * self.cfg.algorithm_scale) / total_vote_weight_used * 100) sec_since_last_vote = self.db.already_voted_today(account) minutes_since_last_vote = sec_since_last_vote / 60 print("Base percentage: " + str(adj) + "%") # Caps the vote weight if adj > self.cfg.vote_weight_max: adj = self.cfg.vote_weight_max print("Adjusted to " + str(self.cfg.vote_weight_max)) print("Minutes since last upvote: " + str(minutes_since_last_vote)) # Check to see if the account is NVIP. If so let's give them the VIP amount if len(self.cfg.nvip_accounts) > 0: for nvip in self.cfg.nvip_accounts: if account == nvip: adj = self.cfg.nvip_vote_weight print("NVIP account. Adjusted weight to: " + str(adj) + "%") # Check to see if the account is VIP. If so let's give them the VIP amount if len(self.cfg.vip_accounts) > 0: for vip in self.cfg.vip_accounts: if account == vip: adj = self.cfg.vip_vote_weight print("VIP account. Adjusted weight to: " + str(adj) + "%") if sec_since_last_vote is not False and int( sec_since_last_vote) < 86400: # if we voted them in the last 24 hours we scale the vote # based on how long ago the last vote was. # If it was less that 3 hours ago the weight is 3% if int(sec_since_last_vote ) < self.cfg.reduced_vote_wait_time * 60 * 60: adj = self.cfg.reduced_vote_weight else: adj *= sec_since_last_vote / 86400 print( account + " already got a vote in the last 24 hours. Adjusting vote further: " + str(adj) + "%") else: if adj < self.cfg.vote_weight_min: adj = self.cfg.vote_weight_min return adj else: return 10 def verify_tags(self, blognumber): ''' Makes sure the post to be voted on has used the tags set in settings ''' if blognumber is None: return False if self.steem.blog[blognumber]['comment']['json_metadata'] is not None: tags = json.loads( self.steem.blog[blognumber]['comment']['json_metadata']) if tags['tags'] is not None: for t in tags['tags']: for a in self.cfg.allowed_tags: if t == a: return True else: self.msg.error_message("Blog comment was 'none'") return False def boost_post(self, daysback=0, denom="STEEM", amount=0.02): ''' boosts the payout of the daily report using bid bots ''' if daysback < 0: self.msg.error_message("Invalid date to boost post") return False if denom != "STEEM" and denom != "SBD": self.msg.error_message("Invalid denomination to boost post") return False if amount < 0.01: self.msg.error_message("Invalid amount to boost post") return False # If the table doesn't exist create it self.db.initialize_bot_posts() # Get a daiy report from the past identifier = self.db.get_recent_post(daysback) if identifier is None or identifier is False: self.msg.message("No posts found") return False idkey = self.db.id ftag = self.db.firsttag # Make the whole url boostlink = ("https://steemit.com/" + ftag + "/" + identifier) # make sure we have enough in the coffers if self.ensure_balance(denom) is not False: self.msg.message("Boosting " + boostlink) # boost if self.debug is False: self.steem.transfer_funds(self.bidbot, amount, denom, boostlink) self.msg.message("Sent " + str(amount) + " " + denom + " to " + self.bidbot) return True else: return False def ensure_balance(self, denom): ''' Does what it says. Ensures that the steem or sbd balance of the bot account has not reached below the minimums set in settings. Used when "boosting" ''' # If the table doesn't exist create it self.db.initialize_bot_posts() # Check the bot's balances bal = self.steem.check_balances(self.cfg.mainaccount) # Check the reward pool #self.steem.reward_pool_balances() if (denom == "SBD"): print("Current SBD balance: " + str(bal[0])) self.bidbot = self.cfg.sbd_bidbot if float(bal[0]) > float(self.cfg.minimum_balance): return True else: self.msg.message( ("Cannot boost posts because the current " + "SBD balance is {}. The balance must " + "be more than {} SBD").format(bal[0], self.cfg.minimum_balance)) return False elif (denom == "STEEM"): print("Current STEEM balance: " + str(bal[0])) self.bidbot = self.cfg.steem_bidbot if bal[1] > self.cfg.minimum_balance: return True else: self.msg.message( ("Cannot boost posts because the current " + "STEEM balance is {}. The balance must " + "be more than {} STEEM").format(bal[1], self.cfg.minimum_balance)) return False else: return False def claim(self): ''' Claims the rewards of the bot's account ''' return self.steem.claim_rewards() def balance(self): ''' Prints the current balance ''' bal = self.steem.check_balances(self.cfg.mainaccount) self.msg.message(''' __{}__ {} SBD {} STEEM {} STEEM POWER'''.format(self.steem.mainaccount, bal[0], bal[1], bal[2])) return True
class AXtrans: def __init__(self): self.msg = Msg(default.logfilename, default.logpath, default.msgmode) self.db = axdb.AXdb(default.dbuser, default.dbpass, default.dbname) self.steem = SimpleSteem() self.react = Reaction() def parse_memo(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) # Ew this is dirty and came from # strangers! Must be sanitized! try: memo = self.memo.split(":") except: return False self.memoid = memo[0] or None if re.match(r'^[A-Za-z0-9\:]$', self.memoid): return False if len(memo) == 2: self.action = memo[1] or None return True elif len(memo) == 5: self.action = memo[1] or None self.percentage = memo[2] or None self.ratio = memo[3] or None self.duration = memo[4] or None return True else: return False def send(self, to="artopium", amt="0.001 SBD", msg="test"): r = amt.split(" ") if self.steem.transfer_funds(to, float(r[0]), r[1], msg): self.msg.message(("Transaction committed. Sent {} " "{} to {} with the memo: " "{}").format(r[0], r[1], to, msg)) def act(self, acct1, acct2, rstatus, sendto): if not self.db.get_user_token(self.memofrom): self.react.ignore( ("{} is not a current member of " + " https://steemax.trade! Join now " + "using SteemConnect.").format(self.memofrom)) elif (self.action == "start"): self.react.start(acct1, acct2, self.memofrom, rstatus, self.memoid) elif (self.action == "cancel"): self.react.cancel(self.memofrom, self.memoid) elif (self.action == "accept"): self.react.accept(acct1, acct2, self.memofrom, rstatus, self.memoid) elif (self.action == "barter"): self.react.barter(acct1, acct2, self.memoid, self.memofrom, rstatus, self.percentage, self.ratio, self.duration) else: self.react.ignore("Invalid action.") if self.react.reaction == "refund": self.sendto = self.memofrom else: self.sendto = sendto def parse_history_record(self, record, lasttrans): if (record[1]['op'][0] == 'transfer' and datetime.strptime( record[1]['timestamp'], '%Y-%m-%dT%H:%M:%S') > lasttrans and re.match(r'^[0-9]{32}', record[1]['op'][1]['memo']) and record[1]['op'][1]['to'] == self.steem.mainaccount): return True else: return False def fetch_history(self): ''' Processes the transaction history. The memo message is parsed for a valid command and performs the directed action. ''' rt = self.db.get_most_recent_trans() for a in self.steem.get_my_history(): if self.parse_history_record(a, rt): if self.parse_memo(memofrom=a[1]['op'][1]['from'], memo=a[1]['op'][1]['memo'], amount=a[1]['op'][1]['amount'], trxid=a[1]['trx_id'], txtime=a[1]['timestamp']): if self.db.verify_memoid(self.memofrom, self.memoid): self.act(self.db.dbresults[0][0], self.db.dbresults[0][1], int(self.db.dbresults[0][2]), self.db.sendto) else: self.react.ignore("Invalid Memo ID.") self.sendto = self.memofrom else: self.react.ignore("Invalid Memo.") self.sendto = self.memofrom self.send(self.sendto, self.amount, self.react.returnmsg) self.db.add_trans(self.trxid, self.memofrom, self.amount, self.memoid, self.react.reaction, self.txtime)
class Util: def __init__(self): self.msg = Msg() def goodnode(self, nodelist): for n in nodelist: req = urllib.request.Request(url=n) try: self.msg.message("Trying " + n) urllib.request.urlopen(req) except HTTPError as e: self.msg.error_message(e) else: self.msg.message("Using " + n) return n def identifier(self, author, permlink): return ("@" + author + "/" + permlink) def permlink(self, identifier): temp = identifier.split("@") temp2 = temp[1].split("/") return [temp2[0], temp2[1]] def days_back(self, date): daysback = (datetime.now() - datetime.strptime(date,'%Y-%m-%dT%H:%M:%S')).days if daysback < 0: daysback = 0 return daysback def scale_vote(self, value): value = int(value) * 100 if value < 150: value = 150 if value > 10000: value = 10000 return value def calc_regenerated(self, lastvotetime): delta = datetime.utcnow() - datetime.strptime(lastvotetime,'%Y-%m-%dT%H:%M:%S') td = delta.days ts = delta.seconds tt = (td * 86400) + ts return tt * 10000 / 86400 / 5 def retry(self, msg, e, retry_num, waittime): self.msg.error_message(msg) self.msg.error_message(e) self.msg.error_message("Attempt number " + str(retry_num) + ". Retrying in " + str(waittime) + " seconds.") time.sleep(waittime)