Compare commits
65 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f81a40f6b | |||
|
|
3ef3ddf1ba | ||
|
|
9b9e301076 | ||
|
|
6efe723361 | ||
|
|
14612e281e | ||
|
|
3c9b8f4bc9 | ||
|
|
8a3bf1defe | ||
|
|
ced60491b4 | ||
|
|
2fb68c28a5 | ||
|
|
a3d6e00827 | ||
|
|
9cf88aed56 | ||
|
|
ff06093544 | ||
|
|
5c94814a8c | ||
|
|
8d40b96153 | ||
|
|
478c4b5f74 | ||
|
|
64bd4a713f | ||
|
|
82cdc9af28 | ||
|
|
884bd98a20 | ||
|
|
cae6b501dd | ||
|
|
9a1ed5c8a4 | ||
|
|
b7909a3710 | ||
|
|
17637fe750 | ||
|
|
05c448dcc5 | ||
|
|
9baf6ef678 | ||
|
|
81e3b5f5ba | ||
|
|
33cb61b10f | ||
|
|
574fbf3760 | ||
|
|
80b541552b | ||
|
|
1761913dbb | ||
|
|
16899adee3 | ||
|
|
6611765c38 | ||
|
|
6c0f98abff | ||
|
|
605b25a246 | ||
|
|
36ef958eb6 | ||
|
|
ba0d59b90e | ||
|
|
d1ab11327b | ||
|
|
a6eaff7171 | ||
|
|
b40f670411 | ||
|
|
24d58867c9 | ||
|
|
81c350cf00 | ||
|
|
6a963d028c | ||
|
|
deb9344950 | ||
|
|
a5ea077f88 | ||
|
|
36ca7ad5c0 | ||
|
|
6cd20763ae | ||
|
|
892674476e | ||
|
|
4909c87a6f | ||
|
|
45c394881f | ||
|
|
a618891eb1 | ||
|
|
4631b1f6a3 | ||
|
|
8b7d1f8660 | ||
|
|
1890d9c02d | ||
|
|
128e6eafe0 | ||
|
|
4de7edf03f | ||
|
|
ef6c25e60a | ||
|
|
d68d2f8c97 | ||
|
|
e0088dc1e3 | ||
|
|
a372d393ce | ||
|
|
e264073c29 | ||
|
|
3c86c9ccc5 | ||
|
|
6dbf541022 | ||
|
|
0928900177 | ||
|
|
3b915558bc | ||
|
|
aa1e513477 | ||
|
|
4ccbe0f50d |
29 changed files with 4599 additions and 1759 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
.vscode/*
|
||||
styles/taskList.css.map
|
||||
styles/taskList.scss
|
||||
to-do.md
|
||||
updates
|
||||
taskList.css.map
|
||||
202
README.md
202
README.md
|
|
@ -1,203 +1,3 @@
|
|||
# Multi-Task widget Infinity (PRIVATE)
|
||||
# Multi-Task widget Infinity
|
||||
|
||||

|
||||
|
||||
It's basically the same as the original "Multi-Task widget" but with infinite scroll! (more aesthetic, hehe)
|
||||
|
||||
## Logs
|
||||
|
||||
---
|
||||
|
||||
I'll just update you guys on the Discord
|
||||
|
||||
---
|
||||
|
||||
<!-- directory -->
|
||||
|
||||
## Content
|
||||
|
||||
- [Multi-Task widget Infinity (PRIVATE)](#multi-task-widget-infinity-private)
|
||||
- [Logs](#logs)
|
||||
- [Content](#content)
|
||||
- [Commands](#commands)
|
||||
- [Moderators only](#moderators-only)
|
||||
- [Installation](#installation)
|
||||
- [Method 1 (fastest, easiest way)](#method-1-fastest-easiest-way)
|
||||
- [Method 2 (better if you're using an alt account as a bot)](#method-2-better-if-youre-using-an-alt-account-as-a-bot)
|
||||
- [Customization settings](#customization-settings)
|
||||
- [settings](#settings)
|
||||
- [task list styles](#task-list-styles)
|
||||
- [header](#header)
|
||||
- [body](#body)
|
||||
- [task](#task)
|
||||
- [Credits](#credits)
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### Moderators only
|
||||
|
||||
- !clear - Clear all tasks
|
||||
- !cleardone - Clear all done tasks
|
||||
- !adel @user - Remove all tasks from a user
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Method 1 (fastest, easiest way)
|
||||
|
||||
1. Install the zip or clone the repository
|
||||
|
||||
2. Go to https://twitchapps.com/tmi/ to create an oauth token (it acts as a password)
|
||||
|
||||
3. Setup authentication in `auth.js`
|
||||
|
||||
4. Setup `Browser Source` in OBS studio or other streaming software with the following settings:
|
||||
|
||||
- Local File: `checked`
|
||||
- Local File: `index.html`
|
||||
|
||||
### Method 2 (better if you're using an alt account as a bot)
|
||||
|
||||
1. Install the zip or clone the repository
|
||||
|
||||
2. Create a Twitch application [here](https://dev.twitch.tv/console/apps) \(Log in with your alternate Twitch account if you wish to use a different account as a bot account\)
|
||||
|
||||
3. Copy the `Client ID` from the application
|
||||
|
||||
4. Create token using `get_token.txt`. Replace `CLIENT_ID` with the `Client ID` from the application
|
||||
|
||||
5. Setup authentication in `auth.js`
|
||||
|
||||
6. Setup `Browser Source` in OBS studio or other streaming software with the following settings:
|
||||
|
||||
- Local File: `checked`
|
||||
- Local File: `index.html`
|
||||
|
||||
---
|
||||
|
||||
## Customization settings
|
||||
|
||||
Edit `configs.js` to edit the style of the task list
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### settings
|
||||
|
||||
`showDoneTasks`:
|
||||
|
||||
**true**: show the done tasks
|
||||
|
||||
**false**: hide (and remove) the done tasks
|
||||
|
||||
`enableLimit`:
|
||||
|
||||
**true**: limit the number of incomplete tasks stored in the list
|
||||
|
||||
**false**: unlimited incomplete tasks
|
||||
|
||||
`limit`: The number of incomplete tasks to store in the list (integer)
|
||||
|
||||
`OneScrollPerMS`: Per MS, how many pixels to scroll (integer)
|
||||
|
||||
`pauseBetweenScrolls`: How many MS to pause between each end of the scroll (integer)
|
||||
|
||||
### task list styles
|
||||
|
||||
`taskListWidth`: width of the task list (px)
|
||||
|
||||
`taskListHeight`: height of the task list (px)
|
||||
|
||||
`taskListBackgroundColor`: background color of the task list (hex only)
|
||||
|
||||
`taskListBackgroundOpacity`: opacity of the task list background (0.0 - 1.0)
|
||||
|
||||
`taskListBorderWidth`: width of the task list border (px)
|
||||
|
||||
`taskListBorderColor`: color of the task list border (hex or name)
|
||||
|
||||
`taskListBorderRadius`: how round the task list border should be (px)
|
||||
|
||||
`taskListHorizontalPadding`: horizontal padding of the task list (px)
|
||||
|
||||
`taskListVerticalPadding`: vertical padding of the task list (px)
|
||||
|
||||
### header
|
||||
|
||||
`headerBackgroundColor`: background color of the header (hex only)
|
||||
|
||||
`headerBackgroundOpacity`: opacity of the header background (0.0 - 1.0)
|
||||
|
||||
`headerBorderWidth`: width of the header border (px)
|
||||
|
||||
`headerBorderColor`: color of the header border (hex or name)
|
||||
|
||||
`headerBorderRadius`: how round the header border should be (px)
|
||||
|
||||
`headerFontFamily`: font family of the header (font name)
|
||||
|
||||
`headerGoogleFont`: whether to use Google font for the header (true or false)
|
||||
|
||||
`headerFontSize`: font size of the header (px)
|
||||
|
||||
`headerFontWeight`: font weight of the header (normal, bold, or number)
|
||||
|
||||
`headerFontColor`: font color of the header (hex or name)
|
||||
|
||||
### body
|
||||
|
||||
`bodyBackgroundColor`: background color of the body (hex only)
|
||||
|
||||
`bodyBackgroundOpacity`: opacity of the body background (0.0 - 1.0)
|
||||
|
||||
`bodyBorderWidth`: width of the body border (px)
|
||||
|
||||
`bodyBorderColor`: color of the body border (hex or name)
|
||||
|
||||
`bodyBorderRadius`: how round the body border should be (px)
|
||||
|
||||
### task
|
||||
|
||||
`lineHeight`: line height of the task (number)
|
||||
|
||||
`usernameFontWeight`: font weight of the username (normal, bold, or number)
|
||||
|
||||
`usernameColor`: color of the username (hex or name or "" for Twitch user color)
|
||||
|
||||
`taskBackgroundColor`: background color of the task (hex only)
|
||||
|
||||
`taskBackgroundOpacity`: opacity of the task background (0.0 - 1.0)
|
||||
|
||||
`taskFontFamily`: font family of the task (font name)
|
||||
|
||||
`taskGoogleFont`: whether to use Google font for the task (true or false)
|
||||
|
||||
`taskFontSize`: font size of the task (px)
|
||||
|
||||
`taskFontColor`: font color of the task (hex or name)
|
||||
|
||||
`taskBorderColor`: color of the task border (hex or name)
|
||||
|
||||
`taskBorderWidth`: width of the task border (px)
|
||||
|
||||
`taskBorderRadius`: how round the task border should be (px)
|
||||
|
||||
`taskMarginBottom`: bottom margin of the task (px)
|
||||
|
||||
`taskHorizontalPadding`: horizontal padding of the task (px)
|
||||
|
||||
`taskVerticalPadding`: vertical padding of the task (px)
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
**Author:** [**@RyanPython**](https://twitch.tv/RyanPython)
|
||||
|
||||
**Special thanks to:**
|
||||
|
||||
- [**@Instafluff**](https://twitch.tv/Instafluff) \(for the Comfy JS library\)
|
||||
|
|
|
|||
2
auth.js
2
auth.js
|
|
@ -2,7 +2,7 @@ const auth = (function () {
|
|||
// Authentication and channels - required
|
||||
const channel = ""; // your channel
|
||||
const username = ""; // bot account
|
||||
const oauth = ""; // should be oauth:xxxxxxxxxxxx from the bot account DO NOT INCLUDE oauth:
|
||||
const oauth = ""; // should be oauth:xxxxxxxxxxxx from the bot account
|
||||
|
||||
return {
|
||||
channel,
|
||||
|
|
|
|||
730
configs.js
730
configs.js
|
|
@ -1,343 +1,419 @@
|
|||
const configs = (function () {
|
||||
"use strict";
|
||||
|
||||
// settings
|
||||
const enableLimit = true; // true or false
|
||||
const limit = 5; // integer
|
||||
|
||||
// animation
|
||||
const scrollSpeed = 40; // milliseconds
|
||||
const gapBetweenScrolls = 0; // px
|
||||
|
||||
// STYLES
|
||||
// task list
|
||||
const taskListBackgroundColor = "#000000"; // hex only
|
||||
const taskListBackgroundOpacity = 0; // 0.0 - 1.0
|
||||
|
||||
const taskListBorderWidth = "0px"; // px
|
||||
const taskListBorderColor = "#ffffff"; // hex or name
|
||||
const taskListBorderRadius = "5px"; // px
|
||||
|
||||
const taskListHorizontalPadding = "0px"; // px
|
||||
const taskListVerticalPadding = "0px"; // px
|
||||
|
||||
// header
|
||||
const headerHorizontalPadding = "10px"; // px
|
||||
const headerVerticalPadding = "10px"; // px
|
||||
|
||||
const headerBackgroundColor = "#000000"; // hex only
|
||||
const headerBackgroundOpacity = 0.8; // 0.0 - 1.0
|
||||
|
||||
const headerBorderWidth = "2px"; // px
|
||||
const headerBorderColor = "#ffffff"; // hex or name
|
||||
const headerBorderRadius = "5px"; // px
|
||||
|
||||
const headerFontFamily = "Fredoka"; // font name
|
||||
const headerGoogleFont = true; // true: use google font, false: use system font
|
||||
|
||||
const headerFontSize = "25px"; // px
|
||||
const headerFontWeight = "bold"; // normal or bold or number
|
||||
|
||||
const headerFontColor = "#ffffff"; // hex or name
|
||||
|
||||
// body
|
||||
const bodyBackgroundColor = "#000000"; // hex only
|
||||
const bodyBackgroundOpacity = 0; // 0.0 - 1.0
|
||||
|
||||
const bodyBorderWidth = "0px"; // px
|
||||
const bodyBorderColor = "#fff"; // hex or name
|
||||
const bodyBorderRadius = "5px"; // px
|
||||
|
||||
// task
|
||||
const lineHeight = 1.5; // number
|
||||
const usernameFontWeight = "bold"; // normal or bold or number
|
||||
const usernameColor = "#ffc0cb"; // hex or name or "" for twitch user color
|
||||
|
||||
const taskWidth = "100%";
|
||||
|
||||
const taskBackgroundColor = "#000000"; // hex only
|
||||
const taskBackgroundOpacity = 0.7; // 0.0 - 1.0
|
||||
|
||||
const taskFontFamily = "Poppins"; // font name
|
||||
const taskGoogleFont = true; // true: use google font, false: use system font
|
||||
|
||||
const taskFontSize = "20px"; // px
|
||||
const taskFontColor = "#ffffff"; // hex or name
|
||||
|
||||
const taskBorderColor = "#000000"; // hex or name
|
||||
const taskBorderWidth = "0px"; // px
|
||||
const taskBorderRadius = "5px"; // px
|
||||
|
||||
const taskMarginBottom = "5px"; // px
|
||||
const taskHorizontalPadding = "20px"; // px
|
||||
const taskVerticalPadding = "10px"; // px
|
||||
|
||||
// Add task commands - please add commands in the exact format
|
||||
const addTaskCommands = [
|
||||
"!taska",
|
||||
"!taskadd",
|
||||
"!atask",
|
||||
"!addtask",
|
||||
"!task",
|
||||
"!add",
|
||||
"!todo",
|
||||
];
|
||||
|
||||
// Edit task commands - please add commands in the exact format
|
||||
const editTaskCommands = [
|
||||
"!taske",
|
||||
"!taskedit",
|
||||
"!etask",
|
||||
"!edittask",
|
||||
"!edit",
|
||||
];
|
||||
|
||||
// Delete task commands - please add commands in the exact format
|
||||
const deleteTaskCommands = [
|
||||
"!taskd",
|
||||
"!taskdel",
|
||||
"!taskdelete",
|
||||
"!deltask",
|
||||
"!deletetask",
|
||||
"!taskr",
|
||||
"!taskremove",
|
||||
"!rtask",
|
||||
"!removetask",
|
||||
"!remove",
|
||||
"!delete",
|
||||
];
|
||||
|
||||
// Finish task commands - please add commands in the exact format
|
||||
const finishTaskCommands = [
|
||||
"!taskf",
|
||||
"!taskfinish",
|
||||
"!ftask",
|
||||
"!finishtask",
|
||||
"!taskd",
|
||||
"!taskdone",
|
||||
"!donetask",
|
||||
"!dtask",
|
||||
"!finish",
|
||||
"!done",
|
||||
"!finished",
|
||||
];
|
||||
|
||||
// Unfinish task commands - please add commands in the exact format
|
||||
const unfinishTaskCommands = [
|
||||
"!tasku",
|
||||
"!taskunfinish",
|
||||
"!utask",
|
||||
"!unfinishtask",
|
||||
"!taskud",
|
||||
"!taskundone",
|
||||
"!undonetask",
|
||||
"!undone",
|
||||
"!unfinish",
|
||||
"!unfinished",
|
||||
];
|
||||
|
||||
// Check task commands - please add commands in the exact format
|
||||
const checkCommands = [
|
||||
"!taskc",
|
||||
"!taskcheck",
|
||||
"!ctask",
|
||||
"!checktask",
|
||||
"!mytask",
|
||||
"!check",
|
||||
"!mytasks",
|
||||
];
|
||||
|
||||
// List tasks commands - please add commands in the exact format
|
||||
const listCommands = ["!taskl", "!tasklist", "!listtasks", "!list"];
|
||||
|
||||
// Clear my done commands - please add commands in the exact format
|
||||
const clearMyDoneCommands = ["!clearmydone"];
|
||||
|
||||
// Help commands - please add commands in the exact format
|
||||
const helpCommands = [
|
||||
"!taskh",
|
||||
"!taskhelp",
|
||||
"!htask",
|
||||
"!helptask",
|
||||
"!tasks",
|
||||
"!help",
|
||||
];
|
||||
|
||||
// Admin clear all except streamer - please add commands following the exact format
|
||||
const adminClearNotStreamerCommands = [
|
||||
"!clearnotstreamer",
|
||||
"!aclearnotstreamer",
|
||||
"!adminclearnotstreamer",
|
||||
"!clearns",
|
||||
];
|
||||
|
||||
// Admin delete - please add commands following the exact format
|
||||
const adminDeleteCommands = [
|
||||
"!taskadel",
|
||||
"!adel",
|
||||
"!adelete",
|
||||
"!admindelete",
|
||||
];
|
||||
|
||||
// Admin clear done - please add commands following the exact format
|
||||
const adminClearDoneCommands = [
|
||||
"!acleardone",
|
||||
"!admincleardone",
|
||||
"!cleardone",
|
||||
];
|
||||
|
||||
const adminClearAllCommands = [
|
||||
"!clearall",
|
||||
"!allclear",
|
||||
"!adminclearall",
|
||||
"!adminallclear",
|
||||
"!aclearall",
|
||||
"!aclear",
|
||||
];
|
||||
|
||||
// Responses
|
||||
const taskAdded = 'The task(s) "{task}" has been added, {user}!';
|
||||
const noTaskAdded =
|
||||
"Looks like you already hit the limit of incomplete tasks, {user}";
|
||||
const noTaskContent = "Try using !task the-task-you-are-working-on {user}";
|
||||
const taskDeleted =
|
||||
'Task(s) "{task}" has been deleted successfully, {user}';
|
||||
const specifyTaskIndex = "Try specifying the index of the task {user}";
|
||||
|
||||
const duplicateTask =
|
||||
"Looks like you already have the task '{message}' up there {user}!";
|
||||
|
||||
const clearTasksExceptBroadcaster =
|
||||
"All tasks have been cleared except for the streamer's, {user}!";
|
||||
|
||||
const adminDeleteTasks = "All of {mentioned}'s tasks have been deleted";
|
||||
const taskFinished = 'Good job on finishing the task "{task}", {user}!';
|
||||
const allTasksFinished = "Good job on finishing all your tasks, {user}!";
|
||||
const taskUnfinished =
|
||||
'Task(s) "{task}" has been unmarked as done, {user}!';
|
||||
const taskEdited =
|
||||
"Task '{originalTask}' has been edited to '{task}' successfully, {user}";
|
||||
const noTaskEdit = "Try doing !edit [index] [new task] {user}";
|
||||
|
||||
const noTask = "Looks like you don't have a task up there {user}";
|
||||
const noTaskA = "Looks like there is no task from that user there {user}";
|
||||
const notMod = "Permission denied, {user}; Mods only";
|
||||
const clearedAll = "All tasks have been cleared!";
|
||||
const clearedDone = "All completed tasks have been cleared!";
|
||||
const clearedMyDone = "All your completed tasks have been cleared!";
|
||||
const specifyUser = "Try specifying a user to delete their tasks {user}";
|
||||
const help = `{user} Use the following commands to help you out - !task !edit !remove !done. For mods, you can do !adel @user. More commmands here: https://github.com/liyunze-coding/Chat-Task-Tic-Overlay/blob/main/MultiTask.md/`;
|
||||
// to edit check task command, go to
|
||||
// scripts/taskList.js
|
||||
// function checkTasks(username)
|
||||
|
||||
const additionalCommands = {
|
||||
"!botcred":
|
||||
"{user} Ryan is the creator of this bot! You can find him on https://github.com/liyunze-coding or https://www.twitch.tv/RythonDev",
|
||||
};
|
||||
|
||||
const titles = [
|
||||
"!taskhelp",
|
||||
"!task",
|
||||
"!edit",
|
||||
"!remove",
|
||||
"!done",
|
||||
"!botcred",
|
||||
];
|
||||
|
||||
const settings = {
|
||||
enableLimit,
|
||||
limit,
|
||||
headerGoogleFont,
|
||||
taskGoogleFont,
|
||||
};
|
||||
|
||||
const styles = {
|
||||
taskListBackgroundColor,
|
||||
taskListBackgroundOpacity,
|
||||
taskListBorderWidth,
|
||||
taskListBorderColor,
|
||||
taskListBorderRadius,
|
||||
taskListHorizontalPadding,
|
||||
taskListVerticalPadding,
|
||||
headerHorizontalPadding,
|
||||
headerVerticalPadding,
|
||||
headerBackgroundColor,
|
||||
headerBackgroundOpacity,
|
||||
headerBorderWidth,
|
||||
headerBorderColor,
|
||||
headerBorderRadius,
|
||||
headerFontSize,
|
||||
headerFontWeight,
|
||||
headerFontFamily,
|
||||
headerFontColor,
|
||||
bodyBackgroundColor,
|
||||
bodyBackgroundOpacity,
|
||||
bodyBorderWidth,
|
||||
bodyBorderColor,
|
||||
bodyBorderRadius,
|
||||
taskWidth,
|
||||
lineHeight,
|
||||
usernameFontWeight,
|
||||
usernameColor,
|
||||
taskBackgroundColor,
|
||||
taskBackgroundOpacity,
|
||||
taskFontFamily,
|
||||
taskFontSize,
|
||||
taskFontColor,
|
||||
taskBorderColor,
|
||||
taskBorderWidth,
|
||||
taskBorderRadius,
|
||||
taskMarginBottom,
|
||||
taskHorizontalPadding,
|
||||
taskVerticalPadding,
|
||||
replyInChat: true, // true or false
|
||||
enableLimit: false, // true or false
|
||||
limit: 10, // integer
|
||||
automaticDoneIndex: false, // true or false - Automatically assume first unfinished task is complete
|
||||
taskName: "task", // string (singular)
|
||||
pointsName: "points", // string
|
||||
pointsPerTask: 10, // integer
|
||||
taskSeparator: [";", ","], // array of strings
|
||||
openQuote: '"', // string
|
||||
closeQuote: '"', // string
|
||||
testTasks: false, // true or false
|
||||
headerGoogleFont: true, // true: use google font, false: use system font
|
||||
taskGoogleFont: true, // true: use google font, false: use system font
|
||||
displayTaskCount: true, // true or false
|
||||
enableTaskMaster: true, // true or false
|
||||
hideWhenNoTasks: false, // true or false
|
||||
taskCharacterLimit: 15, // integer
|
||||
taskCharacterLimitEnabled: false, // true or false
|
||||
};
|
||||
|
||||
const animation = {
|
||||
scrollSpeed,
|
||||
gapBetweenScrolls,
|
||||
titles,
|
||||
scrollSpeed: 40, // milliseconds
|
||||
gapBetweenScrolls: 0, // px
|
||||
titles: [
|
||||
"!taskhelp",
|
||||
"!task",
|
||||
"!edit",
|
||||
"!remove",
|
||||
"!done",
|
||||
"!undone",
|
||||
"!botcred",
|
||||
"!count",
|
||||
"!boardcount",
|
||||
],
|
||||
};
|
||||
|
||||
const styles = {
|
||||
// task list
|
||||
taskListBackgroundColor: "#000000", // hex only
|
||||
taskListBackgroundOpacity: 0, // 0.0 - 1.0
|
||||
|
||||
taskListBorderWidth: "0px", // px
|
||||
taskListBorderColor: "#ffffff", // hex or name
|
||||
taskListBorderRadius: "0px", // px
|
||||
|
||||
taskListHorizontalPadding: "0px", // px
|
||||
taskListVerticalPadding: "0px", // px
|
||||
|
||||
// header
|
||||
headerHorizontalPadding: "10px", // px
|
||||
headerVerticalPadding: "10px", // px
|
||||
|
||||
headerBackgroundColor: "#000", // hex only
|
||||
headerBackgroundOpacity: 0.9, // 0.0 - 1.0
|
||||
|
||||
headerBorderWidth: "0px", // px
|
||||
headerBorderColor: "#ffffff", // hex or name
|
||||
headerBorderRadius: "10px", // px
|
||||
|
||||
headerFontFamily: "Fredoka", // font name
|
||||
|
||||
headerFontSize: "25px", // px
|
||||
headerFontWeight: "bold", // normal or bold or number
|
||||
|
||||
headerFontColor: "#fff", // hex or name
|
||||
|
||||
// body
|
||||
bodyBackgroundColor: "#fff", // hex only
|
||||
bodyBackgroundOpacity: 0, // 0.0 - 1.0
|
||||
|
||||
bodyBorderWidth: "0px", // px
|
||||
bodyBorderColor: "#fff", // hex or name
|
||||
bodyBorderRadius: "5px", // px
|
||||
|
||||
bodyMarginTop: "1px", // px
|
||||
|
||||
// task
|
||||
numberOfLines: 2, // number
|
||||
lineHeight: 1.5, // number
|
||||
usernameFontWeight: "bold", // normal or bold or number
|
||||
usernameColor: "white", // hex or name or "" for twitch user color
|
||||
usernameFontSize: "20px", // px
|
||||
|
||||
taskWidth: "100%",
|
||||
|
||||
taskBackgroundColor: "#000", // hex only
|
||||
taskBackgroundOpacity: 0.8, // 0.0 - 1.0
|
||||
|
||||
taskFontFamily: "Poppins", // font name
|
||||
|
||||
taskFontSize: "20px", // px
|
||||
taskFontColor: "#fff", // hex or name
|
||||
|
||||
taskBorderColor: "#fff", // hex or name
|
||||
taskBorderWidth: "0px", // px
|
||||
taskBorderRadius: "10px", // px
|
||||
|
||||
taskMarginBottom: "10px", // px
|
||||
taskHorizontalPadding: "20px", // px
|
||||
taskVerticalPadding: "10px", // px
|
||||
|
||||
// done task
|
||||
doneTaskFontColor: "#bbb", // hex or name
|
||||
|
||||
// focus task
|
||||
focusTaskBackgroundColor: "#fff", // hex only
|
||||
focusTaskBackgroundOpacity: 1, // 0.0 - 1.0
|
||||
focusTaskBorderRadius: "5px", // px
|
||||
|
||||
focusTaskFontColor: "#000", // hex or name
|
||||
|
||||
focusTaskHorizontalPadding: "7px", // px
|
||||
focusTaskVerticalPadding: "0px", // px
|
||||
};
|
||||
|
||||
// STYLES
|
||||
|
||||
const commands = {
|
||||
addTaskCommands,
|
||||
editTaskCommands,
|
||||
deleteTaskCommands,
|
||||
finishTaskCommands,
|
||||
unfinishTaskCommands,
|
||||
helpCommands,
|
||||
checkCommands,
|
||||
listCommands,
|
||||
clearMyDoneCommands,
|
||||
adminDeleteCommands,
|
||||
adminClearNotStreamerCommands,
|
||||
adminClearDoneCommands,
|
||||
adminClearAllCommands,
|
||||
additionalCommands,
|
||||
// Add task commands - please add commands in the exact format
|
||||
addTaskCommands: [
|
||||
"!taska",
|
||||
"!taskadd",
|
||||
"!atask",
|
||||
"!addtask",
|
||||
"!task",
|
||||
"!add",
|
||||
"!todo",
|
||||
"!a",
|
||||
],
|
||||
|
||||
// Edit task commands - please add commands in the exact format
|
||||
editTaskCommands: [
|
||||
"!taske",
|
||||
"!taskedit",
|
||||
"!etask",
|
||||
"!edittask",
|
||||
"!edit",
|
||||
"!e",
|
||||
],
|
||||
|
||||
// Delete task commands - please add commands in the exact format
|
||||
deleteTaskCommands: [
|
||||
"!taskdel",
|
||||
"!taskdelete",
|
||||
"!deltask",
|
||||
"!deletetask",
|
||||
"!taskr",
|
||||
"!taskremove",
|
||||
"!rtask",
|
||||
"!removetask",
|
||||
"!remove",
|
||||
"!delete",
|
||||
"!r",
|
||||
],
|
||||
|
||||
// Finish task commands - please add commands in the exact format
|
||||
finishTaskCommands: [
|
||||
"!taskf",
|
||||
"!taskfinish",
|
||||
"!ftask",
|
||||
"!finishtask",
|
||||
"!taskd",
|
||||
"!taskdone",
|
||||
"!donetask",
|
||||
"!dtask",
|
||||
"!finish",
|
||||
"!done",
|
||||
"!finished",
|
||||
"!f",
|
||||
],
|
||||
|
||||
// Unfinish task commands - please add commands in the exact format
|
||||
unfinishTaskCommands: [
|
||||
"!tasku",
|
||||
"!taskunfinish",
|
||||
"!utask",
|
||||
"!unfinishtask",
|
||||
"!taskud",
|
||||
"!taskundone",
|
||||
"!undonetask",
|
||||
"!undone",
|
||||
"!unfinish",
|
||||
"!unfinished",
|
||||
"!u",
|
||||
],
|
||||
|
||||
nextTaskCommands: [
|
||||
"!taskn",
|
||||
"!tasknext",
|
||||
"!ntask",
|
||||
"!nexttask",
|
||||
"!next",
|
||||
"!n",
|
||||
],
|
||||
logTaskCommands: ["!log", "!tasklog", "!logtask", "!logtasks"],
|
||||
|
||||
// focus task commands - please add commands in the exact format
|
||||
focusTaskCommands: ["!focus", "!taskfocus", "!focustask"],
|
||||
focusedTaskCommands: ["!focused", "!ongoing", "!checkfocus"],
|
||||
unfocusTaskCommands: ["!unfocus", "!taskunfocus", "!unfocustask"],
|
||||
|
||||
nowTaskCommands: ["!now", "!tasknow", "!nowtask"],
|
||||
|
||||
// Check task commands - please add commands in the exact format
|
||||
checkCommands: [
|
||||
"!taskc",
|
||||
"!taskcheck",
|
||||
"!ctask",
|
||||
"!checktask",
|
||||
"!mytask",
|
||||
"!check",
|
||||
"!mytasks",
|
||||
"!c",
|
||||
],
|
||||
|
||||
checkCompletedCommands: [
|
||||
"!completed",
|
||||
"!taskcompleted",
|
||||
"!donetasks",
|
||||
"!completedtasks",
|
||||
],
|
||||
|
||||
// List tasks commands - please add commands in the exact format
|
||||
listCommands: ["!taskl", "!tasklist", "!listtasks", "!list"],
|
||||
|
||||
// Clear my done commands - please add commands in the exact format
|
||||
clearMyDoneCommands: ["!clearmydone"],
|
||||
|
||||
// check count commands - please add commands in the exact format
|
||||
checkCountCommands: ["!taskcount", "!count", "!checkcount", "!mycount"],
|
||||
|
||||
checkAllCountCommands: [
|
||||
"!taskallcount",
|
||||
"!allcount",
|
||||
"!checkallcount",
|
||||
"!boardcount",
|
||||
"!checkboardcount",
|
||||
"!taskboardcount",
|
||||
],
|
||||
|
||||
checkMyPointsCommands: [
|
||||
],
|
||||
|
||||
// sync points with count commands - please add commands in the exact format
|
||||
syncCountPointsCommands: [],
|
||||
|
||||
addPointsCommands: [],
|
||||
|
||||
reducePointsCommands: [],
|
||||
|
||||
setUserPointsCommands: [
|
||||
],
|
||||
|
||||
setUserTaskCountCommands: [
|
||||
"!setusertaskcount",
|
||||
"!settaskcount",
|
||||
"!settaskcountuser",
|
||||
"!setusertask",
|
||||
],
|
||||
|
||||
leaderboardCommands: ["!leaderboard", "!lb", "!top"],
|
||||
|
||||
taskMasterCommands: ["!taskmaster", "!taskchampion"],
|
||||
|
||||
resetTaskMasterCommands: ["!resettaskmaster"],
|
||||
|
||||
// Help commands - please add commands in the exact format
|
||||
helpCommands: [
|
||||
"!taskh",
|
||||
"!taskhelp",
|
||||
"!htask",
|
||||
"!helptask",
|
||||
"!tasks",
|
||||
"!help",
|
||||
],
|
||||
|
||||
// Admin clear all except streamer - please add commands following the exact format
|
||||
adminClearNotStreamerCommands: [
|
||||
"!clearnotstreamer",
|
||||
"!aclearnotstreamer",
|
||||
"!adminclearnotstreamer",
|
||||
"!clearns",
|
||||
],
|
||||
|
||||
// Admin delete - please add commands following the exact format
|
||||
adminDeleteCommands: ["!taskadel", "!adel", "!adelete", "!admindelete"],
|
||||
|
||||
// Admin clear done - please add commands following the exact format
|
||||
adminClearDoneCommands: [
|
||||
"!acleardone",
|
||||
"!admincleardone",
|
||||
"!cleardone",
|
||||
],
|
||||
|
||||
adminClearTasksCommands: ["!cleartasks"],
|
||||
|
||||
adminResetBoardCountCommands: ["!resetboardcount", "!resetallcount"],
|
||||
|
||||
adminResetUsersCountCommands: ["!resetuserscount"],
|
||||
|
||||
adminClearAllCommands: ["!clearallnoregrets"],
|
||||
|
||||
adminSetBoardCountCommands: ["!setboardcount", "!setallcount"],
|
||||
additionalCommands: {
|
||||
"!botcred":
|
||||
"{user} Ryan is the creator of this bot! You can find him on https://github.com/liyunze-coding or https://www.twitch.tv/RythonDev",
|
||||
},
|
||||
};
|
||||
|
||||
const responses = {
|
||||
taskAdded,
|
||||
noTaskAdded,
|
||||
noTaskContent,
|
||||
taskDeleted,
|
||||
specifyTaskIndex,
|
||||
duplicateTask,
|
||||
taskFinished,
|
||||
allTasksFinished,
|
||||
taskUnfinished,
|
||||
taskEdited,
|
||||
noTaskEdit,
|
||||
noTask,
|
||||
noTaskA,
|
||||
notMod,
|
||||
help,
|
||||
clearTasksExceptBroadcaster,
|
||||
adminDeleteTasks,
|
||||
clearedMyDone,
|
||||
clearedAll,
|
||||
clearedDone,
|
||||
specifyUser,
|
||||
// Responses related to task addition
|
||||
taskAdded: "The {taskName}(s) {task} has been added, {user}!",
|
||||
noTaskAdded:
|
||||
"Looks like you already hit the limit of incomplete {taskName}s, {user}",
|
||||
noTaskContent:
|
||||
"Try using !task the-{taskName}-you-are-working-on {user}",
|
||||
duplicateTask:
|
||||
"Looks like you already have the {taskName} {task} up there {user}!",
|
||||
taskEdited:
|
||||
"{taskName} {originalTask} has been edited to {task} successfully, {user}",
|
||||
noTaskEdit: "Try doing !edit [index] [new task] {user}",
|
||||
nowTask:
|
||||
"{taskName} {task} is now the task you are working on, {user}!",
|
||||
taskLogged: "Task(s) {task} has been logged, {user}!",
|
||||
taskAlreadyCompleted:
|
||||
"Looks like you already completed that {taskName} {task} up there {user}!",
|
||||
|
||||
// Grouped by task progression responses
|
||||
taskNext:
|
||||
"Good job on finishing the task {oldTask}! Now moving onto {newTask}, {user}!",
|
||||
nextNoContent: "Try using !next the-task-you-want-to-do-next {user}",
|
||||
taskNextFailed:
|
||||
"Unable to perform command with multiple incomplete {taskName}s, {user}!",
|
||||
|
||||
// Grouped by task deletion responses
|
||||
clearedMyDone:
|
||||
"All of your completed {taskName}s have been cleared, {user}!",
|
||||
taskDeleted:
|
||||
"{taskName}(s) {task} has been deleted successfully, {user}",
|
||||
specifyTaskIndex:
|
||||
"Try specifying the index of the incomplete {taskName}(s) {user}",
|
||||
clearTasksExceptBroadcaster:
|
||||
"All {taskName}s have been cleared except for the streamer's, {user}!",
|
||||
clearedTasks: "All {taskName}s have been cleared, {user}!",
|
||||
adminDeleteTasks: "All of {mentioned}'s {taskName}s have been deleted",
|
||||
clearedDone: "All completed {taskName}s have been cleared, {user}!",
|
||||
clearedAll: "All {taskName}s and points have been cleared, {user}!",
|
||||
|
||||
// Grouped by task completion responses
|
||||
taskFinished:
|
||||
"Good job on finishing the {taskName}(s) {task}, {user}! You have completed {doneCount} {taskName}(s) so far!", // {pointCount} {pointName} vars available
|
||||
allTasksFinished:
|
||||
"Good job on finishing all your {taskName}s, {user}! You have completed {doneCount} {taskName}(s) so far!",
|
||||
taskUnfinished:
|
||||
"{taskName}(s) {task} has been unmarked as done, {user}!",
|
||||
taskAlreadyFinished:
|
||||
"Looks like you already finished that {taskName} {user}",
|
||||
|
||||
// Grouped by task focus responses
|
||||
clearFocused: "{taskName} has been unfocused, {user}!",
|
||||
noFocusedTask: "Looks like you don't have a focused {taskName} {user}!",
|
||||
alreadyFocusedTask:
|
||||
"Looks like you already have that task set to focus {user}!",
|
||||
onlyOneFocus: "You can only focus one task at a time {user}!",
|
||||
specifyFocusTask:
|
||||
"Try specifying an incomplete {taskName} to focus {user}!",
|
||||
taskFocused: "Task {task} has been focused, {user}!",
|
||||
focusedTask: "{user} Focused {taskName}: {task}",
|
||||
|
||||
// Grouped by task existence responses
|
||||
noTask: "Looks like you don't have a task up there {user}",
|
||||
noTaskA: "Looks like there is no task from that user there {user}",
|
||||
|
||||
// Grouped by point-related responses
|
||||
checkMyPoints: "You have {pointCount} {pointName}, {user}!",
|
||||
checkUserPoints: "{mentioned} has {pointCount} {pointName}, {user}!",
|
||||
syncCountPoints: "Points have been synced with count, {user}!",
|
||||
addPoints: "{mentioned} has been given {pointCount} {pointName}!",
|
||||
reducePoints: "{mentioned} has lost {pointCount} {pointName}!",
|
||||
setUserPoints: "{mentioned} now has {pointCount} {pointName}!",
|
||||
specifyPoints: "Try specifying a number of points {user}",
|
||||
|
||||
// Grouped by count-related responses
|
||||
checkYourCount:
|
||||
"You have completed {doneCount} {taskName}(s) so far, {user}!",
|
||||
checkUserCount:
|
||||
"{mentioned} has completed {doneCount} {taskName}(s) so far, {user}!",
|
||||
checkAllCount:
|
||||
"Everyone has completed {doneCount} {taskName}(s) so far, {user}!",
|
||||
noCountAll: "Looks like no one has completed a {taskName} yet {user}",
|
||||
leaderboard: "Leaderboard: {leaderboard}",
|
||||
setUserTaskCount: "{mentioned} now has {taskCount} tasks!",
|
||||
|
||||
// Grouped by taskmaster-related responses
|
||||
taskMaster: "{user} Task Master: {taskMaster} [{taskMasterCount}]",
|
||||
noTaskMaster: "No task master yet, {user}!",
|
||||
resetTaskMaster: "Task Master has been reset, {user}!",
|
||||
|
||||
// Grouped by permission-related responses
|
||||
notMod: "Permission denied, {user}; Mods only",
|
||||
notStreamer: "Permission denied, {user}; Streamer only",
|
||||
|
||||
// Help response
|
||||
help: `{user} Use the following commands to help you out - !task !edit !remove !done. For mods, you can do !adel @user. More commmands here: https://github.com/liyunze-coding/Chat-Task-Tic-Overlay/blob/main/MultiTask.md/`,
|
||||
|
||||
"chat-task-tic":
|
||||
"Chat-Task-Tic widget is a free and open source task widget developed by @RythonDev ! https://github.com/liyunze-coding/chat-task-tic-overlay-infinity",
|
||||
multitask:
|
||||
"If you want to add the multi-task widget to your stream, you can buy it from ko-fi! https://ko-fi.com/s/94e7e8dc81",
|
||||
// to edit check task command, go to
|
||||
// scripts/taskList.js
|
||||
// function checkTasks(username)
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
https://id.twitch.tv/oauth2/authorize
|
||||
?client_id=CLIENT_ID_HERE
|
||||
&redirect_uri=http://localhost
|
||||
&response_type=token
|
||||
&scope=channel:moderate+chat:edit+chat:read+channel:manage:broadcast+user:edit:broadcast+channel:read:redemptions+user:read:email
|
||||
103
index.html
103
index.html
|
|
@ -1,32 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="./lib/webfont.js"></script>
|
||||
<script src="./lib/comfy.min.js"></script>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/comfy.js@latest/dist/comfy.min.js"></script>
|
||||
<script src="./auth.js"></script>
|
||||
<script src="./configs.js"></script>
|
||||
<script defer src="./scripts/core/defaultConfigs.js"></script>
|
||||
<script defer src="./scripts/core/constants.js"></script>
|
||||
<script defer src="./scripts/core/functions.js"></script>
|
||||
<script defer src="./scripts/core/db.js"></script>
|
||||
<script defer src="./scripts/core/utils.js"></script>
|
||||
<script defer src="./scripts/ui/styles.js"></script>
|
||||
<script defer src="./scripts/tasks/taskMaster.js"></script>
|
||||
<script defer src="./scripts/tasks/counts.js"></script>
|
||||
<script defer src="./scripts/ui/renderer.js"></script>
|
||||
<script defer src="./scripts/tasks/taskCleanup.js"></script>
|
||||
<script defer src="./scripts/tasks/taskCore.js"></script>
|
||||
<script defer src="./scripts/testing/tests.js"></script>
|
||||
<script defer src="./scripts/chat/processCommand.js"></script>
|
||||
<script defer src="./scripts/chat/index.js"></script>
|
||||
<script defer src="./scripts/ui/taskList.js"></script>
|
||||
|
||||
<script src="./auth.js"></script>
|
||||
<script src="./configs.js"></script>
|
||||
<script defer src="scripts/index.js"></script>
|
||||
<script defer src="scripts/taskList.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./styles/taskList.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="./styles/extraStyles.css"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="styles/taskList.css" />
|
||||
<title>Task List</title>
|
||||
</head>
|
||||
|
||||
<title>Task List</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main-container">
|
||||
<div class="header">
|
||||
<div class="title">!taskhelp</div>
|
||||
<div class="task-count">0/0</div>
|
||||
</div>
|
||||
<div class="task-wrapper">
|
||||
<div class="task-container primary">
|
||||
<!--
|
||||
<body>
|
||||
<div id="main-container">
|
||||
<div class="header">
|
||||
<div class="title">!taskhelp</div>
|
||||
<div class="task-count">0/0</div>
|
||||
</div>
|
||||
<div class="task-wrapper">
|
||||
<div class="task-container primary">
|
||||
<!--
|
||||
<div class="task">
|
||||
<div class="username">RyanPython</div>
|
||||
<ol class="user-tasks">
|
||||
|
|
@ -35,10 +52,42 @@
|
|||
</ol>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="task-container secondary"></div>
|
||||
</div>
|
||||
<div class="task-container secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div
|
||||
id="oauth-expired-modal"
|
||||
class="oauth-modal hidden"
|
||||
role="alertdialog"
|
||||
aria-labelledby="oauth-modal-title"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div class="oauth-modal__content">
|
||||
<h2 id="oauth-modal-title">Twitch OAuth token expired</h2>
|
||||
<p>
|
||||
The token in <code>auth.js</code> is no longer valid. Chat
|
||||
commands will not work until you replace it.
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
Open
|
||||
<a
|
||||
href="https://sarayalth.github.io/TwitchOAuthGenerator/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Twitch OAuth Generator</a
|
||||
>
|
||||
</li>
|
||||
<li>Click <strong>Set Default Scopes</strong></li>
|
||||
<li>
|
||||
Click <strong>Get OAuth Token</strong>, then paste the
|
||||
token into <code>auth.js</code>
|
||||
</li>
|
||||
<li>Reload this browser source</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
1
lib/comfy.min.js
vendored
Normal file
1
lib/comfy.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
33
lib/webfont.js
Normal file
33
lib/webfont.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2016 Small Batch, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
/* Web Font Loader v1.6.26 - (c) Adobe Systems, Google. License: Apache 2.0 */(function(){function aa(a,b,c){return a.call.apply(a.bind,arguments)}function ba(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}function p(a,b,c){p=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?aa:ba;return p.apply(null,arguments)}var q=Date.now||function(){return+new Date};function ca(a,b){this.a=a;this.m=b||a;this.c=this.m.document}var da=!!window.FontFace;function t(a,b,c,d){b=a.c.createElement(b);if(c)for(var e in c)c.hasOwnProperty(e)&&("style"==e?b.style.cssText=c[e]:b.setAttribute(e,c[e]));d&&b.appendChild(a.c.createTextNode(d));return b}function u(a,b,c){a=a.c.getElementsByTagName(b)[0];a||(a=document.documentElement);a.insertBefore(c,a.lastChild)}function v(a){a.parentNode&&a.parentNode.removeChild(a)}
|
||||
function w(a,b,c){b=b||[];c=c||[];for(var d=a.className.split(/\s+/),e=0;e<b.length;e+=1){for(var f=!1,g=0;g<d.length;g+=1)if(b[e]===d[g]){f=!0;break}f||d.push(b[e])}b=[];for(e=0;e<d.length;e+=1){f=!1;for(g=0;g<c.length;g+=1)if(d[e]===c[g]){f=!0;break}f||b.push(d[e])}a.className=b.join(" ").replace(/\s+/g," ").replace(/^\s+|\s+$/,"")}function y(a,b){for(var c=a.className.split(/\s+/),d=0,e=c.length;d<e;d++)if(c[d]==b)return!0;return!1}
|
||||
function z(a){if("string"===typeof a.f)return a.f;var b=a.m.location.protocol;"about:"==b&&(b=a.a.location.protocol);return"https:"==b?"https:":"http:"}function ea(a){return a.m.location.hostname||a.a.location.hostname}
|
||||
function A(a,b,c){function d(){k&&e&&f&&(k(g),k=null)}b=t(a,"link",{rel:"stylesheet",href:b,media:"all"});var e=!1,f=!0,g=null,k=c||null;da?(b.onload=function(){e=!0;d()},b.onerror=function(){e=!0;g=Error("Stylesheet failed to load");d()}):setTimeout(function(){e=!0;d()},0);u(a,"head",b)}
|
||||
function B(a,b,c,d){var e=a.c.getElementsByTagName("head")[0];if(e){var f=t(a,"script",{src:b}),g=!1;f.onload=f.onreadystatechange=function(){g||this.readyState&&"loaded"!=this.readyState&&"complete"!=this.readyState||(g=!0,c&&c(null),f.onload=f.onreadystatechange=null,"HEAD"==f.parentNode.tagName&&e.removeChild(f))};e.appendChild(f);setTimeout(function(){g||(g=!0,c&&c(Error("Script load timeout")))},d||5E3);return f}return null};function C(){this.a=0;this.c=null}function D(a){a.a++;return function(){a.a--;E(a)}}function F(a,b){a.c=b;E(a)}function E(a){0==a.a&&a.c&&(a.c(),a.c=null)};function G(a){this.a=a||"-"}G.prototype.c=function(a){for(var b=[],c=0;c<arguments.length;c++)b.push(arguments[c].replace(/[\W_]+/g,"").toLowerCase());return b.join(this.a)};function H(a,b){this.c=a;this.f=4;this.a="n";var c=(b||"n4").match(/^([nio])([1-9])$/i);c&&(this.a=c[1],this.f=parseInt(c[2],10))}function fa(a){return I(a)+" "+(a.f+"00")+" 300px "+J(a.c)}function J(a){var b=[];a=a.split(/,\s*/);for(var c=0;c<a.length;c++){var d=a[c].replace(/['"]/g,"");-1!=d.indexOf(" ")||/^\d/.test(d)?b.push("'"+d+"'"):b.push(d)}return b.join(",")}function K(a){return a.a+a.f}function I(a){var b="normal";"o"===a.a?b="oblique":"i"===a.a&&(b="italic");return b}
|
||||
function ga(a){var b=4,c="n",d=null;a&&((d=a.match(/(normal|oblique|italic)/i))&&d[1]&&(c=d[1].substr(0,1).toLowerCase()),(d=a.match(/([1-9]00|normal|bold)/i))&&d[1]&&(/bold/i.test(d[1])?b=7:/[1-9]00/.test(d[1])&&(b=parseInt(d[1].substr(0,1),10))));return c+b};function ha(a,b){this.c=a;this.f=a.m.document.documentElement;this.h=b;this.a=new G("-");this.j=!1!==b.events;this.g=!1!==b.classes}function ia(a){a.g&&w(a.f,[a.a.c("wf","loading")]);L(a,"loading")}function M(a){if(a.g){var b=y(a.f,a.a.c("wf","active")),c=[],d=[a.a.c("wf","loading")];b||c.push(a.a.c("wf","inactive"));w(a.f,c,d)}L(a,"inactive")}function L(a,b,c){if(a.j&&a.h[b])if(c)a.h[b](c.c,K(c));else a.h[b]()};function ja(){this.c={}}function ka(a,b,c){var d=[],e;for(e in b)if(b.hasOwnProperty(e)){var f=a.c[e];f&&d.push(f(b[e],c))}return d};function N(a,b){this.c=a;this.f=b;this.a=t(this.c,"span",{"aria-hidden":"true"},this.f)}function O(a){u(a.c,"body",a.a)}function P(a){return"display:block;position:absolute;top:-9999px;left:-9999px;font-size:300px;width:auto;height:auto;line-height:normal;margin:0;padding:0;font-variant:normal;white-space:nowrap;font-family:"+J(a.c)+";"+("font-style:"+I(a)+";font-weight:"+(a.f+"00")+";")};function Q(a,b,c,d,e,f){this.g=a;this.j=b;this.a=d;this.c=c;this.f=e||3E3;this.h=f||void 0}Q.prototype.start=function(){var a=this.c.m.document,b=this,c=q(),d=new Promise(function(d,e){function k(){q()-c>=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?d():setTimeout(k,25)},function(){e()})}k()}),e=new Promise(function(a,d){setTimeout(d,b.f)});Promise.race([e,d]).then(function(){b.g(b.a)},function(){b.j(b.a)})};function R(a,b,c,d,e,f,g){this.v=a;this.B=b;this.c=c;this.a=d;this.s=g||"BESbswy";this.f={};this.w=e||3E3;this.u=f||null;this.o=this.j=this.h=this.g=null;this.g=new N(this.c,this.s);this.h=new N(this.c,this.s);this.j=new N(this.c,this.s);this.o=new N(this.c,this.s);a=new H(this.a.c+",serif",K(this.a));a=P(a);this.g.a.style.cssText=a;a=new H(this.a.c+",sans-serif",K(this.a));a=P(a);this.h.a.style.cssText=a;a=new H("serif",K(this.a));a=P(a);this.j.a.style.cssText=a;a=new H("sans-serif",K(this.a));a=
|
||||
P(a);this.o.a.style.cssText=a;O(this.g);O(this.h);O(this.j);O(this.o)}var S={D:"serif",C:"sans-serif"},T=null;function U(){if(null===T){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent);T=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return T}R.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f["sans-serif"]=this.o.a.offsetWidth;this.A=q();la(this)};
|
||||
function ma(a,b,c){for(var d in S)if(S.hasOwnProperty(d)&&b===a.f[S[d]]&&c===a.f[S[d]])return!0;return!1}function la(a){var b=a.g.a.offsetWidth,c=a.h.a.offsetWidth,d;(d=b===a.f.serif&&c===a.f["sans-serif"])||(d=U()&&ma(a,b,c));d?q()-a.A>=a.w?U()&&ma(a,b,c)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):na(a):V(a,a.v)}function na(a){setTimeout(p(function(){la(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.o.a);b(this.a)},a),0)};function W(a,b,c){this.c=a;this.a=b;this.f=0;this.o=this.j=!1;this.s=c}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c("wf",a.c,K(a).toString(),"active")],[b.a.c("wf",a.c,K(a).toString(),"loading"),b.a.c("wf",a.c,K(a).toString(),"inactive")]);L(b,"fontactive",a);this.o=!0;oa(this)};
|
||||
W.prototype.h=function(a){var b=this.a;if(b.g){var c=y(b.f,b.a.c("wf",a.c,K(a).toString(),"active")),d=[],e=[b.a.c("wf",a.c,K(a).toString(),"loading")];c||d.push(b.a.c("wf",a.c,K(a).toString(),"inactive"));w(b.f,d,e)}L(b,"fontinactive",a);oa(this)};function oa(a){0==--a.f&&a.j&&(a.o?(a=a.a,a.g&&w(a.f,[a.a.c("wf","active")],[a.a.c("wf","loading"),a.a.c("wf","inactive")]),L(a,"active")):M(a.a))};function pa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}pa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;qa(this,new ha(this.c,a),a)};
|
||||
function ra(a,b,c,d,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,k=d||null||{};if(0===c.length&&f)M(b.a);else{b.f+=c.length;f&&(b.j=f);var h,m=[];for(h=0;h<c.length;h++){var l=c[h],n=k[l.c],r=b.a,x=l;r.g&&w(r.f,[r.a.c("wf",x.c,K(x).toString(),"loading")]);L(r,"fontloading",x);r=null;null===X&&(X=window.FontFace?(x=/Gecko.*Firefox\/(\d+)/.exec(window.navigator.userAgent))?42<parseInt(x[1],10):!0:!1);X?r=new Q(p(b.g,b),p(b.h,b),b.c,l,b.s,n):r=new R(p(b.g,b),p(b.h,b),b.c,l,b.s,a,
|
||||
n);m.push(r)}for(h=0;h<m.length;h++)m[h].start()}},0)}function qa(a,b,c){var d=[],e=c.timeout;ia(b);var d=ka(a.a,c,a.c),f=new W(a.c,b,e);a.h=d.length;b=0;for(c=d.length;b<c;b++)d[b].load(function(b,d,c){ra(a,f,b,d,c)})};function sa(a,b){this.c=a;this.a=b}function ta(a,b,c){var d=z(a.c);a=(a.a.api||"fast.fonts.net/jsapi").replace(/^.*http(s?):(\/\/)?/,"");return d+"//"+a+"/"+b+".js"+(c?"?v="+c:"")}
|
||||
sa.prototype.load=function(a){function b(){if(f["__mti_fntLst"+d]){var c=f["__mti_fntLst"+d](),e=[],h;if(c)for(var m=0;m<c.length;m++){var l=c[m].fontfamily;void 0!=c[m].fontStyle&&void 0!=c[m].fontWeight?(h=c[m].fontStyle+c[m].fontWeight,e.push(new H(l,h))):e.push(new H(l))}a(e)}else setTimeout(function(){b()},50)}var c=this,d=c.a.projectId,e=c.a.version;if(d){var f=c.c.m;B(this.c,ta(c,d,e),function(e){e?a([]):(f["__MonotypeConfiguration__"+d]=function(){return c.a},b())}).id="__MonotypeAPIScript__"+
|
||||
d}else a([])};function ua(a,b){this.c=a;this.a=b}ua.prototype.load=function(a){var b,c,d=this.a.urls||[],e=this.a.families||[],f=this.a.testStrings||{},g=new C;b=0;for(c=d.length;b<c;b++)A(this.c,d[b],D(g));var k=[];b=0;for(c=e.length;b<c;b++)if(d=e[b].split(":"),d[1])for(var h=d[1].split(","),m=0;m<h.length;m+=1)k.push(new H(d[0],h[m]));else k.push(new H(d[0]));F(g,function(){a(k,f)})};function va(a,b,c){a?this.c=a:this.c=b+wa;this.a=[];this.f=[];this.g=c||""}var wa="//fonts.googleapis.com/css";function xa(a,b){for(var c=b.length,d=0;d<c;d++){var e=b[d].split(":");3==e.length&&a.f.push(e.pop());var f="";2==e.length&&""!=e[1]&&(f=":");a.a.push(e.join(f))}}
|
||||
function ya(a){if(0==a.a.length)throw Error("No fonts to load!");if(-1!=a.c.indexOf("kit="))return a.c;for(var b=a.a.length,c=[],d=0;d<b;d++)c.push(a.a[d].replace(/ /g,"+"));b=a.c+"?family="+c.join("%7C");0<a.f.length&&(b+="&subset="+a.f.join(","));0<a.g.length&&(b+="&text="+encodeURIComponent(a.g));return b};function za(a){this.f=a;this.a=[];this.c={}}
|
||||
var Aa={latin:"BESbswy","latin-ext":"\u00e7\u00f6\u00fc\u011f\u015f",cyrillic:"\u0439\u044f\u0416",greek:"\u03b1\u03b2\u03a3",khmer:"\u1780\u1781\u1782",Hanuman:"\u1780\u1781\u1782"},Ba={thin:"1",extralight:"2","extra-light":"2",ultralight:"2","ultra-light":"2",light:"3",regular:"4",book:"4",medium:"5","semi-bold":"6",semibold:"6","demi-bold":"6",demibold:"6",bold:"7","extra-bold":"8",extrabold:"8","ultra-bold":"8",ultrabold:"8",black:"9",heavy:"9",l:"3",r:"4",b:"7"},Ca={i:"i",italic:"i",n:"n",normal:"n"},
|
||||
Da=/^(thin|(?:(?:extra|ultra)-?)?light|regular|book|medium|(?:(?:semi|demi|extra|ultra)-?)?bold|black|heavy|l|r|b|[1-9]00)?(n|i|normal|italic)?$/;
|
||||
function Ea(a){for(var b=a.f.length,c=0;c<b;c++){var d=a.f[c].split(":"),e=d[0].replace(/\+/g," "),f=["n4"];if(2<=d.length){var g;var k=d[1];g=[];if(k)for(var k=k.split(","),h=k.length,m=0;m<h;m++){var l;l=k[m];if(l.match(/^[\w-]+$/)){var n=Da.exec(l.toLowerCase());if(null==n)l="";else{l=n[2];l=null==l||""==l?"n":Ca[l];n=n[1];if(null==n||""==n)n="4";else var r=Ba[n],n=r?r:isNaN(n)?"4":n.substr(0,1);l=[l,n].join("")}}else l="";l&&g.push(l)}0<g.length&&(f=g);3==d.length&&(d=d[2],g=[],d=d?d.split(","):
|
||||
g,0<d.length&&(d=Aa[d[0]])&&(a.c[e]=d))}a.c[e]||(d=Aa[e])&&(a.c[e]=d);for(d=0;d<f.length;d+=1)a.a.push(new H(e,f[d]))}};function Fa(a,b){this.c=a;this.a=b}var Ga={Arimo:!0,Cousine:!0,Tinos:!0};Fa.prototype.load=function(a){var b=new C,c=this.c,d=new va(this.a.api,z(c),this.a.text),e=this.a.families;xa(d,e);var f=new za(e);Ea(f);A(c,ya(d),D(b));F(b,function(){a(f.a,f.c,Ga)})};function Ha(a,b){this.c=a;this.a=b}Ha.prototype.load=function(a){var b=this.a.id,c=this.c.m;b?B(this.c,(this.a.api||"https://use.typekit.net")+"/"+b+".js",function(b){if(b)a([]);else if(c.Typekit&&c.Typekit.config&&c.Typekit.config.fn){b=c.Typekit.config.fn;for(var e=[],f=0;f<b.length;f+=2)for(var g=b[f],k=b[f+1],h=0;h<k.length;h++)e.push(new H(g,k[h]));try{c.Typekit.load({events:!1,classes:!1,async:!0})}catch(m){}a(e)}},2E3):a([])};function Ia(a,b){this.c=a;this.f=b;this.a=[]}Ia.prototype.load=function(a){var b=this.f.id,c=this.c.m,d=this;b?(c.__webfontfontdeckmodule__||(c.__webfontfontdeckmodule__={}),c.__webfontfontdeckmodule__[b]=function(b,c){for(var g=0,k=c.fonts.length;g<k;++g){var h=c.fonts[g];d.a.push(new H(h.name,ga("font-weight:"+h.weight+";font-style:"+h.style)))}a(d.a)},B(this.c,z(this.c)+(this.f.api||"//f.fontdeck.com/s/css/js/")+ea(this.c)+"/"+b+".js",function(b){b&&a([])})):a([])};var Y=new pa(window);Y.a.c.custom=function(a,b){return new ua(b,a)};Y.a.c.fontdeck=function(a,b){return new Ia(b,a)};Y.a.c.monotype=function(a,b){return new sa(b,a)};Y.a.c.typekit=function(a,b){return new Ha(b,a)};Y.a.c.google=function(a,b){return new Fa(b,a)};var Z={load:p(Y.load,Y)};"function"===typeof define&&define.amd?define(function(){return Z}):"undefined"!==typeof module&&module.exports?module.exports=Z:(window.WebFont=Z,window.WebFontConfig&&Y.load(window.WebFontConfig));}());
|
||||
74
scripts/chat/index.js
Normal file
74
scripts/chat/index.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
let params = {};
|
||||
|
||||
function respond(template, params, parentId = null) {
|
||||
if (getSetting("replyInChat")) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
console.log(`{${key}}: ${params[key]}`);
|
||||
template = template.replaceAll(`{${key}}`, params[key]);
|
||||
});
|
||||
|
||||
template = template.charAt(0).toUpperCase() + template.slice(1);
|
||||
|
||||
if (!parentId) {
|
||||
ComfyJS.Say(template);
|
||||
} else {
|
||||
ComfyJS.Reply(parentId, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMod(flags) {
|
||||
return flags.broadcaster || flags.mod;
|
||||
}
|
||||
|
||||
function isStreamer(flags) {
|
||||
return flags.broadcaster;
|
||||
}
|
||||
|
||||
ComfyJS.onCommand = (user, command, message, flags, extra) => {
|
||||
if (!getSetting("listenAcrossSharedChat")) {
|
||||
// Reject messages from another chat during shared chat if room information
|
||||
// is present and doesn't match the current chat.
|
||||
const { roomId, userState } = extra;
|
||||
const sourceRoomId = userState["source-room-id"];
|
||||
if (roomId && sourceRoomId && roomId !== sourceRoomId) return;
|
||||
}
|
||||
|
||||
command = `!${command.toLowerCase()}`;
|
||||
|
||||
processCommand(user, command, message, flags, extra);
|
||||
};
|
||||
|
||||
const oauth_token = auth.oauth.includes("oauth:")
|
||||
? auth.oauth
|
||||
: `oauth:${auth.oauth}`;
|
||||
const auth_username = auth.username ? auth.username : auth.channel;
|
||||
|
||||
function showOAuthExpiredModal() {
|
||||
const modal = document.getElementById("oauth-expired-modal");
|
||||
if (modal) modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
async function validateOAuthToken() {
|
||||
const bareToken = oauth_token.replace(/^oauth:/i, "");
|
||||
const response = await fetch("https://id.twitch.tv/oauth2/validate", {
|
||||
headers: { Authorization: `OAuth ${bareToken}` },
|
||||
});
|
||||
return response.ok;
|
||||
}
|
||||
|
||||
async function initChat() {
|
||||
try {
|
||||
const valid = await validateOAuthToken();
|
||||
if (!valid) {
|
||||
showOAuthExpiredModal();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Could not validate OAuth token; connecting anyway.", err);
|
||||
}
|
||||
|
||||
ComfyJS.Init(auth_username, oauth_token, [auth.channel]);
|
||||
}
|
||||
|
||||
initChat();
|
||||
668
scripts/chat/processCommand.js
Normal file
668
scripts/chat/processCommand.js
Normal file
|
|
@ -0,0 +1,668 @@
|
|||
const commands = configs.commands;
|
||||
|
||||
async function processCommand(user, command, message, flags, extra) {
|
||||
params = {
|
||||
user: user,
|
||||
message: message,
|
||||
pointName: getSetting("pointsName"),
|
||||
taskName: getSetting("taskName"),
|
||||
};
|
||||
|
||||
command = command.toLowerCase();
|
||||
|
||||
if (getCommand("addTaskCommands").includes(command)) {
|
||||
let addRequest = await addTask(user, extra.userColor, message);
|
||||
|
||||
if (addRequest.status !== 200) {
|
||||
params.task = `${openQuote}${message}${closeQuote}`;
|
||||
respond(addRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let addedResponse = getResponse("taskAdded");
|
||||
params.task = `${openQuote}${addRequest.body.task}${closeQuote}`;
|
||||
|
||||
if (addRequest.body.tasksFailedToAdd !== "") {
|
||||
addedResponse += ` | Failed to add task(s): "${addRequest.body.tasksFailedToAdd}"`;
|
||||
}
|
||||
|
||||
await respond(addedResponse, params, extra.id);
|
||||
} else if (getCommand("editTaskCommands").includes(command)) {
|
||||
let editRequest = await editTask(user, message);
|
||||
|
||||
if (editRequest.status !== 200) {
|
||||
respond(editRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let originalTask = editRequest.body.originalTask;
|
||||
let newTask = editRequest.body.newTask;
|
||||
|
||||
params.task = `${openQuote}${newTask}${closeQuote}`;
|
||||
params.originalTask = `${openQuote}${originalTask}${closeQuote}`;
|
||||
|
||||
respond(getResponse("taskEdited"), params, extra.id);
|
||||
} else if (getCommand("deleteTaskCommands").includes(command)) {
|
||||
let removeRequest = await removeTask(user, message);
|
||||
|
||||
if (removeRequest.status !== 200) {
|
||||
respond(removeRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
let deletedResponse = getResponse("taskDeleted");
|
||||
|
||||
let removedTasks = `${openQuote}${removeRequest.body.removedTasks}${closeQuote}`;
|
||||
let failedTasks = removeRequest.body.failedTasks;
|
||||
|
||||
params.task = removedTasks;
|
||||
|
||||
if (failedTasks !== "") {
|
||||
deletedResponse += ` | Failed to delete task(s): ${openQuote}${failedTasks}${closeQuote}`;
|
||||
}
|
||||
|
||||
respond(deletedResponse, params, extra.id);
|
||||
} else if (getCommand("finishTaskCommands").includes(command)) {
|
||||
if (message === "all") {
|
||||
let finishAllRequest = await markAllTasksAsDone(user);
|
||||
|
||||
if (finishAllRequest.status !== 200) {
|
||||
respond(finishAllRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let finishedAllResponse = getResponse("allTasksFinished");
|
||||
params.doneCount = await completedTasksCount(user);
|
||||
finishedAllResponse = finishedAllResponse.replaceAll(
|
||||
"{pointName}",
|
||||
getSetting("pointsName")
|
||||
);
|
||||
|
||||
finishedAllResponse = finishedAllResponse.replaceAll(
|
||||
"{taskName}",
|
||||
getSetting("taskName")
|
||||
);
|
||||
|
||||
respond(finishedAllResponse, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let finishRequest = await markTaskDone(user, message);
|
||||
|
||||
if (finishRequest.status !== 200) {
|
||||
respond(finishRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let finishedResponse = getResponse("taskFinished");
|
||||
params.task = `${openQuote}${finishRequest.body.markedTasks}${closeQuote}`;
|
||||
params.doneCount = await completedTasksCount(user);
|
||||
params.pointCount =
|
||||
finishRequest.body.markedTasksCount * getSetting("pointsPerTask");
|
||||
|
||||
if (finishRequest.body.failedTasks !== "") {
|
||||
finishedResponse += ` | Failed to finish {taskName}(s): "${finishRequest.body.failedTasks}"`;
|
||||
}
|
||||
|
||||
finishedResponse = finishedResponse.replaceAll(
|
||||
"{pointName}",
|
||||
getSetting("pointsName")
|
||||
);
|
||||
|
||||
finishedResponse = finishedResponse.replaceAll(
|
||||
"{taskName}",
|
||||
getSetting("taskName")
|
||||
);
|
||||
|
||||
respond(finishedResponse, params, extra.id);
|
||||
} else if (getCommand("unfinishTaskCommands").includes(command)) {
|
||||
let unfinishRequest = await markTaskUndone(user, message);
|
||||
|
||||
if (unfinishRequest.status !== 200) {
|
||||
respond(unfinishRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let unfinishedResponse = getResponse("taskUnfinished");
|
||||
|
||||
params.task = `${openQuote}${unfinishRequest.body.markedTasks}${closeQuote}`;
|
||||
|
||||
if (unfinishRequest.body.failedTasks !== "") {
|
||||
unfinishedResponse += ` | Failed to unfinish task(s): "${unfinishRequest.body.failedTasks}"`;
|
||||
}
|
||||
|
||||
respond(unfinishedResponse, params, extra.id);
|
||||
} else if (getCommand("nextTaskCommands").includes(command)) {
|
||||
let nextRequest = await nextTask(user, message);
|
||||
|
||||
if (nextRequest.status !== 200) {
|
||||
params.task = `${openQuote}${nextRequest.body.task}${closeQuote}`;
|
||||
await respond(nextRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let nextResponse = getResponse("taskNext");
|
||||
|
||||
params.oldTask = `${openQuote}${nextRequest.body.oldTask}${closeQuote}`;
|
||||
params.newTask = `${openQuote}${nextRequest.body.newTask}${closeQuote}`;
|
||||
|
||||
respond(nextResponse, params, extra.id);
|
||||
} else if (getCommand("logTaskCommands").includes(command)) {
|
||||
let logRequest = await logTask(user, extra.userColor, message);
|
||||
|
||||
if (logRequest.status !== 200) {
|
||||
respond(logRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let loggedResponse = await getResponse("taskLogged");
|
||||
|
||||
params.task = `${openQuote}${logRequest.body.task}${closeQuote}`;
|
||||
|
||||
if (logRequest.body.tasksFailedToLog !== "") {
|
||||
loggedResponse += ` | Failed to log task(s): ${openQuote}${logRequest.body.tasksFailedToLog}${closeQuote}`;
|
||||
}
|
||||
|
||||
await respond(loggedResponse, params, extra.id);
|
||||
} else if (
|
||||
getCommand("focusTaskCommands").includes(command) ||
|
||||
getCommand("nowTaskCommands").includes(command)
|
||||
) {
|
||||
let focusRequest = await focusTask(user, extra.userColor, message);
|
||||
|
||||
if (focusRequest.status !== 200) {
|
||||
respond(focusRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let focusedResponse = getResponse("nowTask");
|
||||
|
||||
params.task = `${openQuote}${focusRequest.body.focusedTask}${closeQuote}`;
|
||||
|
||||
respond(focusedResponse, params, extra.id);
|
||||
} else if (getCommand("focusedTaskCommands").includes(command)) {
|
||||
let focusedRequest = await focusedTask(user);
|
||||
|
||||
if (focusedRequest.status !== 200) {
|
||||
respond(focusedRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let focusedResponse = getResponse("focusedTask");
|
||||
|
||||
params.task = `${openQuote}${focusedRequest.body.focusedTask}${closeQuote}`;
|
||||
|
||||
await respond(focusedResponse, params, extra.id);
|
||||
} else if (getCommand("unfocusTaskCommands").includes(command)) {
|
||||
let unfocusRequest = await unfocusTask(user);
|
||||
|
||||
if (unfocusRequest.status !== 200) {
|
||||
respond(unfocusRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let unfocusedResponse = getResponse("clearFocused");
|
||||
|
||||
respond(unfocusedResponse, params, extra.id);
|
||||
} else if (
|
||||
getCommand("checkCommands").includes(command) ||
|
||||
getCommand("checkCompletedCommands").includes(command)
|
||||
) {
|
||||
let completed = getCommand("checkCompletedCommands").includes(command);
|
||||
|
||||
if (message === "") {
|
||||
let checkRequest = await checkTasks(user, completed);
|
||||
if (checkRequest.status !== 200) {
|
||||
respond(checkRequest.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let filteredTasks = checkRequest.body.reply.split(" | ");
|
||||
|
||||
for (let i = 0; i < filteredTasks.length; i += 10) {
|
||||
let tasks = filteredTasks.slice(i, i + 10);
|
||||
params.tasks = tasks
|
||||
.join(" | ")
|
||||
.replaceAll("{taskName}", getSetting("taskName"));
|
||||
|
||||
const checkingUser = user;
|
||||
params.checkingUser = checkingUser;
|
||||
|
||||
await respond(`{tasks}`, params, extra.id);
|
||||
await sleep(200);
|
||||
}
|
||||
} else {
|
||||
let mentioned = message.replace("@", "");
|
||||
|
||||
if (mentioned.toLowerCase() === "id") {
|
||||
await respond(getResponse("noTaskA"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let checkRequest = await checkTasks(mentioned, completed);
|
||||
|
||||
let filteredTasks = checkRequest.body.reply.split(" | ");
|
||||
|
||||
if (checkRequest.status !== 200) {
|
||||
respond(getResponse("noTaskA"), params, extra.id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < filteredTasks.length; i += 10) {
|
||||
let tasks = filteredTasks.slice(i, i + 10);
|
||||
params.tasks = tasks
|
||||
.join(" | ")
|
||||
.replaceAll("{taskName}", getSetting("taskName"));
|
||||
|
||||
const checkingUser = user;
|
||||
|
||||
params.checkingUser = checkingUser;
|
||||
|
||||
await respond(`{checkingUser} {tasks}`, params, extra.id);
|
||||
await sleep(200);
|
||||
}
|
||||
}
|
||||
} else if (getCommand("adminDeleteCommands").includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(getResponse("notMod"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
let mentioned = message.replace("@", "");
|
||||
|
||||
if (mentioned === "") {
|
||||
respond(getResponse("specifyUser"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let clearUserTaskResponse = await clearUserTasks(mentioned);
|
||||
|
||||
params.mentioned = mentioned;
|
||||
|
||||
if (clearUserTaskResponse.status === 0) {
|
||||
respond(getResponse("noTaskA"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
respond(getResponse("adminDeleteTasks"), params, extra.id);
|
||||
return;
|
||||
} else if (getCommand("adminClearDoneCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await clearAllDoneTasks();
|
||||
respond(getResponse("clearedDone"), params, extra.id);
|
||||
return;
|
||||
} else if (getCommand("adminClearAllCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await clearAll();
|
||||
respond(getResponse("clearedAll"), params, extra.id);
|
||||
return;
|
||||
} else if (getCommand("clearMyDoneCommands").includes(command)) {
|
||||
let clearOwnDoneResponse = await clearOwnDoneTasks(user);
|
||||
|
||||
if (clearOwnDoneResponse.status !== 200) {
|
||||
respond(getResponse("noTask"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
respond(getResponse("clearedMyDone"), params, extra.id);
|
||||
} else if (getCommand("adminClearNotStreamerCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
await clearMemory();
|
||||
|
||||
await clearAllExceptStreamer(auth.channel);
|
||||
respond(getResponse("clearTasksExceptBroadcaster"), params, extra.id);
|
||||
} else if (getCommand("adminClearTasksCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
await clearMemory();
|
||||
|
||||
await clearAllTasks();
|
||||
respond(getResponse("clearedTasks"), params, extra.id);
|
||||
} else if (getCommand("listCommands").includes(command)) {
|
||||
const interval = 100;
|
||||
if (message === "") {
|
||||
let listTaskResponse = await listTasks(user);
|
||||
|
||||
if (listTaskResponse.status !== 200) {
|
||||
respond(listTaskResponse.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
for (reply of listTaskResponse.body.replies) {
|
||||
params.tasks = reply;
|
||||
await respond("{tasks}", params, extra.id);
|
||||
await sleep(interval);
|
||||
}
|
||||
} else {
|
||||
let separator = ",";
|
||||
let args = message.split(" ");
|
||||
let anotherUser = true;
|
||||
|
||||
if (args[0].length === 1) {
|
||||
separator = args[0];
|
||||
args.shift();
|
||||
} else if (args[1] && args[1].length === 1) {
|
||||
separator = args[1];
|
||||
args.splice(1, 1);
|
||||
} else {
|
||||
}
|
||||
|
||||
let mentioned = args.join(" ").replace("@", "");
|
||||
|
||||
if (args.length === 0) {
|
||||
mentioned = user;
|
||||
anotherUser = false;
|
||||
}
|
||||
|
||||
if (mentioned.toLowerCase() === "id") {
|
||||
await respond(getResponse("noTaskA"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let listTaskResponse = await listTasks(mentioned, separator);
|
||||
|
||||
if (listTaskResponse.status !== 200 && anotherUser) {
|
||||
respond(getResponse("noTaskA"), params, extra.id);
|
||||
return;
|
||||
} else if (listTaskResponse.status !== 200 && !anotherUser) {
|
||||
respond(getResponse("noTask"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
for (reply of listTaskResponse.body.replies) {
|
||||
params.tasks = reply;
|
||||
await respond("{tasks}", params, extra.id);
|
||||
await sleep(interval);
|
||||
}
|
||||
}
|
||||
} else if (getCommand("checkCountCommands").includes(command)) {
|
||||
if (message === "") {
|
||||
let count = await completedTasksCount(user);
|
||||
|
||||
params.doneCount = count;
|
||||
|
||||
return respond(getResponse("checkYourCount"), params, extra.id);
|
||||
} else {
|
||||
let mentioned = message.replace("@", "");
|
||||
let count = await completedTasksCount(mentioned);
|
||||
|
||||
params.doneCount = count;
|
||||
params.mentioned = mentioned;
|
||||
|
||||
return respond(getResponse("checkUserCount"), params, extra.id);
|
||||
}
|
||||
} else if (getCommand("checkAllCountCommands").includes(command)) {
|
||||
let count = await getBoardTotalTaskCount();
|
||||
|
||||
if (count === 0) {
|
||||
respond(getResponse("noCountAll"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
params.doneCount = count;
|
||||
|
||||
respond(getResponse("checkAllCount"), params, extra.id);
|
||||
} else if (getCommand("checkMyPointsCommands").includes(command)) {
|
||||
if (message === "") {
|
||||
let points = await getUserPoints(user);
|
||||
|
||||
params.pointCount = points;
|
||||
|
||||
return respond(getResponse("checkMyPoints"), params, extra.id);
|
||||
} else {
|
||||
let mentioned = message.replace("@", "");
|
||||
let points = await getUserPoints(mentioned);
|
||||
|
||||
params.pointCount = points;
|
||||
params.mentioned = mentioned;
|
||||
|
||||
return respond(getResponse("checkUserPoints"), params, extra.id);
|
||||
}
|
||||
} else if (getCommand("syncCountPointsCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncPointsToCount();
|
||||
respond(getResponse("syncCountPoints"), params, extra.id);
|
||||
} else if (getCommand("addPointsCommands").includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(getResponse("notMod"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let mentioned = message.split(" ")[0].replace("@", "");
|
||||
|
||||
if (mentioned === "") {
|
||||
respond(getResponse("specifyUser"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let points = message.split(" ")[1];
|
||||
|
||||
if (points === undefined) {
|
||||
respond(getResponse("specifyPoints"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(points)) {
|
||||
respond(getResponse("invalidNumber"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
points = parseInt(points);
|
||||
|
||||
await addPoints(mentioned, points);
|
||||
|
||||
params.mentioned = mentioned;
|
||||
params.pointCount = points;
|
||||
|
||||
respond(getResponse("addPoints"), params, extra.id);
|
||||
} else if (getCommand("reducePointsCommands").includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(getResponse("notMod"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let mentioned = message.split(" ")[0].replace("@", "");
|
||||
|
||||
if (mentioned === "") {
|
||||
respond(getResponse("specifyUser"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let points = message.split(" ")[1];
|
||||
|
||||
if (points == undefined) {
|
||||
respond(getResponse("specifyPoints"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(points)) {
|
||||
respond(getResponse("invalidNumber"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await reducePoints(mentioned, points);
|
||||
|
||||
params.mentioned = mentioned;
|
||||
params.pointCount = points;
|
||||
|
||||
respond(getResponse("reducePoints"), params, extra.id);
|
||||
} else if (getCommand("setUserPointsCommands").includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(getResponse("notMod"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let mentioned = message.split(" ")[0].replace("@", "");
|
||||
|
||||
if (mentioned === "") {
|
||||
respond(getResponse("specifyUser"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let points = message.split(" ")[1];
|
||||
|
||||
if (points === undefined) {
|
||||
respond(getResponse("specifyPoints"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(points)) {
|
||||
respond(getResponse("invalidNumber"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
points = parseInt(points);
|
||||
|
||||
await setUserPoints(mentioned, points);
|
||||
|
||||
params.mentioned = mentioned;
|
||||
params.pointCount = points;
|
||||
|
||||
respond(getResponse("setUserPoints"), params, extra.id);
|
||||
} else if (getCommand("setUserTaskCountCommands").includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(getResponse("notMod"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let mentioned = message.split(" ")[0].replace("@", "");
|
||||
|
||||
if (mentioned === "") {
|
||||
respond(getResponse("specifyUser"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let count = message.split(" ")[1];
|
||||
|
||||
if (count === undefined) {
|
||||
respond(getResponse("specifyCount"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(count)) {
|
||||
respond(getResponse("invalidNumber"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let setUserTaskCountResponse = await setUserTaskCount(mentioned, count);
|
||||
|
||||
if (setUserTaskCountResponse.status !== 200) {
|
||||
respond(setUserTaskCountResponse.body.message, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
params.mentioned = mentioned;
|
||||
params.taskCount = count;
|
||||
|
||||
respond(setUserTaskCountResponse.body.message, params, extra.id);
|
||||
} else if (getCommand("leaderboardCommands").includes(command)) {
|
||||
let leaderboardResponse = await leaderboardTaskCount(5);
|
||||
|
||||
if (leaderboardResponse.status !== 200) {
|
||||
respond(leaderboardResponse.body.error, params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let leaderboard = leaderboardResponse.body.leaderboard;
|
||||
|
||||
await respond(leaderboard, params, extra.id);
|
||||
} else if (getCommand("adminSetBoardCountCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(message)) {
|
||||
respond(getResponse("invalidNumber"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
params.count = message;
|
||||
|
||||
await setTotalCompleteCount(message);
|
||||
|
||||
respond(getResponse("setBoardCount"), params, extra.id);
|
||||
} else if (getCommand("adminResetBoardCountCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await resetBoardCount();
|
||||
respond(getResponse("clearedBoardCount"), params, extra.id);
|
||||
} else if (getCommand("adminResetUsersCountCommands").includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await resetUsersCount();
|
||||
respond(getResponse("clearedUsersCount"), params, extra.id);
|
||||
} else if (getCommand("taskMasterCommands").includes(command)) {
|
||||
if (!getSetting("enableTaskMaster")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let champion = await getTaskMasterChampion();
|
||||
|
||||
if (champion === null) {
|
||||
respond(getResponse("noTaskMaster"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
params.taskMaster = champion.username;
|
||||
params.taskMasterCount = champion.count;
|
||||
|
||||
respond(getResponse("taskMaster"), params, extra.id);
|
||||
} else if (getCommand("resetTaskMasterCommands").includes(command)) {
|
||||
if (!getSetting("enableTaskMaster")) {
|
||||
return;
|
||||
}
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await resetTaskMaster();
|
||||
|
||||
respond(getResponse("resetTaskMaster"), params, extra.id);
|
||||
} else if (getCommand("helpCommands").includes(command)) {
|
||||
respond(getResponse("help"), params, extra.id);
|
||||
} else if (command === "!clearlocalstorage") {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
clearLocalStorage();
|
||||
|
||||
respond(getResponse("clearLocalStorage"), params, extra.id);
|
||||
} else if (["!transferdata"].includes(command)) {
|
||||
if (!isStreamer(flags)) {
|
||||
respond(getResponse("notStreamer"), params, extra.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await transferLocalStorageToIndexedDB();
|
||||
} else if (commands.additionalCommands[command]) {
|
||||
respond(commands.additionalCommands[command], params, extra.id);
|
||||
} else {
|
||||
// command not found
|
||||
}
|
||||
}
|
||||
38
scripts/core/constants.js
Normal file
38
scripts/core/constants.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
const STATUS = Object.freeze({
|
||||
ERROR: 0,
|
||||
INVALID_INPUT: 1,
|
||||
ALREADY_COMPLETED: 2,
|
||||
SUCCESS: 200,
|
||||
});
|
||||
|
||||
const DB_CONFIG = Object.freeze({
|
||||
NAME: "tasksDB",
|
||||
VERSION: 1,
|
||||
STORE: "tasks",
|
||||
KEY_TASKS: "tasks",
|
||||
KEY_COUNTS: "counts",
|
||||
KEY_TASKMASTER: "taskmaster",
|
||||
});
|
||||
|
||||
const PAGINATION = Object.freeze({
|
||||
SIZE: 10,
|
||||
SLEEP_MS: 200,
|
||||
});
|
||||
|
||||
const ANIMATION = Object.freeze({
|
||||
TITLE_FADE_IN_MS: 500,
|
||||
TITLE_FADE_OUT_MS: 100,
|
||||
TITLE_CYCLE_INTERVAL_MS: 8000,
|
||||
});
|
||||
|
||||
const TEST_CONFIG = Object.freeze({
|
||||
RANDOM_TASK_MAX: 10,
|
||||
TASK_DELAY_MS: 1000,
|
||||
USER_COUNT: 7,
|
||||
RANDOM_TASK_COUNT_MIN: 1,
|
||||
RANDOM_TASK_COUNT_MAX: 5,
|
||||
});
|
||||
|
||||
const RESERVED_KEYS = Object.freeze({
|
||||
ID: "id",
|
||||
});
|
||||
165
scripts/core/db.js
Normal file
165
scripts/core/db.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
DB structure:
|
||||
|
||||
- tasks
|
||||
- [username]:
|
||||
- todos (array)
|
||||
- [{text, done, focus}, {text, done, focus}]
|
||||
- userColor
|
||||
- counts
|
||||
- users
|
||||
- [username]:
|
||||
- completeCount
|
||||
- points
|
||||
- totalCompleteCount
|
||||
|
||||
- taskmaster
|
||||
- users
|
||||
- [username]:
|
||||
- taskMasterCompleteCount
|
||||
- startDate
|
||||
*/
|
||||
|
||||
let taskListMemory = {
|
||||
doneTaskCount: 0,
|
||||
totalTaskCount: 0,
|
||||
};
|
||||
|
||||
const DBHandler = {
|
||||
db: null,
|
||||
open: function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = indexedDB.open("tasksDB", 1);
|
||||
|
||||
request.onupgradeneeded = function (e) {
|
||||
let db = e.target.result;
|
||||
if (!db.objectStoreNames.contains("tasks")) {
|
||||
db.createObjectStore("tasks", { autoIncrement: true });
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = function (e) {
|
||||
DBHandler.db = e.target.result;
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onerror = function (e) {
|
||||
console.log("Error opening db", e);
|
||||
reject(e);
|
||||
};
|
||||
});
|
||||
},
|
||||
get: function (key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let transaction = DBHandler.db.transaction(["tasks"], "readonly");
|
||||
let store = transaction.objectStore("tasks");
|
||||
let request = store.get(key);
|
||||
|
||||
request.onsuccess = function (e) {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
request.onerror = function (e) {
|
||||
reject("Error getting item", e);
|
||||
};
|
||||
});
|
||||
},
|
||||
set: function (key, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let transaction = DBHandler.db.transaction(["tasks"], "readwrite");
|
||||
let store = transaction.objectStore("tasks");
|
||||
let request = store.put(value, key);
|
||||
|
||||
request.onsuccess = function (e) {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
request.onerror = function (e) {
|
||||
reject("Error setting item", e);
|
||||
};
|
||||
});
|
||||
},
|
||||
remove: function (key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let transaction = DBHandler.db.transaction(["tasks"], "readwrite");
|
||||
let store = transaction.objectStore("tasks");
|
||||
let request = store.delete(key);
|
||||
|
||||
request.onsuccess = function (e) {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
request.onerror = function (e) {
|
||||
reject("Error removing item", e);
|
||||
};
|
||||
});
|
||||
},
|
||||
clear: function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let transaction = DBHandler.db.transaction(["tasks"], "readwrite");
|
||||
let store = transaction.objectStore("tasks");
|
||||
let request = store.clear();
|
||||
|
||||
request.onsuccess = function (e) {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
request.onerror = function (e) {
|
||||
reject("Error clearing store", e);
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
async function transferLocalStorageToIndexedDB() {
|
||||
let keys = Object.keys(localStorage);
|
||||
|
||||
for (let key of keys) {
|
||||
let value = JSON.parse(localStorage.getItem(key));
|
||||
await DBHandler.set(key, value);
|
||||
}
|
||||
|
||||
console.log("Transfer complete");
|
||||
|
||||
await renderTaskListToDOM();
|
||||
}
|
||||
|
||||
function clearLocalStorage() {
|
||||
localStorage.removeItem("tasks");
|
||||
localStorage.removeItem("counts");
|
||||
}
|
||||
|
||||
async function resetDB() {
|
||||
await DBHandler.clear();
|
||||
await setupDB();
|
||||
}
|
||||
|
||||
async function setupDB() {
|
||||
const keys = ["tasks", "counts", "taskmaster"];
|
||||
const defaultValues = [
|
||||
{},
|
||||
{ users: {} },
|
||||
{
|
||||
users: {},
|
||||
startDate: new Date(),
|
||||
taskMasterCompleteCount: 0,
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let value = await DBHandler.get(keys[i]);
|
||||
if (!value) {
|
||||
value = defaultValues[i];
|
||||
}
|
||||
await DBHandler.set(keys[i], value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function clearMemory() {
|
||||
taskListMemory = {
|
||||
doneTaskCount: 0,
|
||||
totalTaskCount: 0,
|
||||
};
|
||||
}
|
||||
436
scripts/core/defaultConfigs.js
Normal file
436
scripts/core/defaultConfigs.js
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
const defaultConfigs = (function () {
|
||||
"use strict";
|
||||
|
||||
const settings = {
|
||||
listenAcrossSharedChat: false,
|
||||
replyInChat: true, // true or false
|
||||
enableLimit: false, // true or false
|
||||
limit: 10, // integer
|
||||
automaticDoneIndex: false, // true or false - Automatically assume first unfinished task is complete
|
||||
taskName: "task", // string (singular)
|
||||
pointsName: "points", // string
|
||||
pointsPerTask: 10, // integer
|
||||
taskSeparator: [";", ","], // array of strings
|
||||
openQuote: '"', // string
|
||||
closeQuote: '"', // string
|
||||
testTasks: false, // true or false
|
||||
headerGoogleFont: true, // true: use google font, false: use system font
|
||||
taskGoogleFont: true, // true: use google font, false: use system font
|
||||
displayTaskCount: true, // true or false
|
||||
enableTaskMaster: true, // true or false
|
||||
hideWhenNoTasks: false, // true or false
|
||||
taskCharacterLimit: 15, // integer
|
||||
taskCharacterLimitEnabled: false, // true or false
|
||||
};
|
||||
|
||||
const animation = {
|
||||
scrollSpeed: 40, // milliseconds
|
||||
gapBetweenScrolls: 0, // px
|
||||
titles: [
|
||||
"!taskhelp",
|
||||
"!task",
|
||||
"!edit",
|
||||
"!remove",
|
||||
"!done",
|
||||
"!undone",
|
||||
"!botcred",
|
||||
"!count",
|
||||
"!points",
|
||||
"!boardcount",
|
||||
],
|
||||
};
|
||||
|
||||
const styles = {
|
||||
// task list
|
||||
taskListBackgroundColor: "#000000", // hex only
|
||||
taskListBackgroundOpacity: 0, // 0.0 - 1.0
|
||||
|
||||
taskListBorderWidth: "0px", // px
|
||||
taskListBorderColor: "#ffffff", // hex or name
|
||||
taskListBorderRadius: "0px", // px
|
||||
|
||||
taskListHorizontalPadding: "0px", // px
|
||||
taskListVerticalPadding: "0px", // px
|
||||
|
||||
// header
|
||||
headerHorizontalPadding: "10px", // px
|
||||
headerVerticalPadding: "10px", // px
|
||||
|
||||
headerBackgroundColor: "#000", // hex only
|
||||
headerBackgroundOpacity: 0.9, // 0.0 - 1.0
|
||||
|
||||
headerBorderWidth: "0px", // px
|
||||
headerBorderColor: "#ffffff", // hex or name
|
||||
headerBorderRadius: "10px", // px
|
||||
|
||||
headerFontFamily: "Fredoka", // font name
|
||||
|
||||
headerFontSize: "25px", // px
|
||||
headerFontWeight: "bold", // normal or bold or number
|
||||
|
||||
headerFontColor: "#fff", // hex or name
|
||||
|
||||
// body
|
||||
bodyBackgroundColor: "#fff", // hex only
|
||||
bodyBackgroundOpacity: 0, // 0.0 - 1.0
|
||||
|
||||
bodyBorderWidth: "0px", // px
|
||||
bodyBorderColor: "#fff", // hex or name
|
||||
bodyBorderRadius: "5px", // px
|
||||
|
||||
bodyMarginTop: "1px", // px
|
||||
|
||||
// task
|
||||
numberOfLines: 2, // number
|
||||
lineHeight: 1.5, // number
|
||||
usernameFontWeight: "bold", // normal or bold or number
|
||||
usernameColor: "white", // hex or name or "" for twitch user color
|
||||
usernameFontSize: "20px", // px
|
||||
|
||||
taskWidth: "100%",
|
||||
|
||||
taskBackgroundColor: "#000", // hex only
|
||||
taskBackgroundOpacity: 0.8, // 0.0 - 1.0
|
||||
|
||||
taskFontFamily: "Poppins", // font name
|
||||
|
||||
taskFontSize: "20px", // px
|
||||
taskFontColor: "#fff", // hex or name
|
||||
|
||||
taskBorderColor: "#fff", // hex or name
|
||||
taskBorderWidth: "0px", // px
|
||||
taskBorderRadius: "10px", // px
|
||||
|
||||
taskMarginBottom: "10px", // px
|
||||
taskHorizontalPadding: "20px", // px
|
||||
taskVerticalPadding: "10px", // px
|
||||
|
||||
// done task
|
||||
doneTaskFontColor: "#bbb", // hex or name
|
||||
|
||||
// focus task
|
||||
focusTaskBackgroundColor: "#fff", // hex only
|
||||
focusTaskBackgroundOpacity: 1, // 0.0 - 1.0
|
||||
focusTaskBorderRadius: "5px", // px
|
||||
|
||||
focusTaskFontColor: "#000", // hex or name
|
||||
|
||||
focusTaskHorizontalPadding: "7px", // px
|
||||
focusTaskVerticalPadding: "0px", // px
|
||||
};
|
||||
|
||||
// STYLES
|
||||
|
||||
const commands = {
|
||||
// Add task commands - please add commands in the exact format
|
||||
addTaskCommands: [
|
||||
"!taska",
|
||||
"!taskadd",
|
||||
"!atask",
|
||||
"!addtask",
|
||||
"!task",
|
||||
"!add",
|
||||
"!todo",
|
||||
"!a",
|
||||
],
|
||||
|
||||
// Edit task commands - please add commands in the exact format
|
||||
editTaskCommands: [
|
||||
"!taske",
|
||||
"!taskedit",
|
||||
"!etask",
|
||||
"!edittask",
|
||||
"!edit",
|
||||
"!e",
|
||||
],
|
||||
|
||||
// Delete task commands - please add commands in the exact format
|
||||
deleteTaskCommands: [
|
||||
"!taskdel",
|
||||
"!taskdelete",
|
||||
"!deltask",
|
||||
"!deletetask",
|
||||
"!taskr",
|
||||
"!taskremove",
|
||||
"!rtask",
|
||||
"!removetask",
|
||||
"!remove",
|
||||
"!delete",
|
||||
"!r",
|
||||
],
|
||||
|
||||
// Finish task commands - please add commands in the exact format
|
||||
finishTaskCommands: [
|
||||
"!taskf",
|
||||
"!taskfinish",
|
||||
"!ftask",
|
||||
"!finishtask",
|
||||
"!taskd",
|
||||
"!taskdone",
|
||||
"!donetask",
|
||||
"!dtask",
|
||||
"!finish",
|
||||
"!done",
|
||||
"!finished",
|
||||
"!f",
|
||||
],
|
||||
|
||||
// Unfinish task commands - please add commands in the exact format
|
||||
unfinishTaskCommands: [
|
||||
"!tasku",
|
||||
"!taskunfinish",
|
||||
"!utask",
|
||||
"!unfinishtask",
|
||||
"!taskud",
|
||||
"!taskundone",
|
||||
"!undonetask",
|
||||
"!undone",
|
||||
"!unfinish",
|
||||
"!unfinished",
|
||||
"!u",
|
||||
],
|
||||
|
||||
nextTaskCommands: [
|
||||
"!taskn",
|
||||
"!tasknext",
|
||||
"!ntask",
|
||||
"!nexttask",
|
||||
"!next",
|
||||
"!n",
|
||||
],
|
||||
logTaskCommands: ["!log", "!tasklog", "!logtask", "!logtasks"],
|
||||
|
||||
// focus task commands - please add commands in the exact format
|
||||
focusTaskCommands: ["!focus", "!taskfocus", "!focustask"],
|
||||
focusedTaskCommands: ["!focused", "!ongoing", "!checkfocus"],
|
||||
unfocusTaskCommands: ["!unfocus", "!taskunfocus", "!unfocustask"],
|
||||
|
||||
nowTaskCommands: ["!now", "!tasknow", "!nowtask"],
|
||||
|
||||
// Check task commands - please add commands in the exact format
|
||||
checkCommands: [
|
||||
"!taskc",
|
||||
"!taskcheck",
|
||||
"!ctask",
|
||||
"!checktask",
|
||||
"!mytask",
|
||||
"!check",
|
||||
"!mytasks",
|
||||
"!c",
|
||||
],
|
||||
|
||||
checkCompletedCommands: [
|
||||
"!completed",
|
||||
"!taskcompleted",
|
||||
"!donetasks",
|
||||
"!completedtasks",
|
||||
],
|
||||
|
||||
// List tasks commands - please add commands in the exact format
|
||||
listCommands: ["!taskl", "!tasklist", "!listtasks", "!list"],
|
||||
|
||||
// Clear my done commands - please add commands in the exact format
|
||||
clearMyDoneCommands: ["!clearmydone"],
|
||||
|
||||
// check count commands - please add commands in the exact format
|
||||
checkCountCommands: ["!taskcount", "!count", "!checkcount", "!mycount"],
|
||||
|
||||
checkAllCountCommands: [
|
||||
"!taskallcount",
|
||||
"!allcount",
|
||||
"!checkallcount",
|
||||
"!boardcount",
|
||||
"!checkboardcount",
|
||||
"!taskboardcount",
|
||||
],
|
||||
|
||||
checkMyPointsCommands: [
|
||||
"!taskpoints",
|
||||
"!points",
|
||||
"!mypoints",
|
||||
"!checkpoints",
|
||||
],
|
||||
|
||||
// sync points with count commands - please add commands in the exact format
|
||||
syncCountPointsCommands: ["!syncpoints", "!synccount"],
|
||||
|
||||
addPointsCommands: ["!addpoints", "!givepoints", "!adduserpoints"],
|
||||
|
||||
reducePointsCommands: ["!reducepoints", "!takepoints"],
|
||||
|
||||
setUserPointsCommands: [
|
||||
"!setuserpoints",
|
||||
"!setpoints",
|
||||
"!setpointsuser",
|
||||
"!setuserpoint",
|
||||
],
|
||||
|
||||
setUserTaskCountCommands: [
|
||||
"!setusertaskcount",
|
||||
"!settaskcount",
|
||||
"!settaskcountuser",
|
||||
"!setusertask",
|
||||
],
|
||||
|
||||
leaderboardCommands: ["!leaderboard", "!lb", "!top", "!toppoints"],
|
||||
|
||||
taskMasterCommands: ["!taskmaster", "!taskchampion"],
|
||||
|
||||
resetTaskMasterCommands: ["!resettaskmaster"],
|
||||
|
||||
// Help commands - please add commands in the exact format
|
||||
helpCommands: [
|
||||
"!taskh",
|
||||
"!taskhelp",
|
||||
"!htask",
|
||||
"!helptask",
|
||||
"!tasks",
|
||||
"!help",
|
||||
],
|
||||
|
||||
// Admin clear all except streamer - please add commands following the exact format
|
||||
adminClearNotStreamerCommands: [
|
||||
"!clearnotstreamer",
|
||||
"!aclearnotstreamer",
|
||||
"!adminclearnotstreamer",
|
||||
"!clearns",
|
||||
],
|
||||
|
||||
// Admin delete - please add commands following the exact format
|
||||
adminDeleteCommands: ["!taskadel", "!adel", "!adelete", "!admindelete"],
|
||||
|
||||
// Admin clear done - please add commands following the exact format
|
||||
adminClearDoneCommands: [
|
||||
"!acleardone",
|
||||
"!admincleardone",
|
||||
"!cleardone",
|
||||
],
|
||||
|
||||
adminClearTasksCommands: ["!cleartasks"],
|
||||
|
||||
adminResetBoardCountCommands: ["!resetboardcount", "!resetallcount"],
|
||||
|
||||
adminResetUsersCountCommands: ["!resetuserscount"],
|
||||
|
||||
adminClearAllCommands: ["!clearallnoregrets"],
|
||||
|
||||
adminSetBoardCountCommands: ["!setboardcount", "!setallcount"],
|
||||
additionalCommands: {
|
||||
"!botcred":
|
||||
"{user} Ryan is the creator of this bot! You can find him on https://github.com/liyunze-coding or https://www.twitch.tv/RythonDev",
|
||||
},
|
||||
};
|
||||
|
||||
const responses = {
|
||||
// Responses related to task addition
|
||||
taskAdded: "The {taskName}(s) {task} has been added, {user}!",
|
||||
noTaskAdded:
|
||||
"Looks like you already hit the limit of incomplete {taskName}s, {user}",
|
||||
noTaskContent:
|
||||
"Try using !task the-{taskName}-you-are-working-on {user}",
|
||||
duplicateTask:
|
||||
"Looks like you already have the {taskName} {task} up there {user}!",
|
||||
taskEdited:
|
||||
"{taskName} {originalTask} has been edited to {task} successfully, {user}",
|
||||
noTaskEdit: "Try doing !edit [index] [new task] {user}",
|
||||
nowTask:
|
||||
"{taskName} {task} is now the task you are working on, {user}!",
|
||||
taskLogged: "Task(s) {task} has been logged, {user}!",
|
||||
taskAlreadyCompleted:
|
||||
"Looks like you already completed that {taskName} {task} up there {user}!",
|
||||
|
||||
// Grouped by task progression responses
|
||||
taskNext:
|
||||
"Good job on finishing the task {oldTask}! Now moving onto {newTask}, {user}!",
|
||||
nextNoContent: "Try using !next the-task-you-want-to-do-next {user}",
|
||||
taskNextFailed:
|
||||
"Unable to perform command with multiple incomplete {taskName}s, {user}!",
|
||||
|
||||
// Grouped by task deletion responses
|
||||
clearedMyDone:
|
||||
"All of your completed {taskName}s have been cleared, {user}!",
|
||||
taskDeleted:
|
||||
"{taskName}(s) {task} has been deleted successfully, {user}",
|
||||
specifyTaskIndex:
|
||||
"Try specifying the index of the incomplete {taskName}(s) {user}",
|
||||
clearTasksExceptBroadcaster:
|
||||
"All {taskName}s have been cleared except for the streamer's, {user}!",
|
||||
clearedTasks: "All {taskName}s have been cleared, {user}!",
|
||||
adminDeleteTasks: "All of {mentioned}'s {taskName}s have been deleted",
|
||||
clearedDone: "All completed {taskName}s have been cleared, {user}!",
|
||||
clearedAll: "All {taskName}s and points have been cleared, {user}!",
|
||||
|
||||
// Grouped by task completion responses
|
||||
taskFinished:
|
||||
"Good job on finishing the {taskName}(s) {task}, {user}! You have earned {pointCount} {pointName} and completed {doneCount} {taskName}(s) so far!",
|
||||
allTasksFinished:
|
||||
"Good job on finishing all your {taskName}s, {user}! You have completed {doneCount} {taskName}(s) so far!",
|
||||
taskUnfinished:
|
||||
"{taskName}(s) {task} has been unmarked as done, {user}!",
|
||||
taskAlreadyFinished:
|
||||
"Looks like you already finished that {taskName} {user}",
|
||||
|
||||
// Grouped by task focus responses
|
||||
clearFocused: "{taskName} has been unfocused, {user}!",
|
||||
noFocusedTask: "Looks like you don't have a focused {taskName} {user}!",
|
||||
alreadyFocusedTask:
|
||||
"Looks like you already have that task set to focus {user}!",
|
||||
onlyOneFocus: "You can only focus one task at a time {user}!",
|
||||
specifyFocusTask:
|
||||
"Try specifying an incomplete {taskName} to focus {user}!",
|
||||
taskFocused: "Task {task} has been focused, {user}!",
|
||||
focusedTask: "{user} Focused {taskName}: {task}",
|
||||
|
||||
// Grouped by task existence responses
|
||||
noTask: "Looks like you don't have a task up there {user}",
|
||||
noTaskA: "Looks like there is no task from that user there {user}",
|
||||
|
||||
// Grouped by point-related responses
|
||||
checkMyPoints: "You have {pointCount} {pointName}, {user}!",
|
||||
checkUserPoints: "{mentioned} has {pointCount} {pointName}, {user}!",
|
||||
syncCountPoints: "Points have been synced with count, {user}!",
|
||||
addPoints: "{mentioned} has been given {pointCount} {pointName}!",
|
||||
reducePoints: "{mentioned} has lost {pointCount} {pointName}!",
|
||||
setUserPoints: "{mentioned} now has {pointCount} {pointName}!",
|
||||
specifyPoints: "Try specifying a number of points {user}",
|
||||
|
||||
// Grouped by count-related responses
|
||||
checkYourCount:
|
||||
"You have completed {doneCount} {taskName}(s) so far, {user}!",
|
||||
checkUserCount:
|
||||
"{mentioned} has completed {doneCount} {taskName}(s) so far, {user}!",
|
||||
checkAllCount:
|
||||
"Everyone has completed {doneCount} {taskName}(s) so far, {user}!",
|
||||
noCountAll: "Looks like no one has completed a {taskName} yet {user}",
|
||||
leaderboard: "Leaderboard: {leaderboard}",
|
||||
setUserTaskCount: "{mentioned} now has {taskCount} tasks!",
|
||||
|
||||
// Grouped by taskmaster-related responses
|
||||
taskMaster: "{user} Task Master: {taskMaster} [{taskMasterCount}]",
|
||||
noTaskMaster: "No task master yet, {user}!",
|
||||
resetTaskMaster: "Task Master has been reset, {user}!",
|
||||
|
||||
// Grouped by permission-related responses
|
||||
notMod: "Permission denied, {user}; Mods only",
|
||||
notStreamer: "Permission denied, {user}; Streamer only",
|
||||
|
||||
// Help response
|
||||
help: `{user} Use the following commands to help you out - !task !edit !remove !done. For mods, you can do !adel @user. More commmands here: https://github.com/liyunze-coding/Chat-Task-Tic-Overlay/blob/main/MultiTask.md/`,
|
||||
|
||||
"chat-task-tic":
|
||||
"Chat-Task-Tic widget is a free and open source task widget developed by @RythonDev ! https://github.com/liyunze-coding/chat-task-tic-overlay-infinity",
|
||||
multitask:
|
||||
"If you want to add the multi-task widget to your stream, you can buy it from ko-fi! https://ko-fi.com/s/94e7e8dc81",
|
||||
// to edit check task command, go to
|
||||
// scripts/taskList.js
|
||||
// function checkTasks(username)
|
||||
};
|
||||
|
||||
return {
|
||||
settings,
|
||||
styles,
|
||||
animation,
|
||||
commands,
|
||||
responses,
|
||||
};
|
||||
})();
|
||||
18
scripts/core/functions.js
Normal file
18
scripts/core/functions.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
function getResponse(responseName) {
|
||||
return (
|
||||
configs.responses[responseName] ??
|
||||
defaultConfigs.responses[responseName]
|
||||
);
|
||||
}
|
||||
|
||||
function getCommand(commandName) {
|
||||
return (
|
||||
configs.commands[commandName] ?? defaultConfigs.commands[commandName]
|
||||
);
|
||||
}
|
||||
|
||||
function getSetting(settingName) {
|
||||
return (
|
||||
configs.settings[settingName] ?? defaultConfigs.settings[settingName]
|
||||
);
|
||||
}
|
||||
64
scripts/core/utils.js
Normal file
64
scripts/core/utils.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
function isInt(value) {
|
||||
return /^\d+$/.test(String(value).trim());
|
||||
}
|
||||
|
||||
function createErrorResponse(errorMessage, errorType, status = 0) {
|
||||
return {
|
||||
status: status,
|
||||
body: {
|
||||
"error message": errorMessage,
|
||||
error: errorType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a camelCase string to a CSS variable format.
|
||||
*
|
||||
* @param {string} name - The camelCase string to convert.
|
||||
* @returns {string} The converted string in CSS variable format.
|
||||
*/
|
||||
function convertToCSSVar(name) {
|
||||
let cssVar = name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||
return `--${cssVar}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hexadecimal color value to its RGB equivalent.
|
||||
*
|
||||
* @param {string} hex - The hexadecimal color value. Can be 3 or 6 digits, with or without a leading '#'.
|
||||
* @returns {string} The RGB color value as a string in the format 'r, g, b'.
|
||||
*/
|
||||
function hexToRgb(hex) {
|
||||
// remove # if present
|
||||
if (hex[0] === "#") {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
if (hex.length == 3) {
|
||||
// 3 digits
|
||||
r = "0x" + hex[0] + hex[0];
|
||||
g = "0x" + hex[1] + hex[1];
|
||||
b = "0x" + hex[2] + hex[2];
|
||||
} else if (hex.length == 6) {
|
||||
// 6 digits
|
||||
r = "0x" + hex[0] + hex[1];
|
||||
g = "0x" + hex[2] + hex[3];
|
||||
b = "0x" + hex[4] + hex[5];
|
||||
}
|
||||
|
||||
// integer value of rgb
|
||||
r = +r;
|
||||
g = +g;
|
||||
b = +b;
|
||||
|
||||
return `${r}, ${g}, ${b}`;
|
||||
}
|
||||
225
scripts/index.js
225
scripts/index.js
|
|
@ -1,225 +0,0 @@
|
|||
const commands = configs.commands;
|
||||
const responses = configs.responses;
|
||||
|
||||
let params = {};
|
||||
|
||||
function respond(template, params) {
|
||||
ComfyJS.Say(
|
||||
template
|
||||
.replace("{user}", `@${params.user}`)
|
||||
.replace("{message}", params.message)
|
||||
.replace("{mentioned}", `@${params.mentioned}`)
|
||||
.replace("{task}", params.task)
|
||||
.replace("{originalTask}", params.originalTask)
|
||||
);
|
||||
}
|
||||
|
||||
function isMod(flags) {
|
||||
return flags.broadcaster || flags.mod;
|
||||
}
|
||||
|
||||
ComfyJS.onCommand = (user, command, message, flags, extra) => {
|
||||
command = `!${command.toLowerCase()}`;
|
||||
|
||||
params = {
|
||||
user: user,
|
||||
message: message,
|
||||
mentioned: "",
|
||||
task: "",
|
||||
originalTask: "",
|
||||
};
|
||||
|
||||
if (commands.addTaskCommands.includes(command)) {
|
||||
let addStatus = addTask(user, extra.userColor, message);
|
||||
|
||||
if (addStatus === 0) {
|
||||
// limit has reached
|
||||
respond(responses.noTaskAdded, params);
|
||||
} else if (addStatus === 1) {
|
||||
// duplicate task
|
||||
respond(responses.duplicateTask, params);
|
||||
} else if (addStatus === 2) {
|
||||
// task has no content
|
||||
respond(responses.noTaskContent, params);
|
||||
} else {
|
||||
let addResponse = responses.taskAdded;
|
||||
params.task = addStatus.task;
|
||||
|
||||
if (addStatus.tasksFailedToAdd !== "") {
|
||||
addResponse += ` | Failed to add task(s): "${addStatus.tasksFailedToAdd}"`;
|
||||
}
|
||||
|
||||
// task added
|
||||
respond(addResponse, params);
|
||||
}
|
||||
} else if (commands.editTaskCommands.includes(command)) {
|
||||
let editStatus = editTask(user, message);
|
||||
|
||||
if (editStatus === 0) {
|
||||
// no task
|
||||
respond(responses.noTask, params);
|
||||
} else if (editStatus === 1) {
|
||||
// invalid input
|
||||
respond(responses.noTaskEdit, params);
|
||||
} else {
|
||||
// task edited
|
||||
params.task = editStatus[1];
|
||||
params.originalTask = editStatus[0];
|
||||
respond(responses.taskEdited, params);
|
||||
}
|
||||
} else if (commands.deleteTaskCommands.includes(command)) {
|
||||
let removeStatus = removeTask(user, message);
|
||||
if (removeStatus === 0) {
|
||||
// no task
|
||||
respond(responses.noTask, params);
|
||||
} else if (removeStatus === 1) {
|
||||
// invalid input
|
||||
respond(responses.specifyTaskIndex, params);
|
||||
} else {
|
||||
// task deleted
|
||||
let deletedResponse = responses.taskDeleted;
|
||||
params.task = removeStatus.removedTasks;
|
||||
|
||||
if (removeStatus.failedTasks !== "") {
|
||||
deletedResponse += ` | Failed to delete task(s): "${removeStatus.failedTasks}"`;
|
||||
}
|
||||
|
||||
respond(deletedResponse, params);
|
||||
}
|
||||
} else if (commands.finishTaskCommands.includes(command)) {
|
||||
if (message === "all") {
|
||||
let finishAllStatus = markAllTasksAsDone(user);
|
||||
|
||||
if (finishAllStatus === 0) {
|
||||
// user has no tasks
|
||||
respond(responses.noTask, params);
|
||||
} else {
|
||||
// all tasks finished
|
||||
let finishedResponse = responses.allTasksFinished;
|
||||
|
||||
respond(finishedResponse, params);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let finishStatus = markTaskDone(user, message);
|
||||
|
||||
if (finishStatus === 0) {
|
||||
// user has no tasks
|
||||
respond(responses.noTask, params);
|
||||
} else if (finishStatus === 1) {
|
||||
// invalid input
|
||||
respond(responses.specifyTaskIndex, params);
|
||||
} else {
|
||||
// task finished
|
||||
let finishedResponse = responses.taskFinished;
|
||||
|
||||
params.task = finishStatus.markedTasks;
|
||||
|
||||
if (finishStatus.failedTasks !== "") {
|
||||
finishedResponse += ` | Failed to finish task(s): "${finishStatus.failedTasks}"`;
|
||||
}
|
||||
|
||||
respond(finishedResponse, params);
|
||||
}
|
||||
} else if (commands.unfinishTaskCommands.includes(command)) {
|
||||
let unfinishStatus = markTaskUndone(user, message);
|
||||
|
||||
if (unfinishStatus === 0) {
|
||||
// user has no tasks
|
||||
respond(responses.noTask, params);
|
||||
} else if (unfinishStatus === 1) {
|
||||
// invalid input
|
||||
respond(responses.specifyTaskIndex, params);
|
||||
} else {
|
||||
// task unfinished
|
||||
let unfinishedResponse = responses.taskUnfinished;
|
||||
|
||||
params.task = unfinishStatus.markedTasks;
|
||||
|
||||
if (unfinishStatus.failedTasks !== "") {
|
||||
unfinishedResponse += ` | Failed to unfinish task(s): "${unfinishStatus.failedTasks}"`;
|
||||
}
|
||||
|
||||
respond(unfinishedResponse, params);
|
||||
}
|
||||
} else if (commands.checkCommands.includes(command)) {
|
||||
if (message === "") {
|
||||
let response = checkTasks(user);
|
||||
if (response === 0) {
|
||||
return respond(responses.noTask, params);
|
||||
}
|
||||
return ComfyJS.Say(response);
|
||||
} else {
|
||||
let mentioned = message.replace("@", "");
|
||||
let response = checkTasks(mentioned);
|
||||
if (response === 0) {
|
||||
// no tasks
|
||||
return respond(responses.noTaskA, params);
|
||||
}
|
||||
return ComfyJS.Say(response);
|
||||
}
|
||||
} else if (commands.adminDeleteCommands.includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(responses.notMod, params);
|
||||
return;
|
||||
}
|
||||
let mentioned = message.replace("@", "");
|
||||
|
||||
if (mentioned === "") {
|
||||
respond(responses.specifyUser, params);
|
||||
return;
|
||||
}
|
||||
|
||||
clearUserTasks(mentioned);
|
||||
|
||||
params.mentioned = mentioned;
|
||||
respond(responses.adminDeleteTasks, params);
|
||||
return mentioned;
|
||||
} else if (commands.adminClearDoneCommands.includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(responses.notMod, params);
|
||||
return;
|
||||
} else {
|
||||
clearAllDoneTasks();
|
||||
respond(responses.clearedDone, params);
|
||||
}
|
||||
} else if (commands.adminClearAllCommands.includes(command)) {
|
||||
if (!isMod(flags)) {
|
||||
respond(responses.notMod, params);
|
||||
return;
|
||||
} else {
|
||||
clearAllTasks();
|
||||
respond(responses.clearedAll, params);
|
||||
}
|
||||
} else if (commands.clearMyDoneCommands.includes(command)) {
|
||||
let response = clearOwnDoneTasks(user);
|
||||
|
||||
if (response === 0) {
|
||||
// no tasks
|
||||
respond(responses.noTask, params);
|
||||
return;
|
||||
}
|
||||
|
||||
respond(responses.clearedMyDone, params);
|
||||
} else if (commands.adminClearNotStreamerCommands.includes(command)) {
|
||||
clearAllExceptStreamer(auth.channel);
|
||||
respond(responses.clearTasksExceptBroadcaster, params);
|
||||
} else if (commands.helpCommands.includes(command)) {
|
||||
respond(responses.help, params);
|
||||
} else if (commands.listCommands.includes(command)) {
|
||||
let response = listTasks(user);
|
||||
if (response === 0) {
|
||||
respond(responses.noTask, params);
|
||||
return;
|
||||
}
|
||||
ComfyJS.Say(response);
|
||||
} else if (commands.additionalCommands[command]) {
|
||||
respond(commands.additionalCommands[command], params);
|
||||
} else {
|
||||
// command not found
|
||||
}
|
||||
};
|
||||
|
||||
ComfyJS.Init(auth.username, `oauth:${auth.oauth}`, [auth.channel]);
|
||||
|
|
@ -1,964 +0,0 @@
|
|||
/*
|
||||
DB structure:
|
||||
|
||||
- tasks
|
||||
- username:
|
||||
- todos (array)
|
||||
- [{text, done}, {text, done}]
|
||||
- done
|
||||
- userColor
|
||||
*/
|
||||
|
||||
const settings = configs.settings;
|
||||
const styles = configs.styles;
|
||||
const scrollSpeed = configs.animation.scrollSpeed;
|
||||
let scrolling = false;
|
||||
let primaryAnimation, secondaryAnimation;
|
||||
|
||||
function loadGoogleFont(font) {
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: [font],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// convert taskListBorderColor to task-list-border-color
|
||||
function convertToCSSVar(name) {
|
||||
let cssVar = name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||
return `--${cssVar}`;
|
||||
}
|
||||
|
||||
// hex to rgb that accepts 3 or 6 digits
|
||||
function hexToRgb(hex) {
|
||||
// remove # if present
|
||||
if (hex[0] === "#") {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
if (hex.length == 3) {
|
||||
// 3 digits
|
||||
r = "0x" + hex[0] + hex[0];
|
||||
g = "0x" + hex[1] + hex[1];
|
||||
b = "0x" + hex[2] + hex[2];
|
||||
} else if (hex.length == 6) {
|
||||
// 6 digits
|
||||
r = "0x" + hex[0] + hex[1];
|
||||
g = "0x" + hex[2] + hex[3];
|
||||
b = "0x" + hex[4] + hex[5];
|
||||
}
|
||||
|
||||
// interger value of rgb
|
||||
r = +r;
|
||||
g = +g;
|
||||
b = +b;
|
||||
|
||||
return `${r}, ${g}, ${b}`;
|
||||
}
|
||||
|
||||
// import styles from configs
|
||||
function importStyles() {
|
||||
const styles = configs.styles;
|
||||
|
||||
// fonts
|
||||
if (configs.settings.headerGoogleFont) {
|
||||
loadGoogleFont(styles.headerFontFamily);
|
||||
}
|
||||
if (configs.settings.taskGoogleFont) {
|
||||
loadGoogleFont(styles.taskFontFamily);
|
||||
}
|
||||
|
||||
const stylesToImport = Object.keys(styles).filter((style) => {
|
||||
return !style.includes("Background");
|
||||
});
|
||||
|
||||
stylesToImport.forEach((style) => {
|
||||
document.documentElement.style.setProperty(
|
||||
convertToCSSVar(style),
|
||||
styles[style]
|
||||
);
|
||||
});
|
||||
|
||||
let backgroundStyles = Object.keys(styles).filter((style) => {
|
||||
return style.includes("Background");
|
||||
});
|
||||
|
||||
// use regex to filter out after "Background"
|
||||
backgroundStyles = backgroundStyles.map((style) => {
|
||||
return style.replace(/Background.*/, "");
|
||||
});
|
||||
|
||||
// loop through backgroundstyles
|
||||
backgroundStyles.forEach((style) => {
|
||||
// get background color and opacity
|
||||
let backgroundColor = styles[`${style}BackgroundColor`];
|
||||
let backgroundOpacity = styles[`${style}BackgroundOpacity`];
|
||||
|
||||
let cssStyle = convertToCSSVar(style);
|
||||
|
||||
// set background color
|
||||
document.documentElement.style.setProperty(
|
||||
`${cssStyle}-background-color`,
|
||||
`rgba(${hexToRgb(backgroundColor)}, ${backgroundOpacity})`
|
||||
);
|
||||
});
|
||||
|
||||
let currentTitle = 0;
|
||||
// interval the task title
|
||||
setInterval(async () => {
|
||||
let taskTitle = document.querySelector(".title");
|
||||
|
||||
// cycle through a list of titles
|
||||
let titles = configs.animation.titles;
|
||||
|
||||
// if current title is the last title, set it to the first title
|
||||
if (currentTitle === titles.length - 1) {
|
||||
currentTitle = 0;
|
||||
} else {
|
||||
currentTitle++;
|
||||
}
|
||||
|
||||
// on change title, add fade animation
|
||||
taskTitle.classList.add("fade");
|
||||
await sleep(500);
|
||||
|
||||
// set new title
|
||||
taskTitle.innerText = titles[currentTitle];
|
||||
|
||||
await sleep(100);
|
||||
|
||||
// remove fade animation
|
||||
taskTitle.classList.remove("fade");
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
function resetDB() {
|
||||
localStorage.clear();
|
||||
setupDB();
|
||||
}
|
||||
|
||||
function setupDB() {
|
||||
if (!localStorage.tasks) {
|
||||
localStorage.setItem(`tasks`, "{}");
|
||||
}
|
||||
}
|
||||
|
||||
// 0: limit has reached
|
||||
// 1: duplicate task
|
||||
// 2: invalid input
|
||||
// string (single or multiple tasks): success
|
||||
function addTask(username, userColor, task) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
if (!tasks[username]) {
|
||||
tasks[username] = {
|
||||
todos: [],
|
||||
done: [],
|
||||
userColor: userColor,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
incompleteTasksCount(username) >= settings.limit &&
|
||||
settings.enableLimit
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (
|
||||
tasks[username].todos.find(
|
||||
(t) => t.text.toLowerCase() === task.toLowerCase()
|
||||
)
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if task is whitespace or empty
|
||||
if (!task || !task.trim()) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
let tasksFailedToAdd = [];
|
||||
|
||||
// if task has commas
|
||||
if (task.includes(",")) {
|
||||
let tasksToAdd = task.split(",").map((t) => t.trim());
|
||||
|
||||
if (
|
||||
incompleteTasksCount(username) + tasksToAdd.length >
|
||||
settings.limit &&
|
||||
settings.enableLimit
|
||||
) {
|
||||
tasksFailedToAdd = tasksToAdd.slice(
|
||||
settings.limit - incompleteTasksCount(username),
|
||||
tasksToAdd.length
|
||||
);
|
||||
|
||||
tasksToAdd = tasksToAdd.slice(
|
||||
0,
|
||||
settings.limit - incompleteTasksCount(username)
|
||||
);
|
||||
}
|
||||
|
||||
for (const t of tasksToAdd) {
|
||||
tasks[username].todos.push({ text: t, done: false });
|
||||
}
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
return {
|
||||
task: tasksToAdd.join('", "'),
|
||||
tasksFailedToAdd: tasksFailedToAdd.join('", "') || "",
|
||||
};
|
||||
}
|
||||
|
||||
tasks[username].todos.push({ text: task, done: false });
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return {
|
||||
task: task,
|
||||
tasksFailedToAdd: "",
|
||||
};
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
// 1: invalid input
|
||||
// removed task: success
|
||||
function removeTask(username, task) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
|
||||
|
||||
// match regex: integers separated by space e.g. '1 2 3 4'
|
||||
if (task.match(/^(\d+ )*\d+$/)) {
|
||||
// insert commas between integers
|
||||
task = task.replace(/(\d+)/g, "$1,");
|
||||
// remove trailing comma
|
||||
task = task.slice(0, -1);
|
||||
}
|
||||
let removedTaskIndex = [];
|
||||
let tasksRemoved = [];
|
||||
let tasksFailedToRemove = [];
|
||||
|
||||
// check if there's a comma in the task (multiple tasks)
|
||||
if (task.includes(",")) {
|
||||
let tasksToRemove = task.split(",").map((t) => t.trim());
|
||||
for (const t of tasksToRemove) {
|
||||
let index = tasks[username].todos.findIndex(
|
||||
(task) => task.text.toLowerCase() === t.toLowerCase()
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
if (isInt(t)) {
|
||||
index = parseInt(t) - 1; // ACTUAL INDEX
|
||||
} else if (incompleteTasks.length === 1) {
|
||||
index = tasks[username].todos.findIndex((t) => !t.done);
|
||||
tasksRemoved.push(tasks[username].todos[index].text);
|
||||
removedTaskIndex.push(index);
|
||||
} else {
|
||||
tasksFailedToRemove.push(t);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
tasksFailedToRemove.push(t);
|
||||
continue;
|
||||
} else {
|
||||
tasksRemoved.push(tasks[username].todos[index].text);
|
||||
removedTaskIndex.push(index);
|
||||
}
|
||||
} else {
|
||||
tasksRemoved.push(tasks[username].todos[index].text);
|
||||
removedTaskIndex.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (tasksRemoved.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sort removedTaskIndex in descending order
|
||||
removedTaskIndex.sort((a, b) => b - a);
|
||||
|
||||
// remove tasks from tasks array
|
||||
for (const index of removedTaskIndex) {
|
||||
tasks[username].todos.splice(index, 1);
|
||||
}
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
return {
|
||||
removedTasks: tasksRemoved.join('", "'),
|
||||
failedTasks: tasksFailedToRemove.join('", "'),
|
||||
};
|
||||
} else {
|
||||
let index = tasks[username].todos.findIndex(
|
||||
(t) => t.text.toLowerCase() === task.toLowerCase()
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
if (isInt(task)) {
|
||||
index = parseInt(task) - 1; // ACTUAL INDEX
|
||||
} else if (incompleteTasks.length === 1) {
|
||||
index = tasks[username].todos.findIndex((t) => !t.done);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
task = tasks[username].todos[index].text;
|
||||
|
||||
tasks[username].todos.splice(index, 1);
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
renderTaskListToDOM();
|
||||
return {
|
||||
removedTasks: task,
|
||||
failedTasks: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// function to determine if string is integer
|
||||
function isInt(value) {
|
||||
return (
|
||||
!isNaN(value) &&
|
||||
parseInt(Number(value)) == value &&
|
||||
!isNaN(parseInt(value, 10))
|
||||
);
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
// 1: success
|
||||
function clearOwnDoneTasks(username) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let incompleteUserTasks = tasks[username].todos.filter((t) => !t.done);
|
||||
|
||||
// replace user's task with incomplete tasks
|
||||
tasks[username].todos = incompleteUserTasks;
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
// 1: invalid input
|
||||
// task: succcess
|
||||
function markTaskDone(username, task) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
|
||||
// user does not have any tasks
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
|
||||
|
||||
// match regex: integers separated by space e.g. '1 2 3 4'
|
||||
if (task.match(/^(\d+ )*\d+$/)) {
|
||||
// insert commas between integers
|
||||
task = task.replace(/(\d+)/g, "$1,");
|
||||
// remove trailing comma
|
||||
task = task.slice(0, -1);
|
||||
}
|
||||
|
||||
let tasksMarkedComplete = [];
|
||||
let tasksFailedToComplete = [];
|
||||
|
||||
// check if there's a comma in the task (multiple tasks)
|
||||
if (task.includes(",")) {
|
||||
let tasksToMarkDone = task.split(",").map((t) => t.trim());
|
||||
for (const t of tasksToMarkDone) {
|
||||
let index = tasks[username].todos.findIndex(
|
||||
(task) => task.text.toLowerCase() === t.toLowerCase()
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
if (isInt(t)) {
|
||||
index = parseInt(t) - 1; // ACTUAL INDEX
|
||||
} else if (incompleteTasks.length === 1) {
|
||||
index = tasks[username].todos.findIndex((t) => !t.done);
|
||||
tasksMarkedComplete.push(tasks[username].todos[index].text);
|
||||
} else {
|
||||
tasksFailedToComplete.push(t);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
tasksFailedToComplete.push(t);
|
||||
continue;
|
||||
} else {
|
||||
tasksMarkedComplete.push(tasks[username].todos[index].text);
|
||||
}
|
||||
} else {
|
||||
tasksMarkedComplete.push(tasks[username].todos[index].text);
|
||||
}
|
||||
tasks[username].todos[index].done = true;
|
||||
}
|
||||
|
||||
if (tasksMarkedComplete.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
return {
|
||||
markedTasks: tasksMarkedComplete.join('", "'),
|
||||
failedTasks: tasksFailedToComplete.join('", "'),
|
||||
};
|
||||
} else {
|
||||
let index = tasks[username].todos.findIndex(
|
||||
(t) => t.text.toLowerCase() === task.toLowerCase()
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
if (isInt(task)) {
|
||||
index = parseInt(task) - 1; // ACTUAL INDEX
|
||||
} else if (incompleteTasks.length === 1) {
|
||||
index = tasks[username].todos.findIndex((t) => !t.done);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
task = tasks[username].todos[index].text;
|
||||
tasks[username].todos[index].done = true;
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
return {
|
||||
markedTasks: task,
|
||||
failedTasks: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function markAllTasksAsDone(username) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
|
||||
// user does not have any tasks
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const task of tasks[username].todos) {
|
||||
task.done = true;
|
||||
}
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
function markTaskUndone(username, task) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
|
||||
// user does not have any tasks
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const completedTasks = tasks[username].todos.filter((t) => t.done);
|
||||
|
||||
// match regex: integers separated by space e.g. '1 2 3 4'
|
||||
if (task.match(/^(\d+ )*\d+$/)) {
|
||||
// insert commas between integers
|
||||
task = task.replace(/(\d+)/g, "$1,");
|
||||
// remove trailing comma
|
||||
task = task.slice(0, -1);
|
||||
}
|
||||
|
||||
let tasksMarkedUndone = [];
|
||||
let tasksFailedToMarkUndone = [];
|
||||
|
||||
// check if there's a comma in the task (multiple tasks)
|
||||
if (task.includes(",")) {
|
||||
let tasksToMarkUndone = task.split(",").map((t) => t.trim());
|
||||
for (const t of tasksToMarkUndone) {
|
||||
let index = tasks[username].todos.findIndex(
|
||||
(task) => task.text.toLowerCase() === t.toLowerCase()
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
// user input isn't the task name
|
||||
if (isInt(t)) {
|
||||
index = parseInt(t) - 1; // ACTUAL INDEX
|
||||
} else {
|
||||
tasksFailedToMarkUndone.push(t);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
tasksFailedToMarkUndone.push(t);
|
||||
continue;
|
||||
} else {
|
||||
tasksMarkedUndone.push(tasks[username].todos[index].text);
|
||||
}
|
||||
} else {
|
||||
tasksMarkedUndone.push(tasks[username].todos[index].text);
|
||||
}
|
||||
tasks[username].todos[index].done = false;
|
||||
}
|
||||
|
||||
if (tasksMarkedUndone.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return {
|
||||
markedTasks: tasksMarkedUndone.join('", "'),
|
||||
failedTasks: tasksFailedToMarkUndone.join('", "'),
|
||||
};
|
||||
} else {
|
||||
let index = tasks[username].todos.findIndex(
|
||||
(t) => t.text.toLowerCase() === task.toLowerCase()
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
if (isInt(task)) {
|
||||
index = parseInt(task) - 1; // ACTUAL INDEX
|
||||
} else if (completedTasks.length === 1) {
|
||||
index = tasks[username].todos.findIndex((t) => t.done);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
tasks[username].todos[index].done = false;
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
return {
|
||||
markedTasks: tasks[username].todos[index].text,
|
||||
failedTasks: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
// 1: invalid input
|
||||
// [original task, new task]: succcess
|
||||
function editTask(username, message) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
let noSpecifiedIndex = false;
|
||||
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// example 'message': 1 new task
|
||||
let index = parseInt(message.split(" ")[0]) - 1; // ACTUAL INDEX
|
||||
|
||||
let incompleteTaskCount = tasks[username].todos.filter(
|
||||
(t) => !t.done
|
||||
).length;
|
||||
|
||||
if (incompleteTaskCount === 1 && index !== 0) {
|
||||
// find index of incomplete task
|
||||
index = tasks[username].todos.findIndex((t) => !t.done);
|
||||
noSpecifiedIndex = true;
|
||||
}
|
||||
|
||||
if (isNaN(index)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let newTask = message.split(" ").slice(1).join(" ");
|
||||
|
||||
if (noSpecifiedIndex) {
|
||||
newTask = message;
|
||||
}
|
||||
|
||||
// if newTask is empty or whitespace, return 1
|
||||
if (!newTask || !newTask.trim()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let originalTask = tasks[username].todos[index].text;
|
||||
|
||||
tasks[username].todos[index].text = newTask;
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
if (!scrolling) {
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return [originalTask, newTask];
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
function checkTasks(username) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
|
||||
// Go through keys of tasks, find match of lowercased username
|
||||
username = Object.keys(tasks).find(
|
||||
(user) => user.toLowerCase() === username.toLowerCase()
|
||||
);
|
||||
|
||||
if (!tasks[username]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// filter completed tasks
|
||||
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
|
||||
const completedTasks = tasks[username].todos.filter((t) => t.done);
|
||||
|
||||
// format incomplete tasks into string: 1. task 1 | 2. task 2 | 3. task 3...
|
||||
let reply = `@${username} incomplete tasks (${incompleteTasks.length}) : `;
|
||||
let taskIndex;
|
||||
for (let i = 0; i < incompleteTasks.length; i++) {
|
||||
// get index of task by task name
|
||||
taskIndex =
|
||||
tasks[username].todos.findIndex(
|
||||
(t) =>
|
||||
t.text.toLowerCase() ===
|
||||
incompleteTasks[i].text.toLowerCase()
|
||||
) + 1;
|
||||
reply += `${taskIndex}. ${incompleteTasks[i].text} | `;
|
||||
}
|
||||
reply = reply.slice(0, -3);
|
||||
|
||||
// format completed tasks into string: 1. task 1 | 2. task 2 | 3. task 3...
|
||||
// reply += `\n|| completed tasks (${completedTasks.length}) : `;
|
||||
|
||||
// for (let j = 0; j < completedTasks.length; j++) {
|
||||
// taskIndex =
|
||||
// tasks[username].todos.findIndex(
|
||||
// (t) =>
|
||||
// t.text.toLowerCase() ===
|
||||
// completedTasks[j].text.toLowerCase()
|
||||
// ) + 1;
|
||||
// reply += `${taskIndex}. ${completedTasks[j].text} | `;
|
||||
// }
|
||||
// reply = reply.slice(0, -3);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
function listTasks(username) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
if (!tasks[username]) {
|
||||
return 0;
|
||||
}
|
||||
// filter completed tasks
|
||||
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
|
||||
|
||||
if (incompleteTasks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// format incomplete tasks into string: 1. task 1 | 2. task 2 | 3. task 3...
|
||||
let reply = `@${username} `;
|
||||
let taskIndex;
|
||||
for (let i = 0; i < incompleteTasks.length; i++) {
|
||||
// get index of task by task name
|
||||
taskIndex =
|
||||
tasks[username].todos.findIndex(
|
||||
(t) =>
|
||||
t.text.toLowerCase() ===
|
||||
incompleteTasks[i].text.toLowerCase()
|
||||
) + 1;
|
||||
reply += `${incompleteTasks[i].text}, `;
|
||||
}
|
||||
reply = reply.slice(0, -2);
|
||||
return reply;
|
||||
}
|
||||
|
||||
function incompleteTasksCount(username) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
if (!tasks[username]) {
|
||||
return 0;
|
||||
}
|
||||
return tasks[username].todos.filter((t) => !t.done).length;
|
||||
}
|
||||
|
||||
// 0: user has no tasks
|
||||
function clearUserTasks(username) {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
// find username where lowercase matches username
|
||||
username = Object.keys(tasks).find(
|
||||
(user) => user.toLowerCase() === username.toLowerCase()
|
||||
);
|
||||
|
||||
if (!username) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
tasks[username].todos = [];
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
}
|
||||
|
||||
function clearAllDoneTasks() {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
for (const user in tasks) {
|
||||
tasks[user].todos = tasks[user].todos.filter((t) => !t.done);
|
||||
}
|
||||
cancelAnimation();
|
||||
checkToAnimate();
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
function clearAllTasks() {
|
||||
resetDB();
|
||||
cancelAnimation();
|
||||
checkToAnimate();
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
function clearAllExceptStreamer(streamer) {
|
||||
// clear all tasks except for broadcasters
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
for (const user in tasks) {
|
||||
if (user.toLowerCase() !== streamer.toLowerCase()) {
|
||||
tasks[user].todos = [];
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||
|
||||
clearAllDoneTasks();
|
||||
|
||||
cancelAnimation();
|
||||
checkToAnimate();
|
||||
|
||||
renderTaskListToDOM();
|
||||
}
|
||||
|
||||
function renderTaskListToDOM() {
|
||||
const tasks = JSON.parse(localStorage.tasks);
|
||||
|
||||
const taskContainers = document.querySelectorAll(".task-container");
|
||||
taskContainers.forEach((taskList) => {
|
||||
taskList.innerHTML = "";
|
||||
|
||||
let totalTaskCount = 0;
|
||||
let completedTasksCount = 0;
|
||||
for (const user in tasks) {
|
||||
const userTasks = tasks[user];
|
||||
if (userTasks.todos.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userColor = userTasks.userColor;
|
||||
|
||||
const taskDiv = document.createElement("div");
|
||||
taskDiv.classList.add("task");
|
||||
taskList.appendChild(taskDiv);
|
||||
|
||||
const usernameDiv = document.createElement("div");
|
||||
usernameDiv.classList.add("username");
|
||||
usernameDiv.innerText = user;
|
||||
|
||||
if (styles.usernameColor === "") {
|
||||
usernameDiv.style.color = userColor;
|
||||
} else {
|
||||
usernameDiv.style.color = styles.usernameColor;
|
||||
}
|
||||
|
||||
taskDiv.appendChild(usernameDiv);
|
||||
|
||||
const olListDiv = document.createElement("ol");
|
||||
olListDiv.classList.add("user-tasks");
|
||||
taskDiv.appendChild(olListDiv);
|
||||
|
||||
for (const task of userTasks.todos) {
|
||||
const taskElement = document.createElement("li");
|
||||
taskElement.classList.add("todo");
|
||||
|
||||
totalTaskCount++;
|
||||
|
||||
if (task.done) {
|
||||
taskElement.classList.add("done");
|
||||
completedTasksCount++;
|
||||
}
|
||||
|
||||
taskElement.innerText = task.text;
|
||||
olListDiv.appendChild(taskElement);
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector(
|
||||
".task-count"
|
||||
).innerText = `${completedTasksCount}/${totalTaskCount}`;
|
||||
});
|
||||
checkToAnimate();
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// unit tests
|
||||
async function tests1() {
|
||||
let listOfStreamers = [
|
||||
`cloudydayzzz`,
|
||||
`berryspace`,
|
||||
`MohFocus`,
|
||||
`xeno_hiraeth`,
|
||||
`euphie___`,
|
||||
`unknownnie`,
|
||||
`theyolotato`,
|
||||
// `charliosaurus`,
|
||||
// `jutstreams`,
|
||||
// `mikewhatwhere`,
|
||||
// `studypaws`,
|
||||
// `pcc_lanezzz`,
|
||||
// `workwithjandj`,
|
||||
// `studylena`,
|
||||
];
|
||||
|
||||
for (let i = 0; i < listOfStreamers.length; i++) {
|
||||
addTask(
|
||||
listOfStreamers[i],
|
||||
"#ffc0cb",
|
||||
`task 1, task 2, task 3, task 4, task 5`
|
||||
);
|
||||
await sleep(1000);
|
||||
markTaskDone(listOfStreamers[i], `1, 2, 3, 4, 5`);
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkToAnimate() {
|
||||
// compare task container height and task wrapper height
|
||||
|
||||
let taskContainer = document.querySelector(".task-container");
|
||||
let taskContainerHeight = taskContainer.scrollHeight;
|
||||
|
||||
let taskWrapper = document.querySelector(".task-wrapper");
|
||||
let taskWrapperHeight = taskWrapper.clientHeight;
|
||||
|
||||
// scroll task wrapper up and down once
|
||||
if (taskContainerHeight > taskWrapperHeight && !scrolling) {
|
||||
if (!scrolling) {
|
||||
document.querySelector(".secondary").style.display = "flex";
|
||||
|
||||
let finalHeight =
|
||||
taskContainerHeight + configs.animation.gapBetweenScrolls;
|
||||
|
||||
let primaryKeyFrames = [
|
||||
{ transform: `translateY(0)` },
|
||||
{ transform: `translateY(-${finalHeight}px)` },
|
||||
];
|
||||
|
||||
let secondaryKeyFrames = [
|
||||
{ transform: `translateY(${finalHeight}px)` },
|
||||
{ transform: `translateY(0)` },
|
||||
];
|
||||
|
||||
let scrollingSpeed = (finalHeight / scrollSpeed) * 1000;
|
||||
|
||||
let options = {
|
||||
duration: scrollingSpeed,
|
||||
iterations: 1,
|
||||
easing: "linear",
|
||||
};
|
||||
|
||||
primaryAnimation = document
|
||||
.querySelector(".primary")
|
||||
.animate(primaryKeyFrames, options);
|
||||
|
||||
secondaryAnimation = document
|
||||
.querySelector(".secondary")
|
||||
.animate(secondaryKeyFrames, options);
|
||||
|
||||
primaryAnimation.play();
|
||||
secondaryAnimation.play();
|
||||
|
||||
scrolling = true;
|
||||
|
||||
addAnimationListeners();
|
||||
}
|
||||
} else if (!scrolling) {
|
||||
document.querySelector(".secondary").style.display = "none";
|
||||
|
||||
// cancel animations
|
||||
if (primaryAnimation) {
|
||||
primaryAnimation.cancel();
|
||||
}
|
||||
if (secondaryAnimation) {
|
||||
secondaryAnimation.cancel();
|
||||
}
|
||||
scrolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addAnimationListeners() {
|
||||
if (primaryAnimation) {
|
||||
primaryAnimation.addEventListener("finish", animationFinished);
|
||||
primaryAnimation.addEventListener("cancel", animationFinished);
|
||||
}
|
||||
}
|
||||
|
||||
function animationFinished() {
|
||||
scrolling = false;
|
||||
renderTaskListToDOM();
|
||||
checkToAnimate();
|
||||
}
|
||||
|
||||
function cancelAnimation() {
|
||||
if (primaryAnimation) {
|
||||
primaryAnimation.cancel();
|
||||
}
|
||||
if (secondaryAnimation) {
|
||||
secondaryAnimation.cancel();
|
||||
}
|
||||
scrolling = false;
|
||||
console.log("animation cancelled");
|
||||
}
|
||||
|
||||
(function () {
|
||||
setupDB();
|
||||
importStyles();
|
||||
renderTaskListToDOM();
|
||||
})();
|
||||
402
scripts/tasks/counts.js
Normal file
402
scripts/tasks/counts.js
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/**
|
||||
* Counts the number of incomplete tasks for a given user.
|
||||
*
|
||||
* @param {string} username - The name of the user whose tasks are to be counted.
|
||||
* @returns {number} The number of incomplete tasks for the user.
|
||||
*/
|
||||
async function incompleteTasksCount(username) {
|
||||
const tasks = await DBHandler.get("tasks");
|
||||
if (!tasks[username]) {
|
||||
return 0;
|
||||
}
|
||||
return tasks[username].todos.filter((t) => !t.done).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of completed tasks for a given user.
|
||||
*
|
||||
* @param {string} username - The name of the user whose tasks are to be counted.
|
||||
* @returns {number} The number of completed tasks for the user.
|
||||
*/
|
||||
async function completedTasksCount(username) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
return 0;
|
||||
}
|
||||
return counts.users[username.toLowerCase()].completeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of tasks for a given user.
|
||||
*
|
||||
* @param {string} username - The name of the user whose tasks are to be counted.
|
||||
* @returns {number} The total number of tasks for the user.
|
||||
*/
|
||||
async function getUserTotalTaskCount(username) {
|
||||
return (
|
||||
(await incompleteTasksCount(username)) +
|
||||
(await completedTasksCount(username))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total points for a given user based on the number of completed tasks and the points per task setting.
|
||||
*
|
||||
* @param {string} username - The name of the user whose points are to be calculated.
|
||||
* @returns {number} The total points for the user.
|
||||
*/
|
||||
async function calculatePoints(username) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
counts.users[username.toLowerCase()].completeCount *
|
||||
getSetting("pointsPerTask")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the points of a specific user from local storage.
|
||||
*
|
||||
* @param {string} username - The name of the user.
|
||||
* @returns {number} The points of the user. If the user does not exist, returns 0.
|
||||
*/
|
||||
async function getUserPoints(username) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return counts.users[username.toLowerCase()].points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a specified value to the completed task count for a given user and the total completed task count.
|
||||
*
|
||||
* @param {string} username - The name of the user whose completed task count is to be incremented.
|
||||
* @param {number} value - The value to be added to the completed task count.
|
||||
*/
|
||||
async function addDoneCount(username, value) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
counts.users[username.toLowerCase()] = {
|
||||
completeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (!counts.totalCompleteCount) {
|
||||
counts.totalCompleteCount = 0;
|
||||
}
|
||||
|
||||
counts.users[username.toLowerCase()].completeCount += value;
|
||||
counts.totalCompleteCount += value;
|
||||
taskListMemory.doneTaskCount += value;
|
||||
|
||||
// check if user has points
|
||||
if (!counts.users[username.toLowerCase()].points) {
|
||||
counts.users[username.toLowerCase()].points = 0;
|
||||
}
|
||||
|
||||
// add to points
|
||||
counts.users[username.toLowerCase()].points +=
|
||||
value * getSetting("pointsPerTask");
|
||||
|
||||
// add to taskmaster
|
||||
addCountToTaskMasterUser(username, value);
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the completed task count for a given user.
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {int} value
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message.
|
||||
*/
|
||||
async function setUserCompleteCount(username, value) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
counts.users[username.toLowerCase()] = {
|
||||
completeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
counts.users[username.toLowerCase()].completeCount = parseInt(value);
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: `Successfully set ${username}'s complete count to ${value}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total count of completed tasks in local storage.
|
||||
*
|
||||
* @param {number} value - The new total count of completed tasks.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message.
|
||||
*/
|
||||
async function setTotalCompleteCount(value) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
counts.totalCompleteCount = parseInt(value);
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: `Successfully set total complete count to ${value}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a specified number of points to a user's total in local storage.
|
||||
*
|
||||
* @param {string} username - The name of the user.
|
||||
* @param {number} value - The number of points to add.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message.
|
||||
*/
|
||||
async function addPoints(username, value) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
counts.users[username.toLowerCase()] = {
|
||||
points: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (!counts.users[username.toLowerCase()].points) {
|
||||
counts.users[username.toLowerCase()].points = 0;
|
||||
}
|
||||
|
||||
counts.users[username.toLowerCase()].points += value;
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: `Successfully added ${value} points to ${username}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the points for a specific user in local storage.
|
||||
*
|
||||
* @param {string} username - The name of the user.
|
||||
* @param {number} pointsCount - The new points count for the user.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message.
|
||||
*/
|
||||
async function setUserPoints(username, pointsCount) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
counts.users[username.toLowerCase()] = {
|
||||
points: 0,
|
||||
};
|
||||
}
|
||||
|
||||
counts.users[username.toLowerCase()].points = pointsCount;
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: `Successfully set ${username}'s points to ${pointsCount}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specified number of points from a user's total in local storage.
|
||||
*
|
||||
* @param {string} username - The name of the user.
|
||||
* @param {number} value - The number of points to reduce.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message.
|
||||
*/
|
||||
async function reducePoints(username, value) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
counts.users[username.toLowerCase()] = {
|
||||
points: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (!counts.users[username.toLowerCase()].points) {
|
||||
counts.users[username.toLowerCase()].points = 0;
|
||||
}
|
||||
|
||||
counts.users[username.toLowerCase()].points -= parseInt(value);
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: `Successfully reduced ${username}'s points by ${value}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the total count of tasks from the board.
|
||||
*
|
||||
* @returns {number} The total count of tasks. If no tasks are found, returns 0.
|
||||
*/
|
||||
async function getBoardTotalTaskCount() {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.totalCompleteCount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return counts.totalCompleteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the task count for a specific user in local storage and returns a success message.
|
||||
*
|
||||
* @param {string} username - The name of the user.
|
||||
* @param {number} value - The new task count for the user.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message.
|
||||
*/
|
||||
async function setUserTaskCount(username, value) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
if (!counts.users[username.toLowerCase()]) {
|
||||
counts.users[username.toLowerCase()] = {
|
||||
completeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (!counts.users[username.toLowerCase()].completeCount) {
|
||||
counts.users[username.toLowerCase()].completeCount = 0;
|
||||
}
|
||||
|
||||
counts.users[username.toLowerCase()].completeCount = parseInt(value);
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: `Successfully set ${username}'s task count to ${value}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the points of each user with their task count in local storage. After the synchronization, the function returns an object with a status of 200 and a success message.
|
||||
*
|
||||
* @returns {Object} An object with a status and body. The status is 200 if the synchronization is successful, and the body contains a success message.
|
||||
*/
|
||||
async function syncPointsToCount() {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
for (const user in counts.users) {
|
||||
counts.users[user].points = calculatePoints(user);
|
||||
}
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: "Successfully synced counts to points",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the leaderboard of users based on task count.
|
||||
*
|
||||
* @param {number} limit - The maximum number of users to include in the leaderboard.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains an array of user objects, each with a username and task count, sorted in descending order of task count.
|
||||
*/
|
||||
async function leaderboardTaskCount(limit) {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
let leaderboardArray = [];
|
||||
|
||||
for (const user in counts.users) {
|
||||
let userTaskCount = await completedTasksCount(user);
|
||||
|
||||
leaderboardArray.push({
|
||||
username: user,
|
||||
taskCount: userTaskCount,
|
||||
});
|
||||
}
|
||||
|
||||
leaderboardArray.sort((a, b) => b.taskCount - a.taskCount);
|
||||
|
||||
// shorten to limit
|
||||
leaderboardArray = leaderboardArray.slice(0, limit);
|
||||
|
||||
let leaderboardstring = "";
|
||||
|
||||
leaderboardArray.map((user, index) => {
|
||||
leaderboardstring += `${index + 1}. @${user.username} : ${
|
||||
user.taskCount
|
||||
} | `;
|
||||
});
|
||||
|
||||
leaderboardstring = leaderboardstring.slice(0, -3);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
leaderboard: leaderboardstring,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the total count of completed tasks in the board to zero.
|
||||
*/
|
||||
async function resetBoardCount() {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
counts.totalCompleteCount = 0;
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: "Successfully reset board count",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the task count for all users.
|
||||
*/
|
||||
async function resetUsersCount() {
|
||||
const counts = await DBHandler.get("counts");
|
||||
|
||||
counts.users = {};
|
||||
|
||||
await DBHandler.set("counts", counts);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: "Successfully reset users count",
|
||||
},
|
||||
};
|
||||
}
|
||||
140
scripts/tasks/taskCleanup.js
Normal file
140
scripts/tasks/taskCleanup.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* Clears all completed tasks for a given user. If the tasks are successfully cleared, the function returns an object with a status of 200. If the tasks are not cleared due to the user having no tasks, the function returns an object with a status indicating the error and an error message.
|
||||
*
|
||||
* @param {string} username - The name of the user whose completed tasks are to be cleared.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if the tasks are successfully cleared. If the tasks are not cleared, the status indicates the error and the body contains an error message.
|
||||
*/
|
||||
async function clearOwnDoneTasks(username) {
|
||||
const tasks = await DBHandler.get("tasks");
|
||||
|
||||
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||
return createErrorResponse(
|
||||
`@${username} has no tasks`,
|
||||
getResponse("noTask")
|
||||
);
|
||||
}
|
||||
|
||||
let incompleteUserTasks = tasks[username].todos.filter((t) => !t.done);
|
||||
|
||||
// replace user's task with incomplete tasks
|
||||
tasks[username].todos = incompleteUserTasks;
|
||||
|
||||
await DBHandler.set("tasks", tasks);
|
||||
if (!scrolling) {
|
||||
await renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all tasks for a given user. If the tasks are successfully cleared, the function returns an object with a status of 200.
|
||||
* If the tasks are not cleared due to the user having no tasks, the function returns an object with a status indicating the error and an error message.
|
||||
*
|
||||
* @param {string} username - The name of the user whose tasks are to be cleared.
|
||||
* @returns {Object} An object with a status and body. The status is 200 if the tasks are successfully cleared. If the tasks are not cleared, the status indicates the error and the body contains an error message.
|
||||
*/
|
||||
async function clearUserTasks(username) {
|
||||
const tasks = await DBHandler.get("tasks");
|
||||
// find username where lowercase matches username
|
||||
username = Object.keys(tasks).find(
|
||||
(user) => user.toLowerCase() === username.toLowerCase()
|
||||
);
|
||||
|
||||
if (!username) {
|
||||
return {
|
||||
status: 0,
|
||||
body: {
|
||||
"error message": `@${username} has no tasks`,
|
||||
error: getResponse("noTask"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
tasks[username].todos = [];
|
||||
|
||||
await DBHandler.set("tasks", tasks);
|
||||
|
||||
if (!scrolling) {
|
||||
await renderTaskListToDOM();
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all completed tasks for all users. If the tasks are successfully cleared, the function returns an object with a status of 200. If there are no tasks to clear, the function still returns an object with a status of 200 as it's considered a successful operation.
|
||||
*
|
||||
* @returns {Object} An object with a status. The status is 200 if the tasks are successfully cleared.
|
||||
*/
|
||||
async function clearAllDoneTasks() {
|
||||
const tasks = await DBHandler.get("tasks");
|
||||
for (const user in tasks) {
|
||||
if (!tasks[user].todos) continue;
|
||||
if (user.toLowerCase() === "id") continue;
|
||||
tasks[user].todos = tasks[user].todos.filter((t) => !t.done);
|
||||
}
|
||||
cancelAnimation();
|
||||
await DBHandler.set("tasks", tasks);
|
||||
await renderTaskListToDOM();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
||||
async function clearAllTasks() {
|
||||
await DBHandler.set("tasks", {});
|
||||
await setupDB();
|
||||
cancelAnimation();
|
||||
await renderTaskListToDOM();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
||||
async function clearAll() {
|
||||
await resetDB();
|
||||
cancelAnimation();
|
||||
checkToAnimate();
|
||||
await renderTaskListToDOM();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all tasks from the local storage except for those belonging to specified streamer usernames.
|
||||
* After clearing, it also clears all done tasks, cancels any ongoing animation and re-renders the task list to the DOM.
|
||||
*
|
||||
* @param {string[]} streamerUsername - An array of streamer usernames whose tasks should not be cleared.
|
||||
* @returns {Object} An object with a status property indicating the success of the operation (200 for success).
|
||||
*/
|
||||
async function clearAllExceptStreamer(streamerUsername) {
|
||||
const tasks = await DBHandler.get("tasks");
|
||||
|
||||
for (const user in tasks) {
|
||||
if (user.toLowerCase() === "id") continue;
|
||||
if (user.toLowerCase() !== streamerUsername.toLowerCase()) {
|
||||
tasks[user].todos = [];
|
||||
}
|
||||
}
|
||||
|
||||
await DBHandler.set("tasks", tasks);
|
||||
|
||||
await clearAllDoneTasks();
|
||||
|
||||
cancelAnimation();
|
||||
|
||||
await renderTaskListToDOM();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
1326
scripts/tasks/taskCore.js
Normal file
1326
scripts/tasks/taskCore.js
Normal file
File diff suppressed because it is too large
Load diff
72
scripts/tasks/taskMaster.js
Normal file
72
scripts/tasks/taskMaster.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* adds a count to user for 'taskmaster'
|
||||
*
|
||||
* @param {string} username
|
||||
*/
|
||||
async function addCountToTaskMasterUser(username, count) {
|
||||
const taskmaster = await DBHandler.get("taskmaster");
|
||||
|
||||
if (!taskmaster.users[username.toLowerCase()]) {
|
||||
taskmaster.users[username.toLowerCase()] = {
|
||||
taskMasterCompleteCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
taskmaster.users[username.toLowerCase()].taskMasterCompleteCount +=
|
||||
parseInt(count);
|
||||
|
||||
await DBHandler.set("taskmaster", taskmaster);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the count of a user from 'taskmaster'
|
||||
*
|
||||
* @param {string} username
|
||||
* @returns {number} the count of the user
|
||||
*/
|
||||
async function getUserTaskMasterCount(username) {
|
||||
const taskmaster = await DBHandler.get("taskmaster");
|
||||
|
||||
if (!taskmaster.users[username.toLowerCase()]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return taskmaster.users[username.toLowerCase()].taskMasterCompleteCount;
|
||||
}
|
||||
|
||||
async function getTaskMasterChampion() {
|
||||
const taskmaster = await DBHandler.get("taskmaster");
|
||||
|
||||
if (!taskmaster.users) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let champion = {
|
||||
username: "",
|
||||
count: 0,
|
||||
};
|
||||
|
||||
for (const user in taskmaster.users) {
|
||||
if (
|
||||
user.toLowerCase() !== auth.channel.toLowerCase() &&
|
||||
taskmaster.users[user].taskMasterCompleteCount > champion.count
|
||||
) {
|
||||
champion.username = user;
|
||||
champion.count = taskmaster.users[user].taskMasterCompleteCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (champion.count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return champion;
|
||||
}
|
||||
|
||||
async function resetTaskMaster() {
|
||||
await DBHandler.set("taskmaster", {
|
||||
users: {},
|
||||
startDate: new Date(),
|
||||
totalCompleteCount: 0,
|
||||
});
|
||||
}
|
||||
27
scripts/testing/tests.js
Normal file
27
scripts/testing/tests.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
function randomTasks(num) {
|
||||
let tasks = [];
|
||||
for (let i = 0; i < num; i++) {
|
||||
tasks.push(`task ${i + 1}`);
|
||||
}
|
||||
|
||||
return tasks.join(", ");
|
||||
}
|
||||
|
||||
async function tests() {
|
||||
let listOfStreamers = [
|
||||
`followRythonDev1`,
|
||||
`followRythonDev2`,
|
||||
`followRythonDev3`,
|
||||
`followRythonDev4`,
|
||||
`followRythonDev5`,
|
||||
`followRythonDev6`,
|
||||
`followRythonDev7`,
|
||||
];
|
||||
|
||||
for (let i = 0; i < listOfStreamers.length; i++) {
|
||||
let randomNum = Math.floor(Math.random() * 10) + 1;
|
||||
|
||||
addTask(listOfStreamers[i], "#ffc0cb", randomTasks(randomNum));
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
180
scripts/ui/renderer.js
Normal file
180
scripts/ui/renderer.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
async function renderTaskListToDOM() {
|
||||
const tasks = await DBHandler.get("tasks");
|
||||
|
||||
const taskContainers = document.querySelectorAll(".task-container");
|
||||
|
||||
let hasTasks = false;
|
||||
|
||||
taskContainers.forEach((taskList) => {
|
||||
taskList.innerHTML = "";
|
||||
|
||||
let totalTaskCount = 0;
|
||||
let completedTasksCount = 0;
|
||||
|
||||
for (const user in tasks) {
|
||||
if (user.toLowerCase() === "id") continue;
|
||||
if (!tasks[user].todos) continue;
|
||||
|
||||
const userTasks = tasks[user];
|
||||
|
||||
if (userTasks.todos.length === 0) {
|
||||
// remove user from tasks
|
||||
delete tasks[user];
|
||||
continue;
|
||||
}
|
||||
|
||||
const userColor = userTasks.userColor;
|
||||
|
||||
const taskDiv = document.createElement("div");
|
||||
taskDiv.classList.add("task");
|
||||
taskList.appendChild(taskDiv);
|
||||
|
||||
const usernameDiv = document.createElement("div");
|
||||
usernameDiv.classList.add("username");
|
||||
usernameDiv.innerText = user;
|
||||
|
||||
if (styles.usernameColor === "") {
|
||||
usernameDiv.style.color = userColor;
|
||||
} else {
|
||||
usernameDiv.style.color = styles.usernameColor;
|
||||
}
|
||||
|
||||
taskDiv.appendChild(usernameDiv);
|
||||
|
||||
const olListDiv = document.createElement("ol");
|
||||
olListDiv.classList.add("user-tasks");
|
||||
taskDiv.appendChild(olListDiv);
|
||||
|
||||
for (const task of userTasks.todos) {
|
||||
const taskElement = document.createElement("li");
|
||||
taskElement.classList.add("todo");
|
||||
|
||||
totalTaskCount++;
|
||||
|
||||
if (task.done) {
|
||||
taskElement.classList.add("done");
|
||||
completedTasksCount++;
|
||||
} else if (task.focus) {
|
||||
taskElement.classList.add("focus");
|
||||
}
|
||||
|
||||
const taskContent = document.createElement("div");
|
||||
taskContent.classList.add("content");
|
||||
|
||||
taskContent.innerText = task.text;
|
||||
|
||||
taskElement.appendChild(taskContent);
|
||||
olListDiv.appendChild(taskElement);
|
||||
|
||||
hasTasks = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (taskListMemory.doneTaskCount > completedTasksCount) {
|
||||
completedTasksCount = taskListMemory.doneTaskCount;
|
||||
totalTaskCount = taskListMemory.totalTaskCount;
|
||||
}
|
||||
|
||||
if (totalTaskCount < completedTasksCount) {
|
||||
totalTaskCount = completedTasksCount;
|
||||
}
|
||||
|
||||
document.querySelector(
|
||||
".task-count"
|
||||
).innerText = `${completedTasksCount}/${totalTaskCount}`;
|
||||
|
||||
taskListMemory.doneTaskCount = completedTasksCount;
|
||||
taskListMemory.totalTaskCount = totalTaskCount;
|
||||
});
|
||||
|
||||
if (getSetting("hideWhenNoTasks") && !hasTasks) {
|
||||
document.querySelector("#main-container").style.opacity = "0";
|
||||
visible = false;
|
||||
} else if (getSetting("hideWhenNoTasks") && hasTasks && !visible) {
|
||||
document.querySelector("#main-container").style.opacity = "1";
|
||||
visible = true;
|
||||
}
|
||||
|
||||
await DBHandler.set("tasks", tasks);
|
||||
|
||||
checkToAnimate();
|
||||
}
|
||||
|
||||
async function checkToAnimate() {
|
||||
let taskContainer = document.querySelector(".task-container");
|
||||
let taskContainerHeight = taskContainer.scrollHeight;
|
||||
|
||||
let taskWrapper = document.querySelector(".task-wrapper");
|
||||
let taskWrapperHeight = taskWrapper.clientHeight;
|
||||
|
||||
let secondary = document.querySelector(".secondary");
|
||||
|
||||
if (taskContainerHeight > taskWrapperHeight && !scrolling) {
|
||||
if (!scrolling) {
|
||||
secondary.style.display = "flex";
|
||||
|
||||
let finalHeight =
|
||||
taskContainerHeight + configs.animation.gapBetweenScrolls;
|
||||
|
||||
let keyframes = [
|
||||
{ transform: `translateY(0)` },
|
||||
{ transform: `translateY(-${finalHeight}px)` },
|
||||
];
|
||||
let scrollingSpeed = (finalHeight / scrollSpeed) * 1000;
|
||||
|
||||
let options = {
|
||||
duration: scrollingSpeed,
|
||||
iterations: 1,
|
||||
easing: "linear",
|
||||
};
|
||||
|
||||
primaryAnimation = document
|
||||
.querySelector(".primary")
|
||||
.animate(keyframes, options);
|
||||
|
||||
secondaryAnimation = document
|
||||
.querySelector(".secondary")
|
||||
.animate(keyframes, options);
|
||||
|
||||
primaryAnimation.play();
|
||||
secondaryAnimation.play();
|
||||
|
||||
scrolling = true;
|
||||
|
||||
addAnimationListeners();
|
||||
}
|
||||
} else if (!scrolling) {
|
||||
secondary.style.display = "none";
|
||||
|
||||
if (primaryAnimation) {
|
||||
primaryAnimation.cancel();
|
||||
}
|
||||
if (secondaryAnimation) {
|
||||
secondaryAnimation.cancel();
|
||||
}
|
||||
scrolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addAnimationListeners() {
|
||||
if (primaryAnimation) {
|
||||
primaryAnimation.addEventListener("finish", animationFinished);
|
||||
primaryAnimation.addEventListener("cancel", animationFinished);
|
||||
}
|
||||
}
|
||||
|
||||
async function animationFinished() {
|
||||
scrolling = false;
|
||||
await renderTaskListToDOM();
|
||||
checkToAnimate();
|
||||
}
|
||||
|
||||
function cancelAnimation() {
|
||||
if (primaryAnimation) {
|
||||
primaryAnimation.cancel();
|
||||
}
|
||||
if (secondaryAnimation) {
|
||||
secondaryAnimation.cancel();
|
||||
}
|
||||
scrolling = false;
|
||||
}
|
||||
94
scripts/ui/styles.js
Normal file
94
scripts/ui/styles.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
function loadGoogleFont(font) {
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: [font],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* import styles from configs
|
||||
*/
|
||||
function importStyles() {
|
||||
const styles = configs.styles;
|
||||
|
||||
// fonts
|
||||
if (getSetting("headerGoogleFont")) {
|
||||
loadGoogleFont(styles.headerFontFamily);
|
||||
}
|
||||
if (getSetting("taskGoogleFont")) {
|
||||
loadGoogleFont(styles.taskFontFamily);
|
||||
}
|
||||
|
||||
document.documentElement.style.setProperty(
|
||||
"--gap-between-scrolls",
|
||||
`${configs.animation.gapBetweenScrolls}px` ?? "0px"
|
||||
);
|
||||
|
||||
const stylesToImport = Object.keys(styles).filter((style) => {
|
||||
return !style.includes("Background");
|
||||
});
|
||||
|
||||
stylesToImport.forEach((style) => {
|
||||
document.documentElement.style.setProperty(
|
||||
convertToCSSVar(style),
|
||||
styles[style]
|
||||
);
|
||||
});
|
||||
|
||||
let backgroundStyles = Object.keys(styles).filter((style) => {
|
||||
return style.includes("Background");
|
||||
});
|
||||
|
||||
// use regex to filter out after "Background"
|
||||
backgroundStyles = backgroundStyles.map((style) => {
|
||||
return style.replace(/Background.*/, "");
|
||||
});
|
||||
|
||||
// loop through backgroundstyles
|
||||
backgroundStyles.forEach((style) => {
|
||||
// get background color and opacity
|
||||
let backgroundColor = styles[`${style}BackgroundColor`];
|
||||
let backgroundOpacity = styles[`${style}BackgroundOpacity`];
|
||||
|
||||
let cssStyle = convertToCSSVar(style);
|
||||
|
||||
// set background color
|
||||
document.documentElement.style.setProperty(
|
||||
`${cssStyle}-background-color`,
|
||||
`rgba(${hexToRgb(backgroundColor)}, ${backgroundOpacity})`
|
||||
);
|
||||
});
|
||||
|
||||
if (!getSetting("displayTaskCount")) {
|
||||
document.querySelector(".task-count").style.display = "none";
|
||||
}
|
||||
|
||||
let currentTitle = 0;
|
||||
// interval the task title
|
||||
setInterval(async () => {
|
||||
let taskTitle = document.querySelector(".title");
|
||||
|
||||
// cycle through a list of titles
|
||||
let titles = configs.animation.titles;
|
||||
|
||||
// if current title is the last title, set it to the first title
|
||||
if (currentTitle === titles.length - 1) {
|
||||
currentTitle = 0;
|
||||
} else {
|
||||
currentTitle++;
|
||||
}
|
||||
|
||||
// on change title, add fade animation
|
||||
taskTitle.classList.add("fade");
|
||||
await sleep(500);
|
||||
|
||||
// set new title
|
||||
taskTitle.innerText = titles[currentTitle];
|
||||
|
||||
await sleep(100);
|
||||
|
||||
// remove fade animation
|
||||
taskTitle.classList.remove("fade");
|
||||
}, 8000);
|
||||
}
|
||||
23
scripts/ui/taskList.js
Normal file
23
scripts/ui/taskList.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const styles = configs.styles;
|
||||
const scrollSpeed = configs.animation.scrollSpeed;
|
||||
let openQuote = getSetting("openQuote");
|
||||
let closeQuote = getSetting("closeQuote");
|
||||
let visible = false;
|
||||
let scrolling = false;
|
||||
let primaryAnimation, secondaryAnimation;
|
||||
const taskSeparator = getSetting("taskSeparator");
|
||||
|
||||
DBHandler.open()
|
||||
.then(async () => {
|
||||
await setupDB();
|
||||
importStyles();
|
||||
if (getSetting("testTasks")) {
|
||||
await resetDB();
|
||||
await tests();
|
||||
}
|
||||
|
||||
await renderTaskListToDOM();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error opening database:", error);
|
||||
});
|
||||
4
styles/extraStyles.css
Normal file
4
styles/extraStyles.css
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#main-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
|
@ -17,10 +17,13 @@
|
|||
--header-font-family: sans-serif;
|
||||
--header-font-color: white;
|
||||
--header-font-weight: bold;
|
||||
--task-count-margin-right: 0px;
|
||||
--body-background-color: rgba(0, 0, 0, 0.7);
|
||||
--body-border-width: 5px;
|
||||
--body-border-color: white;
|
||||
--body-border-radius: 5px;
|
||||
--body-margin-top: 0px;
|
||||
--number-of-lines: 1;
|
||||
--line-height: 1.5;
|
||||
--username-font-weight: bold;
|
||||
--task-background-color: rgba(0, 0, 0, 0.7);
|
||||
|
|
@ -35,6 +38,7 @@
|
|||
--task-margin-bottom: 5px;
|
||||
--task-horizontal-padding: 5px;
|
||||
--task-vertical-padding: 5px;
|
||||
--gap-between-scrolls: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -43,8 +47,16 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: gray;
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
.primary, .secondary {
|
||||
animation: scroll linear infinite;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
|
|
@ -56,6 +68,7 @@ body {
|
|||
background-color: var(--task-list-background-color);
|
||||
border: solid var(--task-list-border-width) var(--task-list-border-color);
|
||||
border-radius: var(--task-list-border-radius);
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
#main-container .header {
|
||||
display: flex;
|
||||
|
|
@ -75,6 +88,12 @@ body {
|
|||
#main-container .header .fade {
|
||||
opacity: 0;
|
||||
}
|
||||
#main-container .header .task-count {
|
||||
margin-right: var(--task-count-margin-right);
|
||||
}
|
||||
#main-container .task-wrapper, #main-container .task-container {
|
||||
transition: height 0.5s ease-out;
|
||||
}
|
||||
#main-container .task-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -89,8 +108,10 @@ body {
|
|||
border-radius: var(--body-border-radius);
|
||||
z-index: 1;
|
||||
}
|
||||
#main-container .task-wrapper .task-container.secondary {
|
||||
margin-top: var(--gap-between-scrolls);
|
||||
}
|
||||
#main-container .task-wrapper .task-container {
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
left: 0;
|
||||
display: flex;
|
||||
|
|
@ -98,6 +119,8 @@ body {
|
|||
align-items: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: var(--body-margin-top);
|
||||
transition: transform 0.4s ease-in-out;
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task {
|
||||
padding: var(--task-vertical-padding) var(--task-horizontal-padding);
|
||||
|
|
@ -109,15 +132,94 @@ body {
|
|||
}
|
||||
#main-container .task-wrapper .task-container .task .username {
|
||||
font-weight: var(--username-font-weight);
|
||||
font-size: var(--username-font-size);
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks {
|
||||
list-style-position: inside;
|
||||
counter-reset: item;
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks .todo {
|
||||
display: flex;
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks .todo:before {
|
||||
counter-increment: item;
|
||||
content: counters(item, ".") ". ";
|
||||
margin-right: 5px;
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks .todo .content {
|
||||
display: -webkit-box;
|
||||
line-clamp: var(--number-of-lines);
|
||||
-webkit-line-clamp: var(--number-of-lines);
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks .done {
|
||||
color: var(--done-task-font-color);
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks .done .content {
|
||||
text-decoration: line-through;
|
||||
}/*# sourceMappingURL=taskList.css.map */
|
||||
color: var(--done-task-font-color);
|
||||
}
|
||||
#main-container .task-wrapper .task-container .task .user-tasks .focus {
|
||||
background: var(--focus-task-background-color);
|
||||
color: var(--focus-task-font-color);
|
||||
padding: var(--focus-task-vertical-padding) var(--focus-task-horizontal-padding);
|
||||
border-radius: var(--focus-task-border-radius);
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
margin-left: calc(var(--focus-task-horizontal-padding) * -1);
|
||||
}/*# sourceMappingURL=taskList.css.map */
|
||||
.oauth-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.oauth-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oauth-modal__content {
|
||||
max-width: 28rem;
|
||||
padding: 1.5rem 1.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #18181b;
|
||||
color: #efeff1;
|
||||
font-family: system-ui, -apple-system, Segoe UI, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.oauth-modal__content h2 {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.oauth-modal__content p {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.oauth-modal__content ol {
|
||||
margin-left: 1.25rem;
|
||||
}
|
||||
|
||||
.oauth-modal__content li + li {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.oauth-modal__content a {
|
||||
color: #9147ff;
|
||||
}
|
||||
|
||||
.oauth-modal__content code {
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 0.2rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
|
|
|||
1
styles/taskList.css.map
Normal file
1
styles/taskList.css.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["taskList.scss","taskList.css"],"names":[],"mappings":"AAAA;EACC,uBAAA;EACA,yBAAA;EAEA,gDAAA;EAEA,6BAAA;EACA,+BAAA;EACA,8BAAA;EAEA,mCAAA;EACA,iCAAA;EAEA,iCAAA;EACA,+BAAA;EAEA,6CAAA;EAEA,0BAAA;EACA,4BAAA;EACA,2BAAA;EAEA,wBAAA;EACA,gCAAA;EACA,0BAAA;EACA,0BAAA;EAEA,8BAAA;EAEA,2CAAA;EAEA,wBAAA;EACA,0BAAA;EACA,yBAAA;EAEA,sBAAA;EAEA,oBAAA;EACA,kBAAA;EACA,4BAAA;EAEA,2CAAA;EAEA,8BAAA;EACA,sBAAA;EACA,wBAAA;EAEA,0BAAA;EACA,wBAAA;EACA,yBAAA;EAEA,8BAAA;EACA,4BAAA;EAEA,yBAAA;EACA,8BAAA;EACA,4BAAA;EAEA,wBAAA;ACjBD;;ADoBA;EACC,SAAA;EACA,UAAA;EACA,sBAAA;ACjBD;;ADoBA;EACI;IACI,wBAAA;ECjBN;EDmBE;IACI,4BAAA;ECjBN;AACF;ADoBA;EACI,iCAAA;AClBJ;;ADsBA;EACC,WAAA;EACA,aAAA;EAEA,aAAA;EACA,sBAAA;EAEA,8EAAA;EAGA,mDAAA;EAEA,yEAAA;EACA,6CAAA;EAEA,oCAAA;ACzBD;AD2BC;EACC,aAAA;EACA,8BAAA;EAEA,wEAAA;EAEA,mEAAA;EACA,0CAAA;EAEA,+BAAA;EACA,0CAAA;EAEA,sCAAA;EACA,kCAAA;EACA,sCAAA;AC7BF;AD+BE;EACC,6BAAA;AC7BH;AD+BE;EACC,UAAA;AC7BH;ADgCE;EACC,4CAAA;AC9BH;ADkCC;EACC,gCAAA;AChCF;ADmCC;EACC,WAAA;EACA,YAAA;EAEA,gBAAA;EAEA,kBAAA;EAEA,wCAAA;EAEA,6BAAA;EACA,oCAAA;EACA,gCAAA;EAEA,+BAAA;EAEA,+DAAA;EACA,wCAAA;EAEA,UAAA;ACxCF;AD0CE;EACC,sCAAA;ACxCH;AD2CE;EAEC,cAAA;EAEA,OAAA;EAEA,aAAA;EACA,sBAAA;EAEA,mBAAA;EAEA,iBAAA;EACA,kBAAA;EAEA,kCAAA;EAEA,sCAAA;AChDH;ADmDG;EACC,oEAAA;EAEA,wCAAA;EAEA,wCAAA;EAEA,wBAAA;EAEA,+DAAA;EACA,wCAAA;ACrDJ;ADuDI;EACC,wCAAA;EACA,oCAAA;ACrDL;ADwDI;EACC,mBAAA;ACtDL;ADwDK;EACC,aAAA;ACtDN;ADwDM;EACC,uBAAA;EACA,iCAAA;EACA,iBAAA;ACtDP;ADyDM;EACC,oBAAA;EACA,kCAAA;EACA,0CAAA;EACA,4BAAA;EACA,gBAAA;ACvDP;AD2DK;EACC,kCAAA;ACzDN;AD2DM;EACC,6BAAA;EACA,kCAAA;ACzDP;AD8DK;EACC,8CAAA;EACA,mCAAA;EAEA,gFAAA;EAGA,8CAAA;EAEA,uBAAA;EAAA,kBAAA;EAEA,4DAAA;ACjEN","file":"taskList.css"}
|
||||
240
styles/taskList.scss
Normal file
240
styles/taskList.scss
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
:root {
|
||||
--task-list-width: 100%;
|
||||
--task-list-height: 100vh;
|
||||
|
||||
--task-list-background-color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
--task-list-border-width: 2px;
|
||||
--task-list-border-color: white;
|
||||
--task-list-border-radius: 5px;
|
||||
|
||||
--task-list-horizontal-padding: 0px;
|
||||
--task-list-vertical-padding: 0px;
|
||||
|
||||
--header-horizontal-padding: 10px;
|
||||
--header-vertical-padding: 10px;
|
||||
|
||||
--header-background-color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
--header-border-width: 2px;
|
||||
--header-border-color: white;
|
||||
--header-border-radius: 5px;
|
||||
|
||||
--header-font-size: 20px;
|
||||
--header-font-family: sans-serif;
|
||||
--header-font-color: white;
|
||||
--header-font-weight: bold;
|
||||
|
||||
--task-count-margin-right: 0px;
|
||||
|
||||
--body-background-color: rgba(0, 0, 0, 0.7);
|
||||
|
||||
--body-border-width: 5px;
|
||||
--body-border-color: white;
|
||||
--body-border-radius: 5px;
|
||||
|
||||
--body-margin-top: 0px;
|
||||
|
||||
--number-of-lines: 1;
|
||||
--line-height: 1.5;
|
||||
--username-font-weight: bold;
|
||||
|
||||
--task-background-color: rgba(0, 0, 0, 0.7);
|
||||
|
||||
--task-font-family: sans-serif;
|
||||
--task-font-size: 25px;
|
||||
--task-font-color: white;
|
||||
|
||||
--task-border-color: black;
|
||||
--task-border-width: 0px;
|
||||
--task-border-radius: 5px;
|
||||
|
||||
--task-horizontal-padding: 0px;
|
||||
--task-vertical-padding: 0px;
|
||||
|
||||
--task-margin-bottom: 5px;
|
||||
--task-horizontal-padding: 5px;
|
||||
--task-vertical-padding: 5px;
|
||||
|
||||
--gap-between-scrolls: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.primary, .secondary {
|
||||
animation: scroll linear infinite;
|
||||
}
|
||||
|
||||
|
||||
#main-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding: var(--task-list-vertical-padding)
|
||||
var(--task-list-horizontal-padding);
|
||||
|
||||
background-color: var(--task-list-background-color);
|
||||
|
||||
border: solid var(--task-list-border-width) var(--task-list-border-color);
|
||||
border-radius: var(--task-list-border-radius);
|
||||
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: var(--header-vertical-padding) var(--header-horizontal-padding);
|
||||
|
||||
border: solid var(--header-border-width) var(--header-border-color);
|
||||
border-radius: var(--header-border-radius);
|
||||
|
||||
color: var(--header-font-color);
|
||||
background: var(--header-background-color);
|
||||
|
||||
font-family: var(--header-font-family);
|
||||
font-size: var(--header-font-size);
|
||||
font-weight: var(--header-font-weight);
|
||||
|
||||
.title {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
.fade {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.task-count {
|
||||
margin-right: var(--task-count-margin-right);
|
||||
}
|
||||
}
|
||||
|
||||
.task-wrapper, .task-container {
|
||||
transition: height 0.5s ease-out;
|
||||
}
|
||||
|
||||
.task-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
position: relative;
|
||||
|
||||
background: var(--body-background-color);
|
||||
|
||||
color: var(--task-font-color);
|
||||
font-family: var(--task-font-family);
|
||||
font-size: var(--task-font-size);
|
||||
|
||||
line-height: var(--line-height);
|
||||
|
||||
border: var(--body-border-width) solid var(--body-border-color);
|
||||
border-radius: var(--body-border-radius);
|
||||
|
||||
z-index: 1;
|
||||
|
||||
.task-container.secondary {
|
||||
margin-top: var(--gap-between-scrolls);
|
||||
}
|
||||
|
||||
.task-container {
|
||||
// position: absolute;
|
||||
width: inherit;
|
||||
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
margin-top: var(--body-margin-top);
|
||||
|
||||
transition: transform 0.4s ease-in-out;
|
||||
|
||||
|
||||
.task {
|
||||
padding: var(--task-vertical-padding)
|
||||
var(--task-horizontal-padding);
|
||||
background: var(--task-background-color);
|
||||
|
||||
margin-bottom: var(--task-margin-bottom);
|
||||
|
||||
width: var(--task-width);
|
||||
|
||||
border: solid var(--task-border-width) var(--task-border-color);
|
||||
border-radius: var(--task-border-radius);
|
||||
|
||||
.username {
|
||||
font-weight: var(--username-font-weight);
|
||||
font-size: var(--username-font-size);
|
||||
}
|
||||
|
||||
.user-tasks {
|
||||
counter-reset: item;
|
||||
|
||||
.todo {
|
||||
display: flex;
|
||||
|
||||
&:before {
|
||||
counter-increment: item;
|
||||
content: counters(item, ".") ". ";
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: -webkit-box;
|
||||
line-clamp: var(--number-of-lines);
|
||||
-webkit-line-clamp: var(--number-of-lines); // change this to the number of lines you want
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.done {
|
||||
color: var(--done-task-font-color);
|
||||
|
||||
.content {
|
||||
text-decoration: line-through;
|
||||
color: var(--done-task-font-color);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.focus {
|
||||
background: var(--focus-task-background-color);
|
||||
color: var(--focus-task-font-color);
|
||||
|
||||
padding: var(--focus-task-vertical-padding)
|
||||
var(--focus-task-horizontal-padding);
|
||||
|
||||
border-radius: var(--focus-task-border-radius);
|
||||
|
||||
width:fit-content;
|
||||
|
||||
margin-left: calc(var(--focus-task-horizontal-padding) * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue