const Twit = require("twit"); const config = require("config"); const winston = require("winston"); require("winston-daily-rotate-file"); const Client = require("bitcoin-core"); const lbry = new Client({ username: config.get("lbrycrd.username"), password: config.get("lbrycrd.password"), port: config.get("lbrycrd.port") }); const logger = winston.createLogger({ level: "info", format: winston.format.json(), transports: [ new winston.transports.DailyRotateFile({ filename: "tipbot-%DATE%.log", dirname: "./logs", datePattern: "YYYY-MM-DD-HH", zippedArchive: true, maxSize: "20m", maxFiles: "14d" }), new winston.transports.Console({ format: winston.format.simple(), level: "debug" }) ] }); const T = new Twit({ consumer_key: config.get("twitter.consumer_key"), consumer_secret: config.get("twitter.consumer_secret"), access_token: config.get("twitter.access_token"), access_token_secret: config.get("twitter.access_token_secret"), timeout_ms: 60 * 1000, // optional HTTP request timeout to apply to all requests. strictSSL: true // optional - requires SSL certificates to be valid. }); const stream = T.stream("statuses/filter", { track: config.get("bot.handle") }); logger.info("Started LBRY twitter tipbot."); stream.on("tweet", function(tweet) { if(tweet.user.screen_name === config.get("bot.handle").substring(1)) return; let msg = checkTrunc(tweet); msg = msg.replace(/[\n\\]/g, " ").slice(msg.lastIndexOf(config.get("bot.handle"))).split(" "); if (msg.length >= 2) checkTweet(tweet, msg); }); function checkTweet(tweet, msg) { switch (msg[1]) { case "help": doHelp(tweet, msg); break; case "balance": doBalance(tweet, msg); break; case "deposit": doDeposit(tweet, msg); break; case "withdraw": doWithdraw(tweet, msg); break; case "tip": doTip(tweet, msg); break; case "terms": doTerms(tweet, msg); break; case "lbryian": logger.info("Got a command with the old format, handling it..."); checkTweet(tweet, msg.splice(1)); break; } } async function doHelp(tweet, msg) { try { let post = await T.post("statuses/update", { status: `@${tweet.user.screen_name} `+ `Call commands with: ${config.get("bot.handle")} + \n` + "help - Shows this command.\n" + "balance - Get your balance.\n" + "deposit - Get address for your deposits.\n" + "withdraw ADDRESS AMOUNT - Withdraw AMOUNT credits to ADDRESS.\n" + "tip USER AMOUNT - Tip USER AMOUNT.\n"+ "terms - Sends the TOS.", in_reply_to_status_id: tweet.id_str }); logger.info( `Sent help to ${tweet.user.screen_name}, tweet id: ${tweet.id_str}` ); } catch (e) { logger.error(e); } } async function doTerms(tweet, msg){ // ADD terms await T.post("statuses/update", { status: `@${tweet.user.screen_name} `+ "There are no fees to use this bot except the automatic daemon fee. \n"+ "Under no circumstances shall LBRY Inc. be held responsible for lost, stolen or misdirected funds.", in_reply_to_status_id: tweet.id_str }); } async function doBalance(tweet, msg) { try { const balance = await lbry.getBalance(id(tweet.user.id_str), config.get("bot.requiredConfirms")); // Amount of confirms before we can use it. const post = await T.post("statuses/update", { in_reply_to_status_id: tweet.id_str, status: `@${tweet.user.screen_name} You have ${balance} LBC.` }); logger.info( `Sent balance command to ${tweet.user.screen_name}, tweet id: ${ tweet.id_str }` ); } catch (e) { logger.error(e); } } async function doDeposit(tweet, msg) { try { const post = await T.post("statuses/update", { status: `@${tweet.user.screen_name} Your deposit address is ${await getAddress(id(tweet.user.id_str))}.`, in_reply_to_status_id: tweet.id_str }); logger.info( `Sent deposit address to ${tweet.user.screen_name}, tweet id: ${ tweet.id_str }` ); } catch (e) { logger.error(e); } } async function doWithdraw(tweet, msg) { try { if (msg.length < 4) return doHelp(tweet, msg); let address = msg[2]; let amount = getValidatedAmount(msg[3]); if (amount === null) { return await T.post("statuses/update", { status: `@${tweet.user.screen_name} I don´t know how to withdraw that many credits...`, in_reply_to_status_id: tweet.id_str }); } let txId = await lbry.sendFrom(id(tweet.user.id_str), address, amount); await T.post("statuses/update", { status: `@${tweet.user.screen_name} You withdrew ${amount} LBC to ${address}. \n${txLink(txId)}`, in_reply_to_status_id: tweet.id_str }); logger.info( `User ${ tweet.user.screen_name } withdraw ${amount} LBC to ${address}, tweet id: ${tweet.id_str}` ); } catch (e) { logger.error(e); } } async function doTip(tweet, msg) { try { if (msg.length < 3) { return doHelp(tweet, msg); } const amount = getValidatedAmount(msg[3]); if (amount === null) { return await T.post("statuses/update", { status: `@${tweet.user.screen_name} I don´t know how to tip that many credits...`, in_reply_to_status_id: tweet.id_str }); } const userToTip = tweet.entities.user_mentions.find(u => `@${u.screen_name}` === msg[2]).id_str; let tipToAddress = await getAddress(id(userToTip)) // Call this to ensure user has an account. if (userToTip === null) { return await T.post("statuses/update", { status: `@${tweet.user.screen_name} I could not find that user...`, in_reply_to_status_id: tweet.id_str }); } const balanceFromUser = await lbry.getBalance(id(tweet.user.id_str), config.get("bot.requiredConfirms")); if (balanceFromUser < amount) { return await T.post("statuses/update", { status: `@${tweet.user.screen_name} You tried tipping more than you have! You are ${amount-balanceFromUser} LBC short.`, in_reply_to_status_id: tweet.id_str }); } const txId = await lbry.sendFrom(id(tweet.user.id_str), tipToAddress, Number(amount), 1); await T.post("statuses/update", { status: `@${tweet.user.screen_name} Tipped ${ msg[2] } ${amount} LBC! \nTransaction: ${txLink(txId)} \nSee https://lbry.io/faq/tipbot-twitter for more information.`, in_reply_to_status_id: tweet.id_str }); logger.info( `@${tweet.user.screen_name}(${tweet.user.id_str}) tipped ${ msg[2] }(${userToTip}) ${amount} LBC.` ); } catch (e) { logger.error(e); } } async function getAddress(userId) { try { let uAddresses = await lbry.getAddressesByAccount(userId); if (uAddresses.length > 0) return uAddresses[0]; let nAddress = await lbry.getNewAddress(userId); return nAddress; } catch (e) { logger.error(e); } } function getValidatedAmount(amount) { amount = amount.trim(); if (amount.toLowerCase().endsWith("lbc")) { amount = amount.substring(0, amount.length - 3); } return amount.match(/^[0-9]+(\.[0-9]+)?$/) ? amount : null; } function txLink(txId) { return `https://explorer.lbry.io/tx/${txId}`; } function checkTrunc(tweet) { if (tweet.truncated) return tweet.extended_tweet.full_text; return tweet.text; } function id(usrId){ return `t-${usrId}`; }