1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

Add chatbot and stulbe integration for alerts

This commit is contained in:
Ash Keel 2021-09-30 11:57:18 +02:00
parent 97b42831c0
commit bc12772959
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
12 changed files with 554 additions and 29 deletions

2
.gitignore vendored
View file

@ -2,6 +2,6 @@
*.db
*.db.lock
rice-box.go
data/
/data/
.vscode
strimertul_*

View file

@ -0,0 +1,152 @@
const transport = {
method: 'webhook',
callback: 'https://example.com/webhooks/callback',
};
const sub = {
id: 'f1c2a387-161a-49f9-a165-0f21d7a4e1c4',
status: 'enabled',
cost: 0,
condition: {
broadcaster_user_id: '1337',
},
created_at: '2019-11-16T10:11:12.123Z',
transport,
};
export default {
'channel.update': {
subscription: {
...sub,
type: 'channel.update',
version: '1',
},
event: {
broadcaster_user_id: '1337',
broadcaster_user_login: 'cool_user',
broadcaster_user_name: 'Cool_User',
title: 'Best Stream Ever',
language: 'en',
category_id: '21779',
category_name: 'Fortnite',
is_mature: false,
},
},
'channel.follow': {
subscription: {
...sub,
type: 'channel.follow',
version: '1',
},
event: {
user_id: '1234',
user_login: 'cool_user',
user_name: 'Cool_User',
broadcaster_user_id: '1337',
broadcaster_user_login: 'cooler_user',
broadcaster_user_name: 'Cooler_User',
followed_at: '2020-07-15T18:16:11.17106713Z',
},
},
'channel.subscribe': {
subscription: {
...sub,
type: 'channel.subscribe',
version: '1',
},
event: {
user_id: '1234',
user_login: 'cool_user',
user_name: 'Cool_User',
broadcaster_user_id: '1337',
broadcaster_user_login: 'cooler_user',
broadcaster_user_name: 'Cooler_User',
tier: '1000',
is_gift: false,
},
},
'channel.subscription.gift': {
subscription: {
...sub,
type: 'channel.subscription.gift',
version: '1',
},
event: {
user_id: '1234',
user_login: 'cool_user',
user_name: 'Cool_User',
broadcaster_user_id: '1337',
broadcaster_user_login: 'cooler_user',
broadcaster_user_name: 'Cooler_User',
total: 2,
tier: '1000',
cumulative_total: 284, // null if anonymous or not shared by the user
is_anonymous: false,
},
},
'channel.subscription.message': {
subscription: {
...sub,
type: 'channel.subscription.message',
version: '1',
},
event: {
user_id: '1234',
user_login: 'cool_user',
user_name: 'Cool_User',
broadcaster_user_id: '1337',
broadcaster_user_login: 'cooler_user',
broadcaster_user_name: 'Cooler_User',
tier: '1000',
message: {
text: 'Love the stream! FevziGG',
emotes: [
{
end: 30,
id: '302976485',
},
],
},
cumulative_months: 15,
streak_months: 1, // null if not shared
duration_months: 6,
},
},
'channel.cheer': {
subscription: {
...sub,
type: 'channel.cheer',
version: '1',
},
event: {
is_anonymous: false,
user_id: '1234', // null if is_anonymous=true
user_login: 'cool_user', // null if is_anonymous=true
user_name: 'Cool_User', // null if is_anonymous=true
broadcaster_user_id: '1337',
broadcaster_user_login: 'cooler_user',
broadcaster_user_name: 'Cooler_User',
message: 'pogchamp',
bits: 1000,
},
},
'channel.raid': {
subscription: {
...sub,
type: 'channel.raid',
version: '1',
condition: {
to_broadcaster_user_id: '1337',
},
},
event: {
from_broadcaster_user_id: '1234',
from_broadcaster_user_login: 'cool_user',
from_broadcaster_user_name: 'Cool_User',
to_broadcaster_user_id: '1337',
to_broadcaster_user_login: 'cooler_user',
to_broadcaster_user_name: 'Cooler_User',
viewers: 9001,
},
},
};

View file

@ -0,0 +1,56 @@
export interface StulbeOptions {
controller: AbortController;
}
export default class Stulbe {
private token: string;
// eslint-disable-next-line no-useless-constructor
constructor(
private readonly endpoint: string,
private readonly options?: StulbeOptions,
) {}
public async auth(user: string, key: string): Promise<boolean> {
const res = await (
await fetch(`${this.endpoint}/api/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user,
key,
}),
signal: this.options?.controller.signal,
})
).json();
if (!res.ok) {
throw new Error(res.error);
}
this.token = res.token;
return res.ok;
}
public async makeRequest<T>(
method: string,
path: string,
body?: any,
): Promise<T> {
if (!this.token) {
throw new Error('not authenticated');
}
const res = await (
await fetch(`${this.endpoint}/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.token}`,
},
body,
signal: this.options?.controller.signal,
})
).json();
return res;
}
}

View file

@ -13,7 +13,9 @@
"/loyalty/queue": "Redempions",
"/loyalty/rewards": "Rewards",
"/loyalty/goals": "Goals",
"/stulbe": "Back-end integration"
"/stulbe": "Back-end integration",
"/stulbe/config": "Back-end configuration",
"/stulbe/webhooks": "Webhooks (for alerts)"
},
"system": {
"menu-header": "Navigation",
@ -39,7 +41,8 @@
"enable": "Enable",
"create": "Create",
"cancel": "Cancel",
"ok": "OK"
"ok": "OK",
"test": "Test"
},
"http": {
"header": "Web server configuration",
@ -50,13 +53,33 @@
"static-root-path": "Static content root path"
},
"backend": {
"header": "Back-end integration settings",
"enable": "Enable back-end (stulbe) integration",
"endpoint": "Stulbe Endpoint",
"username": "User name",
"username-placeholder": "myUserName",
"authkey": "Authorization key",
"authkey-placeholder": "key goes here"
"config": {
"header": "Back-end integration settings",
"enable": "Enable back-end (stulbe) integration",
"endpoint": "Stulbe Endpoint",
"username": "User name",
"username-placeholder": "myUserName",
"authkey": "Authorization key",
"authkey-placeholder": "key goes here",
"authenticated": "Authenticated as"
},
"webhook": {
"header": "Webhook subscriptions",
"err-not-enabled": "Back-end integration must be enabled for this to work",
"loading": "Querying user data from backend…",
"err-no-user": "No twitch user is currently associated (and therefore webhooks are disabled!)",
"current-status": "Current status:",
"auth-message": "Click the following button to authenticate the back-end with your Twitch account:",
"auth-button": "Authenticate with Twitch",
"fake-header": "Simulate events",
"sim-channel.update": "Channel update",
"sim-channel.follow": "New follow",
"sim-channel.subscribe": "New sub",
"sim-channel.subscription.gift": "Gift sub",
"sim-channel.subscription.message": "Re-sub with message",
"sim-channel.cheer": "Cheer",
"sim-channel.raid": "Raid"
}
},
"loyalty": {
"goals": {
@ -90,7 +113,6 @@
"err-not-available": "Redemption queue is not available (loyalty disabled or no one has redeemed anything yet)"
},
"rewards": {
"test": "Test",
"cooldown": "Cooldown",
"required-info": "Required info",
"id": "Reward ID",

View file

@ -8,7 +8,7 @@ import { createWSClient } from '../store/api/reducer';
import Home from './pages/Home';
import HTTPPage from './pages/HTTP';
import TwitchPage from './pages/twitch/Main';
import StulbePage from './pages/Stulbe';
import StulbePage from './pages/stulbe/Main';
import LoyaltyPage from './pages/loyalty/Main';
import DebugPage from './pages/Debug';
import LoyaltySettingPage from './pages/loyalty/Settings';
@ -20,6 +20,8 @@ import TwitchSettingsPage from './pages/twitch/APISettings';
import TwitchBotSettingsPage from './pages/twitch/BotSettings';
import TwitchBotCommandsPage from './pages/twitch/Commands';
import TwitchBotModulesPage from './pages/twitch/Modules';
import StulbeConfigPage from './pages/stulbe/Config';
import StulbeWebhooksPage from './pages/stulbe/Webhook';
interface RouteItem {
name?: string;
@ -49,7 +51,10 @@ const menu: RouteItem[] = [
{ route: '/loyalty/goals' },
],
},
{ route: '/stulbe' },
{
route: '/stulbe',
subroutes: [{ route: '/stulbe/config' }, { route: '/stulbe/webhooks' }],
},
];
export default function App(): React.ReactElement {
@ -132,7 +137,10 @@ export default function App(): React.ReactElement {
<LoyaltyRewardsPage path="rewards" />
<LoyaltyGoalsPage path="goals" />
</LoyaltyPage>
<StulbePage path="stulbe" />
<StulbePage path="stulbe">
<StulbeConfigPage path="config" />
<StulbeWebhooksPage path="webhooks" />
</StulbePage>
<DebugPage path="debug" />
</Router>
</div>

View file

@ -89,7 +89,7 @@ function RewardItem({
) : null}
<div style={{ marginTop: '1rem' }}>
<a className="button is-small" onClick={onTest}>
{t('loyalty.rewards.test')}
{t('actions.test')}
</a>{' '}
<a className="button is-small" onClick={onToggleState}>
{item.enabled ? 'Disable' : 'Enable'}

View file

@ -1,25 +1,37 @@
import { RouteComponentProps } from '@reach/router';
import React from 'react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useModule } from '../../lib/react-utils';
import apiReducer, { modules } from '../../store/api/reducer';
import { useModule } from '../../../lib/react-utils';
import Stulbe from '../../../lib/stulbe-lib';
import apiReducer, { modules } from '../../../store/api/reducer';
export default function StulbePage(
export default function StulbeConfigPage(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
params: RouteComponentProps<unknown>,
): React.ReactElement {
const { t } = useTranslation();
const [moduleConfig, setModuleConfig] = useModule(modules.moduleConfig);
const [stulbeConfig, setStulbeConfig] = useModule(modules.stulbeConfig);
const [testResult, setTestResult] = useState<string>(null);
const dispatch = useDispatch();
const busy = moduleConfig === null;
const active = moduleConfig?.stulbe ?? false;
const test = async () => {
try {
const client = new Stulbe(stulbeConfig.endpoint);
await client.auth(stulbeConfig.username, stulbeConfig.auth_key);
setTestResult('Connection/Authentication were successful!');
} catch (e) {
setTestResult(e.message);
}
};
return (
<>
<h1 className="title is-4">{t('backend.header')}</h1>
<h1 className="title is-4">{t('backend.config.header')}</h1>
<div className="field">
<label className="checkbox">
<input
@ -35,11 +47,11 @@ export default function StulbePage(
)
}
/>{' '}
{t('backend.enable')}
{t('backend.config.enable')}
</label>
</div>
<div className="field">
<label className="label">{t('backend.endpoint')}</label>
<label className="label">{t('backend.config.endpoint')}</label>
<p className="control">
<input
className="input"
@ -59,12 +71,12 @@ export default function StulbePage(
</p>
</div>
<div className="field">
<label className="label">{t('backend.username')}</label>
<label className="label">{t('backend.config.username')}</label>
<p className="control">
<input
className="input"
type="text"
placeholder={t('backend.username-placeholder')}
placeholder={t('backend.config.username-placeholder')}
disabled={busy || !active}
value={stulbeConfig?.username ?? ''}
onChange={(ev) =>
@ -79,12 +91,12 @@ export default function StulbePage(
</p>
</div>
<div className="field">
<label className="label">{t('backend.authkey')}</label>
<label className="label">{t('backend.config.authkey')}</label>
<p className="control">
<input
className="input"
type="password"
placeholder={t('backend.authkey-placeholder')}
placeholder={t('backend.config.authkey-placeholder')}
disabled={busy || !active}
value={stulbeConfig?.auth_key ?? ''}
onChange={(ev) =>
@ -105,8 +117,16 @@ export default function StulbePage(
dispatch(setStulbeConfig(stulbeConfig));
}}
>
Save
{t('actions.save')}
</button>
<button className="button" onClick={test}>
{t('actions.test')}
</button>
{testResult ? (
<div className="notification" style={{ marginTop: '1rem' }}>
{testResult}
</div>
) : null}
</>
);
}

View file

@ -0,0 +1,8 @@
import { RouteComponentProps } from '@reach/router';
import React from 'react';
export default function StulbePage({
children,
}: RouteComponentProps<React.PropsWithChildren<unknown>>): React.ReactElement {
return <>{children}</>;
}

View file

@ -0,0 +1,189 @@
/* eslint-disable camelcase */
import { RouteComponentProps } from '@reach/router';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import eventsubTests from '../../../data/eventsub-tests';
import { useModule } from '../../../lib/react-utils';
import Stulbe from '../../../lib/stulbe-lib';
import { modules } from '../../../store/api/reducer';
import { RootState } from '../../../store';
interface UserData {
id: string;
login: string;
display_name: string;
profile_image_url: string;
}
interface SyncError {
ok: false;
error: string;
}
const eventSubTestFn = {
'channel.update': (send) => {
send(eventsubTests['channel.update']);
},
'channel.follow': (send) => {
send(eventsubTests['channel.follow']);
},
'channel.subscribe': (send) => {
send(eventsubTests['channel.subscribe']);
},
'channel.subscription.gift': (send) => {
send(eventsubTests['channel.subscription.gift']);
setTimeout(() => {
send(eventsubTests['channel.subscribe']);
}, 2000);
},
'channel.subscription.message': (send) => {
send(eventsubTests['channel.subscribe']);
setTimeout(() => {
send(eventsubTests['channel.subscription.message']);
}, 2000);
},
'channel.cheer': (send) => {
send(eventsubTests['channel.cheer']);
},
'channel.raid': (send) => {
send(eventsubTests['channel.raid']);
},
};
export default function StulbeWebhooksPage(
props: RouteComponentProps<unknown>,
): React.ReactElement {
const { t } = useTranslation();
const kv = useSelector((state: RootState) => state.api.client);
const [moduleConfig] = useModule(modules.moduleConfig);
const [stulbeConfig] = useModule(modules.stulbeConfig);
const [userStatus, setUserStatus] = useState<UserData | SyncError>(null);
const [client, setClient] = useState<Stulbe>(null);
const getUserInfo = async () => {
try {
const res = (await client.makeRequest(
'GET',
'api/twitch/user',
)) as UserData;
setUserStatus(res);
} catch (e) {
setUserStatus({ ok: false, error: e.message });
}
};
const startAuthFlow = async () => {
const res = (await client.makeRequest('POST', 'api/twitch/authorize')) as {
auth_url: string;
};
const win = window.open(
res.auth_url,
'_blank',
'height=800,width=520,scrollbars=yes,status=yes',
);
// Hack, have to poll because no events are reliable for this
const iv = setInterval(() => {
if (win.closed) {
clearInterval(iv);
setUserStatus(null);
getUserInfo();
}
}, 1000);
};
const sendFakeEvent = async (event: keyof typeof eventSubTestFn) => {
eventSubTestFn[event]((data) => {
kv.putJSON('stulbe/ev/webhook', {
...data,
subscription: {
...data.subscription,
created_at: new Date().toISOString(),
},
});
});
};
// eslint-disable-next-line consistent-return
useEffect(() => {
if (client) {
// Get user info
getUserInfo();
} else if (
moduleConfig &&
moduleConfig.stulbe &&
stulbeConfig &&
stulbeConfig.endpoint &&
stulbeConfig.auth_key &&
stulbeConfig.username
) {
const tryAuth = async () => {
// Try authenticating
const stulbeClient = new Stulbe(stulbeConfig.endpoint);
await stulbeClient.auth(stulbeConfig.username, stulbeConfig.auth_key);
setClient(stulbeClient);
};
tryAuth();
}
}, [moduleConfig, stulbeConfig, client]);
if (!moduleConfig || !moduleConfig.stulbe) {
return (
<>
<h1 className="title is-4">{t('backend.webhook.err-not-enabled')}</h1>
</>
);
}
let userBlock = <i>{t('backend.webhook.loading')}</i>;
if (userStatus !== null) {
if ('id' in userStatus) {
userBlock = (
<>
<div
className="is-flex"
style={{ alignItems: 'center', gap: '0.25rem' }}
>
<p>{t('backend.config.authenticated')}</p>
<img
style={{ width: '20px', borderRadius: '5px' }}
src={userStatus.profile_image_url}
/>
<b>{userStatus.display_name}</b>
</div>
</>
);
} else {
userBlock = t('backend.webhook.err-no-user');
}
}
return (
<>
<h1 className="title is-4">{t('backend.webhook.header')}</h1>
<div className="box">
<div className="title is-5" style={{ marginBottom: '0.75rem' }}>
{t('backend.webhook.current-status')}
</div>
<p>{userBlock}</p>
</div>
<div className="field">
<p>{t('backend.webhook.auth-message')}</p>
</div>
<button className="button" onClick={startAuthFlow} disabled={!client}>
{t('backend.webhook.auth-button')}
</button>
<h2 className="title is-4" style={{ marginTop: '2rem' }}>
{t('backend.webhook.fake-header')}
</h2>
{Object.keys(eventSubTestFn).map((ev: keyof typeof eventsubTests) => (
<button
className="button"
onClick={() => sendFakeEvent(ev)}
style={{ margin: '0.2rem' }}
>
{t(`backend.webhook.sim-${ev}`, { defaultValue: ev })}
</button>
))}
</>
);
}

4
go.mod
View file

@ -14,10 +14,10 @@ require (
github.com/json-iterator/go v1.1.11
github.com/mattn/go-colorable v0.1.8
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/nicklaw5/helix v1.15.0
github.com/nicklaw5/helix v1.25.0
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4
github.com/sirupsen/logrus v1.8.1
github.com/strimertul/kilovolt/v4 v4.0.1
github.com/strimertul/stulbe v0.4.3
github.com/strimertul/stulbe-client-go v0.2.0
github.com/strimertul/stulbe-client-go v0.4.0
)

70
go.sum
View file

@ -1,4 +1,6 @@
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
@ -10,15 +12,23 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -27,18 +37,25 @@ github.com/dgraph-io/badger/v3 v3.2011.1 h1:Hmyof0WMEF/QtutX5SQHzIMnJQxb/IrSzhjc
github.com/dgraph-io/badger/v3 v3.2011.1/go.mod h1:0rLLrQpKVQAL0or/lBLMQznhr6dWWX7h5AKnmnqx268=
github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d h1:eQYOG6A4td1tht0NdJB9Ls6DsXRGb2Ft6X9REU/MbbE=
github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d/go.mod h1:tv2ec8nA7vRpSYX7/MbP52ihrUMXIHit54CQMq8npXQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gempir/go-twitch-irc/v2 v2.5.0 h1:aybXNoyDNQaa4vHhXb0UpIDmspqutQUmXIYUFsjgecU=
github.com/gempir/go-twitch-irc/v2 v2.5.0/go.mod h1:120d2SdlRYg8tRnZwsyNPeS+mWPn+YmNEzB7Bv/CDGE=
github.com/go-delve/delve v1.5.0 h1:gQsRvFdR0BGk19NROQZsAv6iG4w5QIZoJlxJeEUBb0c=
github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
@ -47,27 +64,39 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/flatbuffers v1.12.0 h1:/PtAHvnBY4Kqnx/xCQ3OIV9uYcSFGScBsWI3Oogeh6w=
github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-dap v0.2.0 h1:whjIGQRumwbR40qRU7CEKuFLmePUUc2s4Nt9DoXXxWk=
github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
@ -77,10 +106,13 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f h1:D4I34fbgczGrhrN4DzBCZXT3u/nMWJnGmviIjSzzXSw=
github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -89,9 +121,13 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/nicklaw5/helix v1.13.1/go.mod h1:XeeXY7oY5W+MVMu6wF4qGm8uvjZ1/Nss0FqprVkXKrg=
github.com/nicklaw5/helix v1.15.0 h1:CiWWPk7sBOnqS8ZrsJogx8wzDRD4d+CnrtVHMMyWJSY=
github.com/nicklaw5/helix v1.15.0/go.mod h1:XeeXY7oY5W+MVMu6wF4qGm8uvjZ1/Nss0FqprVkXKrg=
github.com/nicklaw5/helix v1.25.0 h1:Mrz537izZVsGdM3I46uGAAlslj61frgkhS/9xQqyT/M=
github.com/nicklaw5/helix v1.25.0/go.mod h1:yvXZFapT6afIoxnAvlWiJiUMsYnoHl7tNs+t0bloAMw=
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc h1:Ak86L+yDSOzKFa7WM5bf5itSOo1e3Xh8bm5YCMUXIjQ=
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b h1:8uaXtUkxiy+T/zdLWuxa/PG4so0TPZDZfafFNNSaptE=
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
@ -99,6 +135,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
@ -106,14 +143,21 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -123,25 +167,35 @@ github.com/strimertul/kilovolt-client-go v1.1.1 h1:uy6y/WKJyubAPHb+wPJz5We+fVwzW
github.com/strimertul/kilovolt-client-go v1.1.1/go.mod h1:jXbd37kXDdDeKnOWao/JSNMdSYmuhBBHG+LWIBzuXr8=
github.com/strimertul/kilovolt-client-go/v2 v2.0.0 h1:0z+PCw3NQ7icpfoS7+yQUNQ1kvr33R4rQhjFr27oS8c=
github.com/strimertul/kilovolt-client-go/v2 v2.0.0/go.mod h1:y98V8uVMiJZhnBLtSVkkh7bajrEiaJR/AdKXSmeV3lQ=
github.com/strimertul/kilovolt/v3 v3.0.0 h1:3gE0FdH3fL5UgMocR6Z7lI9hk3jD8ds8yL47D4Z7758=
github.com/strimertul/kilovolt/v3 v3.0.0/go.mod h1:AgfPYRp+kffN64tcqCcQUZdpL/Dm5DGHIYRDm9t3E0Y=
github.com/strimertul/kilovolt/v4 v4.0.1 h1:81isohdSixVURO2+dZKKZBPw97HJmNN4/BXn6ADFoWM=
github.com/strimertul/kilovolt/v4 v4.0.1/go.mod h1:AO2ZFQtSB+AcjCw0RTkXjbM6XBAjhsXsrRq10BX95kw=
github.com/strimertul/strimertul v1.3.0/go.mod h1:1pSe9zVWF4BYt56ii1Hg+xTxvtfqfvT4FQ7bYffWsUA=
github.com/strimertul/stulbe v0.2.5/go.mod h1:0AsY4OVf1dNCwOn9s7KySuAxJ85w88pXeostu1n9E7w=
github.com/strimertul/stulbe v0.3.0/go.mod h1:Pb0UQCKdyES7UKSKm2i2g9parkgXSJAFeMH/LSOSbgQ=
github.com/strimertul/stulbe v0.4.0/go.mod h1:Pb0UQCKdyES7UKSKm2i2g9parkgXSJAFeMH/LSOSbgQ=
github.com/strimertul/stulbe v0.4.3 h1:apDTvFaChCoMxUokc1Y51Wn43hWyf0qqMNkptnlIj/c=
github.com/strimertul/stulbe v0.4.3/go.mod h1:Pb0UQCKdyES7UKSKm2i2g9parkgXSJAFeMH/LSOSbgQ=
github.com/strimertul/stulbe-client-go v0.1.0/go.mod h1:KtfuDhxCHZ9DCFHnrBOHqb2Pu9zoj+EqA8ZRIUqLD/w=
github.com/strimertul/stulbe-client-go v0.2.0 h1:zoUu+/ssOWz9VsoioJkQNMU88qJ/lQzAu6ak8GcTYxY=
github.com/strimertul/stulbe-client-go v0.2.0/go.mod h1:5S+nDMyzkeoBHi9fq3s37A4mv3XUPJhwgW94UtGzp28=
github.com/strimertul/stulbe-client-go v0.4.0 h1:9DEHnbjU452qFQaK9ilrzydEirpVxwVeiCz7T0kZxEk=
github.com/strimertul/stulbe-client-go v0.4.0/go.mod h1:Ssz1mEEWt4y7yj6Dm4aCaEV651Tzz9M+suzhWzC2QqQ=
github.com/twitchyliquid64/golang-asm v0.15.0 h1:WYZ15YKpC5xM8PwpBTDsAgemoLB/lyhRkzJSEw9eAew=
github.com/twitchyliquid64/golang-asm v0.15.0/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.starlark.net v0.0.0-20190702223751-32f345186213 h1:lkYv5AKwvvduv5XWP6szk/bvvgO6aDeUujhZQXIFTes=
go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff h1:XmKBi9R6duxOB3lfc72wyrwiOY7X2Jl1wuI+RFOyMDE=
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -149,10 +203,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 h1:QuAh/1Gwc0d+u9walMU1NqzhRemNegsv5esp2ALQIY4=
golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -163,11 +220,13 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -183,8 +242,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -192,21 +253,30 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c=
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=