├── .gitignore ├── Procfile ├── README.md ├── bot.js ├── package-lock.json ├── package.json ├── script ├── action │ ├── Actions.js │ └── Chat.js ├── extra │ ├── chat-dictionary.json │ └── util.js └── handlers │ ├── mongo-handler.js │ ├── notion-handler.js │ ├── twitter-handler.js │ └── wit-handler.js └── src ├── block.JPG ├── callout.JPG ├── callout2.JPG ├── converstation_0315.JPG ├── cook.JPG ├── cook2.JPG ├── could you remind me.JPG ├── could you remind me2.JPG ├── cron.JPG ├── cron.gif ├── hey.JPG ├── impression.gif ├── squate.JPG ├── wit.gif └── wit2.gif /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | tmp* -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: node bot.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⭐ Summary 2 | This is the little discord bot project created by [Min Shin](instagram.com/happping_min). 3 | The goal is creating the coordinator bot which manage the notion database and extra more. Development is still going on. 4 | 5 | # ⭐How to communicate with a bot 6 | 7 | ## 1. Example Chat 8 | - [x] "Good Morning", "Hi" : initiate the daily clonJobs 9 | - [x] "Good Night", "Bye" : deactive the daily clonJobs 10 | - [x] "What is today's tasks?" 11 | - [x] "What is today's left tasks?" 12 | - [ ] "Can you create new tasks 'finish coding'"? ,"Add new tasks for laundry tomorrow" 13 | - [x] "What is today's project?" 14 | - [ ] "Can you change the current project due date?" 15 | - [x] "Can you remind me every an hour to stretching?" 16 | - [x] "Can you create new log?" 17 | - [x] "Tell me about all reminders" 18 | - [ ] "Bring more tasks to today" 19 | - [x] "How's the weather like in korea?" or "what is the time in london?" 20 | - [x] "What to cook with chicken?" 21 | - [x] "How's my instagram doing?" ,"Show me my twitter status" 22 | - [x] "Can you tweet that?" : this works after the tweetable command or when you replied to your own message 23 | - [ ] `read this ${URL}` : start slow reading mode 24 | 25 | ## 2. Respond with Yes and No 26 | questions like create new page or post on the social media is require the double check. You can answer with "yes" or "no" or even sending emoji or react the bot's message with emoji. 27 | - [ ] "yes" 28 | - [ ] "no" 29 | - [ ] "more" 30 | - [ ] "stop" 31 | This four message works when you type the default chat or replied on the bot's message 32 | 33 | ## 3. Customize conversation with Notion 34 | - [ ] "I need to write on blog, where to write?" 35 | - [ ] "What I should do when I go back to korea?" 36 | A message like this will be searched on Notion Database as dictionary 37 | 38 | ## 4. Slash Command List 39 | Slash command is added after chat feature for shortcut 40 | - `/goog` + word : search on google 41 | - `/eng` + word : search on english word 42 | - `/read`+ URL : split the article into chat 43 | - `/todo` + name : add on tasks 44 | 45 | ## 5. Fun chat 46 | - [x] Emotional(?) message such as "omg","lol","I am tired" will return GIF 47 | 48 | ---- 49 | # ⭐Configuration 50 | 51 | ## 1. Required Env Var 52 | BOT_TOKEN (discord) , NOTION_TOKEN , NOTION_DB_ID , WIT_TOKEN, TIMEZONE 53 | 54 | ## 2. Wit Ai Project 55 | [wit.ai](wit.ai) 56 | - [ ] 57 | 58 | # ⭐Guide to write on notion 59 | 60 | ## Quick Start 61 | 1. Get Database's ID and assign it on env variable 62 | 2. Properties of Database 63 | - Group : single select, option: Log, Reminder, Task, Project, Dictionary 64 | - Recurring : number 65 | - Completed : checkbox 66 | - Tags: multiple select 67 | - Unit : single select, option : minute, hour, week, day, month, year 68 | - Date : date 69 | - _minute: formula 70 | - _hour:formula 71 | - _day: formula 72 | - _month: formula 73 | - _week : formula 74 | - Week : multi-select 75 | - Cron Time: formula 76 | 77 | ### Formula for Clon Time 78 | ![](/src/cron.JPG =250x) 79 | 80 | This is the fomula to get the cron time. 81 | Pretty sure it can be simpler and clear but for now, it looks like this. 82 | 83 | - _minute : 84 | ``` 85 | empty(prop("Unit")) ? if(minute(prop("Date")) + minute(prop("Date")) == 0, format(minute(prop("Edited"))), format(minute(prop("Date")))) : if(prop("Unit") == "minute", "*/" + format(if(empty(prop("Recurring")), 1, prop("Recurring"))), if(prop("Unit") != "hour" or prop("Unit") != "minute", if(empty(prop("Date")), if(empty(prop("Date")), format(minute(prop("Edited"))), format(minute(prop("Date")))), format(minute(prop("Date")))), "*")) 86 | ``` 87 | 88 | 89 | - _hour_ : 90 | ``` 91 | empty(prop("Unit")) ? if(hour(prop("Date")) + hour(prop("Date")) == 0, format(1 + hour(prop("Edited"))), format(1 + hour(prop("Date")))) : if(prop("Unit") == "hour", "*/" + format(if(empty(prop("Recurring")), 1, prop("Recurring"))), if(prop("Unit") != "minute", if(empty(prop("Date")), "*", format(hour(prop("Date")))), if(prop("Unit") != "hour", "*", format(hour(prop("Date")))))) 92 | ``` 93 | 94 | 95 | - _day : 96 | ``` 97 | empty(prop("Unit")) ? if(day(prop("Date")) + day(prop("Date")) == 0, if(0 != day(prop("Edited")), format(day(prop("Edited"))), "*"), format(day(prop("Date")))) : if(prop("Unit") == "day", "*/" + format(if(empty(prop("Recurring")), 1, prop("Recurring"))), if(prop("Unit") != "minute" or prop("Unit") != "hour", if(prop("Unit") == "week", if(empty(prop("Recurring")), if(empty(prop("Week")) == false, "*", format(day(prop("Edited")))), if(empty(prop("Week")), format(prop("Recurring") * 7), "*")), if(prop("Unit") == "month", format(day(prop("Date"))), if(prop("Unit") == "year", format(1 + month(prop("Date"))), "*"))), "_")) 98 | ``` 99 | - _month : 100 | ``` 101 | empty(prop("Unit")) ? if(month(prop("Date")) + month(prop("Date")) == 0, format(month(prop("Edited"))), format(month(prop("Date")))) : if(prop("Unit") == "month", "*/" + format(if(empty(prop("Recurring")), 1, prop("Recurring"))), if(prop("Unit") == "year", format(month(prop("Date"))), "*")) 102 | ``` 103 | 104 | 105 | - _week : 106 | ``` 107 | if(empty(prop("Week")), (prop("Unit") == "week") ? format(day(prop("Date"))) : "*", prop("Week")) 108 | ``` 109 | 110 | 111 | - Cron Time : 112 | ``` 113 | prop("_minute") + " " + prop("_hour") + " " + prop("_day") + " " + prop("_month") + " " + prop("_week") 114 | ``` 115 | 116 | 117 | ### Custom Message and event for reminder 118 | - Each block of the page will be randomly delivered each time. 119 | - if there is the callout block on the page, it will be recognized as a code block(which is not supported in notion API yet). so create the code block and write any javascript. this will fired when cron job is fired. While you can write anything on the block but except "", ''. Don't use them, **you must use backtick ` instead**. 120 | 121 | ![](/src/callout.JPG) 122 | 123 | 124 | 125 | ---- 126 | 127 | if(empty(prop("Unit")), if(empty(prop("Date")), prop("Edited"), prop("Date")), if(empty(prop("Date")), dateAdd(prop("Edited"), dateBetween(if(empty(prop("Date")), prop("Edited"), prop("Date")), now(), concat(prop("Unit"), "s")) / prop("Recurring"), concat(prop("Unit"), "s")), dateAdd(prop("Date"), dateBetween(if(empty(prop("Date")), prop("Edited"), prop("Date")), now(), concat(prop("Unit"), "s")) / prop("Recurring"), concat(prop("Unit"), "s")))) 128 | 129 | 130 | 131 | if(empty(prop("Unit")), if(empty(prop("Date")), dateAdd(prop("Edited"), 3, "months"), prop("Date")), if(empty(prop("Date")), dateAdd(prop("Edited"), dateBetween(if(empty(prop("Date")), prop("Edited"), prop("Date")), now(), concat(prop("Unit"), "s")) / prop("Recurring"), concat(prop("Unit"), "s")), dateAdd(prop("Date"), dateBetween(if(empty(prop("Date")), prop("Edited"), prop("Date")), now(), concat(prop("Unit"), "s")) / prop("Recurring"), concat(prop("Unit"), "s")))) -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | 2 | import 'dotenv/config' 3 | import Discord, { Intents, Collection } from "discord.js"; 4 | import { SlashCommandBuilder } from '@discordjs/builders' 5 | import { REST } from "@discordjs/rest"; 6 | import { Routes } from 'discord-api-types/v9' 7 | import * as Chat from './script/action/Chat.js' 8 | export var channel; 9 | 10 | 11 | export var bot = new Discord.Client( 12 | {intents:[Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES ,Intents.FLAGS.GUILD_MESSAGE_REACTIONS], 13 | partials: ['MESSAGE', 'CHANNEL', 'REACTION'],}); 14 | 15 | bot.once("ready", ()=>{ 16 | console.log("bot is logged in ") 17 | 18 | var clientId = bot.user.id;//'945038953478754374' 19 | var guildId = '944087799185952778' 20 | 21 | var rest = new REST({ 22 | version:"9" 23 | }).setToken( process.env.BOT_TOKEN ); 24 | 25 | (async () =>{ 26 | try{ 27 | await rest.put(Routes.applicationGuildCommands(clientId ,guildId ),{body:commands}); 28 | console.log("successfully registed commands.") 29 | }catch(err){ 30 | console.log(" 🌀",err) 31 | } 32 | })() 33 | 34 | channel = bot.channels.cache.find(c => c.name === "general") 35 | }) 36 | 37 | bot.login(process.env.BOT_TOKEN); 38 | 39 | 40 | const newSlash = (_name,_input,_description) => {return new SlashCommandBuilder() 41 | .setName(_name) 42 | .setDescription(_description) 43 | .addStringOption(option => 44 | option.setName('input') 45 | .setDescription('The input to echo back') 46 | .setRequired(true));} 47 | 48 | const eng = newSlash('eng',"word","I can help your english word"); 49 | const read = newSlash('read',"URL","I can read article for you"); 50 | const todo = newSlash('todo',"name","you have the new tasks?"); 51 | const goog = newSlash('goog',"name","I can google for you"); 52 | const remind = newSlash('remind',"name","I can add a new reminder for you"); 53 | 54 | const commands = [ eng ,read , todo , goog, remind]; 55 | bot.commands = new Collection(); 56 | commands.forEach(command =>{ 57 | bot.commands.set(command.name, command) 58 | }) 59 | 60 | 61 | bot.on("messageCreate", async message =>{ 62 | if(!message.author.bot){ 63 | if(!message.attachments.size){ 64 | Chat.send( message.content , message.reference ) 65 | ;} 66 | } 67 | }) 68 | 69 | bot.on("interactionCreate", async interaction => { 70 | Chat.gotInteraction(interaction) 71 | }) 72 | 73 | bot.on("messageReactionAdd", reaction => 74 | Chat.gotReaction( reaction ) 75 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-coordinator-bot", 3 | "version": "1.0.0", 4 | "main": "bot.js", 5 | "type": "module", 6 | "scripts": { 7 | "start": "node bot.js", 8 | "dev": "nodemon bot.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "@discordjs/rest": "^0.3.0", 16 | "@google-cloud/translate": "^6.3.1", 17 | "@notionhq/client": "^0.4.13", 18 | "axios": "^0.26.0", 19 | "cron": "^1.8.2", 20 | "discord-api-types": "^0.27.3", 21 | "discord.js": "^13.6.0", 22 | "dotenv": "^16.0.0", 23 | "moment": "^2.29.1", 24 | "moment-timezone": "^0.5.34", 25 | "mongoose": "^6.2.3", 26 | "node-wit": "^6.0.1", 27 | "nodemon": "^2.0.15", 28 | "puppeteer": "^13.4.1", 29 | "rollbar": "^2.24.0", 30 | "twitter-api-v2": "^1.11.0", 31 | "unfluff": "^3.2.0", 32 | "weather-js": "^2.0.0", 33 | "xhr2": "^0.2.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /script/action/Actions.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { notion } from "../handlers/notion-handler.js"; 3 | import { channel } from "../../bot.js"//"../handlers/discord-handler.js"; 4 | import { CronJob } from 'cron' 5 | import { tweet } from "../handlers/twitter-handler.js"; 6 | import weather from 'weather-js'; 7 | import { MessageEmbed, MessageButton , MessageActionRow, CommandInteractionOptionResolver} from 'discord.js'; 8 | import moment from 'moment'; 9 | import pkg from 'puppeteer'; 10 | import * as Variable from '../extra/util.js'; 11 | import axios from 'axios'; 12 | import extractor from 'unfluff' 13 | import {yesEmojies, noEmojies} from './Chat.js' 14 | moment.locale('en-ca') 15 | 16 | const puppeteer = pkg; 17 | export const yesAction ={}; export const noAction = {}; export const moreAction = {}; 18 | export const stored = {datas: null , numb: null}; const intervals = []; 19 | var allCrons= [] 20 | 21 | var now = (DATE) => moment(DATE).tz(process.env.TIMEZONE) 22 | var monday = (DATE) => moment().tz(process.env.TIMEZONE).subtract( DATE.getDay()-1, 'days') 23 | var sunday = (DATE) => moment().tz(process.env.TIMEZONE).add( 7-DATE.getDay(), 'days') 24 | var getDayMondayStart = (MOMENT) => MOMENT.format('d')-1 > 0 ? MOMENT.format('d')-1 : 6 25 | var mmdd = (MOMENT) =>{ 26 | var week = MOMENT.format('L').split('-'); 27 | return week[1] + week[2] 28 | } 29 | var isThisWeek = DATE => { 30 | // console.log( moment(DATE) - sunday(new Date())) 31 | return moment(DATE)- sunday(new Date()) < 0 32 | } 33 | //console.log( "🥨",isThisWeek('2022-03-12' )) 34 | 35 | //yes 36 | export async function createNewTask( _entitie ){ 37 | try{ 38 | 39 | yesAction["createNewTask"] = async () =>{ 40 | if( 'datetime' in _entitie && isThisWeek(new Date(_entitie.datetime)) ){ // 1-A : this will create on today's log 41 | var worklog = await getTodaysWorklog(); 42 | var columns = await notion.getColumns( worklog ); 43 | var day = getDayMondayStart ( moment(_entitie.datetime )) 44 | var block = { 45 | object:'block', 46 | type : 'to_do', 47 | to_do : {text :[{type : 'text', text : {content: _entitie.agenda_entry }} ] } 48 | } 49 | await notion.appendChild( columns.at(day) , [block] ); 50 | 51 | var _embed = new MessageEmbed(); 52 | _embed.setDescription(`The new tasks [${ _entitie.agenda_entry }](${worklog.url}) on this week`) 53 | return { embeds : [_embed] } ; 54 | } 55 | else{ // 1-B : this will create on tasks database 56 | var style ={ Name : _entitie.agenda_entry , Group: "Task" } 57 | if('datetime' in _entitie ){ 58 | style.Date= {start : new moment( _entitie.datetime ) } 59 | } 60 | var newPage = await notion.createNew(process.env.NOTION_DB_ID, style, null); 61 | notion.datas.push(newPage) 62 | var _embed = new MessageEmbed(); 63 | _embed.setDescription(`The new tasks [${ _entitie.agenda_entry }](${newPage.url})`) 64 | return { embeds : [_embed] } ; 65 | } 66 | } 67 | 68 | // 0. send this first 69 | return `Do you want me to add this ${_entitie.agenda_entry} as a new task?` 70 | 71 | }catch(error){ 72 | return "Something went wrong " + error.message 73 | } 74 | 75 | } 76 | 77 | //inspectMode 78 | export async function inspectOldTasks(_entitie){ 79 | 80 | var worklog = await getTodaysWorklog(); 81 | var columns = await notion.getColumns( worklog ); 82 | var today = getDayMondayStart(now(new Date)); 83 | 84 | var tasks = notion.datas.filter( data => notion.groupFilter(data, "Task") ) 85 | tasks = tasks.reverse(); 86 | 87 | var items = []; 88 | //var deleteItems = []; 89 | 90 | 91 | stored.numb = 0 ; 92 | var ask = () => { 93 | stored.numb += 1; 94 | var _name = tasks[stored.numb].properties.Name.title[0].plain_text; 95 | return `How about doing ${_name}?` 96 | } 97 | 98 | ask(); 99 | 100 | moreAction["inspectOldTasks"] = _entitie =>{ 101 | //channel.send(" - "); 102 | return ask(); 103 | } 104 | 105 | noAction["inspectOldTasks"] = _entitie =>{ 106 | return ask(); 107 | } 108 | 109 | yesAction["inspectOldTasks"] = async _entitie =>{ 110 | if( ["perfect","that","awesome"].includes( _entitie.yes.substring() ) ){ 111 | // 0. Add new blocks 112 | var names = items.map( item => item.Name.title[0].plain_text ) 113 | var blocks = names.map( item =>{return { 114 | object:'block', 115 | type : 'to_do', 116 | to_do : {text :[{type : 'text', text : {content: item }} ] } 117 | }}) 118 | await notion.appendChild( columns.at(today) , blocks ); 119 | // 1. Remove original blocks 120 | items.forEach(item=>{ 121 | deleteItem(item.id) 122 | }) 123 | 124 | // 2. send message 125 | var _embed = new MessageEmbed(); 126 | _embed.setDescription( Variable.arrayToString2(names) ); 127 | channel.send(`I just moved ${items.length} tasks to your [${worklog.properties.Name.title[0].plain_text}](${worklog.url})`) 128 | return {embeds : [_embed] } 129 | } 130 | else{ 131 | channel.send(" + "); 132 | items.push( tasks[stored.numb] ); //add 133 | return ask(); 134 | } 135 | } 136 | } 137 | 138 | 139 | export async function fromLogToTasks(){ 140 | try{ 141 | var worklog = await getTodaysWorklog(); 142 | var columns = await notion.getColumns( worklog ); 143 | var today = getDayMondayStart(now(new Date)); 144 | var TodaysColumn = columns[today]; 145 | var blocks = await notion.getChildren( TodaysColumn, {type:'to_do'} ); 146 | blocks = blocks.filter( b => !b.to_do.checked ) 147 | 148 | blocks.forEach( async b =>{ 149 | var style = { Name: b.to_do.text[0].plain_text ,Group:"Task"} 150 | await notion.createNew( process.env.NOTION_DB_ID , style , null ) 151 | await notion.deleteItem(b.id) 152 | }) 153 | return "I just moved today's left tasks to [Tasks]" 154 | 155 | 156 | }catch(err){ 157 | return "Something went wrong with fromLogToTasks():" + err.message 158 | } 159 | 160 | } 161 | 162 | 163 | 164 | 165 | 166 | export var CreateNewLog = async () =>{ 167 | try{ 168 | var BUILD = async ( _container ) =>{ 169 | var allowed = ["to_do", "heading_1","heading_2", "heading_3", "column", "column_list" ] 170 | var all = _container.body.filter( i => allowed.includes(i.type) ) 171 | all = await notion.itemFilter( all, {checked: true } ); 172 | var leftTodo = all.filter(item => item.type == 'to_do' ); 173 | _container.header = _container.header.concat(leftTodo); 174 | _container.body = all.filter( item => !leftTodo.includes(item) ) 175 | return _container; 176 | } 177 | 178 | var style = { Name : mmdd( monday(new Date())) , Group : 'Log', icon : '📙'} 179 | style.Date = {start : monday(new Date()).format('L') ,end : sunday(new Date()).format('L') } 180 | 181 | yesAction["CreateNewLog"] = async() =>{ 182 | var newPage = await notion.createNew( process.env.NOTION_DB_ID, style ,BUILD ); 183 | notion.datas.push(newPage) 184 | // if( newPage.children.length > 0 ){await notion.spreadItem( newPage , 7 );} 185 | 186 | channel.send(`Here it is!`) 187 | var _embed = new MessageEmbed() 188 | _embed.setDescription(` [📒${style.Name}](${newPage.url}) `); 189 | return {embeds : [_embed] } 190 | } 191 | 192 | return `Do you want me to create new 📒log?` 193 | 194 | 195 | }catch(error){ 196 | return "Something went wrong 👉",error.message 197 | } 198 | 199 | 200 | } 201 | 202 | export async function clearChannel(){ 203 | Promise.resolve( await channel.messages.fetch({limit: 100}) ) 204 | .then( fetched =>{ 205 | channel.bulkDelete(fetched); 206 | //channel.send(`Awesome New Beginning❤️`) 207 | }) 208 | } 209 | 210 | var getCalendar = (wit_datetime ) =>{ 211 | return [wit_datetime].map( t => { 212 | var _t = t.split("T"); 213 | return { 214 | min: _t[1].split(":")[1], 215 | hour: _t[1].split(":")[0], 216 | day : _t[0].split("-")[2], 217 | month : _t[0].split("-")[1], 218 | year : _t[0].split("-")[0], 219 | } 220 | })[0]; 221 | } 222 | 223 | 224 | export async function initCrons( pages ){ 225 | 226 | pages = pages.filter( d => d.properties.Unit.select == null || ['minute','hour','day'].includes(d.properties.Unit.select.name) ) 227 | pages.forEach( async reminder => { 228 | //var repeat = reminder.properties.Reapeat["multi_select"].map( r => r.name) 229 | // console.log(repeat) 230 | var cronTime = reminder.properties['Cron Time'].formula.string; 231 | var cron = new CronJob( cronTime , async function(){ 232 | await sendNotification(reminder.id) 233 | } , null, null , process.env.TIMEZONE); 234 | allCrons.push(cron); 235 | cron.start(); 236 | }) 237 | } 238 | 239 | export async function restartCron(){ 240 | allCrons.forEach( cron => cron.stop()) 241 | allCrons = [] 242 | var reminders = await notion.datas.filter( data => notion.groupFilter(data,"Reminder" ) ) 243 | const currentHour = moment().tz(process.env.TIMEZONE).format('H'); 244 | const isDaytime = currentHour <= 23 && currentHour >= 9; 245 | if( isDaytime ){ initCrons(reminders); } 246 | } 247 | 248 | async function sendNotification(page_id){ 249 | var page = await notion.getPageById(page_id) 250 | var name = page.properties['Name'].title[0].plain_text 251 | var blocks = await notion.getChildren( page ); 252 | blocks = blocks.map( block => { 253 | return { [block.type]: 254 | block.type =="image" ? block[block.type].external.url : 255 | block.type =="video" ? block[block.type].external.url : 256 | block[block.type].text.map( item => item.plain_text)[0] }}) 257 | .filter( block => Object.values(block)[0] != undefined ) 258 | 259 | var scripts = blocks.filter( block => Object.keys(block)[0] == "callout" ) 260 | var messages = blocks.filter( block => !scripts.includes(block)) 261 | scripts = scripts.map( block => Object.values(block)[0] ) 262 | messages = messages.map( block => Object.values(block)[0] ) 263 | var message = messages.length > 0 ? messages[ Math.floor( messages.length * Math.random() )] : "it's time for "+ name +" ✨" 264 | channel.send( message ); 265 | 266 | if(scripts.length >0 ){ 267 | await eval(scripts[0]) 268 | } 269 | 270 | 271 | //intervals.push(setInterval(sendMessage, 3000 )) 272 | 273 | } 274 | 275 | export async function respondYes( _entitie, _id ){ 276 | if (_id){ 277 | try{ 278 | var respond = await yesAction[_id](_entitie) ; 279 | delete yesAction[_id] 280 | return respond; 281 | }catch(err){ 282 | return "Sorry, I fell asleep. What do you want?" 283 | } 284 | } 285 | else{ 286 | var arr = Object.values(yesAction); 287 | if( arr.length > 0 ){ 288 | return await arr.at(-1)(_entitie); 289 | } 290 | else{ 291 | return yesEmojies[Math.floor(Math.random() * yesEmojies.length )] 292 | } 293 | } 294 | 295 | } 296 | export async function respondNo( _entitie , _id ){ 297 | console.log("NO") 298 | if (_id){ 299 | try{ 300 | var respond = await noAction[_id](_entitie) ; 301 | return respond; 302 | }catch(err){ 303 | return "Sorry, I fell asleep. What do you want?" 304 | } 305 | } 306 | else{ 307 | var arr = Object.values(noAction); 308 | if( arr.length > 0 ){ 309 | return arr.at(-1); 310 | } 311 | else{ 312 | return noEmojies[Math.floor(Math.random() * noEmojies.length )] 313 | } 314 | } 315 | 316 | } 317 | // Repeating Action 318 | export async function requestMore(_entitie){ 319 | try{ 320 | const latestMore = Object.values(moreAction)[0] 321 | return latestMore(_entitie) 322 | }catch(err){ 323 | 324 | } 325 | } 326 | 327 | export async function requestStop(_entitie){ 328 | yesAction={}, noAction= {} , moreAction ={} 329 | return "No problem!" 330 | } 331 | 332 | export async function createReminder(entitie){ 333 | 334 | // 0. sort 335 | var _agenda = entitie.agenda_entry ? entitie.agenda_entry : "something" ; 336 | var style = { Name : _agenda , Group: 'Reminder' , icon: "🔔"}; 337 | if('duration' in entitie){ 338 | //it's recurring task 339 | style.Unit = Object.keys(entitie.duration)[0] ; 340 | style.Recurring = Object.values(entitie.duration)[0] ; 341 | console.log( style.Recurring ) 342 | } 343 | if('datetime' in entitie ){ 344 | //it's one time event 345 | var CAL = getCalendar(entitie.datetime); 346 | console.log(CAL) 347 | // style.Date = {start : CAL.year +"-" + CAL.month +"-"+CAL.day , end: null } 348 | style.Date = {start : entitie.datetime , end: null } 349 | //console.log(CAL , style.Date ) 350 | } 351 | 352 | yesAction["createReminder"] = async() =>{ 353 | 354 | // 1. add notion 355 | var page = await notion.createNew( process.env.NOTION_DB_ID , style ,null ); 356 | notion.datas.push(page) 357 | var cronTime = await page.properties['Cron Time'].formula.string ; 358 | 359 | // 2. set Cron 360 | var newCron = new CronJob(cronTime ,()=>{ 361 | channel.send( _agenda ) 362 | },null, null , process.env.TIMEZONE); 363 | allCrons.push(newCron); 364 | newCron.start(); 365 | const _embed = new MessageEmbed().setDescription(`[🔔${_agenda}](${page.url})`) 366 | channel.send({embeds : [_embed] }) 367 | return "I added a new reminder for you!" 368 | } 369 | // 1. check up message 370 | var _embed = new MessageEmbed(); 371 | _embed.setTitle("🔔New Reminder"); 372 | var text; 373 | if('duration' in entitie){ 374 | text = `${ style.Name } : every ${ style.Recurring.toString()+' ' + style.Unit.toString() } ` 375 | } 376 | if('datetime' in entitie){ 377 | text = `I can remind you ** ${ moment(style.Date.start).fromNow() } ** for ** ${ style.Name } ** ` 378 | } 379 | _embed.setDescription(text); 380 | 381 | channel.send("Want me to create like this?") 382 | return {embeds : [_embed] } 383 | 384 | } 385 | 386 | 387 | export var tellMeAboutReminders = async () =>{ 388 | try{ 389 | // 1. get 390 | var reminders = await notion.datas.filter( data => notion.groupFilter(data,"Reminder" ) ) 391 | reminders = reminders.map( item => { 392 | return { Name : item.properties.Name.title[0].plain_text, 393 | Date : item.properties.Date.date ? item.properties.Date.date.start : null , 394 | Recurring : item.properties.Recurring ? item.properties.Recurring.number : null , 395 | Unit : item.properties.Unit.select ? item.properties.Unit.select.name : null , 396 | URL : item.url, 397 | id: item.id, 398 | cronTime: item.properties['Cron Time'].formula.string 399 | } 400 | 401 | }) 402 | 403 | // 2. Create Message 404 | var _embed = new MessageEmbed; 405 | _embed.setTitle("⏰ All Reminders") 406 | var _arr = [] 407 | 408 | for( var i = 0 ; i < reminders.length ; i ++ ){ 409 | //var _time = reminders[i].Date; 410 | //var _name = reminders[i].Name 411 | //_embed.addFields({ name : _name , value : `[ ${i}. ${reminders[i].cronTime} ](${reminders[i].URL} )` } ) 412 | _arr.push(`[ ${reminders[i].Name} ](${reminders[i].URL} ) ${reminders[i].cronTime} `) 413 | } 414 | _embed.setDescription(await Variable.arrayToString2(_arr) ); 415 | 416 | stored.datas = reminders; 417 | return {embeds : [_embed] }; 418 | 419 | }catch(err){ 420 | return err.message 421 | } 422 | 423 | } 424 | 425 | // ⬜ Need Update 426 | export var deleteSelected = async ( _entities ) =>{ 427 | 428 | if( !stored.datas ){ 429 | channel.send("hmmm.... delete from where? 😗❔ ") 430 | } 431 | 432 | else{ 433 | channel.send("I can delete if you want!") 434 | yesAction["tellMeAboutReminders"] = async () =>{ 435 | var numbers = _entities['number'] 436 | numbers = numbers.map( numb => numb.value ) 437 | for (var i = 0; i < numbers.length ; i ++ ){ 438 | var ID = numbers[i] 439 | ID= stored.datas[ID].id 440 | await notion.deleteItem( ID ) 441 | } 442 | channel.send( 'Mission Complete! I deleted ' + numbers.length + " items 🙌" ) 443 | } 444 | 445 | } 446 | 447 | } 448 | 449 | 450 | var lineChange = ` 451 | ` 452 | 453 | /* 454 | export var spreadTodo = async ()=>{ 455 | var pages = await notion.getPages( notion.databases["Worklog"] ); 456 | var latest = pages[0]; 457 | notion.spreadItem(latest , 7 ); 458 | }*/ 459 | 460 | export var tweetThat = async ( textBody , mediaURLs ) =>{ 461 | if(!textBody){ 462 | await channel.messages.fetch( {limit:5} ).then( messages =>{ 463 | // 0. Clean up 464 | messages = messages.filter( msg => !msg.author.bot ); 465 | var keys = Array.from(messages.keys()) 466 | 467 | // 1. Assign 468 | textBody = messages.get(keys[1]).content.length != 0 ? 469 | messages.get(keys[1]).content : messages.get(keys[2]).content; 470 | 471 | mediaURLs = messages.get(keys[1]).attachments.size ? 472 | messages.get(keys[1]).attachments : 473 | messages.get(keys[2]).attachments.size ? 474 | messages.get(keys[2]).attachments : new Map() ; 475 | 476 | if( mediaURLs.size > 0 ){ 477 | mediaURLs = Array.from( mediaURLs.values() ) 478 | mediaURLs = mediaURLs.map( media => media.attachment )} 479 | }) 480 | 481 | } 482 | 483 | // 2. set Post Tweet 484 | yesAction["tweetThat"] = () =>{ 485 | tweet( textBody, mediaURLs ) 486 | } 487 | // 2. Create Message 488 | var _embed = new MessageEmbed().setTitle("💬 Your Tweet ") 489 | .setDescription(textBody); 490 | 491 | if(mediaURLs){_embed.setImage(mediaURLs[0])} 492 | return {embeds : [_embed] } 493 | 494 | } 495 | 496 | //https://github.com/devfacet/weather 497 | export function getWeather( _embeded , _city ){ 498 | return new Promise(async (resolve,error)=>{ 499 | weather.find({search: _city, degreeType: 'F'}, function(err, result) { 500 | var data = result[0]; 501 | _embeded 502 | .setThumbnail(data.current.imageUrl) 503 | .addField("Sky Condition", data.current.skytext, true) 504 | .addField("Temperature", data.current.temperature, true) 505 | .addField("Day", data.current.day, true) 506 | resolve(_embeded) 507 | }); 508 | }) 509 | } 510 | 511 | var witTimeToDate = _witTime =>{ 512 | return new Date(_witTime.split('T')[0].replace('-',',')) 513 | } 514 | 515 | export var TellMeAboutTasks = async (_entitie) =>{ 516 | try{ 517 | var date = 'datetime' in _entitie ? witTimeToDate(_entitie.datetime) : new Date() 518 | var day = moment().tz(process.env.TIMEZONE).format('d') 519 | day = day == 0 ? 6: day - 1; //start of the week is monday 520 | 521 | var worklog = await getTodaysWorklog(); 522 | var columns = await notion.getColumns( worklog ) ; 523 | 524 | var [allTodo, leftTodo] = await getTasks(day,columns ) 525 | var text = "" ; 526 | var _newEmbed = await new MessageEmbed(); 527 | _newEmbed.setTitle ( "🌈 " + date.toDateString() ); 528 | if("how_many" in _entitie){ 529 | text = "You have " + leftTodo.length.toString() +"/" + allTodo.length.toString() +" tasks" ; 530 | } 531 | else{ 532 | stored.datas = await !"remain" in _entitie ? allTodo : leftTodo ; 533 | text = await notion.blocks_to_text( stored.datas ); 534 | } 535 | text += lineChange += `[📙${worklog.properties.Name.title[0].plain_text}](${worklog.url})` 536 | _newEmbed.setDescription( text ); 537 | var nextColumn = columns[Math.min(day + 1, columns.length)] 538 | setTimeout(()=>{askBusy( 10 ,leftTodo , nextColumn )}, 1000) 539 | return {embeds : [_newEmbed] } 540 | 541 | }catch(error){ 542 | return "😧Something went wrong "+ error.message 543 | } 544 | 545 | 546 | } 547 | 548 | var getTasks = async( day , columns ) =>{ 549 | //var columns = await notion.getColumns( page ); 550 | var TodaysColumn = columns[day]; 551 | var allTodo = await notion.getChildren( TodaysColumn, {type:'to_do'} ); 552 | allTodo = await allTodo.filter(b => b.to_do ) 553 | var leftTodo = await allTodo.filter( b => !b.to_do.checked ); 554 | return [allTodo, leftTodo]; 555 | } 556 | 557 | 558 | 559 | var notionDateToDate = (stringDate) =>{ 560 | var Cal = stringDate.split("-").map(i => parseInt(i) ) 561 | return new Date(Cal[0], Cal[1]-1, Cal[2]); // ⬜ month number seems larger... 562 | } 563 | 564 | 565 | export var TellMeAboutProject = async (_entitie)=>{ 566 | try{ 567 | var AllProjects = await notion.datas.filter( data => notion.groupFilter(data,"Project" ) ) 568 | var Now = new Date()//now(new Date()) //timezone(new Date()) 569 | var Scheduled = AllProjects.filter( p => p.properties.Date.date != null && p.properties.Date.date.end != null ) 570 | var Completed = Scheduled.filter( p => Now.getTime() >= notionDateToDate(p.properties.Date.date.end).getTime() ) 571 | 572 | var Incompleted = Scheduled.filter( p => !Completed.includes(p)); 573 | 574 | var Project ; 575 | if ('next' in _entitie ){ 576 | Project = Incompleted[1] 577 | } 578 | else if ( 'previous' in _entitie ){ 579 | Project = Completed.at(-1) 580 | } 581 | else{ 582 | Project = Incompleted[0] 583 | } 584 | 585 | if( Project ){ 586 | //found 587 | var title = Project.properties.Name.title[0].plain_text; 588 | var start = Project.properties.Date.date.start; 589 | var end = Project.properties.Date.date.end; 590 | //var Now = now(new Date()) 591 | var leftDays = Math.floor( (notionDateToDate(end) - new Date() )/(1000 * 60 * 60 * 24) ); 592 | leftDays = leftDays < 2 ? leftDays.toString() +" day" :leftDays.toString() +" days" 593 | 594 | var _embeded = new MessageEmbed() 595 | _embeded.setDescription(`[ 🏞️ **${title}** ](${Project.url})`) 596 | var text =[] 597 | 598 | if('next' in _entitie){ 599 | const startIn = moment(start).endOf('day').fromNow(); 600 | text.push("◽ Start in " + startIn) 601 | } 602 | else if ( 'previous' in _entitie ){ 603 | text.push("◽ Started " + start ) 604 | text.push("◽ Due is " + end ) 605 | } 606 | else{ 607 | text.push("◽ Due is " + leftDays + lineChange) 608 | 609 | //_embeded.addFields({name :'⭐Left' , value : leftDays, inline : true }) 610 | } 611 | 612 | var Text = '' 613 | for(var i = 0; i < text.length; i++){ 614 | Text += text[i]; 615 | if( i != text.length ){ Text+= lineChange; } 616 | } 617 | 618 | _embeded.addFields({name :'Information' , value : Text }) 619 | return {embeds : [_embeded] } 620 | } 621 | else{ 622 | return ( 623 | `You don't have any specific project assigned! 624 | Do anything you like!❤`) 625 | } 626 | 627 | }catch(err){ 628 | return err.message 629 | } 630 | 631 | 632 | 633 | } 634 | 635 | 636 | 637 | 638 | export async function TellMeAboutLocation(_entitie){ 639 | var location = "location" in _entitie ? _entitie.location.name : "Vancouver" 640 | 641 | // 1. Create Embed 642 | var _newEmbed = new MessageEmbed(); 643 | _newEmbed.setTitle( "🗺️ " + location ); 644 | 645 | if( "time" in _entitie ){ 646 | var requestTime = new Date(); 647 | var localTime = moment.tz( requestTime , _entitie.location.timezone ).format('LT'); 648 | _newEmbed.setFields({name : "Local Time", value : localTime} ) 649 | } 650 | 651 | if("weather" in _entitie){ 652 | _newEmbed = await getWeather(_newEmbed , location ); 653 | } 654 | 655 | // 2. Send 656 | if(!_newEmbed.description && !_newEmbed.fields ){} 657 | else{ return {embeds : [_newEmbed] }} 658 | 659 | } 660 | 661 | export async function getGIF(search_term){ 662 | return new Promise(async (resolve, err)=>{ 663 | var url = `http://api.giphy.com/v1/gifs/search?q=${search_term}&api_key=${process.env.GIPHY_KEY}&limit=10` 664 | fetch(url) 665 | .then( response =>response.json()) 666 | .then(content => { 667 | var rand = Math.floor(content.data.length * Math.random()) 668 | var imgURL = content.data[rand].images.downsized.url; 669 | resolve(imgURL) 670 | }) 671 | }) 672 | } 673 | 674 | export async function getRecipe(_keywords){ 675 | console.log( "🌿" ) 676 | var _keyword = _keywords ? _keywords : "healthy" 677 | var URL = 'https://tasty.co/search?q='+_keyword+'&sort=popular' 678 | var _selector ='.feed-item__img-wrapper'; 679 | 680 | moreAction["getRecipe"] = async ()=> { 681 | 682 | 683 | const browser = await puppeteer.launch({ 684 | args: ['--no-sandbox','--disable-setuid-sandbox'] 685 | }) 686 | const page = await browser.newPage(); 687 | await page.goto(URL, {waitUntil: 'networkidle2'}); 688 | 689 | var contents = await page.$$(_selector); 690 | var random = contents[Math.floor(contents.length * Math.random())] 691 | var alink = await random.getProperty('parentNode') 692 | alink = await alink.getProperty("href"); 693 | alink = alink._remoteObject.value 694 | 695 | await page.goto( alink,{waitUntil: 'networkidle2'}) 696 | 697 | const recipe = {}; 698 | 699 | recipe.ingredients = await page.$$eval('.ingredient', el => el.map( el=> el.textContent) ) 700 | recipe.ingredients = Variable.arrayToString(recipe.ingredients) 701 | 702 | recipe.instruction = await page.$$eval('.xs-mb2', els => { 703 | return els.filter( el => el.classList.length == 1 ) 704 | .map(el=> el.textContent) 705 | }) 706 | recipe.instruction = recipe.instruction.slice(recipe.instruction.length/2) 707 | recipe.instruction = Variable.arrayToString2(recipe.instruction) 708 | 709 | recipe.thumbnail = await page.$eval('.video-js',el => el.getAttribute('poster') ) 710 | recipe.video = await page.$eval('source',el => el.src ) 711 | await browser.close; 712 | 713 | // Send 714 | var _embeded = new MessageEmbed().setTitle(` 👩‍🍳💘 Recipe of Love `) 715 | _embeded.setImage( recipe.thumbnail ); 716 | _embeded.addFields( {name :"ingredients" , value : recipe.ingredients , inline:true }) 717 | _embeded.addFields( {name :"instruction" , value : recipe.instruction , inline:true }) 718 | channel.send(recipe.video); 719 | return {embeds : [_embeded] } 720 | } 721 | try{ 722 | return moreAction["getRecipe"](); 723 | }catch(error){ 724 | return "Something went wrong with.. "+ error.message 725 | } 726 | 727 | } 728 | 729 | export async function TellMeAboutSocialStat(_entitie){ 730 | return 'Sorry, This feature is not available' 731 | /* 732 | try{ 733 | 734 | var stats = {} 735 | 736 | const browser = await puppeteer.launch({ 737 | args: ['--no-sandbox','--disable-setuid-sandbox'] 738 | }) 739 | const page = await browser.newPage(); 740 | 741 | var URL = 'https://www.instagram.com/happping_min/' 742 | const navigationPromise = page.waitForNavigation({waitUntil: "domcontentloaded"}); 743 | await page.goto(URL,{ 744 | waitUntil: "load", 745 | timeout: 0, // Remove the timeout 746 | }); 747 | await page.screenshot({ path: 'temp/tmp_1.png' }) 748 | await navigationPromise; 749 | await page.waitForSelector('.Y8-fY' ); //, { timeout: 6000 } 750 | stats.instagram = await page.$$eval('.Y8-fY', els => els.map(el => el.textContent ) ); //posts, followers, following 751 | //stats.instagram = stats.instagram[1]; 752 | console.log("🍝stats", stats ) 753 | 754 | 755 | //⬜ tiwtter not working now 756 | 757 | var URL = 'https://twitter.com/happping_min' 758 | console.log("💛URL", URL ) 759 | await page.goto(URL); 760 | await navigationPromise; 761 | // await page.waitForSelector('.css-4rbku'); 762 | //stats.twitter = await page.$$eval('.css-4rbku5', els => els.map(el => el.textContent ).filter(el => el.includes("Followers") ) ); 763 | //stats.twitter = stats.twitter[0] 764 | 765 | var _embeded = new MessageEmbed() 766 | Object.keys(stats).forEach(key =>{ 767 | if(stats[key]){ 768 | _embeded.addFields( {name :`❤️${key}` , value : stats[key] }) 769 | } 770 | }) 771 | await browser.close; 772 | return {embeds : [_embeded] } 773 | 774 | 775 | }catch(err){ 776 | return err.message 777 | } 778 | */ 779 | 780 | } 781 | 782 | 783 | export async function getTodaysWorklog(){ 784 | return new Promise(async (resolve,error)=>{ 785 | var worklogs = await notion.datas.filter( data => notion.groupFilter(data,"Log" ) ) 786 | var start = notionDateToDate(worklogs[0].properties.Date.date.start); 787 | var end = notionDateToDate(worklogs[0].properties.Date.date.end); 788 | var Now = new Date()//now(new Date()); 789 | 790 | if( Now.getTime() <= end.getTime() && Now.getTime() >= start.getTime() ){ 791 | resolve(worklogs[0]); 792 | } 793 | else{ 794 | channel.send("There are no worklog for this week."); 795 | CreateNewLog(); 796 | } 797 | }) 798 | } 799 | 800 | 801 | //|| isThisWeek(new Date(t.properties["Last Added"].date.start)) 802 | 803 | export async function botIn(){ 804 | channel.send("Hey I came back!❤️") 805 | // 0. get the last message on the channel and if it's today, -> cron, if not, skip 806 | Promise.resolve( await channel.messages.fetch({limit: 1}) ) 807 | .then(messages =>{ 808 | var lastmsg_date = moment(messages.last().createdTimestamp).tz(process.env.TIMEZONE) 809 | var now = moment().tz(process.env.TIMEZONE) 810 | if(lastmsg_date.format('MMDD') == now.format('MMDD') ){ 811 | restartCron() 812 | intervals.push( setInterval( ()=>{ restartCron()} , 1000 * 60 * 60) ) //every 1 hour reset 813 | } 814 | }) 815 | } 816 | 817 | async function addScheduledTasks( columns, day ){ 818 | var tasks = await notion.datas.filter(data => notion.groupFilter(data,"Task") ) 819 | tasks = tasks.filter( t => t.properties['Next Date'].formula.date != null ) 820 | tasks = tasks.filter ( t => isThisWeek(new Date( t.properties['Next Date'].formula.date.start) ) ) 821 | 822 | for(var i = day; i < 7; i++){ 823 | var Tasks_of_the_day = tasks.filter( t =>{ 824 | if( t.properties.Unit.select != null && t.properties.Unit.select.name =="day" ){ return true; } 825 | else{ return getDayMondayStart(moment(t.properties['Next Date'].formula.date.start)) == i } 826 | }) 827 | 828 | var Tasks_on_the_log = await notion.getChildren( columns[i] ) 829 | Tasks_on_the_log = await Tasks_on_the_log.filter(t => t.type =="to_do" && t["to_do"].text.length > 0 ).map( t=> t["to_do"].text[0].plain_text) 830 | 831 | Tasks_of_the_day = Tasks_of_the_day.filter( t=> !Tasks_on_the_log.includes( t.properties.Name.title[0].plain_text )) 832 | 833 | if( Tasks_of_the_day.length > 0 ){ 834 | var blocks = Tasks_of_the_day.map( t =>{return { 835 | object:'block', 836 | type : 'to_do', 837 | to_do : {text :[{type : 'text', text : {content: t.properties.Name.title[0].plain_text }} ] } 838 | }}) 839 | await notion.appendChild( columns[i] , blocks ); 840 | 841 | Tasks_of_the_day.forEach(t =>{ 842 | if(t.properties.Unit.select == null){ // not repeating task will be delete 843 | console.log("Delete" , t.properties.Name.title[0].plain_text ) 844 | notion.deleteItem(t.id) 845 | } 846 | }) 847 | } 848 | } 849 | } 850 | 851 | export async function userIn(){ 852 | 853 | try{ 854 | restartCron() 855 | intervals.push( setInterval( ()=>{ restartCron()} , 1000 * 60 * 60) ) //every 1 hour reset 856 | 857 | 858 | // 1. Random Message 859 | var messages = ['Hello!','You came back!',"Hey Darling!"]; 860 | channel.send( messages[Math.floor( Math.random() * messages.length )]); 861 | 862 | // 2. Create Today's log 863 | var _embed = new MessageEmbed() 864 | _embed.setTitle(` ♥ Let's start Today `) 865 | _embed = await getWeather( _embed , 'Vancouver, BC'); 866 | 867 | var worklog = await getTodaysWorklog(); 868 | var columns = await notion.getColumns( worklog ) ; //page 869 | var day = moment().tz(process.env.TIMEZONE).format('d') 870 | //new Date().getDay() 871 | day = day == 0 ? 6: day - 1; 872 | console.log(day) 873 | addScheduledTasks( columns, day ); 874 | 875 | var [allTodo , leftTodo] = await getTasks(day, columns ) ; 876 | var todos = await notion.blocks_to_text(allTodo); 877 | _embed.addFields({name : "Tasks", value : todos}) 878 | _embed.addFields({name : "Count", value : `${allTodo.length-leftTodo.length}/${allTodo.length}`}) 879 | 880 | // 9. if task is too many 881 | var nextColumn = columns[Math.min(day + 1, columns.length)] 882 | askBusy(10, leftTodo , nextColumn ); 883 | return { embeds : [ _embed] } 884 | 885 | }catch(error){ 886 | return "Something went wrong 👉"+ error.message 887 | } 888 | } 889 | 890 | export async function userOut(){ 891 | var messages = ["Bye! Have a good day!" ,"See ya!"] 892 | allCrons.forEach( cron => { cron.stop() }) 893 | intervals.forEach( interval => clearInterval(interval) ) 894 | return messages[Math.floor( Math.random() * messages.length )] 895 | } 896 | 897 | 898 | async function askBusy( _maxCount , tasks , moveGoal ){ 899 | if( tasks.length > _maxCount ){ 900 | const messages = [`😲You are too busy today! 901 | Do you want me to move some tasks to tmr?`]; 902 | var leftArr = tasks.slice(_maxCount) ; 903 | yesAction["askBusy"] = async() =>{ 904 | leftArr.forEach(async task =>{ 905 | try{ 906 | await notion.parent( task, moveGoal ) 907 | }catch(error){ 908 | return`🤷🏽‍♀️ Something went wrong. 👉${error.message}` 909 | } 910 | }) 911 | channel.send('Yay! You have less tasks for today now!') 912 | } 913 | channel.send( messages[Math.floor( messages.length * Math.random() )]) 914 | } 915 | else if( tasks.length < 4 ){ 916 | var TASKS_URL = `https://www.notion.so/happpingmin/30ddc8bbffcc481cb702da35789f3cf5?v=f6894f5cc1d246a0b49179d270748e2e` 917 | channel.send(`You seem like quite free today. Maybe it is time to reopen the old task list 😆💕`) 918 | channel.send({embeds : [new MessageEmbed().setDescription(`[Tasks](${TASKS_URL})`) ]}) 919 | } 920 | } 921 | 922 | export async function SearchDictionary( mm, entitie , traits ){ 923 | 924 | try{ 925 | var myDictionaries = await notion.datas.filter( data => notion.groupFilter(data,"Dictionary" ) ) 926 | var myDictionary = myDictionaries[0] 927 | 928 | var Headers = await notion.getChildren( myDictionary ); 929 | Headers = Headers.filter( header => header[header.type].text.length > 0 && header.has_children ) 930 | var searchThese =[]; 931 | Object.values(entitie).forEach( text => text.substring().split(" ").forEach( t => searchThese.push(t) )) 932 | Headers = Headers.filter( header =>{ 933 | var header_keywords = header[header.type].text[0].plain_text.split(" ").map( text => text.toLowerCase() ); 934 | return Variable.anyisIn(searchThese , header_keywords ); 935 | }) 936 | 937 | if( Headers.length > 0 ){ 938 | // 0. get random item 939 | var header = Headers[Math.floor( Headers.length * Math.random() )] 940 | var children = await notion.getChildren( header ); 941 | // 1. get random children 942 | var ChildBlock = children[Math.floor( children.length * Math.random() )] 943 | var textElements = ChildBlock[ChildBlock.type].text ;//.map(b => b.text) 944 | 945 | if(textElements.length == 1 ){ 946 | return textElements[0].plain_text 947 | } 948 | else{ 949 | textElements = textElements.map( text => { 950 | if(text.href){ return `[${text.plain_text}](${text.href})` } 951 | else{ return text.plain_text} 952 | } ) 953 | 954 | var foundInfo = "" 955 | textElements.forEach(text => foundInfo += text ) 956 | 957 | // 2. Send 958 | 959 | var _embed = await new MessageEmbed(); 960 | _embed.setTitle(header[header.type].text[0].plain_text); 961 | _embed.setDescription( foundInfo ); 962 | channel.send('Maybe check this out?') 963 | return {embeds : [_embed] } 964 | } 965 | 966 | } 967 | 968 | else if( "emotion" in entitie || Variable.anyisIn(["positive","negative"], Object.values(traits))){ 969 | return await getGIF( mm ) 970 | } 971 | 972 | }catch(err){ 973 | channel.send("Something went wrong on SearchDictionary()") 974 | return (err.message) 975 | } 976 | } 977 | 978 | export async function SearchGoogle(mm){ 979 | try{ 980 | var URL = "https://www.google.com/search?q=" + mm ; 981 | const browser = await puppeteer.launch({ 982 | args: ['--no-sandbox','--disable-setuid-sandbox'] 983 | }) 984 | const page = await browser.newPage(); 985 | await page.goto(URL, {waitUntil: 'networkidle2'}); 986 | var classList = ['.V3FYCf','.hgKElc','.hb8SAc'] 987 | var explain = "" ; var i = 0; 988 | do{ 989 | var el = await page.$(classList[i]) 990 | if(el){explain = await el.evaluate(el=>el.textContent )} 991 | i++; 992 | }while( explain == "" ) 993 | 994 | var _embed = await new MessageEmbed(); 995 | if(explain.length > 0){ 996 | explain += lineChange + `[Link](${page._target._targetInfo.url})` 997 | _embed.setTitle ( "😎 " + mm ); 998 | _embed.setDescription(explain); 999 | } 1000 | else{ 1001 | _embed.setDescription(`[Link](${page._target._targetInfo.url})`); 1002 | } 1003 | await browser.close; 1004 | return {embeds : [_embed] }; 1005 | }catch(err){ 1006 | channel.send(err.message) 1007 | return "Hmmmm.. Something went wrong on SearchGoogle()"; 1008 | } 1009 | } 1010 | 1011 | 1012 | export async function ReadSlowly( URL ){ 1013 | var data = await axios.get(URL).then( res => { 1014 | return extractor.lazy(res.data) 1015 | }) 1016 | var texts = data.text().split(lineChange).filter( t => t!= '').map(t => t.split('.')) 1017 | var ArticleBody = [] 1018 | if( data.image() ){ ArticleBody.push(data.image()) } 1019 | texts.forEach( text =>{ text.forEach(t => ArticleBody.push(t))}) 1020 | ArticleBody = ArticleBody.filter(t=> t.length > 0 ) 1021 | if(ArticleBody.length > 0 ){ 1022 | var i = 0; 1023 | yesAction['ReadSlowly'] = () =>{ 1024 | const row = new MessageActionRow() 1025 | .addComponents( 1026 | new MessageButton() 1027 | .setCustomId('ReadSlowly') 1028 | .setLabel('Next') 1029 | .setStyle('SUCCESS'), 1030 | ); 1031 | 1032 | var content = {content : ArticleBody[i] } 1033 | 1034 | i += 1; 1035 | 1036 | if( i == ArticleBody.length){ 1037 | channel.send("Article is finished") 1038 | delete yesAction['ReadSlowly'] 1039 | } 1040 | else{ 1041 | content.components = [row] 1042 | } 1043 | return content; 1044 | } 1045 | return yesAction['ReadSlowly']() 1046 | } 1047 | else{ 1048 | return "hm... I can't fetch article body" 1049 | } 1050 | } 1051 | 1052 | export async function helpEnglish(_word){ 1053 | var _embed = new MessageEmbed(); 1054 | var URL= ""; var text = `` 1055 | const browser = await puppeteer.launch({ 1056 | args: ['--no-sandbox','--disable-setuid-sandbox'] 1057 | }) 1058 | const page = await browser.newPage(); 1059 | 1060 | // 0. Get Meaning 1061 | try{ 1062 | URL = `https://dic.daum.net/search.do?q=${_word}&dic=eng` 1063 | await page.goto(URL, {waitUntil: 'networkidle2'}); 1064 | await page.waitForSelector('.list_mean'); 1065 | var meaning = await page.$eval('.list_mean', el=> el.textContent.trim() ) 1066 | if(meaning==""){ 1067 | _word = await page.$eval('.link_speller', el=> el.textContent) 1068 | URL = `https://dic.daum.net/search.do?q=${_word}&dic=eng` 1069 | await page.goto(URL, {waitUntil: 'networkidle2'}); 1070 | await page.waitForSelector('.list_mean'); 1071 | meaning = await page.$eval('.list_mean', el=> el.textContent.trim() ) 1072 | } 1073 | //await page.screenshot({ path: 'temp/tmp_1.png' }) 1074 | text += `**[${_word}](${URL})**\n` 1075 | text += meaning; 1076 | }catch(err){ 1077 | channel.send("Sorry, I am having trouble with a Dictionary😭 ", err.message) 1078 | } 1079 | 1080 | // 1. Get Synonyms 1081 | try{ 1082 | URL = 'https://www.thesaurus.com/browse/' + _word 1083 | await page.goto( URL, {waitUntil: 'networkidle2'}); 1084 | var synonym = await page.$$eval(".css-1kg1yv8", els =>els.map(el => el.textContent) ); 1085 | text += `\n`+ synonym 1086 | }catch(err){ 1087 | channel.send("Sorry, I am having trouble with Saurus😭", err.message) 1088 | } 1089 | await browser.close; 1090 | 1091 | _embed.setDescription(text) 1092 | return {embeds : [_embed] }; 1093 | } 1094 | 1095 | -------------------------------------------------------------------------------- /script/action/Chat.js: -------------------------------------------------------------------------------- 1 | import * as Wit from '../handlers/wit-handler.js' 2 | import * as Action from './Actions.js' 3 | import { channel } from '../../bot.js'//'../handlers/discord-handler.js'; 4 | import * as Utils from '../extra/util.js' 5 | import fs from 'fs' 6 | export const talkDB = JSON.parse(fs.readFileSync('./script/extra/chat-dictionary.json')); 7 | const replyDB = talkDB.filter( el => el.onReply == true) 8 | 9 | //https://raw.githubusercontent.com/omnidan/node-emoji/master/lib/emoji.json 10 | export var yesEmojies = ['👍','😍','🙏','❤️',"💯","👌","💖","😃","😄","😆","😆","😀","😁","😇","😎","🔥"] 11 | export var noEmojies = ['😑','😬','👎'] 12 | 13 | export async function send( message , reference ){ 14 | var mm = message.substring(); 15 | var words = mm.split(' '); 16 | var DB = !reference ? talkDB : replyDB; 17 | var refMessage ; 18 | if( reference ){ 19 | await channel.messages.fetch(reference.messageId).then( 20 | original =>{ 21 | refMessage = original.content; 22 | } 23 | )} 24 | // 0. Clear 25 | if ( mm.substring() == "clear" ){ 26 | Action.clearChannel(); 27 | } 28 | // 1. Read Slowly 29 | else if( Utils.isValidHttpUrl( words.at(-1)) ){ // 1. if it has valid URL 30 | channel.send(await Action.ReadSlowly( words.at(-1) )) 31 | } 32 | // 2. Emoji Reaction 33 | else if( yesEmojies.includes(mm) ){ 34 | channel.send(await Action.respondYes()); 35 | } 36 | else if( noEmojies.includes(mm) ){ 37 | channel.send(await Action.respondNo()); 38 | } 39 | 40 | // 3. 41 | else{ 42 | Wit.client.message(mm).then( async ( {entities, intents, traits} ) => { 43 | 44 | var entities = Wit.entitiesFilter(entities); 45 | var intents = Wit.intentFilter(intents); 46 | var traits = Wit.traitFilter(traits); 47 | console.log("💖", entities , intents , traits) 48 | var findDB = await Wit.findIntention( entities, intents, traits, DB ) ; 49 | console.log("🐸", findDB ) 50 | 51 | if('script' in findDB ){ 52 | var response = await eval(findDB.script[0] ); 53 | channel.send( response ) 54 | 55 | }})} 56 | } 57 | 58 | 59 | 60 | export async function gotInteraction(interaction){ 61 | if( interaction.isCommand() ){ commandRequested(interaction) } 62 | else if (interaction.isButton()){ buttonPressed(interaction) } 63 | } 64 | 65 | async function commandRequested(interaction){ 66 | await interaction.deferReply(); 67 | var commandOptions = interaction.options._hoistedOptions 68 | var input = commandOptions[0].value 69 | 70 | switch(interaction.commandName){ 71 | case "eng": //✍ 72 | var response = await Action.helpEnglish(input); 73 | break; 74 | case "read": //📒 75 | var response = await Action.ReadSlowly(input); 76 | break; 77 | case "todo": //✅ 78 | var response = await Action.createNewTask({agenda_entry: input}); 79 | break; 80 | case "goog": //🔍 81 | var response = await Action.SearchGoogle(input); 82 | break; 83 | case "remind": //⏰ 84 | var response = await Action.createReminder(input); 85 | break; 86 | 87 | } 88 | await interaction.editReply( response ) 89 | } 90 | 91 | 92 | async function buttonPressed(interaction){ 93 | 94 | // 0. remove button I pressed 95 | await interaction.channel.messages.fetch( interaction.message.id ).then(async message => { 96 | await message.edit({content : message.content , components : [] }); //delete button! 97 | }).catch(err => { 98 | console.error("👉" , err.message); 99 | }); 100 | 101 | // 1. update the latest message 102 | interaction.reply('...') 103 | interaction.deleteReply(); 104 | 105 | switch( interaction.customId ){ 106 | case "Next": 107 | var content = await Action.yesAction['ReadSlowly']() 108 | channel.send(content); 109 | //await interaction.editReply(content); 110 | break; 111 | } 112 | } 113 | 114 | export async function gotReaction( reaction ){ 115 | var emoji = reaction._emoji.name; 116 | console.log( emoji ) 117 | 118 | if( yesEmojies.includes(emoji) ){ 119 | channel.send(await Action.respondYes()); 120 | } 121 | else if( noEmojies.includes(emoji) ){ 122 | channel.send(await Action.respondNo()); 123 | } 124 | 125 | } 126 | 127 | -------------------------------------------------------------------------------- /script/extra/chat-dictionary.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "entities" :["task"], "intents" : ["request_notion_read"] , "traits" :{}, 4 | "script" : ["Action.TellMeAboutTasks(entities)"] 5 | }, 6 | { 7 | "entities" :["project"], "intents" : ["request_notion_read"] , "traits" :{}, 8 | "script" : ["Action.TellMeAboutProject(entities)"] 9 | }, 10 | { 11 | "entities" :["create" ,"log"] , "intents" : ["request_notion_write"] , "traits" :{} , 12 | "script" : ["Action.CreateNewLog()"] 13 | }, 14 | { 15 | "entities" :["yes"], "intents" : ["respond"] , "traits" :{} , 16 | "script" : ["Action.respondYes(entities)"] , "onReply" : true 17 | }, 18 | { 19 | "entities" :["no"], "intents" : ["respond"] , "traits" :{} , 20 | "script" : ["Action.respondNo(entities)"] , "onReply" : true 21 | }, 22 | { 23 | "entities" :["more"], "intents" : ["request"] , "traits" :{} , 24 | "script" : ["Action.requestMore(entities)"] , "onReply" : true 25 | }, 26 | { 27 | "entities" :["stop"], "intents" : ["request"] , "traits" :{} , 28 | "script" : ["Action.requestStop(entities)"] , "onReply" : true 29 | }, 30 | { 31 | "entities" :["reminder"], "intents" : ["request_notion_write"] , "traits" :{} , 32 | "script" : ["Action.createReminder(entities )"] 33 | }, 34 | { 35 | "entities" :["twitter"], "intents" : ["request_share"] , "traits" :{} , 36 | "script": ["Action.tweetThat(refMessage)"] , "onReply" : true 37 | }, 38 | { 39 | "entities" :["reminder"], "intents" : ["request_notion_read"] , "traits" :{} , 40 | "script": [" Action.tellMeAboutReminders()"] 41 | }, 42 | { 43 | "entities" :["delete","number"], "intents" : ["request_notion_delete"] , "traits" :{} , 44 | "script": [" Action.deleteSelected( entities ); "] 45 | }, 46 | { 47 | "entities" :["hi"], "intents" : ["greeting"] , "traits" :{} , 48 | "script": ["Action.userIn()"] 49 | }, 50 | { 51 | "entities" :["bye"], "intents" : ["greeting"] , "traits" :{} , 52 | "script": ["Action.userOut()"] 53 | }, 54 | { 55 | "entities" :[], "intents" : ["request_SNS_read"] , "traits" :{} , 56 | "script": ["Action.TellMeAboutSocialStat(entities)"] 57 | }, 58 | { 59 | "entities" :["move","left","task"], "intents" : ["request_notion_write"] , "traits" :{} , 60 | "script": ["Action.fromLogToTasks()"] 61 | }, 62 | { 63 | "entities" :["move","more","task","log"], "intents" : ["request_notion_write"] , "traits" :{} , 64 | "script": ["Action.inspectOldTasks(entities)"] 65 | }, 66 | { 67 | "entities" :["create","task"], "intents" : ["request_notion_write"] , "traits" :{} , 68 | "script": ["Action.createNewTask(entities)"] 69 | }, 70 | { 71 | "entities" :[], "intents" : ["request_recipe"] , "traits" :{} , 72 | "script": ["Action.getRecipe( entities['search_query'] )"] 73 | }, 74 | { 75 | "entities" :[], "intents" : ["request_location"] , "traits" :{} , 76 | "script": ["Action.TellMeAboutLocation(entities)"] 77 | }, 78 | { 79 | "entities" :[], "intents" : [] , "traits" :{} , 80 | "script": ["Action.SearchDictionary(mm, entities , traits)"] 81 | } 82 | 83 | 84 | ] -------------------------------------------------------------------------------- /script/extra/util.js: -------------------------------------------------------------------------------- 1 | export var allisIn = (must , target ) =>{ 2 | if(Array.isArray(must) && Array.isArray(target) ){ // array mode 3 | return must.every(x => target.includes(x)) 4 | } 5 | else{ // object mode 6 | 7 | var keyIn = Object.keys(must).every( x => Object.keys(target).includes(x) ) 8 | if( keyIn ){ 9 | Object.keys(must).forEach( key =>{ 10 | keyIn= must[key]== target[key] 11 | }) 12 | } 13 | return keyIn; 14 | } 15 | } 16 | 17 | 18 | //console.log( "🐊🐊🐊" , allisIn( {value: 1}, {value : 1 })) 19 | 20 | export var anyisIn = (arr1, arr2)=>{ 21 | var bool = false; 22 | var i = 0; 23 | do { 24 | bool = arr2.includes( arr1[i] ) ; 25 | i ++; 26 | } 27 | while ( !bool && i< arr1.length); 28 | return bool; 29 | } 30 | 31 | //to sub array 32 | export function chunk (arr, len) { 33 | 34 | var chunks = [], 35 | i = 0, 36 | n = arr.length; 37 | 38 | while (i < n) { 39 | chunks.push(arr.slice(i, i += len)); 40 | } 41 | 42 | return chunks; 43 | } 44 | 45 | var lineChange = ` 46 | ` 47 | export function arrayToString( arr ){ 48 | var string = '' 49 | for(var i = 0; i < arr.length; i++ ){ 50 | if( i != 0 ){string += lineChange;} 51 | string += arr[i]; 52 | } 53 | return string 54 | } 55 | 56 | export function arrayToString2( arr ){ 57 | var string = '' 58 | for(var i = 0; i < arr.length; i++ ){ 59 | if( i != 0 ){string += lineChange;} 60 | string += i+". "+ arr[i]; 61 | } 62 | return string 63 | } 64 | 65 | export function isValidHttpUrl(string) { 66 | let url; 67 | 68 | try { 69 | url = new URL(string); 70 | } catch (_) { 71 | return false; 72 | } 73 | 74 | return url.protocol === "http:" || url.protocol === "https:"; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /script/handlers/mongo-handler.js: -------------------------------------------------------------------------------- 1 | // db 2 | import mongoose from 'mongoose'; 3 | //import { CronJob } from 'cron' 4 | 5 | mongoose.connect(process.env.MONGODB_SRV,{ 6 | useNewUrlParser : true, 7 | useUnifiedTopology : true 8 | }).then(()=>{ 9 | console.log('connected to the database') 10 | }).catch((err)=>{ 11 | console.log(err) 12 | }) 13 | 14 | 15 | class mongoDB { 16 | constructor(_name, _schema ){ 17 | this.schema = new mongoose.Schema(_schema); 18 | this.model = new mongoose.model(_name, this.schema); 19 | } 20 | 21 | async add(_properties){ 22 | var NEW = await new this.model(_properties ).save(); 23 | return NEW; 24 | } 25 | edit(filter, update){ //{ name : }, {name : } 26 | this.model.updateOne(filter , 27 | {$set: update } ) 28 | } 29 | 30 | 31 | 32 | } 33 | 34 | //Create DB 35 | /* 36 | export const reminder = new mongoDB('reminder' , 37 | { name : String, 38 | cronTime: String, 39 | script : String, 40 | message : [String], 41 | isRecurring : Boolean 42 | }); 43 | */ 44 | /* 45 | export const memory = new mongoDB('memory', 46 | { name : String, 47 | script : String 48 | })*/ 49 | 50 | //reminder.add({name : "test", cronTime : "3 * * * *", message:["test!!🐵 🧞‍♀️🐊 🐋"] }) -------------------------------------------------------------------------------- /script/handlers/notion-handler.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { Client } from '@notionhq/client' 3 | import { channel } from '../../bot.js'//'./discord-handler.js' 4 | import { chunk } from '../extra/util.js'; 5 | import { botIn } from '../action/Actions.js'; 6 | var NOTION; var BLOCKS = [] ; 7 | 8 | 9 | class notionClient{ 10 | constructor(){ 11 | NOTION = new Client( {auth : process.env.NOTION_TOKEN } ); 12 | Promise.resolve( this.getPages(process.env.NOTION_DB_ID) ).then( 13 | resolve => { 14 | this.datas = resolve; 15 | botIn() 16 | } 17 | ) 18 | 19 | } 20 | async getPageById(page_id){ 21 | return await NOTION.pages.retrieve({page_id : page_id}) 22 | } 23 | 24 | async appendChild( parent , children ){ 25 | await NOTION.blocks.children.append({ 26 | block_id: parent.id , 27 | children : children 28 | }); 29 | } 30 | 31 | async getPages( database_ID ) { 32 | 33 | var DB = await NOTION.databases.query({database_id : database_ID }) 34 | var items = DB.results 35 | .filter(log =>{ 36 | if (log.properties.Name.title[0]){ 37 | if(log.properties.Name.title[0].plain_text !=""){ 38 | return true 39 | } 40 | } 41 | }) 42 | return items; 43 | } 44 | 45 | async getColumns( page ){ 46 | var Weeks = []; 47 | 48 | var column_list = await this.getChildren( page ); 49 | column_list = column_list.filter( item => item.type=='column_list' ); 50 | 51 | for(var x = 0; x < column_list.length ; x++ ){ 52 | var column = await this.getChildren(column_list[x]) 53 | column = column.filter( item => item.type=='column' ); 54 | 55 | for ( var y = 0; y < column.length ; y++ ){ 56 | Weeks.push(column[y]) 57 | if(x+1 == column_list.length && y+1 == column.length ){ 58 | return Weeks; 59 | } 60 | } 61 | } 62 | } 63 | 64 | async modifyPage( _page , _properties ){ 65 | _properties = Object.entries(_properties).filter = ( [ key , value ] ) => _page.includes( key ) ; 66 | _properties = Object.fromEntries(_properties) 67 | } 68 | 69 | async getChildren( _block ){ 70 | if(_block.has_children || _block.object == "page"){ 71 | var children = await NOTION.blocks.children.list({ block_id: _block.id }); 72 | return children.results 73 | }else{ 74 | return [] } 75 | } 76 | 77 | 78 | 79 | async allChildren( _block , _function ){ 80 | 81 | 82 | var container = { body:[], header: [] , footer :[] }; //body, header, bottom 83 | if( _block.has_children || _block.object == "page" ){ 84 | var arr = await NOTION.blocks.children.list({ block_id: _block.id }); 85 | container.body = arr.results; 86 | if(_function ){ container = await _function(container) } 87 | 88 | arr = container.body; 89 | 90 | 91 | for( var x = 0; x < arr.length ; x++ ){ 92 | if( hasChildren(arr[x]) ){ 93 | var arr1 = await NOTION.blocks.children.list({ block_id: arr[x].id }); 94 | container.body = arr1.results; 95 | if(_function){container = await _function(container)} 96 | container.body = if_column( arr[x] , container.body); 97 | arr1 = container.body; 98 | 99 | arr[x][arr[x].type].children = arr1 100 | 101 | for( var y = 0; y < arr1.length ; y++ ){ 102 | if( hasChildren(arr1[y]) ){ 103 | var arr2 = await NOTION.blocks.children.list({ block_id: arr1[y].id }); 104 | container.body = arr2.results; 105 | if(_function){container = await _function(container)} 106 | container.body = if_column( arr1[y] , container.body); 107 | arr2 = container.body; 108 | arr1[y][arr1[y].type].children = arr2; 109 | 110 | for( var z = 0; z < arr2.length ; z++ ){ 111 | if( hasChildren(arr2[z]) ){ 112 | var arr3 = await NOTION.blocks.children.list({ block_id: arr2[z].id }); 113 | container.body = arr3.results; 114 | if(_function){container = await _function(container)} 115 | container.body = if_column( arr2[z] , container.body); 116 | arr3 = container.body; 117 | arr2[z][arr2[z].type].children = arr3; 118 | 119 | for( var w = 0; w < arr3.length ; w++ ){ 120 | if( hasChildren(arr3[w]) ){ 121 | var arr4 = await NOTION.blocks.children.list({ block_id: arr3[w].id }); 122 | container.body = arr4.results; 123 | if(_function){container = await _function(container)} 124 | container.body = if_column( arr3[w] , container.body); 125 | arr4 = container.body; 126 | arr3[w][arr3[z].type].children = arr4; 127 | } 128 | } 129 | 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | arr = container.header.concat(arr) 137 | if( !_block.type ) { _block.type = "children" } 138 | if(!_block.type){ _block[_block.type].children = arr } 139 | else{ _block.children = arr; } 140 | return _block; 141 | } 142 | } 143 | 144 | block_to_text( _block ){ 145 | var text = "" 146 | if( _block.type == "to_do" && "to_do" in _block ){ 147 | text += (!_block.to_do.checked) ? "[ ] " : "[x] " ; 148 | } 149 | var Type = _block[_block.type] ; 150 | if( Type && Type.text && Type.text.length > 0 ){ 151 | var t = Type.text[0].plain_text 152 | text += t; 153 | } 154 | return text; 155 | } 156 | 157 | async blocks_to_text( _blocks ){ 158 | var text = "" 159 | _blocks.forEach( b =>{ 160 | text += this.block_to_text(b) + "" + lineChange; 161 | }) 162 | return text; 163 | } 164 | 165 | groupFilter( data , value){ 166 | return data.properties.Group.select != null && data.properties.Group.select.name == value; 167 | } 168 | 169 | async createNew( DATABASE_ID , style , BUILD ){ 170 | var pages = this.datas.filter(d => this.groupFilter(d, style.Group) ) 171 | //var pages = await this.getPages(DATABASE_ID) 172 | var latest = pages[0]; 173 | latest = await this.allChildren( latest , BUILD ) 174 | style.children = latest.children; 175 | var info = await newPageInfo( latest , style ); 176 | return await NOTION.pages.create( info ); 177 | } 178 | 179 | async deleteItem( block_ID ){ 180 | await NOTION.blocks.delete({block_id: block_ID}); 181 | } 182 | 183 | 184 | async spreadItem( page , _maxcount ){ 185 | // get all chldren 186 | var children = await this.getChildren( page ); 187 | children = children.filter( child => !["column","column_list"].includes(child.type) ) 188 | var Chunks = chunk( children, _maxcount); 189 | 190 | // get columns 191 | var columns = await this.getColumns( page ); 192 | 193 | // create new 194 | for(var x = 0; x < Chunks.length; x ++){ 195 | x = Chunks.length > columns.length ? columns.length : Chunks.length ; 196 | for(var y = 0; y < Chunks[x].length; y ++ ){ 197 | await NOTION.blocks.children.append({ 198 | block_id : columns[x].id, children :[ duplicatedBlock( Chunks[x][y] ) ] 199 | }) 200 | await NOTION.blocks.delete({block_id : Chunks[x][y].id }) 201 | } 202 | } 203 | } 204 | 205 | 206 | async parent( block , goalParent ){ 207 | 208 | await NOTION.blocks.children.append({ 209 | block_id : goalParent.id, children :[ duplicatedBlock( block ) ] 210 | }) 211 | await NOTION.blocks.delete({ block_id : block.id }) 212 | } 213 | 214 | 215 | 216 | 217 | 218 | async itemFilter ( arr, _filters){ 219 | return await arr.filter(item => { 220 | var result = true; 221 | Object.keys(_filters).forEach(key =>{ 222 | if( key in item[item.type] ){ 223 | if( item[item.type][key] == _filters[key] ){ 224 | result = false; 225 | } 226 | } 227 | }) 228 | return result; 229 | }) 230 | } 231 | 232 | delete(){ 233 | 234 | } 235 | 236 | } 237 | 238 | //////////////////////////////////// 239 | var duplicatedBlock = (block) => { 240 | var Text = block[block.type].text; 241 | var object = {object: 'block' } 242 | object.type = block.type 243 | object[block.type] = {text: Text,checked: false} 244 | return object 245 | } 246 | var PropertyHQ = ( _type, _value ) =>{ 247 | if ( _type == 'text'){ 248 | return { type :_type, [_type]: { content : _value } } 249 | } 250 | if( _type == 'title' ) { 251 | return { type :_type, [_type]: [ { type :"text", text: { content : _value } } ]} 252 | } 253 | if( _type == 'select' ){ 254 | return { type :_type, [_type]: { name : _value }} 255 | } 256 | if ( _type =='number'){ 257 | return { type :_type, [_type]: _value } 258 | } 259 | if(_type == 'date'){ 260 | return {type : _type , [_type] : _value } 261 | } 262 | if(_type =="icon"){ 263 | return {type : "emoji" , [_type] : _value } 264 | } 265 | } 266 | 267 | var newPageInfo = async( _refPage , _style) =>{ 268 | _style.Name = 'Name' in _style ? _style.Name : "New"; 269 | var _properties = {} 270 | var styleKeys = Object.keys(_style).filter( _st => _st != "children" && _st != "icon" ) 271 | 272 | styleKeys.forEach(_st => { 273 | _properties[_st] = PropertyHQ( _refPage.properties[_st].type , _style[_st] ); 274 | }) 275 | var _info = { parent: { database_id : _refPage.parent.database_id }, 276 | properties: _properties, 277 | children: "children" in _style ? _style.children : emptyChildren} 278 | /* 279 | if("icon" in _refPage && _info.icon != null ){ 280 | _info.icon = {type:"emoji", emoji: _refPage.icon.emoji } 281 | } 282 | */ 283 | if("icon" in _style){ 284 | _info.icon = { type: 'emoji', emoji: _style.icon } 285 | } 286 | 287 | return _info; 288 | } 289 | 290 | var itemCheck = ( item, style )=>{ 291 | var check = false; 292 | if ( Object.keys(style).length > 0 ){ 293 | Object.keys(style).forEach( key =>{ 294 | if( key in item){ 295 | if (item[key] = style[key]){ 296 | check = true; 297 | } 298 | } 299 | }) 300 | } 301 | else{ 302 | check = true; 303 | } 304 | return check 305 | } 306 | 307 | 308 | async function getLatestTasks(database_ID){ 309 | Promise.resolve( getPages(database_ID ) ) 310 | .then( pages => { 311 | var latest = pages[0] 312 | 313 | Promise.resolve( getAllBlocks(latest.id)) 314 | .then( () =>{ 315 | setTimeout(()=>{ 316 | var todos = getItemWithType('to_do', BLOCKS) 317 | .map(item => ({ 318 | text : getText(item.to_do) , //item.to_do.text[0], 319 | checked : item.to_do.checked, 320 | id : item.id 321 | })); 322 | 323 | var uncompleted = todos.filter(item => 324 | item.checked === false 325 | ) 326 | 327 | var text = "🌈 Today's Tasks " + lineChange 328 | uncompleted.forEach(check=>{ 329 | text += '[ ] '+ check.text + lineChange; 330 | }) 331 | //send message 332 | channel.send(text) 333 | 334 | }, 2000 ) 335 | }) 336 | }) 337 | 338 | } 339 | var lineChange = ` 340 | ` 341 | 342 | var getText = (item) => { 343 | return (item.text && item.text.length > 0 && item.text[0].plain_text ) ? item.text[0].plain_text : "" 344 | } 345 | function getItemWithType( typeName, array){ 346 | var sorted = [] 347 | sorted = array.filter(item =>item.type === typeName) 348 | return sorted; 349 | } 350 | 351 | async function getPages( database_ID ){ 352 | var DB = await NOTION.databases.query({database_id : database_ID }) 353 | var items = DB.results 354 | .filter(log =>{ 355 | if (log.properties.Name.title[0]){ 356 | if(log.properties.Name.title[0].plain_text !=""){ 357 | return true 358 | } 359 | } 360 | }) 361 | return items 362 | } 363 | 364 | async function getAllBlocks(pageID){ 365 | BLOCKS = [] 366 | Promise.resolve( await NOTION.blocks.children.list({block_id: pageID})).then( resolve => { 367 | BLOCKS = resolve.results; 368 | 369 | BLOCKS.forEach(b =>{ 370 | if(b.has_children){ 371 | Promise.resolve( getChildren(b.id) ).then( resolve2 =>{ 372 | resolve2.results.forEach(b2 =>{ 373 | BLOCKS.push(b2); 374 | 375 | if(b2.has_children){ 376 | Promise.resolve( getChildren(b2.id) ).then( resolve3 =>{ 377 | resolve3.results.forEach(b3 =>{ 378 | BLOCKS.push(b3); 379 | 380 | if(b3.has_children){ 381 | Promise.resolve( getChildren(b3.id) ).then( resolve4 =>{ 382 | resolve4.results.forEach(b4 =>{ 383 | BLOCKS.push(b4); 384 | return BLOCKS; 385 | }) 386 | }) 387 | } 388 | }) 389 | }) 390 | } 391 | }) 392 | }) 393 | } 394 | }) 395 | }) 396 | } 397 | 398 | 399 | 400 | var emptyChildren = 401 | [ 402 | { 403 | object: 'block', 404 | type: 'paragraph', 405 | paragraph: { 406 | text: [ 407 | { 408 | type: 'text', 409 | text: { 410 | content: ' ', 411 | }, 412 | }, 413 | ], 414 | }, 415 | }, 416 | ] 417 | var hasChildren = ( block ) =>{ 418 | return (["column_list" , "column" ].includes( block.type ) || block.has_children) 419 | } 420 | var if_column = ( block , array ) =>{ 421 | if(["column_list" , "column" ].includes( block.type ) && array.length < 1){ 422 | return emptyChildren; } 423 | else{ return array; } 424 | } 425 | 426 | export var notion = new notionClient(); 427 | 428 | 429 | 430 | -------------------------------------------------------------------------------- /script/handlers/twitter-handler.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { TwitterApi } from 'twitter-api-v2'; 3 | import axios from 'axios'; 4 | import * as fs from 'fs' 5 | 6 | export var client = new TwitterApi({ 7 | appKey: process.env.TWITTER_API, 8 | appSecret: process.env.TWITTER_SECRET, 9 | accessToken: process.env.TWITTER_TOKEN, 10 | accessSecret: process.env.TWITTER_TOKENSECRET, 11 | }) 12 | 13 | 14 | const twitterClient = client.readWrite; 15 | 16 | function writeToFile( _URL ){ 17 | return new Promise(async (resolve, reject)=>{ 18 | var response = await axios({method: 'get',url: _URL , responseType: 'stream'}); 19 | var stream = fs.createWriteStream('temp/tmp_' + _URL.split('/').at(-1)) ; 20 | response.data.pipe(stream); 21 | stream.on("finish",()=>{ 22 | resolve(stream) 23 | }) 24 | }) 25 | } 26 | 27 | export const tweet = async (text , urls ) => { 28 | try { 29 | 30 | if(urls){ 31 | const Files = await Promise.all( urls.map(async url => await writeToFile(url) ) ) 32 | const mediaIds = await Promise.all( Files.map( file => twitterClient.v1.uploadMedia(file.path) )) 33 | await twitterClient.v1.tweet( text , { media_ids: mediaIds } ) 34 | } 35 | else{ 36 | await twitterClient.v1.tweet( text ) 37 | } 38 | 39 | } catch(err){ 40 | console.log(err) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /script/handlers/wit-handler.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import Wit from 'node-wit' 3 | import { allisIn, anyisIn } from '../extra/util.js'; 4 | //import { talkDB } from '../action/Chat.js'; 5 | 6 | export const client = new Wit.Wit({ 7 | accessToken: process.env.WIT_TOKEN, 8 | logger: new Wit.log.Logger(Wit.log.DEBUG) 9 | }); 10 | 11 | //////////// 12 | export const entitiesFilter = ( _entities ) =>{ 13 | var entities = {}; 14 | Object.keys(_entities).forEach( key => { 15 | if( _entities[key][0].role == 'duration'){ 16 | var duration = {}; 17 | [ "second","minute", "hour","day","week","month", "year"].forEach(unit=>{ 18 | if(unit in _entities[key][0]){ 19 | duration[unit] = _entities[key][0][unit] 20 | } 21 | }) 22 | entities[_entities[key][0].role]= duration ; //_entities[key][0].normalized.value 23 | } 24 | else if(_entities[key][0].role == 'location' ){ 25 | entities[_entities[key][0].role] = _entities[key][0].resolved.values[0] 26 | } 27 | else{ 28 | entities[_entities[key][0].role]= _entities[key][0].value 29 | } 30 | 31 | } 32 | ) 33 | return entities; 34 | } 35 | export const intentFilter = ( _intents ) =>{ 36 | return _intents.filter(e => e.confidence > .75).map( e=>e.name); 37 | } 38 | export const traitFilter = ( _traits ) =>{ 39 | var traits = {}; 40 | Object.keys(_traits).forEach( k => { 41 | traits[k]= _traits[k].map(a =>a.value); 42 | }) 43 | return traits; 44 | } 45 | /////////// 46 | 47 | export const findIntention = async ( entities, intents, traits , talkDB ) =>{ 48 | 49 | return new Promise( (resolve,error)=>{ 50 | 51 | for(var i = 0; i< talkDB.length;){ 52 | 53 | var db = talkDB[i]; 54 | var entitieIn = false ; var intentIn = false ; var traitKeyIn = false ; var traitValIn = false ; 55 | 56 | // 1. 57 | if( 'entities' in db && Object.keys(db.entities).length > 0 ) { 58 | entitieIn = allisIn( db.entities, Object.keys(entities) ) 59 | } 60 | else{ 61 | entitieIn= true 62 | } 63 | 64 | // 2. intent is must 65 | if( 'intents' in db && db.intents.length > 0 ) { 66 | intentIn = allisIn(intents , db.intents) 67 | } 68 | else{ 69 | intentIn = true 70 | } 71 | 72 | // 3. 73 | if('traits' in db && Object.keys(db.traits).length > 0 ) 74 | { 75 | traitKeyIn = anyisIn( Object.keys(traits) , Object.keys(db.traits) ) ; 76 | if(traitKeyIn){ 77 | Object.keys(traits).forEach( key =>{ 78 | var test = anyisIn(traits[key] , db.traits[key]) ; 79 | traitValIn = test == false ? false : test ; 80 | } 81 | ) 82 | } 83 | } 84 | 85 | else{ traitKeyIn = true ; traitValIn = true ;} 86 | 87 | // fin 88 | var result = !([entitieIn, intentIn, traitKeyIn, traitValIn ].includes(false)); 89 | //console.log( entitieIn, intentIn, traitKeyIn, traitValIn ) 90 | if ( result ){ 91 | resolve(db); 92 | break; 93 | } 94 | else{ i += 1; } 95 | 96 | 97 | 98 | } 99 | 100 | 101 | 102 | }) 103 | 104 | 105 | 106 | 107 | } 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/block.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/block.JPG -------------------------------------------------------------------------------- /src/callout.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/callout.JPG -------------------------------------------------------------------------------- /src/callout2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/callout2.JPG -------------------------------------------------------------------------------- /src/converstation_0315.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/converstation_0315.JPG -------------------------------------------------------------------------------- /src/cook.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/cook.JPG -------------------------------------------------------------------------------- /src/cook2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/cook2.JPG -------------------------------------------------------------------------------- /src/could you remind me.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/could you remind me.JPG -------------------------------------------------------------------------------- /src/could you remind me2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/could you remind me2.JPG -------------------------------------------------------------------------------- /src/cron.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/cron.JPG -------------------------------------------------------------------------------- /src/cron.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/cron.gif -------------------------------------------------------------------------------- /src/hey.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/hey.JPG -------------------------------------------------------------------------------- /src/impression.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/impression.gif -------------------------------------------------------------------------------- /src/squate.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/squate.JPG -------------------------------------------------------------------------------- /src/wit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/wit.gif -------------------------------------------------------------------------------- /src/wit2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choisohan/discord-coordinator-bot/496783eceef1459442c413c5eab54e3f903a45c4/src/wit2.gif --------------------------------------------------------------------------------