mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-10-04 02:50:33 +00:00
Compare commits
2 commits
82b7d51df7
...
3c3ea7bdb4
Author | SHA1 | Date | |
---|---|---|---|
|
3c3ea7bdb4 | ||
|
b5f5c2975c |
5 changed files with 99 additions and 17 deletions
|
@ -8,7 +8,14 @@ import {
|
||||||
import { useLiveKey, useModule } from '~/lib/react';
|
import { useLiveKey, useModule } from '~/lib/react';
|
||||||
import { useAppDispatch, useAppSelector } from '~/store';
|
import { useAppDispatch, useAppSelector } from '~/store';
|
||||||
import { modules } from '~/store/api/reducer';
|
import { modules } from '~/store/api/reducer';
|
||||||
import { PageContainer, SectionHeader, styled, TextBlock } from '../theme';
|
import * as HoverCard from '@radix-ui/react-hover-card';
|
||||||
|
import {
|
||||||
|
PageContainer,
|
||||||
|
SectionHeader,
|
||||||
|
styled,
|
||||||
|
TextBlock,
|
||||||
|
TooltipContent,
|
||||||
|
} from '../theme';
|
||||||
import BrowserLink from '../components/BrowserLink';
|
import BrowserLink from '../components/BrowserLink';
|
||||||
import Scrollbar from '../components/utils/Scrollbar';
|
import Scrollbar from '../components/utils/Scrollbar';
|
||||||
import RevealLink from '../components/utils/RevealLink';
|
import RevealLink from '../components/utils/RevealLink';
|
||||||
|
@ -345,15 +352,21 @@ function TwitchEventLog({ events }: { events: EventSubNotification[] }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<HoverCard.Root>
|
||||||
|
<HoverCard.Trigger asChild>
|
||||||
<SectionHeader>
|
<SectionHeader>
|
||||||
{t('pages.dashboard.twitch-events.header')}
|
{t('pages.dashboard.twitch-events.header')}
|
||||||
<a
|
<a style={{ marginLeft: '10px' }}>
|
||||||
style={{ marginLeft: '10px' }}
|
|
||||||
title={t('pages.dashboard.twitch-events.warning')}
|
|
||||||
>
|
|
||||||
<InfoCircledIcon />
|
<InfoCircledIcon />
|
||||||
</a>
|
</a>
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
|
</HoverCard.Trigger>
|
||||||
|
<HoverCard.Portal>
|
||||||
|
<TooltipContent>
|
||||||
|
{t('pages.dashboard.twitch-events.warning')}
|
||||||
|
</TooltipContent>
|
||||||
|
</HoverCard.Portal>
|
||||||
|
</HoverCard.Root>
|
||||||
<Scrollbar vertical={true} viewport={{ maxHeight: '250px' }}>
|
<Scrollbar vertical={true} viewport={{ maxHeight: '250px' }}>
|
||||||
<EventListContainer>
|
<EventListContainer>
|
||||||
{events
|
{events
|
||||||
|
@ -427,10 +440,15 @@ function TwitchSection() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ProblemList() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Dashboard(): React.ReactElement {
|
export default function Dashboard(): React.ReactElement {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
|
<ProblemList />
|
||||||
<TwitchSection />
|
<TwitchSection />
|
||||||
<SectionHeader>{t('pages.dashboard.quick-links')}</SectionHeader>
|
<SectionHeader>{t('pages.dashboard.quick-links')}</SectionHeader>
|
||||||
<UsefulLinksMenu>
|
<UsefulLinksMenu>
|
||||||
|
|
|
@ -26,6 +26,8 @@ import extensionsReducer, {
|
||||||
startExtension,
|
startExtension,
|
||||||
stopExtension,
|
stopExtension,
|
||||||
} from '~/store/extensions/reducer';
|
} from '~/store/extensions/reducer';
|
||||||
|
import { useModule } from '~/lib/react';
|
||||||
|
import { modules } from '~/store/api/reducer';
|
||||||
import AlertContent from '../components/AlertContent';
|
import AlertContent from '../components/AlertContent';
|
||||||
import DialogContent from '../components/DialogContent';
|
import DialogContent from '../components/DialogContent';
|
||||||
import Loading from '../components/Loading';
|
import Loading from '../components/Loading';
|
||||||
|
@ -37,6 +39,7 @@ import {
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Field,
|
Field,
|
||||||
FlexRow,
|
FlexRow,
|
||||||
|
getTheme,
|
||||||
InputBox,
|
InputBox,
|
||||||
Label,
|
Label,
|
||||||
MultiButton,
|
MultiButton,
|
||||||
|
@ -220,7 +223,7 @@ function ExtensionListItem(props: ExtensionListItemProps) {
|
||||||
}}
|
}}
|
||||||
showCancel={false}
|
showCancel={false}
|
||||||
>
|
>
|
||||||
<code>{props.error.toString()}</code>
|
<code>{props.error.message}</code>
|
||||||
</AlertContent>
|
</AlertContent>
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -583,6 +586,7 @@ function ExtensionEditor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ExtensionsPage(): React.ReactElement {
|
export default function ExtensionsPage(): React.ReactElement {
|
||||||
|
const [uiConfig] = useModule(modules.uiConfig);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const extensions = useAppSelector((state) => state.extensions);
|
const extensions = useAppSelector((state) => state.extensions);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -619,12 +623,20 @@ export default function ExtensionsPage(): React.ReactElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!extensions.ready) {
|
if (!extensions.ready) {
|
||||||
|
const theme = getTheme(
|
||||||
|
uiConfig?.theme ?? localStorage.getItem('theme') ?? 'dark',
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
<PageTitle>{t('pages.extensions.title')}</PageTitle>
|
<PageTitle>{t('pages.extensions.title')}</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Loading size="fill" message={t('pages.extensions.loading')} />
|
<Loading
|
||||||
|
theme={theme}
|
||||||
|
size="fill"
|
||||||
|
message={t('pages.extensions.loading')}
|
||||||
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
|
import { Content as HoverCardContent } from '@radix-ui/react-hover-card';
|
||||||
import { styled, theme } from './theme';
|
import { styled, theme } from './theme';
|
||||||
|
|
||||||
export const FlexRow = styled('div', {
|
export const FlexRow = styled('div', {
|
||||||
|
@ -26,3 +27,15 @@ export const FlexRow = styled('div', {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const TooltipContent = styled(HoverCardContent, {
|
||||||
|
borderRadius: 6,
|
||||||
|
display: 'flex',
|
||||||
|
padding: '0.5rem',
|
||||||
|
gap: '0.5rem',
|
||||||
|
flexDirection: 'column',
|
||||||
|
border: '2px solid $gray6',
|
||||||
|
backgroundColor: '$gray2',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
boxShadow: '0px 5px 20px rgba(0,0,0,0.4)',
|
||||||
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ package twitch
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nicklaw5/helix/v2"
|
"github.com/nicklaw5/helix/v2"
|
||||||
|
@ -16,22 +17,60 @@ type AuthResponse struct {
|
||||||
Time time.Time
|
Time time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scopes = []string{
|
||||||
|
"bits:read",
|
||||||
|
"channel:bot",
|
||||||
|
"channel:moderate",
|
||||||
|
"channel:read:hype_train",
|
||||||
|
"channel:read:polls",
|
||||||
|
"channel:read:predictions",
|
||||||
|
"channel:read:redemptions",
|
||||||
|
"channel:read:subscriptions",
|
||||||
|
"chat:edit",
|
||||||
|
"chat:read",
|
||||||
|
"moderator:manage:announcements",
|
||||||
|
"moderator:read:chatters",
|
||||||
|
"moderator:read:followers",
|
||||||
|
"user_read",
|
||||||
|
"user:bot",
|
||||||
|
"user:manage:whispers",
|
||||||
|
"user:read:chat",
|
||||||
|
"whispers:edit",
|
||||||
|
"whispers:read",
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetAuthorizationURL() string {
|
func (c *Client) GetAuthorizationURL() string {
|
||||||
if c.API == nil {
|
if c.API == nil {
|
||||||
return "twitch-not-configured"
|
return "twitch-not-configured"
|
||||||
}
|
}
|
||||||
return c.API.GetAuthorizationURL(&helix.AuthorizationURLParams{
|
return c.API.GetAuthorizationURL(&helix.AuthorizationURLParams{
|
||||||
ResponseType: "code",
|
ResponseType: "code",
|
||||||
Scopes: []string{"bits:read channel:read:subscriptions channel:read:redemptions channel:read:polls channel:read:predictions channel:read:hype_train user_read chat:read chat:edit channel:moderate whispers:read whispers:edit moderator:read:chatters moderator:read:followers user:manage:whispers moderator:manage:announcements"},
|
Scopes: scopes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckScopes checks if the user has authorized all required scopes
|
||||||
|
// Normally this would be the case but between versions strimertul has changed
|
||||||
|
// the required scopes, and it's possible that some users have not re-authorized
|
||||||
|
// the application with the new scopes.
|
||||||
|
func (c *Client) CheckScopes() (bool, error) {
|
||||||
|
var authResp AuthResponse
|
||||||
|
if err := c.db.GetJSON(AuthKey, &authResp); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort scopes for comparison
|
||||||
|
slices.Sort(authResp.Scope)
|
||||||
|
|
||||||
|
return slices.Equal(scopes, authResp.Scope), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetUserClient(forceRefresh bool) (*helix.Client, error) {
|
func (c *Client) GetUserClient(forceRefresh bool) (*helix.Client, error) {
|
||||||
var authResp AuthResponse
|
var authResp AuthResponse
|
||||||
err := c.db.GetJSON(AuthKey, &authResp)
|
if err := c.db.GetJSON(AuthKey, &authResp); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle token expiration
|
// Handle token expiration
|
||||||
if forceRefresh || time.Now().After(authResp.Time.Add(time.Duration(authResp.ExpiresIn)*time.Second)) {
|
if forceRefresh || time.Now().After(authResp.Time.Add(time.Duration(authResp.ExpiresIn)*time.Second)) {
|
||||||
// Refresh tokens
|
// Refresh tokens
|
||||||
|
@ -108,7 +147,7 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
fmt.Fprintf(w, `<html><body><h2>All done, you can close me now!</h2><script>window.close();</script></body></html>`)
|
_, _ = fmt.Fprintf(w, `<html><body><h2>All done, you can close me now!</h2><script>window.close();</script></body></html>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RefreshResponse struct {
|
type RefreshResponse struct {
|
||||||
|
|
|
@ -14,7 +14,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func setStdHandle(stdhandle int32, handle syscall.Handle) error {
|
func setStdHandle(stdhandle int32, handle syscall.Handle) error {
|
||||||
r0, _, e1 := syscall.SyscallN(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0)
|
r0, _, e1 := syscall.SyscallN(procSetStdHandle.Addr(), uintptr(stdhandle), uintptr(handle), 0)
|
||||||
if r0 == 0 {
|
if r0 == 0 {
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
return error(e1)
|
return error(e1)
|
||||||
|
|
Loading…
Reference in a new issue