Version 0.1.1 New Features

in #utopian-io6 years ago (edited)

Next phase of the ethical voting bot release.

Changes

Support for main bot user


Originally, it was a collective vote from many users. Now we are including the bot user itself. This allows the following:

  • Bot can now send comments/replies to protagonists, antagonists, and victims.
    • Messages can be configured via template
  • Bot now supports voting for users posts and comments (from the bot. not collective votes)
  • Bot supports delegation

Voting threshold now enforced


  • Voting thresholds are now functioning. Users do not vote beyond their specified voting power threshold. Yay!

Logging


  • Logging was updated to be more meaningful.
  • Redundant logging was removed.

Orchestration


  • Orchestration configuration for kubernetes clusters. This will allow us to deploy to the application, provisioning, and any dependencies to a cluster simultaneously. It's da bomb.

Details


diff --git a/Procfile b/Procfile
index 6c983c9..3a76fa8 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1,2 @@
 web: node app/index.js
+worker: node app/bot.js
diff --git a/app/bot.js b/app/bot.js
new file mode 100644
index 0000000..e5dfdc0
--- /dev/null
+++ b/app/bot.js
@@ -0,0 +1,4 @@
+const bot = require('./helpers/bot')
+
+
+bot.run();
\ No newline at end of file

Created a new standalone execution file and heroku configuration for separate execution.

diff --git a/app/controllers/preferences.js b/app/controllers/preferences.js
index cb2564a..99f1fe0 100644
--- a/app/controllers/preferences.js
+++ b/app/controllers/preferences.js
@@ -55,7 +55,6 @@ function handle_prefs_from_database(username, res) {
     return models.Preferences.findOne({where: { username: username } })
         .then(prefs => {
             var preferences = prefs.dataValues
-            console.log(preferences)
             res.render('pages/index', {
                 redirect: 'https://we-resist-bot.herokuapp.com/',
                 username: username,

This change removes redundant logging from the controller that was initially used for determining the preferences model.

diff --git a/app/helpers/bot/task.js b/app/helpers/bot/task.js
index 3fbd502..e332754 100644
--- a/app/helpers/bot/task.js
+++ b/app/helpers/bot/task.js
@@ -2,12 +2,16 @@
 
 const Promise = require('bluebird')
 const steem = Promise.promisifyAll(require('steem'))
-const config = require('../../config')
+const sc2 = Promise.promisifyAll(require('sc2-sdk'))
+const { user, wif } = require('../../config')
 const moment = require('moment')
 const schedule = require('node-schedule')
 const Sequelize = require('sequelize')
 const models = require('../../../models')
 const Op = Sequelize.Op;
+const Handlebars = require('handlebars')
+const fs = Promise.promisifyAll(require('fs'))
+const path = require('path')

Including dependencies required for templating messages used in comments and adding the ability for the bot to function independently.

 const UNVOTE_WEIGHT = 0
 
@@ -15,6 +19,29 @@ module.exports = {
     execute
 }
 
+const SECONDS_PER_HOUR = 3600
+const PERCENT_PER_DAY = 20
+const HOURS_PER_DAY = 24
+const MAX_VOTING_POWER = 10000
+const DAYS_TO_100_PERCENT = 100 / PERCENT_PER_DAY
+const SECONDS_FOR_100_PERCENT = DAYS_TO_100_PERCENT * HOURS_PER_DAY * SECONDS_PER_HOUR
+const RECOVERY_RATE = MAX_VOTING_POWER / SECONDS_FOR_100_PERCENT
+const DEFAULT_THRESHOLD = 9500
+
+
+function current_voting_power(vp_last, last_vote) {
+    var seconds_since_vote = moment().add(7, 'hours').diff(moment(last_vote), 'seconds')
+    return (RECOVERY_RATE * seconds_since_vote) + vp_last
+}
+
+function time_needed_to_recover(voting_power, threshold) {
+    return RECOVERY_RATE * (threshold - voting_power)
+}

Adding threshold management functions and constants

+
+function loadTemplate(template) {
+    return fs.readFileAsync(template, 'utf8')
+}
+

The above change loads a template from the filesystem to be used as a message.

 /**
@@ -74,7 +114,8 @@ function processVote(vote) {
  */
 function list_of_resisters() {
     return models.Preferences.findAll( {
-        attributes: [ 'username', 'wif', 'upvoteWeight', 'downvoteWeight', 'threshold' ]
+        attributes: [ 'username', 'wif', 'upvoteWeight', 'downvoteWeight', 'threshold' ],
+        logging: (query) => {}
     })
 }

Removing logging of every single SQL query

@@ -83,31 +124,91 @@ function is_active(resister) {
 }
 
 function processDownvote(vote) {
+    console.log("Upvoting ", vote)
     return collectiveUpvote(vote.author, vote.permlink)
 }
 
 function processUpvote(vote) {
-    if (vote.is_for_grumpy()) {
-        return collectiveDownvote(vote.author, vote.permlink)
-    }
-    return false
+    return vote.is_author_grumpy()
+        .then((is_grumpy) => {
+            if (is_grumpy) { // Test for self-vote
+                console.log("Downvoting ", vote)
+                return collectiveDownvote(vote.author, vote.permlink)
+            }
+
+            // Not a self-vote
+            Promise.reject("Not a self vote")
+        })
+        .catch(err => {
+            console.log(err)
+        })
 }
 
 function processUnvote(vote) {
-    if (!vote.is_grumpy()) {
+    if (!vote.is_voter_grumpy()) {
         return false
     }
 
     return collectiveUnvote(author, permlink)
 }

The above is to add voting refinements based on whether the vote was a self-vote or not. Logging is also refined here.

+function invite(author, permlink) {
+    return reply(author, permlink, "invite");
+}
+
+function reply(author, permlink, type) {
+    var context = {
+    }
+
+    return loadTemplate(path.join(__dirname, '..', 'templates', `${type}.hb`))
+        .then((template) => {
+            var templateSpec = Handlebars.compile(template)
+            return templateSpec(context)
+        })
+        .then((message) => {
+            var new_permlink = 're-' + author 
+                + '-' + permlink 
+                + '-' + new Date().toISOString().replace(/[^a-zA-Z0-9]+/g, '').toLowerCase();
+            steem.broadcast.commentAsync(
+                wif,
+                author, // Leave parent author empty
+                permlink, // Main tag
+                user, // Author
+                new_permlink, // Permlink
+                new_permlink,
+                message, // Body
+                { tags: [], app: 'we-resist-bot/0.1.0' }
+            ).then((results) => {
+                console.log(results)
+            })
+        })
+}
 
 function downvote(author, permlink, resister) {
-    return vote(author, permlink, resister, resister.downvoteWeight * -1)
+    return vote(author, permlink, resister, resister.downvoteWeight * -100)
+        .then((promise) => { return reply(author, permlink, "downvote") });
 }
 
 function upvote(author, permlink, resister) {
-    return vote(author, permlink, resister, resister.upvoteWeight)
+    var recovery_wait = 0
+    return steem.api.getAccountsAsync([ resister.username ]).then((account) => {
+        var voting_power = current_voting_power(account.voting_power, account.last_vote_time)
+        recovery_wait = time_needed_to_recover(voting_power, resister.threshold) / 60
+        return account
+    })
+    .then((account) => {
+        // Reschedule vote
+        if (recovery_wait > 0) {
+            var later = moment().add(recovery_wait, 'minutes').toDate()
+            console.log("Rescheduling ", recovery_wait, " minutes to recover")
+            schedule.scheduleJob(later, function() {
+                upvote(author, permlink, resister, resister.upvoteWeight * 100)
+            })
+            return account
+        }
+        return vote(author, permlink, resister, resister.upvoteWeight * 100)
+            .then((promise) => { return reply(author, permlink, "upvote") });
+    })
 }
 
 function unvote(author, permlink, resister) {
@@ -142,17 +243,46 @@ function collectiveUnvote(author, permlink) {
     return list_of_resisters().each((resister) => { return unvote(author, permlink, resister) })
 }

The above is an implementation to add replies based on behavior of voting/downvoting. These replies make use of message templating.

+function processComment(comment) {
+    return list_of_resisters()
+        .filter((resister) => comment.author == resister.username)
+        .each((resister) => {
+            var recovery_wait = 0
+            return steem.api.getAccountsAsync([ user ]).then((account) => {
+                var voting_power = current_voting_power(account.voting_power, account.last_vote_time)
+                recovery_wait = time_needed_to_recover(voting_power, DEFAULT_THRESHOLD) / 60
+                return account
+            })
+            .then((account) => {
+                // Reschedule vote
+                if (recovery_wait > 0) {
+                    var later = moment().add(recovery_wait, 'minutes').toDate()
+                    console.log("Rescheduling ", recovery_wait, " minutes to recover")
+                    schedule.scheduleJob(later, function() {
+                        processComment(comment)
+                    })
+                    return account
+                }
+                return vote(comment.author, comment.permlink, { username: user, wif: wif }, 10000)
+            })
+        })
+}

The above change instructs the voting bot to vote on comments/posts and to handle voting power threshold.

diff --git a/orchestration/charts/we-resist-bot/Chart.yaml b/orchestration/charts/we-resist-bot/Chart.yaml
new file mode 100644
index 0000000..f325eeb
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/Chart.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+description: A Helm chart for Kubernetes
+name: we-resist-bot
+version: 0.1.0
diff --git a/orchestration/charts/we-resist-bot/templates/NOTES.txt b/orchestration/charts/we-resist-bot/templates/NOTES.txt
new file mode 100644
index 0000000..3e642a3
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/templates/NOTES.txt
@@ -0,0 +1,19 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range .Values.ingress.hosts }}
+  http://{{ . }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "we-resist-bot.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get svc -w {{ template "we-resist-bot.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "we-resist-bot.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
+  echo http://$SERVICE_IP:{{ .Values.service.externalPort }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "we-resist-bot.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl port-forward $POD_NAME 8080:{{ .Values.service.internalPort }}
+{{- end }}
diff --git a/orchestration/charts/we-resist-bot/templates/_helpers.tpl b/orchestration/charts/we-resist-bot/templates/_helpers.tpl
new file mode 100644
index 0000000..ba4b569
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/templates/_helpers.tpl
@@ -0,0 +1,16 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "we-resist-bot.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+*/}}
+{{- define "we-resist-bot.fullname" -}}
+{{- $name := default .Chart.Name .Values.nameOverride -}}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
diff --git a/orchestration/charts/we-resist-bot/templates/deployment.yaml b/orchestration/charts/we-resist-bot/templates/deployment.yaml
new file mode 100644
index 0000000..52c67ea
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/templates/deployment.yaml
@@ -0,0 +1,36 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: {{ template "we-resist-bot.fullname" . }}
+  labels:
+    app: {{ template "we-resist-bot.name" . }}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  template:
+    metadata:
+      labels:
+        app: {{ template "we-resist-bot.name" . }}
+        release: {{ .Release.Name }}
+    spec:
+      containers:
+        - name: {{ .Chart.Name }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          env:
+            - name: STEEM_NAME
+              value: "{{ .Values.steem.name }}"
+            - name: STEEM_WIF
+              value: "{{ .Values.steem.wif }}"
+            - name: DATABASE_URL
+              value: "{{ .Values.persistence.url }}"
+          ports:
+            - containerPort: {{ .Values.service.internalPort }}
+          resources:
+{{ toYaml .Values.resources | indent 12 }}
+    {{- if .Values.nodeSelector }}
+      nodeSelector:
+{{ toYaml .Values.nodeSelector | indent 8 }}
+    {{- end }}
diff --git a/orchestration/charts/we-resist-bot/templates/ingress.yaml b/orchestration/charts/we-resist-bot/templates/ingress.yaml
new file mode 100644
index 0000000..88960cd
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/templates/ingress.yaml
@@ -0,0 +1,32 @@
+{{- if .Values.ingress.enabled -}}
+{{- $serviceName := include "we-resist-bot.fullname" . -}}
+{{- $servicePort := .Values.service.externalPort -}}
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: {{ template "we-resist-bot.fullname" . }}
+  labels:
+    app: {{ template "we-resist-bot.name" . }}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+  annotations:
+    {{- range $key, $value := .Values.ingress.annotations }}
+      {{ $key }}: {{ $value | quote }}
+    {{- end }}
+spec:
+  rules:
+    {{- range $host := .Values.ingress.hosts }}
+    - host: {{ $host }}
+      http:
+        paths:
+          - path: /
+            backend:
+              serviceName: {{ $serviceName }}
+              servicePort: {{ $servicePort }}
+    {{- end -}}
+  {{- if .Values.ingress.tls }}
+  tls:
+{{ toYaml .Values.ingress.tls | indent 4 }}
+  {{- end -}}
+{{- end -}}
diff --git a/orchestration/charts/we-resist-bot/templates/service.yaml b/orchestration/charts/we-resist-bot/templates/service.yaml
new file mode 100644
index 0000000..d83e6a1
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/templates/service.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ template "we-resist-bot.fullname" . }}
+  labels:
+    app: {{ template "we-resist-bot.name" . }}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.externalPort }}
+      targetPort: {{ .Values.service.internalPort }}
+      protocol: TCP
+      name: {{ .Values.service.name }}
+  selector:
+    app: {{ template "we-resist-bot.name" . }}
+    release: {{ .Release.Name }}
diff --git a/orchestration/charts/we-resist-bot/values.yaml b/orchestration/charts/we-resist-bot/values.yaml
new file mode 100644
index 0000000..9411a7d
--- /dev/null
+++ b/orchestration/charts/we-resist-bot/values.yaml
@@ -0,0 +1,45 @@
+# Default values for we-resist-bot.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+replicaCount: 1
+image:
+  repository: r351574nc3/we-resist-bot
+  tag: latest
+  pullPolicy: Always
+
+service:
+  type: ClusterIP
+  internalPort: 5000
+  externalPort: 5000
+
+steem:
+  name: please fill this in
+  wif: please fill this in
+
+persistence:
+  url: please fill this in
+
+ingress:
+  enabled: false
+  # Used to create an Ingress record.
+  hosts:
+    - chart-example.local
+  annotations:
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  tls:
+    # Secrets must be manually created in the namespace.
+    # - secretName: chart-example-tls
+    #   hosts:
+    #     - chart-example.local
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #  cpu: 100m
+  #  memory: 128Mi
+  # requests:
+  #  cpu: 100m
+  #  memory: 128Mi

Kubernetes orchestration configuration



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Moar like this!

Ezekiel 25:17 comes to mind

Roadmap

  1. Reports of freedom fighters who's signedup, grumpycat curation trail for each user, how much your vote/downvote is worth to the resistance
  2. Grumpy reports grumpy curation trail
  3. Freedom Fighter Curation ability to kick spammers/scammers that join the resistance
  4. Rewards Autoacceptance Ability for @the-resistance to auto-accept rewards so as SP is awarded, it can begin using it immediately.(edited)
    That's what's coming up

@r351574nc3, I like your contribution to open source project, so I upvote to support you.

Thank you so much! It's appreciated.

Thank you for the contribution. It has been approved.

You can contact us on Discord.

[utopian-moderator]

Hey @r351574nc3 I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Coin Marketplace

STEEM 0.20
TRX 0.13
JST 0.029
BTC 63339.90
ETH 3485.02
USDT 1.00
SBD 2.53