Skip to content

Commit

Permalink
replace comfy.js with tmi.js
Browse files Browse the repository at this point in the history
  • Loading branch information
snazzyfox committed Nov 25, 2023
1 parent 5021888 commit 749f7b4
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 175 deletions.
22 changes: 11 additions & 11 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ module.exports = {
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
parserOptions: {
parser: require.resolve('@typescript-eslint/parser'),
extraFileExtensions: [ '.vue' ]
extraFileExtensions: ['.vue'],
},

env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true
'vue/setup-compiler-macros': true,
},

// Rules order is important, please avoid shuffling them
Expand All @@ -37,7 +37,7 @@ module.exports = {

// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier'
'prettier',
],

plugins: [
Expand All @@ -46,12 +46,11 @@ module.exports = {

// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue'
'vue',

// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE

],

globals: {
Expand All @@ -64,12 +63,11 @@ module.exports = {
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly'
chrome: 'readonly',
},

// add your custom rules here
rules: {

'prefer-promise-reject-errors': 'off',

quotes: ['warn', 'single', { avoidEscape: true }],
Expand All @@ -85,6 +83,8 @@ module.exports = {
'no-unused-vars': 'off',

// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',

'@typescript-eslint/no-non-null-assertion': 'off',
},
};
10 changes: 0 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"dependencies": {
"@quasar/extras": "^1.15.5",
"@vueuse/core": "^9.2.0",
"comfy.js": "^1.1.16",
"core-js": "^3.6.5",
"ky": "^1.1.3",
"lz-string": "^1.4.4",
Expand Down
19 changes: 19 additions & 0 deletions src/api/twitch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ky, { KyResponse } from 'ky';
import twitchAuthStore from 'src/stores/TwitchAuth';
import tmi from 'tmi.js';

export type ClipID = { broadcaster_id: string } | { game_id: string } | { id: string[] };

Expand Down Expand Up @@ -160,3 +161,21 @@ export async function sendWhisper(user_id: string, message: string) {
await twitchApi.post('whispers', { json: { message }, searchParams });
}
}

export class TmiClientWrapper extends tmi.Client {
protected opts!: tmi.Options;

constructor() {
super({
options: {
skipMembership: true,
skipUpdatingEmotesets: true,
},
});
}

public setAuth(channels: string[], username?: string, token?: string) {
this.opts.channels = channels;
this.opts.identity = username && token ? { username, password: 'oauth:' + token } : {};
}
}
37 changes: 20 additions & 17 deletions src/pages/ChatTimer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@
label-always
/>
</q-field>
<q-checkbox
v-model="config.useAnnounce"
label="Send messages as announcements by prepending /announce"
/>
<q-checkbox v-model="config.useAnnounce" label="Send messages as announcements." />
</q-form>

<div class="q-mt-lg">
Expand Down Expand Up @@ -114,7 +111,7 @@ import { useStorage } from '@vueuse/core';
import TwitchChannelSelection from 'src/components/TwitchChannelSelection.vue';
import { ionTimerOutline, ionStopCircleOutline } from '@quasar/extras/ionicons-v6';
import ComfyJS from 'comfy.js';
import tmi from 'tmi.js';
import { sendAnnouncement } from 'src/api/twitch';
interface ChatTimerOptions {
Expand Down Expand Up @@ -146,6 +143,7 @@ const totalSeconds = computed(
const timeRemainingSec = ref<number | null>(null);
const lastSentTime = ref<number | null>(null);
let interval: number | undefined;
let client: tmi.Client;
onMounted(() => {
if (config.value.timerEndTime) {
Expand Down Expand Up @@ -200,7 +198,7 @@ async function send(message: string) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await sendAnnouncement(config.value.twitchUserId!.toString(), message);
} else {
ComfyJS.Say(message, config.value.channelName);
client.say(config.value.channelName, message);
}
}
Expand All @@ -210,22 +208,27 @@ function startTimer() {
runTimer();
}
function runTimer() {
if (!config.value.useAnnounce) {
ComfyJS.Init(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
config.value.twitchAuth!.username,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
config.value.twitchAuth!.token,
config.value.channelName
);
async function runTimer() {
if (!config.value.useAnnounce && config.value.twitchAuth) {
client = new tmi.Client({
identity: {
username: config.value.twitchAuth.username,
password: 'oauth:' + config.value.twitchAuth.token,
},
channels: [config.value.channelName],
options: {
skipMembership: true,
skipUpdatingEmotesets: true,
},
});
await client.connect();
}
interval = window.setInterval(postTimeRemaining, 500);
}
function stopTimer() {
async function stopTimer() {
if (!config.value.useAnnounce) {
ComfyJS.Disconnect();
await client.disconnect();
}
config.value.timerEndTime = null;
timeRemainingSec.value = null;
Expand Down
133 changes: 64 additions & 69 deletions src/pages/CodeRaffle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,13 @@ import {
ionSendOutline,
} from '@quasar/extras/ionicons-v6';
import { useStorage } from '@vueuse/core';
import { ref, watch } from 'vue';
import { ref } from 'vue';
import { QScrollArea, useQuasar } from 'quasar';
import { mdiSword } from '@quasar/extras/mdi-v7';
import { computed } from 'vue';
import tmi from 'tmi.js';
import { TmiClientWrapper } from 'src/api/twitch';
import { sendWhisper } from 'src/api/twitch';
import { HTTPError } from 'ky';
type ChatRole = 'none' | 'sub' | 'vip' | 'mod';
Expand Down Expand Up @@ -344,20 +345,50 @@ const displayedUsers = computed(() =>
user.username.toLowerCase().includes(filterText.value.toLowerCase().trim())
)
);
const chat = new TmiClientWrapper();
let client: tmi.Client;
watch([() => config.value.twitchAuth, () => config.value.channelName], async () => {
client = new tmi.Client({
channels: [config.value.channelName],
identity: {
username: config.value.twitchAuth?.username,
password: 'oauth:' + config.value.twitchAuth?.token,
},
options: {
skipUpdatingEmotesets: true,
skipMembership: true,
},
});
chat.on('chat', (channel, userstate, message, self) => {
if (
!self &&
entryOpen.value &&
message === '!' + config.value.command &&
userstate['display-name']
) {
if (!config.value.entries.find((u) => u.username === userstate['display-name'])) {
let prob = config.value.luck.none,
roles: ChatRole[] = [];
if (userstate.mod) {
prob = Math.max(prob, config.value.luck.mod);
roles.push('mod');
}
if (userstate.vip) {
prob = Math.max(prob, config.value.luck.vip);
roles.push('vip');
}
if (userstate.subscriber) {
prob = Math.max(prob, config.value.luck.sub);
roles.push('sub');
}
if (
config.value.dynamicLuck.decreaseMode === 'once' &&
config.value.previousEntryCounts[userstate['display-name']]
) {
prob *= 1.0 - config.value.dynamicLuck.decreaseFactor;
} else if (config.value.dynamicLuck.decreaseMode === 'stacked') {
const wins = config.value.previousEntryCounts[userstate['display-name']] ?? 0;
prob *= Math.pow(1.0 - config.value.dynamicLuck.decreaseFactor, wins);
}
config.value.entries.push({
username: userstate['display-name'],
userId: userstate['user-id'] as string,
joinTime: new Date().toLocaleTimeString(),
roles,
prob,
});
userListScroll.value?.setScrollPercentage('vertical', 1, 100);
}
}
});
function clearHistory() {
Expand All @@ -380,54 +411,16 @@ async function startEntry() {
});
return;
}
client.on('chat', (channel, userstate, message, self) => {
if (
!self &&
entryOpen.value &&
message === '!' + config.value.command &&
userstate['display-name']
) {
if (!config.value.entries.find((u) => u.username === userstate['display-name'])) {
let prob = config.value.luck.none,
roles: ChatRole[] = [];
if (userstate.mod) {
prob = Math.max(prob, config.value.luck.mod);
roles.push('mod');
}
if (userstate.vip) {
prob = Math.max(prob, config.value.luck.vip);
roles.push('vip');
}
if (userstate.subscriber) {
prob = Math.max(prob, config.value.luck.sub);
roles.push('sub');
}
if (
config.value.dynamicLuck.decreaseMode === 'once' &&
config.value.previousEntryCounts[userstate['display-name']]
) {
prob *= 1.0 - config.value.dynamicLuck.decreaseFactor;
} else if (config.value.dynamicLuck.decreaseMode === 'stacked') {
const wins = config.value.previousEntryCounts[userstate['display-name']] ?? 0;
prob *= Math.pow(1.0 - config.value.dynamicLuck.decreaseFactor, wins);
}
config.value.entries.push({
username: userstate['display-name'],
userId: userstate['user-id'] as string,
joinTime: new Date().toLocaleTimeString(),
roles,
prob,
});
userListScroll.value?.setScrollPercentage('vertical', 1, 100);
}
}
});
try {
await client.connect();
chat.setAuth(
[config.value.channelName],
config.value.twitchAuth?.username,
config.value.twitchAuth?.token
);
await chat.connect();
if (config.value.message.open) {
await client.say(config.value.channelName, config.value.message.open);
await chat.say(config.value.channelName, config.value.message.open);
}
} catch (error) {
quasar.notify({
Expand All @@ -441,7 +434,7 @@ async function startEntry() {
async function stopEntry() {
if (config.value.message.closed) {
await client.say(config.value.channelName, config.value.message.closed);
await chat.say(config.value.channelName, config.value.message.closed);
}
entryOpen.value = false;
}
Expand Down Expand Up @@ -475,10 +468,11 @@ async function sendWhisperMessage(userId: string) {
// use a direct call because all twitch libs still use the old /w command that twitch no longer supports.
await sendWhisper(userId, config.value.message.whisper.replace('$SECRET', config.value.secret));
} catch (error) {
quasar.notify({
message: `Failed whispering ${userId}, error: ${error}`,
color: 'negative',
});
let reason = `${error}`;
if (error instanceof HTTPError) {
reason = (await error.response.json()).message;
}
quasar.notify({ message: 'Error whispering user: ' + reason, color: 'negative' });
}
}
Expand All @@ -487,13 +481,14 @@ async function confirm() {
config.value.status = 'sending';
await Promise.all(config.value.pendingEntries.map((u) => sendWhisperMessage(u.userId)));
if (config.value.message.winners) {
client.say(
chat.say(
config.value.channelName,
config.value.message.winners.replace('$WINNERS', config.value.pendingEntries.join(', '))
config.value.message.winners.replace(
'$WINNERS',
config.value.pendingEntries.map((e) => e.username).join(', ')
)
);
}
config.value.status = 'confirmed';
}
</script>

<style scoped lang="sass"></style>
Loading

0 comments on commit 749f7b4

Please sign in to comment.