Skip to content

Commit

Permalink
fix: non fatal error (#261)
Browse files Browse the repository at this point in the history
close #226
  • Loading branch information
skarab42 committed Jul 24, 2021
1 parent b608533 commit d4d6367
Show file tree
Hide file tree
Showing 28 changed files with 431 additions and 31 deletions.
3 changes: 2 additions & 1 deletion app/package.json
@@ -1,6 +1,6 @@
{
"name": "marv",
"version": "1.7.0",
"version": "1.7.1",
"description": "Marv - Twitch Bot",
"repository": "github:skarab42/marv",
"author": "skarab42 <skarab42@users.noreply.github.com>",
Expand Down Expand Up @@ -30,6 +30,7 @@
"ms": "^2.1.3",
"obs-websocket-js": "^4.0.2",
"open": "^7.3.0",
"p-retry": "^4.6.1",
"polka": "^0.5.2",
"sequelize": "^6.3.5",
"sirv": "^1.0.7",
Expand Down
16 changes: 13 additions & 3 deletions app/server/api/app.js
@@ -1,5 +1,10 @@
const { getSystemFonts, getUsedFonts } = require("../libs/files");
const settings = require("../libs/settings");
const loggers = require("../libs/loggers");
const io = require("../libs/socket.io");

const logger = loggers.get("app");
const types = ["info", "warning", "error", "notice"];

module.exports = {
quit: () => {
Expand All @@ -12,9 +17,9 @@ module.exports = {
return (await getSystemFonts()).fontNames;
},
loadFont: (url) => {
const io = require("../libs/socket.io")();
if (!io.__overlaySocket) return;
io.__overlaySocket.emit("loadFont", url);
const socket = io();
if (!socket.__overlaySocket) return;
socket.__overlaySocket.emit("loadFont", url);
},
getUsedFonts: () => {
return getUsedFonts();
Expand All @@ -27,4 +32,9 @@ module.exports = {
setSetting: (key, value) => {
return settings.set(`app.${key}`, value);
},
stateNotify(type, message, options = null) {
io().emit("app.notice", { type, message, options });
type = types.includes(type) ? type : "notice";
logger[type](message);
},
};
1 change: 1 addition & 0 deletions app/server/libs/loggers.js
Expand Up @@ -48,5 +48,6 @@ function createLogger({ group, console = false } = {}) {
createLogger({ group: "server", console: watch });
createLogger({ group: "twitch", console: false });
createLogger({ group: "obs", console: watch });
createLogger({ group: "app", console: watch });

module.exports = loggers;
14 changes: 13 additions & 1 deletion app/server/libs/twitch/api/getRewardList.js
@@ -1,12 +1,24 @@
const appApi = require("../../../api/app");
const { _ } = require("../../i18next");
const twitch = require("../index");
const login = require("../login");
const retry = require("../retry");

function mapSort(rewards) {
return rewards.map((reward) => reward._data).sort((a, b) => a.cost - b.cost);
}

module.exports = async function getRewardList() {
async function _getRewardList() {
const user = await login();
const rewards = await twitch.api.helix.channelPoints.getCustomRewards(user);
return rewards ? mapSort(rewards) : null;
}

module.exports = async function getRewardList() {
try {
return await retry(_getRewardList);
} catch (error) {
appApi.stateNotify("error", _("errors.unable_to_fetch_rewards_list"));
return [];
}
};
23 changes: 17 additions & 6 deletions app/server/libs/twitch/api/getUserInfoVars.js
@@ -1,5 +1,8 @@
const getConnectedUser = require("./getConnectedUser");
const appApi = require("../../../api/app");
const { _ } = require("../../i18next");
const twitch = require("../index");
const retry = require("../retry");

function getDefaultVars() {
return {
Expand All @@ -23,16 +26,24 @@ function getChatUserInfoVars(data) {
};
}

async function isSub(from, to) {
return !!(await twitch.api.helix.subscriptions.getSubscriptionForUser(
from,
to
));
}

async function getPubSubUserInfoVars(user) {
const broadcaster = await getConnectedUser();
const isBroadcaster = broadcaster.login === user.login;
let isSubscriber = false;

const isSubscriber =
isBroadcaster ||
!!(await twitch.api.helix.subscriptions.getSubscriptionForUser(
broadcaster.id,
user.id
));
try {
isSubscriber =
isBroadcaster || (await retry(() => isSub(broadcaster.id, user.id)));
} catch (error) {
appApi.stateNotify("error", _("errors.unable_to_fetch_sub_state"));
}

let isMod = false;
let isVip = false;
Expand Down
19 changes: 13 additions & 6 deletions app/server/libs/twitch/plugins/followsPlugin.js
@@ -1,12 +1,19 @@
const getLastFollows = require("../api/getLastFollows");
const pushActions = require("../pushActions");
const appApi = require("../../../api/app");
const { _ } = require("../../i18next");
const retry = require("../retry");

module.exports = async function streamStatePlugin({ delay = 2 } = {}) {
const follows = await getLastFollows();
module.exports = async function followsPlugin({ delay = 0.5 } = {}) {
try {
const follows = await retry(getLastFollows);

follows.forEach((viewer) => {
pushActions("onFollow", { user: viewer.name });
});
follows.forEach((viewer) => {
pushActions("onFollow", { user: viewer.name });
});

setTimeout(streamStatePlugin, delay * 1000);
setTimeout(followsPlugin, delay * 1000);
} catch (error) {
appApi.stateNotify("error", _("errors.unable_to_fetch_new_follows"));
}
};
4 changes: 2 additions & 2 deletions app/server/libs/twitch/plugins/install.js
Expand Up @@ -10,6 +10,6 @@ module.exports = async function install() {
installLock = true;

await updateFollowsPlugin();
streamStatePlugin();
followsPlugin();
await streamStatePlugin();
await followsPlugin();
};
15 changes: 11 additions & 4 deletions app/server/libs/twitch/plugins/streamState.js
@@ -1,9 +1,16 @@
const state = require("../state");
const retry = require("../retry");
const { _ } = require("../../i18next");
const appApi = require("../../../api/app");
const getStreamByChannel = require("../api/getStreamByChannel");

module.exports = async function streamStatePlugin({ delay = 10 } = {}) {
const channel = state.get("user.display_name");
const stream = await getStreamByChannel(channel);
state.set("stream", stream);
setTimeout(streamStatePlugin, delay * 1000);
try {
const channel = state.get("user.display_name");
const stream = await retry(() => getStreamByChannel(channel));
setTimeout(streamStatePlugin, delay * 1000);
state.set("stream", stream);
} catch (error) {
appApi.stateNotify("error", _("errors.unable_to_fetch_stream_state"));
}
};
12 changes: 10 additions & 2 deletions app/server/libs/twitch/plugins/updateFollowsPlugin.js
@@ -1,8 +1,16 @@
const Viewer = require("../../../db/Models/Viewer");
const getFollows = require("../api/getFollows");
const appApi = require("../../../api/app");
const { _ } = require("../../i18next");
const retry = require("../retry");

module.exports = async function updateFollowPlugin() {
module.exports = async function updateFollowsPlugin() {
const all = await Viewer.findAll();
if (all.length) return;
await getFollows();

try {
await retry(getFollows);
} catch (error) {
appApi.stateNotify("error", _("errors.unable_to_fetch_all_follows"));
}
};
14 changes: 14 additions & 0 deletions app/server/libs/twitch/retry.js
@@ -0,0 +1,14 @@
const loggers = require("../../libs/loggers");
const pRetry = require("p-retry");

const logger = loggers.get("app");

module.exports = async function retry(promise, options) {
return await pRetry(promise, {
retries: 5,
...options,
onFailedAttempt: (error) => {
logger.error(error.message, error);
},
});
};
6 changes: 6 additions & 0 deletions app/static/locales/en/app.json
Expand Up @@ -280,5 +280,11 @@
"has": "has",
"hasNot": "has not"
}
},
"errors": {
"unable_to_fetch_follows": "Unable to fetch follows list",
"unable_to_fetch_new_follows": "Unable to fetch new follows",
"unable_to_fetch_rewards_list": "Unable to fetch rewards list",
"unable_to_fetch_stream_state": "Unable to fetch subscriber state"
}
}
6 changes: 6 additions & 0 deletions app/static/locales/fr/app.json
Expand Up @@ -280,5 +280,11 @@
"has": "contient",
"hasNot": "contient pas"
}
},
"errors": {
"unable_to_fetch_follows": "Impossible de récuperer la list des follows",
"unable_to_fetch_new_follows": "Impossible de récuperer les derrniers follows",
"unable_to_fetch_rewards_list": "Impossible de récuperer la liste des récompenses",
"unable_to_fetch_stream_state": "Impossible de récupérer l'état de l'abonné"
}
}
18 changes: 18 additions & 0 deletions app/yarn.lock
Expand Up @@ -152,6 +152,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.6.tgz#fbf249fa46487dd8c7386d785231368b92a33a53"
integrity sha512-U2VopDdmBoYBmtm8Rz340mvvSz34VgX/K9+XCuckvcLGMkt3rbMX8soqFOikIPlPBc5lmw8By9NUK7bEFSBFlQ==

"@types/retry@^0.12.0":
version "0.12.1"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==

"@types/verror@^1.10.4":
version "1.10.4"
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.4.tgz#805c0612b3a0c124cf99f517364142946b74ba3b"
Expand Down Expand Up @@ -1802,6 +1807,14 @@ p-locate@^3.0.0:
dependencies:
p-limit "^2.0.0"

p-retry@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c"
integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==
dependencies:
"@types/retry" "^0.12.0"
retry "^0.13.1"

p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
Expand Down Expand Up @@ -1969,6 +1982,11 @@ retry-as-promised@^3.2.0:
dependencies:
any-promise "^1.3.0"

retry@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==

rimraf@2, rimraf@^2.6.1, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
Expand Down
3 changes: 2 additions & 1 deletion front-src/client/api/app.js
@@ -1,4 +1,4 @@
import { emit } from "@/libs/socket.io";
import { emit, on } from "@/libs/socket.io";

export default {
quit: () => emit("app.quit"),
Expand All @@ -8,4 +8,5 @@ export default {
getUsedFonts: () => emit("app.getUsedFonts"),
getSettings: () => emit("app.getSettings"),
setSetting: (key, value) => emit("app.setSetting", key, value),
on: (key, cb) => on(`app.${key}`, cb),
};
3 changes: 3 additions & 0 deletions front-src/client/components/App.svelte
Expand Up @@ -14,6 +14,7 @@
import appStore, { initialized } from "@/stores/app";
import Connected from "@/components/App/Connected.svelte";
import Notify from "@/components/App/Notify/Notify.svelte";
import Connecting from "@/components/App/Connecting.svelte";
import Disconnected from "@/components/App/Disconnected.svelte";
import ElectronTopbar from "@/components/App/ElectronTopbar.svelte";
Expand Down Expand Up @@ -59,3 +60,5 @@
</div>

<div id="modal"></div>

<Notify />
12 changes: 12 additions & 0 deletions front-src/client/components/App/Notify/Badge.svelte
@@ -0,0 +1,12 @@
<script>
import { color } from "./colors";
export let type;
export let notices;
</script>

{#if notices.length}
<div class="text-sm px-2 inline rounded-full {color(type)}">
{notices.length}
</div>
{/if}
21 changes: 21 additions & 0 deletions front-src/client/components/App/Notify/Icon.svelte
@@ -0,0 +1,21 @@
<script>
import { iconColor } from "./colors";
import Icon from "@/components/UI/Icon.svelte";
import info from "svelte-icons/md/MdInfo.svelte";
import error from "svelte-icons/md/MdError.svelte";
import warning from "svelte-icons/md/MdWarning.svelte";
import success from "svelte-icons/md/MdCheckCircle.svelte";
export let type;
const icons = {
info,
error,
warning,
success,
};
let icon = icons[type] || icons.info;
</script>

<Icon icon="{icon}" class="{iconColor(type)}" />
26 changes: 26 additions & 0 deletions front-src/client/components/App/Notify/Notice.svelte
@@ -0,0 +1,26 @@
<script>
import ms from "ms";
import NoticeIcon from "./Icon.svelte";
import { slide } from "svelte/transition";
import { closeNotice } from "@/stores/notify";
import Icon from "@/components/UI/Icon.svelte";
import MdCheck from "svelte-icons/md/MdCheck.svelte";
export let notice;
export let time = Date.now();
</script>

<div
transition:slide|local
on:mouseenter="{() => closeNotice(notice)}"
class="flex p-2 gap-2 items-center text-gray-800 bg-gray-300 border-b border-gray-600 hover:bg-opacity-100 {notice.read ? 'bg-opacity-75' : ''}"
>
<NoticeIcon type="{notice.type}" />
<div class="flex-auto break-all">{notice.message}</div>
{#if notice.read}
<div class="fill-current ">
<Icon icon="{MdCheck}" />
</div>
{/if}
<div class="opacity-50">{ms(time - notice.time)}</div>
</div>

0 comments on commit d4d6367

Please sign in to comment.