<|start_filename|>src/pages/tests/posts/posts.js<|end_filename|>
const testPosts = [
    { name: 'No Embed', transaction: "ab25c7aa416c67991d13f441da98440a82c568a53c40d3cd286931e0c2c4595c" },
    { name: 'Telegram', transaction: "e1f11da63f697baf2979b522785379e17f8d38ac39a102c72b617902a820f286" },
    { name: 'Twitter', transaction: "8cdd6819d421238bcfa070ad6500d284a9e16c55dbe4662bb34bdd7970d97bae" },
    { name: 'Youtube', transaction: "8df76a2c8fdfdb3e2d339aa4451420289cfd0382fc7c931ccfee2eb640e5a807" },
    { name: 'Instagram', transaction: "59c14291ea28fa91f5112a441e9acaffa0032b716a491e8e8de9bc7f7bb0db6f" },
    { name: 'Soundcloud', transaction: "ddc5607f78c1c8857bef10c72da1dd2106e8e25059f6593248dd92ca238e2672" },
    { name: 'Long', transaction: "9c7b051405f1ccc6d19639c3240ed27ca4ac8fe9bb0007806b852c16f823b2f5" },
    { name: 'Tipped', transaction: "d8b54c5a74da9d0027eb1a6dacd61a0e6cead3bc99c80bb54344ab9b1063d4e0" },
    { name: 'NSFW', transaction: "1e15faaa03f625ea2aefb0f918df49ccd113b55fd0e8edd5f9df0223637f06ce" },
];

export {
    testPosts
}

<|start_filename|>src/bots/staking1.js<|end_filename|>
import { getConfig, sleep } from "@/novusphere-js/utility";
//import { setAPIHost } from "@/novusphere-js/discussions/api";
import { getActiveWallets, getAsset } from "@/novusphere-js/uid";
import eos from "@/novusphere-js/uid/eos";

const fetch = require('node-fetch');
global.fetch = fetch;

(async function () {
    try {
        //setAPIHost("http://localhost:8008");

        const ONE_HOUR = 1 * 60 * 60 * 1000;

        const config = await getConfig('staking1', {
            key: ''
        });

        if (!config.key) throw new Error(`Staking 1.0 key is not configured`);

        for (; ;) {

            let assetMap = {};
            const activeWallets = await getActiveWallets();
            for (const uidw of activeWallets) {
                const asset = await getAsset('ATMOS', uidw);
                assetMap[uidw] = asset;
                await sleep(100);
            }

            let total = Object.values(assetMap).map(a => parseFloat(a)).reduce((a, b) => a + b);
            const payouts = Object.keys(assetMap)
                .map(key => ({ publicKey: key, balance: assetMap[key] }))
                .map(r => ({
                    key: r.publicKey,
                    amount: ((parseFloat(r.balance) / total) * 2740)
                }))
                .filter(r => r.amount >= 0.001)
                .map(r => ({
                    from: `atmosstakerw`,
                    to: `nsuidcntract`,
                    quantity: `${r.amount.toFixed(3)} ATMOS`,
                    memo: r.key,
                }));

            const actions = payouts.map(p => ({
                account: `novusphereio`,
                name: `transfer`,
                data: p,
                authorization: [{
                    actor: `atmosstakerw`,
                    permission: 'active',
                }]
            }));

            const api = await eos.getAPI("https://api.eosn.io", [config.key]);
            const tx = await api.transact({ actions: actions },
                {
                    blocksBehind: 3,
                    expireSeconds: 30,
                });

            console.log(actions.length);
            console.log(tx);

            for (let n = 0; n < 24; n++) {
                console.log(`Resting... ${new Date()}`);
                await sleep(ONE_HOUR);
            }
        }
    }
    catch (ex) {
        console.log(ex);
    }

})();

<|start_filename|>src/mixins/shortTime.js<|end_filename|>
import { formatDistance } from "date-fns";

export const shortTimeMixin = {
    methods: {
        shortTime(t) {
            if (!this.$vuetify.breakpoint.mobile)
                return formatDistance(t, new Date(), { addSuffix: true });
            else {
                const delta = Date.now() - t;
                const second = 1000;
                const minute = second * 60;
                const hour = minute * 60;
                const day = hour * 24;

                let unit = (u, s) => {
                    const n = Math.max(1, Math.ceil(delta / u));
                    return `${n}${s}`;
                };

                if (delta < minute) return unit(second, `s`);
                else if (delta < hour) return unit(minute, `m`);
                else if (delta < day) return unit(hour, `h`);
                else return unit(day, `d`);
            }
        },
    }
}

<|start_filename|>src/server/site.js<|end_filename|>
export default {
    "port": 8008,
    "rendertron": "http://localhost:8010/render",
    "title": "Discussions",
    "description": "An open forum for discussions built on blockchain. Supporting and nurturing token communities of all kind",
    "image": "/static/banner.png",
    "url": "https://discussions.app",
    "domain": "discussions.app", // this is a context field for many of the database contexts
    "testerPublicKeys": [
        '<KEY>', // xia256
        '<KEY>', // xia512
        '<KEY>', // jacques
        '<KEY>', // brain
        '<KEY>', // paul
    ],
    "relay": {
        "account": "",
        "key": "",
        "pub": "<KEY>"
    },
    "trustedRelay": [
        "<KEY>"
    ],
    "botUserAgents": [
        'Baiduspider',
        'bingbot',
        'Discordbot',
        'Embedly',
        'facebookexternalhit',
        'Googlebot',
        'LinkedInBot',
        'outbrain',
        'pinterest',
        'quora link preview',
        'rogerbot',
        'redditbot',
        'showyoubot',
        'Slackbot',
        'TelegramBot',
        'Twitterbot',
        'vkShare',
        'W3C_Validator',
        'WhatsApp',
    ]
}

<|start_filename|>src/novusphere-js/discussions/index.js<|end_filename|>
export { Post } from './Post';

import * as api from './api';
import * as gateway from './gateway';

export {
    api,
    gateway
}

<|start_filename|>src/server/sitemap.js<|end_filename|>
import { getCommunities } from "@/novusphere-js/discussions/api";
import { getConfig } from "@/novusphere-js/utility";
import { argv } from 'yargs';
import VueRouterSitemap from 'vue-router-sitemap';
import path from 'path';
import createRoutes from "./routes";
import siteConfig from "./site";

if (argv.config) {
    console.log(`Updating site settings from config: ${argv.config}`);
    Object.assign(siteConfig, getConfig(argv.config));
}

function unwindRoutes(routes, basePath = '') {
    let result = [];

    for (const { path, children } of routes) {
        if (!children) {
            const fp = `${basePath}${path}`;
            if (fp.indexOf(':') == -1 && fp.indexOf('/tests') != 0)
                result.push({ path: fp });
        }
        else {
            let p = path;
            if (p.lastIndexOf('/') != p.length - 1)
                p += '/';
            result.push(...unwindRoutes(children, `${basePath}${p}`));
        }
    }

    return result;
}

(async function () {
    const communities = await getCommunities();

    const routes = unwindRoutes(createRoutes());
    routes.push(...communities.map(comm => ({ path: `/tag/${comm.tag}` })));
    console.log(routes);

    const staticSitemap = path.resolve('./public/', 'sitemap.xml');
    new VueRouterSitemap({ options: { routes } })
        .build(siteConfig.url)
        .save(staticSitemap);

    console.log(`${staticSitemap} created`);

})();

<|start_filename|>src/server/services/EOSContractService.js<|end_filename|>
import { config, getDatabase, getCollection } from "../mongo";
import { sleep } from "@/novusphere-js/utility";

export default class EOSContractService {
    constructor(table, contract, chain, childService) {
        this.dispatch = {};
        this.updates = {};
        this.table = table;
        this.chain = chain;
        this.childService = childService;
        this.DEFAULT_STATE = {
            name: contract,
            id: 0,
            block: 0,
            time: 0
        };
    }

    async getState() {
        const db = await getDatabase();
        let state = await db.collection(config.table.state)
            .findOne({ name: `${this.chain}::${this.DEFAULT_STATE.name}` });

        if (!state) {
            state = {};
            Object.assign(state, this.DEFAULT_STATE);
        }

        return state;
    }

    async updateState({ id, block, time }) {
        const name = `${this.chain}::${this.DEFAULT_STATE.name}`;
        const db = await getDatabase();
        await db.collection(config.table.state)
            .updateOne(
                { name },
                {
                    $setOnInsert: { name },
                    $set: { id, block, time }
                },
                { upsert: true });
    }

    sanitizeAction(action) {
        if (!action.data) action.data = {};
        if (action.data.metadata) {
            if (typeof (action.data.metadata) == "string") {
                try { action.data.metadata = JSON.parse(action.data.metadata); }
                catch (ex) { action.data.metadata = {}; }
            }
        }
        else {
            action.data.metadata = {};
        }
        return action;
    }

    pushUpdate(table, update) {
        let updates = this.updates[table];
        if (!updates) {
            updates = [];
            this.updates[table] = updates;
        }
        updates.push(update);
    }

    async commit() {
        const db = await getDatabase();
        for (const table in this.updates) {
            const updates = this.updates[table];
            if (updates && updates.length > 0) {
                await db.command({
                    update: table,
                    updates: updates
                });
            }
        }
        this.updates = {};
    }

    async getActionCollection() {
        const collection = await getCollection(this.table);
        return collection;
    }

    async migration() {
    }

    async tick() {
        const dispatch = this.dispatch;
        const state = await this.getState();
        console.log(`[${new Date().toLocaleTimeString()}] [${this.chain}::${this.DEFAULT_STATE.name}] state position = ${new Date(state.time).toLocaleString()}`);

        let actionCollection = await this.getActionCollection();
        let actions = (await actionCollection
            .find({
                id: { $gt: state.id },
                account: this.DEFAULT_STATE.name,
                chain: this.chain
            })
            .sort({ id: 1 })
            .limit(100)
            .toArray())
            .map(a => this.sanitizeAction(a));

        for (let i = 0; i < actions.length; i++) {
            const action = actions[i];
            const dispatcher = dispatch[action.name];
            if (dispatcher) {
                await dispatcher.apply(this, [action]);
            }

            if (i > 0 && actions[i].transaction != actions[i - 1].transaction) {
                await this.commit();
            }
        }

        await this.commit();

        if (actions.length > 0) {
            await this.updateState(actions[actions.length - 1]);
        }
        else {
            // child service can tick, since we're up to date here
            if (this.childService) {
                await this.childService.tick();
            }
        }
    }

    async start() {
        await this.migration();
        for (; ;) {
            await this.tick();
            await sleep(1000);
        }
    }
}

<|start_filename|>src/components/MarkdownEditor/nodes/Mention.js<|end_filename|>
// Modified from: https://github.com/scrumpy/tiptap/blob/a6f4e896dc5723cb807e213966d137e487240631/packages/tiptap-extensions/src/nodes/Mention.js

import { Node, Mark } from 'tiptap'
import { replaceText, pasteRule } from 'tiptap-commands'
import { Suggestions } from "tiptap-extensions";

export class MentionPaste extends Mark {
  constructor(options) {
    super(options);

    this.allSuggestions = this.options.onFilter(undefined, '');
    this.pasteRegex = new RegExp(`@(${this.allSuggestions.map(s => s.displayName).join('|')})`, 'gi');
  }

  get name() {
    return 'mentionpaste'
  }

  get schema() {
    return {
      attrs: {
        href: {}
      },
      group: 'inline',
      inline: true,
      selectable: false,
      atom: true,
      toDOM: node => ['a', { href: `${node.attrs.href}`, target: `_blank` }, ``,],
      parseDOM: [],
    }
  }

  pasteRules({ type }) {
    return this.allSuggestions.length == 0 ? [] : [
      pasteRule(
        this.pasteRegex,
        type,
        (match) => {
          const [{ pub, displayName }] = this.options.onFilter(undefined, match.substring(1));
          return ({ href: `/u/${encodeURIComponent(displayName)}-${pub}` });
        },
      ),
    ]
  }
}

export class Mention extends Node {
  get name() {
    return 'mention'
  }

  get defaultOptions() {
    return {
      matcher: {
        char: '@',
        allowSpaces: false,
        startOfLine: false,
      }
    }
  }

  get schema() {
    return {
      attrs: {
        name: {},
        href: {},
      },
      group: 'inline',
      inline: true,
      selectable: false,
      atom: true,
      toDOM: node => ['a', { href: `${node.attrs.href}`, target: `_blank` }, `${this.options.matcher.char}${node.attrs.name}`],
      parseDOM: [], // they will be parsed as a link, which is ok
    }
  }

  commands({ schema }) {
    return attrs => replaceText(null, schema.nodes[this.name], attrs)
  }

  get plugins() {
    return [
      Suggestions({
        command: ({ range, attrs, schema }) => replaceText(range, schema.nodes[this.name], attrs),
        appendText: ' ',
        matcher: this.options.matcher,
        items: this.options.items,
        onEnter: this.options.onEnter,
        onChange: this.options.onChange,
        onExit: this.options.onExit,
        onKeyDown: this.options.onKeyDown,
        onFilter: this.options.onFilter
      }),
    ]
  }
}

<|start_filename|>src/novusphere-js/uid/workers/ecc.js<|end_filename|>
import ecc from "eosjs-ecc";
import { expose } from "threads/worker";

expose({
    sign: ecc.sign,
    signHash: ecc.signHash
})


<|start_filename|>src/mixins/submitPost.js<|end_filename|>
import { createArtificalTips } from "@/novusphere-js/uid";

export const submitPostMixin = {
    methods: {
        async submitPost({ post, transferActions }) {
            if (post.edit) {
                const p = this.tree[post.parentUuid].post;
                if (p) {
                    p.content = post.content;
                    p.title = post.title;
                }
            } else {
                if (this.tree[post.uuid]) return;

                const reply = { post, replies: [] };
                this.tree[post.uuid] = reply;
                this.tree[post.parentUuid].replies.unshift(reply);

                if (transferActions && transferActions.length > 0) {
                    const parent = this.tree[post.parentUuid];
                    if (parent) {
                        transferActions = transferActions.filter(ta => ta.recipientPublicKey == parent.post.uidw); // filter only tips for the parent post
                        if (transferActions.length > 0) {
                            let artificalTips = await createArtificalTips(
                                this.keys.wallet.pub,
                                post.transaction,
                                transferActions
                            );
                            parent.post.tips.push(...artificalTips);
                        }
                    }
                }
            }
        },
    }
}

<|start_filename|>src/novusphere-js/discussions/AccountSearchQuery.js<|end_filename|>
import { apiRequest } from "./api";

export class AccountSearchQuery {
    constructor(searchQuery) {
        this.setFrom(searchQuery);
    }

    //
    // Set from another search query
    //
    setFrom(searchQuery) {
        this.id = searchQuery.id;
        this.pipeline = searchQuery.pipeline;
        this.count = searchQuery.count;
        this.limit = searchQuery.limit;
    }

    //
    // Resets the search query
    //
    reset() {
        const searchQuery = {
            pipeline: this.pipeline,
            limit: this.limit,
        };

        this.setFrom(searchQuery);
    }

    //
    // Checks whether this search query has more data
    // Note: if next() hasn't been called yet, hasMore() will always return false 
    //
    hasMore() {
        return (this.id != 0);
    }

    async next() {
        const payload = await this.nextRaw();
        const result = payload.map(dbo => dbo);
        return result;
    }

    async nextRaw() {
        const queryObject = {
            id: this.id,
            pipeline: this.pipeline,
            count: this.count,
            limit: this.limit,
        };

        try {
            const startTime = Date.now();

            const { id, count, limit, accounts } = await apiRequest(`/v1/api/search/accounts`, queryObject);

            this.id = id;
            this.count = count;
            this.limit = limit;

            const deltaTime = Date.now() - startTime;
            if (deltaTime > 1000)
                console.proxyLog(`AccountSearchQuery took ${deltaTime}ms to return results`, this.pipeline);

            return accounts;
        }
        catch (ex) {
            console.error(`Trx Search query error`, this, ex);
            throw (ex);
        }
    }
}

<|start_filename|>src/mixins/threadLink.js<|end_filename|>
export const threadLinkMixin = {
    computed: {
        link() {
            if (!this.post) return '';
            return this.getThreadLink(this.post);
        }
    },
    methods: {
        openThreadDialog(post) {
            const sub = post.sub;
            let title = undefined, referenceId = undefined, referenceId2 = undefined;

            if (post.op) {
                title = post.op.getSnakeCaseTitle();
                referenceId = post.op.getEncodedId();
                referenceId2 = post.getEncodedId();
            }
            else {
                title = post.getSnakeCaseTitle();
                referenceId = post.getEncodedId();
                referenceId2 = undefined;
            }

            this.$store.commit("setThreadDialogOpen", {
                value: true,
                sub,
                referenceId,
                title,
                referenceId2,
            });
        },
        getThreadLink(post) {
            let link = `/tag/${post.sub}`;
            if (post.op && post.transaction != post.op.transaction) {
                link += `/${post.op.getEncodedId()}/${post.op.getSnakeCaseTitle()}/${post.getEncodedId()}`;
            } else {
                link += `/${post.getEncodedId()}/${post.getSnakeCaseTitle()}`;
            }
            return link;
        }
    }
}

<|start_filename|>src/novusphere-js/index.js<|end_filename|>
import * as discussions from './discussions';
import * as utility from './utility';
import * as uid from './uid';


export {
    discussions,
    utility,
    uid
}

<|start_filename|>src/server/mongo/index.js<|end_filename|>
import { MongoClient } from 'mongodb';
import config from './config';
import { getFromCache } from "@/novusphere-js/utility";

let cache = {};

async function getMongo() {
    return getFromCache(cache, 'mongo', async () => {
        console.log(`Trying to connect to ${config.connection}`);
        const mongo = await MongoClient.connect(config.connection, { useNewUrlParser: true, useUnifiedTopology: true });

        // set up the indexes
        const database = await mongo.db(config.database);
        for (let collection in config.index) {
            let indexes = config.index[collection];
            for (let name in indexes) {
                let action = {};
                if (name == "$text") {
                    indexes[name].forEach(field => action[field] = "text");
                }
                else {
                    action[name] = indexes[name];
                }

                console.log(`Index ${collection} ${JSON.stringify(action)}`);
                await database.collection(collection).createIndex(action);
            }
        }

        return mongo;
    });
}

async function getDatabase(name) {
    name = name || config.database;

    return getFromCache(cache, `database_${name}`, async () => {
        const mongo = await getMongo();
        const database = await mongo.db(name);
        return database;
    });
}

async function getCollection(name) {
    const nameData = name.split('::');
    if (nameData.length > 1) {
        let database = await getDatabase(nameData[0]);
        return database.collection(nameData[1]);
    }
    else {
        let database = await getDatabase();
        return database.collection(nameData[0]);
    }
}

async function connectDatabase() {
    try {
        await getDatabase();
        return true;
    }
    catch (ex) {
        console.error(ex);
        return false;
    }
}

export {
    config,
    getDatabase,
    getCollection,
    connectDatabase
}

<|start_filename|>src/utility.js<|end_filename|>
import { mapGetters, mapState } from "vuex";
import { waitFor } from "@/novusphere-js/utility";
import { encrypt, decrypt, brainKeyToKeys, isValidBrainKey, findInvalidBrainKeyWord } from "@/novusphere-js/uid";

//
// Wrap a Vue component export to watch for isLoggedIn and require it, otherwise redirect
//
function requireLoggedIn(component) {
    let created = component.created;
    let watch = component.watch;
    let computed = component.computed;

    component.created = async function () {
        if (this.needSyncAccount) {
            await waitFor(async () => !this.needSyncAccount, 100);
        }
        if (!this.isLoggedIn) this.$router.push(`/`);
        else if (created) created.apply(this, arguments);
    };

    component.computed = {
        ...computed,
        ...mapGetters(["isLoggedIn"]),
        ...mapState({
            needSyncAccount: state => state.needSyncAccount
        })
    };

    component.watch = {
        ...watch,
        isLoggedIn() {
            if (!this.isLoggedIn) this.$router.push(`/`);
            if (watch && watch.isLoggedIn) watch.isLoggedIn.apply(this, arguments);
        }
    };

    return component;
}

function displayNameRules(displayName) {
    return {
        displayNameRules() {
            const rules = [];
            if (this[displayName].length < 3) {
                rules.push(`Display name must be at least 3 characters`);
            }

            if (this[displayName].length > 16) {
                rules.push(`Display names can be at most 16 characters`);
            }

            const validNameRegex = /[a-zA-Z0-9_]+/g;
            const match = this[displayName].match(validNameRegex);
            if (!match || match[0] != this[displayName]) {
                rules.push(
                    `Display names may only contain letters, numbers, underscores`
                );
            }
            return rules;
        }
    }
}

function passwordRules(password) {
    return {
        passwordRules() {
            const rules = [];
            if (this[password].length < 5) {
                rules.push(`Password must be at least 5 characters`);
            }
            return rules;
        }
    }
}

function passwordTesterRules(password, encryptedTest) {
    return {
        passwordTesterRules() {
            const rules = [];
            if (decrypt(this[encryptedTest], this[password]) != "test") {
                rules.push(`Password is incorrect`);
            }
            return rules;
        }
    }
}

function brainKeyRules(brainKey) {
    return {
        brainKeyRules() {
            const rules = [];
            const bkValue = this[brainKey];

            if (!isValidBrainKey(bkValue)) {
                const invalidWord = findInvalidBrainKeyWord(bkValue);
                if (invalidWord)
                    rules.push(`Invalid brain key mnemonic - "${invalidWord}" may be invalid or spelled incorrectly`);
                else
                    rules.push(`Invalid brain key mnemonic`);
            }

            return rules;
        }
    }
}

async function createLoginObject({ displayName, brainKey, password }) {
    const keys = await brainKeyToKeys(brainKey);

    return {
        encryptedBrainKey: encrypt(brainKey, password),
        encryptedTest: encrypt("test", password),
        displayName: displayName,
        keys: keys
    };
}

function getBuildVersion() {
    let build = '';
    if (window.__BUILD__) {
        build = `S-${new Date(window.__BUILD__).getTime().toString(16)}`;
    } else {
        build = `C-${new Date().getTime().toString(16)}`;
    }
    return build;
}

export {
    requireLoggedIn,
    displayNameRules,
    passwordRules,
    passwordTesterRules,
    brainKeyRules,
    createLoginObject,
    getBuildVersion
}

<|start_filename|>src/components/MarkdownEditor/nodes/Hashtag.js<|end_filename|>
// Modified from: https://github.com/scrumpy/tiptap/blob/a6f4e896dc5723cb807e213966d137e487240631/packages/tiptap-extensions/src/nodes/Mention.js

import { Node, Mark } from 'tiptap';
import { replaceText, pasteRule } from 'tiptap-commands';
import { Suggestions } from "tiptap-extensions";

export class HashtagPaste extends Mark {
  get name() {
    return 'hashtagpaste'
  }

  get schema() {
    return {
      attrs: {
        href: {}
      },
      group: 'inline',
      inline: true,
      selectable: false,
      atom: true,
      toDOM: node => ['a', { href: `${node.attrs.href}`, target: `_blank` }, ``,],
      parseDOM: [],
    }
  }

  pasteRules({ type }) {
    return [
      pasteRule(
        /#[a-zA-Z0-9]+/gi,
        type,
        (match) => ({ href: `/tag/${match.substring(1)}`, tag: '' }),
      ),
    ]
  }
}

export class Hashtag extends Node {
  get name() {
    return 'hashtag'
  }

  get defaultOptions() {
    return {
      matcher: {
        char: '#',
        allowSpaces: false,
        startOfLine: false,
      }
    }
  }

  get schema() {
    return {
      attrs: {
        tag: {},
        href: {}
      },
      group: 'inline',
      inline: true,
      selectable: false,
      atom: true,
      toDOM: node => ['a', { href: `${node.attrs.href}`, target: `_blank`, }, `${this.options.matcher.char}${node.attrs.tag}`],
      parseDOM: [], // they will be parsed as a link, which is ok
    }
  }

  commands({ schema }) {
    return attrs => replaceText(null, schema.nodes[this.name], attrs)
  }

  get plugins() {
    return [
      Suggestions({
        command: ({ range, attrs, schema }) => replaceText(range, schema.nodes[this.name], attrs),
        appendText: ' ',
        matcher: this.options.matcher,
        items: this.options.items,
        onEnter: this.options.onEnter,
        onChange: this.options.onChange,
        onExit: this.options.onExit,
        onKeyDown: this.options.onKeyDown,
        onFilter: this.options.onFilter
      }),
    ]
  }
}

<|start_filename|>src/bots/twitter.js<|end_filename|>
import { getConfig, saveConfig, sleep, generateUuid } from "@/novusphere-js/utility";
import { brainKeyToKeys } from "@/novusphere-js/uid";
import { submitPost } from "@/novusphere-js/discussions/api";

const Twitter = require('twitter');

(async function () {
    try {
        const PRIMARY_TAG = "twitter";
        const TWEET_DURATION = 1 * 60 * 60 * 1000; // 1 hour -- only tweet from an account every hour
        const DELAY_DURATION = 5 * 60 * 1000; // 5 minutes -- how long do we wait after tweeting
        const SLEEP_DURATION = 10 * 1000; // 10 seconds -- how often we do our process loop

        const config = await getConfig('twitter', {
            key: '',
            options: {},
            watch: [],
        });

        const twitterState = await getConfig('twitter-state', {});

        if (!config.key) throw new Error(`Bot mnemonic brain key is not configured`);
        if (!config.options.consumer_key) throw new Error(`Twitter options/authentication is not configured`);

        const keys = await brainKeyToKeys(config.key);
        const client = new Twitter(config.options);

        for (; ;) {
            for (const w of config.watch) {
                console.log(`Checking ${w.screen_name} for new tweets... ${new Date()}`);

                const { since_id, since_time } = twitterState[w.screen_name] || {};

                if (since_time && (Date.now() - since_time) < TWEET_DURATION) {
                    const remMin = (Date.now() - since_time) / 60 * 1000;
                    console.log(`Skipped ${w.screen_name}... ${remMin} until it can post again`);
                    continue;
                }

                const tweets = await client.get('statuses/user_timeline', {
                    screen_name: `@${w.screen_name}`,
                    count: 1,
                    since_id: since_id,
                    exclude_replies: true,
                    include_rts: false,
                    tweet_mode: 'extended'
                });

                if (tweets.length <= 0) {
                    console.log(`No new tweets`);
                }
                else {

                    console.log(`Found ${tweets.length} new tweets`);

                    // we reverse the tweets to itterate from oldest to newest
                    for (const tweet of tweets.reverse()) {

                        const { id_str, user: { screen_name }, retweeted_status, entities: { hashtags, /*user_mentions*/ } } = tweet;
                        if (retweeted_status) continue; // ignore retweets

                        const url = `https://twitter.com/${screen_name}/status/${id_str}`;
                        const tags = Array.from(new Set([PRIMARY_TAG, "twitter", "bot", ...hashtags.map(({ text }) => text), ...(w.tags || [])])).map(t => t.toLowerCase());
                        //const screen_names = user_mentions.map(({ screen_name }) => screen_name);

                        const uuid = generateUuid();
                        const post = {
                            title: '',
                            displayName: `${screen_name}`,
                            content: `${url} \n\n ${tags.map(t => `[#${t}](/tag/${t})`).join(' ')}`,
                            uuid: uuid,
                            threadUuid: uuid,
                            parentUuid: "",
                            tags: tags,
                            mentions: [],
                            uidw: keys.wallet.pub,
                            sub: tags[0],
                            edit: false
                        };

                        console.log(`Posting ${url} ... ${new Date()}`);

                        const trxid = await submitPost(keys.arbitrary.key, post, []);

                        console.log(trxid);

                        twitterState[w.screen_name] = {
                            ...twitterState[w.screen_name],
                            since_id: id_str,
                            since_url: url,
                            since_trxid: trxid,
                            since_time: Date.now()
                        }

                        await saveConfig('twitter-state', twitterState);
                        await sleep(DELAY_DURATION);
                    }
                }

                await sleep(1000); // take a short break
            }

            console.log(`Resting...`);
            await sleep(SLEEP_DURATION);
        }
    }
    catch (ex) {
        console.log(ex);
    }

})();

<|start_filename|>src/bots/test.js<|end_filename|>
import fetch from "node-fetch";
import { getConfig } from "@/novusphere-js/utility";
import { getMarketCaps } from "@/novusphere-js/uid";

(async function () {
    const config = await getConfig(`test`);

    console.log((await getMarketCaps())['ATMOS']);

})();

<|start_filename|>src/server/controllers/ModerationController.js<|end_filename|>
import { Controller, Post, Get } from '@decorators/express';
import { Api } from "../helpers";
import { config, getDatabase } from "../mongo";

export default @Controller('/moderation') class ModerationController {
    constructor() {
    }

    @Api()
    @Get('/test')
    async test(req, res) {
        return res.success([]);
    }

    /*@Api()
    @Post('/posts/:tag')
    async posts(req, res) {
        const { domain, mods, tag, tags, thread } = req.unpack();
        if (!domain) throw new Error(`Field domain is unspecified`);
        if (!mods || !Array.isArray(mods)) throw new Error(`Field mods must be specified`);

        let db = await getDatabase();


        const matchPosts = {};

        if (thread) {
            matchPosts["$expr"] = { $eq: ["$post.uuid", "$post.threadUuid"] };
        }

        if (tags && tags.length > 0) {
            matchPosts["post.tags"] = { $in: tags };
        }

        let result = await db.collection(config.table.moderation)
            .aggregate([{
                $match: {
                    domain: domain,
                    pub: { $in: mods },
                    tags: tag
                },
            },
            {
                $lookup:
                {
                    from: config.table.posts,
                    let: { uuid: "$uuid" },
                    pipeline: [
                        {
                            $match: {
                                $expr: {
                                    $eq: ["$uuid", "$$uuid"]
                                }
                            }
                        }
                    ],
                    as: "post"
                }
            },
            ...(Object.keys(matchPosts).length > 0 ? [{ $match: matchPosts }] : []),
            {
                $project:
                {
                    mod: "$pub",
                    uuid: "$uuid",
                    tags: "$tags",
                    post: { "$arrayElemAt": ["$post", 0] }
                }
            },
            {
                $project:
                {
                    mod: "$pub",
                    uuid: "$uuid",
                    threadUuid: "$post.threadUuid",
                    transaction: "$post.transaction",
                    createdAt: "$post.createdAt"
                }
            }
            ])
            .toArray();

        return res.success(result);
    }*/

    @Api()
    @Post('/settags')
    async setTags(req, res) {

        const { pub, domain, data: { uuid, tags } } = req.unpackAuthenticated({ tags: [] });

        if (!uuid) throw new Error(`Uuid must be specified`);
        if (!Array.isArray(tags)) throw new Error(`Tags must be an array`);

        let db = await getDatabase();

        await db.collection(config.table.moderation).updateOne({
            pub: pub,
            domain: domain,
            uuid: uuid
        }, {
            $setOnInsert: {
                pub: pub,
                domain: domain,
                uuid: uuid
            },
            $set: {
                tags: tags
            }
        }, {
            upsert: true
        });

        return res.success({
            uuid,
            tags
        });
    }
}

<|start_filename|>src/novusphere-js/utility/lock.js<|end_filename|>
import { waitFor } from "@/novusphere-js/utility";

export default class Lock {
    Lock() {
        this.isLocked = false;
    }
    async lock(asyncTask) {
        await waitFor(() => !this.isLocked);
        this.isLocked = true;
        try {
            await asyncTask();
        }
        finally {
            this.isLocked = false;
        }
    }
}

<|start_filename|>src/server/events/index.js<|end_filename|>
const EventEmitter = require('events');
EventEmitter.defaultMaxListeners = 1000;

const accountEvent = new EventEmitter();

export {
    accountEvent
}

<|start_filename|>src/watcher/dfuse.js<|end_filename|>
import { sleep } from "@/novusphere-js/utility";

const { createDfuseClient } = require("@dfuse/client");
global.fetch = require('node-fetch');
global.WebSocket = require('ws');

export default class DfuseWatcher {
    constructor(apiKey) {
        this.name = 'dfuse';
        this._apiKey = apiKey;
    }

    async getPreviousAction(chain, account, collection) {
        return await collection
            .find({ account, chain })
            .sort({ block: -1 })
            .limit(1)
            .next()
            || { block: 0 };
    }

    async startWatch(account, previousAction, onAction) {
        try {
            for (; ;) {
                const operation = `
                subscription($cursor: String!) {
                    searchTransactionsForward(
                        query: "(auth:${account} OR receiver:${account})"
                        lowBlockNum:${previousAction ? previousAction.block + 1 : 0}
                        cursor: $cursor
                    ) {
                        cursor
                        trace {
                            id
                            status
                            block {
                                id
                                num
                                timestamp
                            }
                            executedActions {
                                seq
                                authorization { actor }
                                account
                                name
                                data
                                hexData
                            }
                        }
                    }
                }`;

                this._client = createDfuseClient({
                    //apiKey: this._apiKey,
                    authentication: false,
                    network: "eos.dfuse.eosnation.io",
                    //network: "mainnet.eos.dfuse.io",
                });

                const stream = await this._client.graphql(operation, message => {
                    if (message.type === 'data') {
                        const {
                            cursor,
                            trace: { id, block, executedActions }
                        } = message.data.searchTransactionsForward;

                        const actions = executedActions.map((act) => ({
                            id: Number(act.seq),
                            position: 0, // ???
                            account: act.account,
                            auth: act.authorization.map(auth => auth.actor),
                            transaction: id,
                            block: Number(block.num),
                            time: new Date(block.timestamp).getTime(),
                            name: act.name,
                            hexData: act.hexData,
                            data: act.data
                        }));

                        actions.forEach(action => {
                            if (onAction) {
                                onAction(action);
                            }
                            if (!previousAction || action.block > previousAction.block) {
                                previousAction = action;
                            }
                        });

                        // Mark stream at cursor location, on re-connect, we will start back at cursor
                        stream.mark({ cursor });
                    }

                    if (message.type === 'error') {
                        console.log('An error occurred', message.errors, message.terminal);
                    }

                    if (message.type === 'complete') {
                        console.log('Completed');
                    }
                });

                // wait 1 hour, then re-establish the connection
                // dfuse seems to randomly stop providing data, and no indication the connection has "died"
                await sleep(1 * 60 * 60 * 1000);

                try { await this._client.release(); }
                catch (ex) {
                    // do nothing, we're reiniting the client
                }

                console.log(`Force re-establishing connection to dfuse...`);
            }
        }
        catch (ex) {
            console.error(`Dfuse stream error for ${account}`, ex);
        }
    }
}

<|start_filename|>src/novusphere-js/uid/newdex.js<|end_filename|>
import crypto from 'crypto';
import axios from 'axios';

class NewDexAPI {
    constructor(key, secret) {
        this.key = key;
        this.secret = secret;
    }

    async request(endpoint, params = {}) {
        const url = `https://api.newdex.io${endpoint}`;

        //params[`api_key`] = this.key;
        //params[`timestamp`] = Math.floor(Date.now() / 1000);

        let queryString = Object
            .keys(params)
            .sort()
            .map(k => `${k}=${encodeURIComponent(params[k])}`).join(`&`);

        let hash = crypto
            .createHmac('sha256', this.secret)
            .update(queryString)
            .digest("hex");

        queryString += `&sign=${hash}`;

        const { data: { data } } = await axios.get(`https://api.newdex.io${endpoint}?${queryString}`);
        return data;
    }

    async depth(symbol) {
        return await this.request(`/v1/depth`, {
            symbol
        });
    }

    async commonSymbols() {
        return await this.request(`/v1/common/symbols`);
    }

    _consumeBook(units, isBaseUnit, book) {
        let expect = 0;
        for (const [p, q] of book) {

            const consume = Math.min(q, isBaseUnit ? (units / p) : units);
            units -= isBaseUnit ? (consume * p) : consume;
            expect += isBaseUnit ? consume : (consume * p);

            if (units <= 0) {
                return { expect, price: String(p) };
            }
        }
        throw new Error(`Insufficient liquidity in orderbooks`); // more details?
    }

    async swapDetailsReverse(end, startSymbol, symbols = undefined) {
        const SLIPPAGE_FACTOR = 1 / 0.98;
        const [endAmount, endSymbol] = end.split(' ');

        if (!symbols) symbols = await this.commonSymbols();
        if (startSymbol == 'EOS') {
            const market = symbols.find(m => m.currency == endSymbol);
            const depth = await this.depth(market.symbol);

            const { expect, price } = this._consumeBook(parseFloat(end) * SLIPPAGE_FACTOR, false, depth.asks.reverse());
            return [{
                //type: `buy-limit`,
                type: `buy-market`,
                market,
                price,
                quantity: `${(expect).toFixed(market.currency_precision)} ${startSymbol}`,
                expect: end
            }];
        }
        else if (endSymbol == 'EOS') {
            const market = symbols.find(m => m.currency == startSymbol);
            const depth = await this.depth(market.symbol);

            const { expect, price } = this._consumeBook(parseFloat(end) * SLIPPAGE_FACTOR, true, depth.bids);
            return [{
                //type: `sell-limit`,
                type: `sell-market`,
                market,
                price,
                quantity: `${(expect).toFixed(market.currency_precision)} ${startSymbol}`,
                expect: end
            }];
        }
        else {
            const [hop2] = await this.swapDetailsReverse(end, 'EOS', symbols);
            const [hop1] = await this.swapDetailsReverse(hop2.quantity, startSymbol, symbols);
            return [hop1, hop2];
        }
    }

    async swapDetails(start, targetSymbol, symbols = undefined) {
        const SLIPPAGE_FACTOR = 0.98;
        const [, startSymbol] = start.split(' ');

        if (!symbols) symbols = await this.commonSymbols();
        if (startSymbol == 'EOS') {
            const market = symbols.find(m => m.currency == targetSymbol);
            const depth = await this.depth(market.symbol);

            const { expect, price } = this._consumeBook(parseFloat(start), true, depth.asks.reverse());
            return [{
                //type: `buy-limit`,
                type: `buy-market`,
                market,
                price,
                quantity: start,
                expect: `${(expect * SLIPPAGE_FACTOR).toFixed(market.currency_precision)} ${targetSymbol}`
            }];
        }
        else if (targetSymbol == 'EOS') {
            const market = symbols.find(m => m.currency == startSymbol);
            const depth = await this.depth(market.symbol);

            const { expect, price } = this._consumeBook(parseFloat(start), false, depth.bids);
            return [{
                //type: `sell-limit`,
                type: `sell-market`,
                market,
                price,
                quantity: start,
                expect: `${(expect * SLIPPAGE_FACTOR).toFixed(4)} ${targetSymbol}`
            }];
        }
        else {
            const [hop1] = await this.swapDetails(start, 'EOS', symbols);
            const [hop2] = await this.swapDetails(hop1.expect, targetSymbol, symbols);
            return [hop1, hop2];
        }
    }
}

export {
    NewDexAPI
}

<|start_filename|>src/server/index.js<|end_filename|>
import express, { Router } from 'express';
import expressSession from 'express-session';
import bodyParser from 'body-parser';
import cors from 'cors';
import axios from 'axios';
import fs from 'fs';
import { argv } from 'yargs';
import { attachControllers } from '@decorators/express';

import { setAPIHost } from "@/novusphere-js/discussions/api";
import { getConfig } from "@/novusphere-js/utility";

import createRoutes from "./routes";
import siteConfig from "./site";
import { connectDatabase } from "./mongo";
import services from "./services";
import gateways from "./gateways";

import AccountController from "./controllers/AccountController";
import BlockchainController from "./controllers/BlockchainController";
import DataController from "./controllers/DataController";
import ModerationController from "./controllers/ModerationController";
import SearchController from "./controllers/SearchController";
import UploadController from "./controllers/UploadController";

function createApp() {
    const app = express();

    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
    app.use(expressSession({
        secret: 'this is open source so its not much of a secret huh',
        resave: false,
        saveUninitialized: true,
    }));

    return app;
}


async function serverMode0() {
    setAPIHost(`http://localhost:${siteConfig.port}`);

    if (!await connectDatabase()) return;

    const INDEX_FILE = fs.readFileSync(`./dist/index.html`, `utf8`);
    const BUILD_TIME = Date.now();

    const app = createApp();

    async function serve(req, res) {
        const botRegex = new RegExp(siteConfig.botUserAgents.join('|'), 'i');
        const userAgent = req.get('user-agent');
        if (!userAgent || userAgent.match(botRegex) || req.query.rendertron) {
            const url = `${siteConfig.rendertron}/${siteConfig.url}${req.path}`;
            console.log(`[rendertron] ${userAgent} - ${siteConfig.url}${req.path}`);
            const { data } = await axios.get(url);
            res.setHeader('Cache-Control', 'no-store');
            res.setHeader('Content-Type', 'text/html');
            res.send(data);
        }
        else {
            const header = `<script>window.__BUILD__ = ${BUILD_TIME}</script>`;
            let index = INDEX_FILE;
            index = index.replace(/<\/head>/, `${header}</head>`);
            res.setHeader('Cache-Control', 'no-store');
            res.setHeader('Content-Type', 'text/html');
            res.send(index);
        }
    }

    function addRoute(route, path = '') {
        if (!route.children || route.redirect) {
            const fullPath = path + route.path;
            console.log(`Added route for ${fullPath}`);
            app.get(fullPath, async (req, res, next) => {
                serve(req, res, next);
            });
        }

        if (route.children) {
            for (const child of route.children) {
                let fullPath = path + route.path;
                if (fullPath[fullPath.length - 1] != '/') {
                    fullPath += '/';
                }
                addRoute(child, fullPath);
            }
        }
    }

    const routes = createRoutes();
    routes.forEach(r => addRoute(r));
    app.get(`/`, serve);

    const apiRouter = express.Router();
    attachControllers(apiRouter, [
        AccountController,
        BlockchainController,
        DataController,
        ModerationController,
        SearchController]);

    app.use('/v1/api', cors(), apiRouter);

    app.use(express.static(`./dist/`));

    app.get('*', (req, res) => {
        res.status(404);
        res.redirect(`/404?path=${req.path}`);
    });

    //app.listen(siteConfig.port, () => console.log(`Server is listening at port ${siteConfig.port}`));
    gateways.start(app, siteConfig.port, () => console.log(`Gateway is listening at port ${siteConfig.port}`));
    services.start();
}

async function serverMode1() {
    const app = createApp();

    const apiRouter = express.Router();
    attachControllers(apiRouter, [
        UploadController]);

    app.use('/v1/api', cors(), apiRouter);
    app.use(express.static(`./dist/`));

    app.listen(siteConfig.port, () => console.log(`Server mode 1 is listening at port ${siteConfig.port}`));
}

(async function () {
    // update our config
    if (argv.config) {
        console.log(`Updating site settings from config: ${argv.config}`);
        Object.assign(siteConfig, getConfig(argv.config));
    }

    if (!siteConfig.mode) {
        await serverMode0();
    }
    else if (siteConfig.mode == 1) {
        await serverMode1();
    }
    else {
        console.error(`Unknown server mode ${siteConfig.mode}`);
    }

})();

<|start_filename|>src/bots/discord.js<|end_filename|>
import Discord from 'discord.js';
import { searchPostsByAll } from "@/novusphere-js/discussions/api";
import { getConfig, saveConfig, sleep } from "@/novusphere-js/utility";

(async function () {

    const BOT_NAME = `discord`;
    const RESET_DURATION = 2 * 60 * 60 * 1000; // 2 hours -- how long until we reset the bot
    const SLEEP_DURATION = 10 * 1000; // 10 seconds -- how often we do our process loop
    const DELAY_DURATION = 5 * 60 * 1000; // 5 minutes -- how often we send a discord message

    let lastSentDiscordMsgTime = 0;

    console.log(`===== Discussions.app Discord Bot =====`);
    console.log(`Loading config...`);

    const config = getConfig(BOT_NAME, {
        "token": "",
        "lastPostTime": 0,
        "ignoreTags": ["test"],
        "channel": "discussions"
    });

    if (!config.token) {
        console.error(`Discord API token is unspecified`);
        return;
    }

    function login(bot) {
        return new Promise((resolve, reject) => {
            bot.on('ready', () => {
                resolve(bot);
            });
            /*bot.on("debug", (e) => {
                console.log(e)
            });*/
            bot.on("error", (e) => {
                console.error(e);
                reject(e);
            });

            bot.login(config.token);
        });
    }

    async function getNewPosts() {
        const cursor = searchPostsByAll(config.ignoreTags);
        cursor.pipeline[0].$match.createdAt = { $gt: config.lastPostTime };
        cursor.pipeline.push({ // non-standard sort option
            $sort: {
                createdAt: 1 // ascending
            }
        });
        cursor.limit = 1; // only get one post at a time, since we only want to send one post every so often...

        const posts = await cursor.next();
        return posts;
    }

    for (;;) {

        console.log('Trying to log in...');
        const bot = await login(new Discord.Client());
        console.log('Logged in!');

        for (let q = 0; q < (RESET_DURATION / SLEEP_DURATION); q++) {
            try {
                const now = Date.now();
                if (now - lastSentDiscordMsgTime > DELAY_DURATION) {
                    console.log(`Checking for new posts at ${new Date().toLocaleString()}`);

                    const posts = await getNewPosts();

                    for (let i = 0; i < posts.length; i++) {
                        const p = posts[i];

                        // this is mainly just a precaution, but it should never occur
                        if (p.createdAt.getTime() <= config.lastPostTime) break;

                        //const time = p.createdAt.toLocaleString("en-US", { timeZone: 'UTC' });
                        const tags = p.tags.map(t => `#${t}`).join(', ');
                        const content = await p.getContentText({ removeImages: true });
                        const links = (await p.getContentDocument()).links;

                        let url = `https://discussions.app/tag/${p.sub}/${p.getEncodedId()}/${p.getSnakeCaseTitle()}`;
                        let message = undefined;
                        if (links && links.length > 0 && content.length < 300 && !p.paywall) {
                            // let discord embed/preview the link, instead of our post
                            message = `**${p.displayName}**: <${url}>\n${tags}\n\n${content}`;
                        }
                        else {
                            message = `**${p.displayName}**: ${url}\n${tags}\n\n`;
                        }

                        console.log(message);

                        const channel = bot.channels.cache.find(ch => ch.name == config.channel);
                        if (!channel) {
                            console.error(`Discord channel "${config.channel}" was not found`);
                            return;
                        }
                        await channel.send(message);

                        lastSentDiscordMsgTime = now;
                    }

                    if (posts.length > 0 && posts[0].createdAt.getTime() > config.lastPostTime) {
                        config.lastPostTime = posts[0].createdAt.getTime();
                        saveConfig(BOT_NAME, config);
                    }
                }
            }
            catch (ex) {
                console.error(ex);
            }

            await sleep(SLEEP_DURATION);
        }

        console.log(`Resetting bot...`);
        bot.destroy();
        await sleep(5000);
    }

})();

<|start_filename|>src/novusphere-js/uid/bufferwriter.js<|end_filename|>
import * as base58 from 'bs58'

export default class BufferWriter {
    static fromBuffer(buffer) {
        let bw = new BufferWriter(buffer.length);
        return bw.write(buffer);
    }

    constructor(size = 256) {
        this.offset = 0;
        this.size = size;
        this.buffer = new Buffer(this.size);
    }

    _resizeForIncomingWrite(writeSize) {
        if (this.offset + writeSize >= this.size) {
            let buffer = new Buffer(this.size * 2);
            this.buffer.copy(buffer, 0, 0, buffer.length);
            this.buffer = buffer;
            this.size = buffer.length;
        }
    }

    skip(amount) {
        this._resizeForIncomingWrite(amount);
        this.offset += amount;
        return this;
    }

    write(buffer, length) {
        length = length || buffer.length;

        this._resizeForIncomingWrite(length);

        buffer.copy(this.buffer, this.offset, 0, length);
        this.offset += length;
        return this;
    }

    writeByte(num) {
        this._resizeForIncomingWrite(1);
        this.buffer[this.offset] = (num & 0xFF);
        this.offset++;
        return this;
    }

    writeUInt64(num) {
        let buffer = Buffer.alloc(8);
        buffer.writeUIntLE(num, 0, 6);
        return this.write(buffer);
    }

    writeAsset(asset) {
        const [balance] = asset.split(' ');
        return this.writeUInt64(parseInt(balance.replace('.', '')));
    }

    writePublicKey(publicKey, prefix = '') {
        if (prefix && publicKey.indexOf(prefix) != 0) throw new Error(`Public key ${publicKey} does not start with expected prefix "${prefix}"`);
        const key = base58.decode(publicKey.substring(prefix.length));
        return this.write(key, 33);
    }

    writeString(str) {
        return this.write(Buffer.from(str));
    }

    writeQuantity(quantity) {
        return this.writeUInt64(parseInt(quantity.replace('.', '')));
    }

    toBuffer() {
        let buffer = new Buffer(this.offset);
        this.buffer.copy(buffer, 0, 0, this.offset);
        return buffer;
    }
}

<|start_filename|>src/mixins/safari.js<|end_filename|>
export const safariMixin = {
    computed: {
        isSafari() {
            if (
                navigator.userAgent.match(/safari/i) &&
                !navigator.userAgent.match(/chrome/i)
            ) {
                return true;
            }
            return false;
        }
    }
}

<|start_filename|>src/watcher/greymass.js<|end_filename|>
import { sleep } from "@/novusphere-js/utility";
import axios from 'axios';

export default class GreymassWatcher {
    constructor(endpoint, name) {
        this.name = name || 'greymass';
        this._endpoint = endpoint || `https://eos.greymass.com`;
    }

    async getPreviousAction(chain, account, collection) {
        return await collection
            .find({ account, chain })
            .sort({ position: -1 })
            .limit(1)
            .next()
            || { block: -1, position: -1 };
    }

    async startWatch(account, previousAction, onAction) {
        try {
            for (; ;) {

                const { data } = await axios.post(`${this._endpoint}/v1/history/get_actions`,
                    JSON.stringify({
                        account_name: account,
                        pos: previousAction.position + 1,
                        offset: 100
                    }),
                    {
                        headers: { 'Content-Type': 'application/json' },
                        timeout: 10000 // 10s timeout
                    });

                const actions = data.actions.map((a, i) => {
                    const { trx_id, act } = a.action_trace;

                    let block_time = a.block_time;
                    if (typeof block_time == 'string') {
                        if (!block_time.endsWith('Z'))
                            block_time = `${block_time}Z`; // UTC
                    }

                    return {
                        id: Number(a.global_action_seq),
                        position: a.account_action_seq,
                        account: act.account,
                        auth: act.authorization.map(auth => auth.actor),
                        transaction: trx_id,
                        block: a.block_num,
                        time: new Date(block_time).getTime(),
                        name: act.name,
                        hexData: act.hex_data,
                        data: act.data
                    };
                });

                for (const action of actions) {
                    if (onAction) {
                        onAction(action);
                    }
                    if (!previousAction || action.position > previousAction.position) {
                        previousAction = action;
                        //console.log(`GM position set to ${account}@${previousAction.position}`);
                    }
                }

                await sleep(2500);
            }
        }
        catch (ex) {
            console.error(`Greymass error for ${account}`, ex);
        }
    }
}

<|start_filename|>src/manager/index.js<|end_filename|>
import { argv } from 'yargs';
import { spawn } from 'child_process';
import { sleep } from "@/novusphere-js/utility";


(async function () {

    const queue = [
        { name: `w`, cmd: `npm run watcher` },
        { name: `s`, cmd: `npm run ${argv.server}` }
    ];

    // TO-DO: automatically restart watcher every 1h and server every 24h

    for (; ;) {

        if (queue.length == 0) {
            await sleep(10000);
            continue;
        }

        const { name, cmd } = queue.pop();
        const p = spawn(cmd, { shell: true, detached: true });
        
        p.on('exit', function (code, signal) {
            console.log(`[${name}] process exited with code ${code} and signal ${signal}`);
            queue.push({ name, cmd });
        });

        p.stdout.on('data', (data) => {

            if (data.indexOf('Terminate batch job') > -1) {
                console.log(new Date());
                p.kill();
            }

            console.log(`[${name}] ${data}`);
        });

        p.stderr.on('data', (data) => {
            console.error(`[${name}] ${data}`);
        });

        await sleep(1000);
    }




})();

<|start_filename|>src/server/services/index.js<|end_filename|>
import discussions from "./discussions";
import analytics from "./analytics";
import { config } from "../mongo";

function start() {
    discussions.start();
    analytics.start(); 
}

export default {
    start
}

<|start_filename|>src/server/controllers/UploadController.js<|end_filename|>
import express from 'express';
import { Controller, Post, Get, All } from '@decorators/express';
import { Api } from "../helpers";
import siteConfig from "../site";
import fs from 'fs';

const multer = require('multer');
const mime = require('mime');
const path = require('path');

export default @Controller('/upload') class UploadController {
    constructor() {
        const storage = multer.diskStorage({
            destination: (req, file, cb) => {
                cb(null, path.join('public', 'uploads'))
            },
            filename: (req, file, cb) => {
                // TO-DO: change to hash?
                const filename = `${Date.now()}.${mime.getExtension(file.mimetype)}`;
                cb(null, filename);
            }
        });

        const upload = multer({
            storage: storage,
            fileFilter: function (req, file, cb) {
                const imageRx = /\.(jpg|jpeg|png|gif)$/i;
                const ext = `.${mime.getExtension(file.mimetype)}`;
                if (!ext.match(imageRx)) {
                    return cb(new Error('Only image files are allowed!'));
                }
                cb(null, true);
            },
            limits: {
                files: 1, // allow only 1 file per request
                fileSize: (siteConfig.uploadLimit || 1) * 1024 * 1024, // (max file size)
            },
        });

        this.uploadSingle = upload.single('image');
    }

    @All('/file/:filename')
    async getFile(req, res) {
        const [filename] = req.params.filename.match(/[a-z0-9]+/i);
        const mimeType = mime.getType(req.params.filename);
        const ext = mime.getExtension(mimeType);

        fs.readFile(path.join('public', 'uploads', `${filename}.${ext}`), function (err, data) {
            if (err) {
                res.writeHead(404);
                return res.end(JSON.stringify({ error: `File not found` }));
            }
            res.setHeader('Content-Type', mimeType);
            res.writeHead(200);
            return res.end(data);
        });
    }

    @Api()
    @Post('/')
    async uploadFile(req, res) {
        const filename = await (new Promise((resolve, reject) => {
            this.uploadSingle(req, res, (err) => {
                if (err) return reject(err);
                const file = req.file;
                if (file) return resolve(file.filename);
                return reject(new Error(`An unexpected error occured while uploading`));
            });
        }));

        return res.success({
            filename
        })
    }
}

<|start_filename|>vue.config.js<|end_filename|>
const ThreadsPlugin = require('threads-plugin');

module.exports = {
  "configureWebpack": {
    plugins: [
      new ThreadsPlugin()
    ]
  },
  "transpileDependencies": [
    "vuetify"
  ]
}

<|start_filename|>src/novusphere-js/uid/eth.js<|end_filename|>
import { sleep, waitFor } from "@/novusphere-js/utility";

class MetamaskWallet {
    constructor() {
        this.provider = {
            id: `metamask`
        };
    }
    async connect() {
        if (typeof window.ethereum == "undefined" || typeof window.web3 == "undefined") {
            throw new Error(`Unable to connect to metamask`);
        }

        this.ethereum = window.ethereum;
        this.web3 = window.web3;

        this.ethereum.enable(); // we do not await this since older versions do not throw ex when canceling
        await waitFor(async () => this.web3.eth.accounts.length > 0, 500, 25000,
            `Connecting to metamask has taken too long, try again`);
    }
    async login() {
        await sleep(1000);
        const account = this.web3.eth.accounts[0];
        console.log(`eth login ${account}`);
        this.auth = {
            accountName: '',
            permission: '',
            publicKey: account
        }
    }
    async logout() {
        this.ethereum = null;
        this.web3 = null;
    }
    signArbitrary(msg) {
        if (!this.auth || !this.auth.publicKey)
            throw new Error(`Not logged into Metamask`);

        return new Promise((resolve, reject) => this.web3.currentProvider.sendAsync({
            method: 'personal_sign',
            params: [msg, this.auth.publicKey],
            from: this.auth.publicKey,
        }, function (err, result) {
            if (err) reject(err);
            else resolve(result.result);
        }));
    }
}

function getWalletNames() {
    return [`metamask`];
}

async function connectWallet(name) {
    if (name == `metamask`) {
        const wallet = new MetamaskWallet();
        await wallet.connect();
        await wallet.login();
        return wallet;
    }
    return undefined;
}

export default {
    MetamaskWallet,
    connectWallet,
    getWalletNames
};

<|start_filename|>src/novusphere-js/uid/xnation.js<|end_filename|>
// Reference Implementation - https://github.com/Novusphere/xnation-ui
// Special Thanks: <NAME>

import _ from "lodash";
import { eos } from "@/novusphere-js/uid";

function symToBaseSymbol(sym) {
    const [precision, symbol] = sym.split(',');
    return { symbol, precision };
}

function assetStringtoBaseSymbol(assetString) {
    const [quantity, symbol] = assetString.split(' ');
    const precision = (quantity.length - 1) - quantity.indexOf('.');
    return { quantity, symbol, precision };
}

function buildTokenId({ contract, symbol }) {
    return contract + "-" + symbol;
}

async function fetchMultiRelays() {
    const contractName = `bancorcnvrtr`;
    const smartTokenContract = `bnt2eoscnvrt`;

    const { rpc } = await eos.getAPI();

    const rawRelays = await rpc.get_table_rows({
        code: contractName,
        table: "converter.v2",
        scope: contractName,
        limit: 99
    });

    const parsedRelays = rawRelays.rows;
    const passedRelays = parsedRelays
        .filter(
            relay =>
                relay.reserve_weights.reduce(
                    (acc, reserve) => reserve.value + acc,
                    0
                ) == 1000000
        )
        .filter(relay => relay.reserve_balances.length == 2);

    const relays = passedRelays.map(relay => ({
        id: buildTokenId({
            contract: smartTokenContract,
            symbol: symToBaseSymbol(relay.currency).symbol
        }),
        reserves: relay.reserve_balances.map(({ value }) => ({
            ...assetStringtoBaseSymbol(value.quantity),
            id: buildTokenId({
                contract: value.contract,
                symbol: assetStringtoBaseSymbol(value.quantity).symbol
            }),
            contract: value.contract,
            network: "eos",
            amount: parseFloat(value.quantity)
        })),
        contract: contractName,
        owner: relay.owner,
        isMultiContract: true,
        smartToken: {
            ...symToBaseSymbol(relay.currency),
            id: buildTokenId({
                contract: smartTokenContract,
                symbol: symToBaseSymbol(relay.currency).symbol
            }),
            contract: smartTokenContract,
            amount: 0,
            network: "eos"
        },
        fee: relay.fee / 1000000
    }));

    return relays;
};

function composeMemo(converters, minReturn, destAccount) {
    const version = 1;
    let returnSymbol = undefined;
    const receiver = converters
        .map(({ account, symbol, multiContractSymbol }) => {
            returnSymbol = symbol;
            return `${account}${multiContractSymbol ? `:${multiContractSymbol}` : ""} ${symbol}`;
        })
        .join(" ");

    let base = `${version},${receiver},${minReturn},${destAccount}`;
    return base;
}

function compareString(stringOne, stringTwo) {
    const strings = [stringOne, stringTwo];
    if (!strings.every(str => typeof str == "string"))
        throw new Error(
            `String one: ${stringOne} String two: ${stringTwo} one of them are falsy or not a string`
        );
    return stringOne.toLowerCase() == stringTwo.toLowerCase();
}

const compareEdge = (edge1, edge2) => edge1.every(edge => edge2.some(e => compareString(edge, e)));

const dfs = (fromId, toId, adjacencyList) => new Promise(resolve => callbackDfs(fromId, toId, adjacencyList, resolve));

const callbackDfs = (
    start,
    goal,
    adjacencyList,
    callBack,
    visited = new Set(),
    path = [start]
) => {
    visited.add(start);
    const destinations = adjacencyList.get(start);
    if (destinations.includes(goal)) {
        callBack([...path, goal]);
    }
    for (const destination of destinations) {
        if (!visited.has(destination)) {
            callbackDfs(destination, goal, adjacencyList, callBack, visited, [
                ...path,
                destination
            ]);
        }
    }
};

function multiToDry(relay) {
    return {
        reserves: relay.reserves.map(reserve => ({
            contract: reserve.contract,
            symbol: { code: reserve.symbol, precision: reserve.precision }
        })),
        contract: relay.contract,
        smartToken: {
            symbol: { code: relay.smartToken.symbol, precision: relay.smartToken.precision },
            contract: relay.smartToken.contract
        },
        isMultiContract: relay.isMultiContract
    }
}

function buildAdjacencyList(edges, nodes) {
    const adjacencyList = new Map();
    nodes.forEach(node => adjacencyList.set(node, []));
    edges.forEach(([from, to]) => adjacencyList.get(from).push(to));
    edges.forEach(([from, to]) => adjacencyList.get(to).push(from));
    return adjacencyList;
}

async function findNewPath(fromId, toId, pools, identifier) {
    const edges = _.uniqWith(pools.map(identifier), compareEdge);
    const nodes = _.uniqWith(edges.flat(1), compareString);

    const adjacencyList = buildAdjacencyList(edges, nodes);
    const startExists = adjacencyList.get(fromId);
    const goalExists = adjacencyList.get(toId);

    if (!(startExists && goalExists))
        throw new Error(
            `Start ${fromId} or goal ${toId} does not exist in adjacency list`
        );

    const dfsResult = await dfs(fromId, toId, adjacencyList);
    if (!dfsResult || dfsResult.length == 0)
        throw new Error("Failed to find path");

    const hops = _.chunk(dfsResult, 2).map((tokenIds, index, arr) => {
        let searchAbleIds = undefined;

        if (tokenIds.length < 2) {
            searchAbleIds = [arr[index - 1][1], tokenIds[0]];
        } else searchAbleIds = tokenIds;

        const accomodatingRelays = pools.filter(pool => {
            const ids = identifier(pool);
            return ids.every(id => searchAbleIds.some(i => id == i));
        });

        return accomodatingRelays;
    });

    return {
        path: dfsResult,
        hops
    };
}

function relaysToConvertPaths(from, relays) {
    return relays
        .map(relay =>
            relay.reserves.map(token => {
                const base = {
                    account: relay.contract,
                    symbol: token.symbol.code
                };
                return relay.isMultiContract
                    ? {
                        ...base,
                        multiContractSymbol: relay.smartToken.symbol.code
                    }
                    : base;
            })
        )
        .reduce((prev, curr) => prev.concat(curr))
        .filter(converter => converter.symbol !== from.code)
        .reduce((accum, item) => {
            return accum.find((converter) => converter.symbol == item.symbol) ? accum : [...accum, item];
        }, []);
}

function sortReservesByAsset(asset, reserves) {
    const [quantity, symbol] = asset.split(' ');

    if (!reserves.some(reserve => assetStringtoBaseSymbol(reserve.amount).symbol == symbol))
        throw new Error("Asset does not exist in these reserves");

    return reserves.sort((a, b) =>
        assetStringtoBaseSymbol(a.amount).symbol == symbol ? -1 : 1
    );
}

function calculateReturn(balanceFrom, balanceTo, amount) {
    if (assetStringtoBaseSymbol(balanceFrom).symbol != assetStringtoBaseSymbol(amount).symbol)
        throw new Error("From symbol does not match amount symbol");
    if (parseFloat(amount) >= parseFloat(balanceFrom))
        throw new Error("Impossible to buy the entire reserve or more");

    const balanceFromNumber = parseFloat(balanceFrom);
    const balanceToNumber = parseFloat(balanceTo);
    const amountNumber = parseFloat(amount);

    const [btoAmount, btoSym] = balanceTo.split(' ');
    const bToPrecision = btoAmount.length - 1 - btoAmount.indexOf('.');

    const reward = amountNumber / (balanceFromNumber + amountNumber) * balanceToNumber;
    const rewardAsset = `${reward.toFixed(bToPrecision)} ${btoSym}`;
    const slippage = parseFloat(amount) / parseFloat(balanceFrom);

    return { reward: rewardAsset, slippage };
}

const highestNumber = (number1, number2) => number1 > number2 ? number1 : number2;

function findReturn(amount, relaysPath) {
    return relaysPath.reduce(
        ({ amount, highestSlippage }, relay) => {

            const [fromReserve, toReserve] = sortReservesByAsset(
                amount,
                relay.reserves
            );

            const { reward, slippage } = calculateReturn(
                fromReserve.amount,
                toReserve.amount,
                amount
            );

            const r = {
                amount: chargeFee(reward, relay.fee, 2),
                highestSlippage: highestNumber(highestSlippage, slippage)
            };

            return r;
        },
        { amount, highestSlippage: 0 }
    );
}

function chargeFee(asset, decimalFee, magnitude = 1) {
    const assetAmount = parseFloat(asset);
    const one = 1;

    const totalFee = assetAmount * (1 - Math.pow(1 - decimalFee, magnitude));
    const newAmount = assetAmount - totalFee;

    const [assetAmt, assetSym] = asset.split(' ');
    const precision = assetAmt.length - 1 - assetAmt.indexOf('.');
    return `${newAmount.toFixed(precision)} ${assetSym}`;
}

async function findPath(fromId, toId) {
    const multiRelays = await fetchMultiRelays();
    const allDry = [...multiRelays.map(multiToDry)];

    const foundPath = await findNewPath(fromId, toId, allDry, dry => {
        const [from, to] = dry.reserves.map(r =>
            buildTokenId({
                contract: r.contract,
                symbol: r.symbol.code
            })
        );
        return [from, to];
    });

    const path = foundPath.hops.flatMap(hop => hop[0]);

    return path;
}

async function getReturn(assetAmount, fromId, toId) {
    if (compareString(fromId, toId))
        throw new Error("Cannot convert a token to itself.");

    const path = await findPath(fromId, toId);

    const hydratedRelays = await hydrateRelays(path);

    const calculatedReturn = findReturn(assetAmount, hydratedRelays);

    return {
        amount: calculatedReturn.amount,
        slippage: calculatedReturn.highestSlippage
    };
}

function number_to_asset(amount, symbol) {
    return `${amount.toFixed(symbol.precision)} ${symbol.code}`;
}

async function hydrateRelays(relays) {
    //const v1Relays = relays.filter(relay => !relay.isMultiContract);
    const v2Relays = relays.filter(relay => relay.isMultiContract);
    //const [v1, v2] = await Promise.all([
    //  hydrateV1Relays(v1Relays),
    //  hydrateV2Relays(v2Relays)
    //]);
    //const flat = [...v2, ...v1];
    const flat = await hydrateV2Relays(v2Relays);

    return relays.map(
        relay =>
            flat.find(
                r =>
                    r.smartToken.symbol.code == (relay.smartToken.symbol.code) &&
                    compareString(r.smartToken.contract, relay.smartToken.contract)
            )
    );
}

async function hydrateV2Relays(relays) {
    if (relays.length == 0) return [];

    const freshRelays = await fetchMultiRelays();
    const hydratedRelays = freshRelays.map(eosMultiToHydrated);

    const result = hydratedRelays.filter(relay =>
        relays.some(
            r =>
                compareString(relay.smartToken.contract, r.smartToken.contract) &&
                relay.smartToken.symbol.code == (r.smartToken.symbol.code)
        )
    );

    if (relays.length !== result.length)
        throw new Error(
            "Failed to hydrate all relays requested in hydrateV2Relays"
        );

    return result;
}

function eosMultiToHydrated(relay) {
    return ({
        reserves: relay.reserves.map(
            (reserve) => ({
                contract: reserve.contract,
                amount: number_to_asset(
                    reserve.amount,
                    { code: reserve.symbol, precision: reserve.precision }
                )
            })
        ),
        contract: relay.contract,
        fee: relay.fee,
        isMultiContract: relay.isMultiContract,
        smartToken: {
            symbol: { code: relay.smartToken.symbol, precision: relay.smartToken.precision },
            contract: relay.smartToken.contract
        }
    });
}

async function getXNationQuote(from, swapAmount, swapFromId, swapToId) {

    const { symbol, precision } = assetStringtoBaseSymbol(swapAmount);
    const swapAsset = { code: symbol, precision: precision };

    const estimate = await getReturn(swapAmount, swapFromId, swapToId);
    const path = await findPath(swapFromId, swapToId);
    const convertPath = relaysToConvertPaths(swapAsset, path);

    const { symbol: estimateSymbol, precision: estimatePrecision } = assetStringtoBaseSymbol(estimate.amount);
    const minReturn = String((parseFloat(estimate.amount) * 0.96).toFixed(estimatePrecision));

    const memo = composeMemo(convertPath,
        minReturn,
        from);

    return {
        depositAccount: `thisisbancor`,
        amount: swapAmount,
        from: swapFromId,
        to: swapToId,
        estimate,
        minReturn: `${minReturn} ${estimateSymbol}`,
        memo
    }
}

export {
    getXNationQuote
}

<|start_filename|>src/main.js<|end_filename|>
// Primarily written and maintained by [bed8c66e8c0c903cf91946f9f625fd9de3b8556972141ee60694faa19d1812da]

import Vue from 'vue';
import vuetify from './plugins/vuetify';
import router from './plugins/router';
import store from './plugins/vuex';
import App from './App.vue';

import * as axios from 'axios';
window._axios = axios;

import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard);

import InfiniteLoading from 'vue-infinite-loading';
Vue.use(InfiniteLoading, { /* options */ });

import VueGtag from "vue-gtag";
Vue.use(VueGtag, {
  config: { id: "UA-178655433-1" },
  enabled: (window.localStorage["analytics"] != "disabled")
}, router);

Vue.config.productionTip = false;

window.$vue = new Vue({
  vuetify,
  router,
  store,
  render: h => h(App)
}).$mount('#app');