15 changed files with 0 additions and 477 deletions
@ -1,78 +0,0 @@ |
|||||||
package twitter |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"net/http" |
|
||||||
|
|
||||||
"github.com/dghubble/oauth1" |
|
||||||
"github.com/g8rswimmer/go-twitter/v2" |
|
||||||
) |
|
||||||
|
|
||||||
/* |
|
||||||
1. developer.twitter.com. Apply to be a developer if needed. |
|
||||||
2. Projects and apps -> Your project name |
|
||||||
3. Settings. |
|
||||||
4. Scroll down to"User authentication settings" Edit |
|
||||||
5. Enable OAuth 1.0a with Read/Write permissions. |
|
||||||
6. Fill out the form with your information. Callback can be anything. |
|
||||||
7. Go to your project "Keys and tokens" |
|
||||||
8. Generate API key and secret. |
|
||||||
9. Generate access token and secret. Verify it says "Read and write permissions." |
|
||||||
10. Generate bearer token. |
|
||||||
*/ |
|
||||||
|
|
||||||
type authorize struct { |
|
||||||
Token string |
|
||||||
} |
|
||||||
|
|
||||||
func (a authorize) Add(req *http.Request) { |
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Token)) |
|
||||||
} |
|
||||||
|
|
||||||
// Twitter is an instance of the Twitter notifier.
|
|
||||||
type Twitter struct { |
|
||||||
apiKey string |
|
||||||
apiSecret string |
|
||||||
accessToken string |
|
||||||
accessTokenSecret string |
|
||||||
bearerToken string |
|
||||||
} |
|
||||||
|
|
||||||
// New returns a new instance of the Twitter notifier.
|
|
||||||
func New(apiKey, apiSecret, accessToken, accessTokenSecret, bearerToken string) (*Twitter, error) { |
|
||||||
if apiKey == "" || apiSecret == "" || accessToken == "" || accessTokenSecret == "" || bearerToken == "" { |
|
||||||
return nil, errors.New("missing some or all of the required twitter configuration values") |
|
||||||
} |
|
||||||
|
|
||||||
return &Twitter{ |
|
||||||
apiKey: apiKey, |
|
||||||
apiSecret: apiSecret, |
|
||||||
accessToken: accessToken, |
|
||||||
accessTokenSecret: accessTokenSecret, |
|
||||||
bearerToken: bearerToken, |
|
||||||
}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Notify will send a notification to Twitter with the supplied text.
|
|
||||||
func (t *Twitter) Notify(text string) error { |
|
||||||
config := oauth1.NewConfig(t.apiKey, t.apiSecret) |
|
||||||
token := oauth1.NewToken(t.accessToken, t.accessTokenSecret) |
|
||||||
httpClient := config.Client(oauth1.NoContext, token) |
|
||||||
|
|
||||||
client := &twitter.Client{ |
|
||||||
Authorizer: authorize{ |
|
||||||
Token: t.bearerToken, |
|
||||||
}, |
|
||||||
Client: httpClient, |
|
||||||
Host: "https://api.twitter.com", |
|
||||||
} |
|
||||||
|
|
||||||
req := twitter.CreateTweetRequest{ |
|
||||||
Text: text, |
|
||||||
} |
|
||||||
|
|
||||||
_, err := client.CreateTweet(context.Background(), req) |
|
||||||
return err |
|
||||||
} |
|
@ -1,220 +0,0 @@ |
|||||||
import { Button, Typography } from 'antd'; |
|
||||||
import React, { useState, useContext, useEffect } from 'react'; |
|
||||||
import { ServerStatusContext } from '../../../utils/server-status-context'; |
|
||||||
import { TextField, TEXTFIELD_TYPE_PASSWORD } from '../TextField'; |
|
||||||
import { FormStatusIndicator } from '../FormStatusIndicator'; |
|
||||||
import { |
|
||||||
postConfigUpdateToAPI, |
|
||||||
RESET_TIMEOUT, |
|
||||||
TWITTER_CONFIG_FIELDS, |
|
||||||
} from '../../../utils/config-constants'; |
|
||||||
import { ToggleSwitch } from '../ToggleSwitch'; |
|
||||||
import { |
|
||||||
createInputStatus, |
|
||||||
StatusState, |
|
||||||
STATUS_ERROR, |
|
||||||
STATUS_SUCCESS, |
|
||||||
} from '../../../utils/input-statuses'; |
|
||||||
import { UpdateArgs } from '../../../types/config-section'; |
|
||||||
import { TEXTFIELD_TYPE_TEXT } from '../TextFieldWithSubmit'; |
|
||||||
|
|
||||||
const { Title } = Typography; |
|
||||||
|
|
||||||
export const ConfigNotify = () => { |
|
||||||
const serverStatusData = useContext(ServerStatusContext); |
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; |
|
||||||
const { notifications } = serverConfig || {}; |
|
||||||
const { twitter } = notifications || {}; |
|
||||||
|
|
||||||
const [formDataValues, setFormDataValues] = useState<any>({}); |
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null); |
|
||||||
|
|
||||||
const [enableSaveButton, setEnableSaveButton] = useState<boolean>(false); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
const { |
|
||||||
enabled, |
|
||||||
apiKey, |
|
||||||
apiSecret, |
|
||||||
accessToken, |
|
||||||
accessTokenSecret, |
|
||||||
bearerToken, |
|
||||||
goLiveMessage, |
|
||||||
} = twitter || {}; |
|
||||||
setFormDataValues({ |
|
||||||
enabled, |
|
||||||
apiKey, |
|
||||||
apiSecret, |
|
||||||
accessToken, |
|
||||||
accessTokenSecret, |
|
||||||
bearerToken, |
|
||||||
goLiveMessage, |
|
||||||
}); |
|
||||||
}, [twitter]); |
|
||||||
|
|
||||||
const canSave = (): boolean => { |
|
||||||
const { apiKey, apiSecret, accessToken, accessTokenSecret, bearerToken, goLiveMessage } = |
|
||||||
formDataValues; |
|
||||||
|
|
||||||
return ( |
|
||||||
!!apiKey && |
|
||||||
!!apiSecret && |
|
||||||
!!accessToken && |
|
||||||
!!accessTokenSecret && |
|
||||||
!!bearerToken && |
|
||||||
!!goLiveMessage |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
setEnableSaveButton(canSave()); |
|
||||||
}, [formDataValues]); |
|
||||||
|
|
||||||
// update individual values in state
|
|
||||||
const handleFieldChange = ({ fieldName, value }: UpdateArgs) => { |
|
||||||
setFormDataValues({ |
|
||||||
...formDataValues, |
|
||||||
[fieldName]: value, |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
// toggle switch.
|
|
||||||
const handleSwitchChange = (switchEnabled: boolean) => { |
|
||||||
const previouslySaved = formDataValues.enabled; |
|
||||||
|
|
||||||
handleFieldChange({ fieldName: 'enabled', value: switchEnabled }); |
|
||||||
|
|
||||||
return switchEnabled !== previouslySaved; |
|
||||||
}; |
|
||||||
|
|
||||||
let resetTimer = null; |
|
||||||
const resetStates = () => { |
|
||||||
setSubmitStatus(null); |
|
||||||
resetTimer = null; |
|
||||||
clearTimeout(resetTimer); |
|
||||||
setEnableSaveButton(false); |
|
||||||
}; |
|
||||||
|
|
||||||
const save = async () => { |
|
||||||
const postValue = formDataValues; |
|
||||||
|
|
||||||
await postConfigUpdateToAPI({ |
|
||||||
apiPath: '/notifications/twitter', |
|
||||||
data: { value: postValue }, |
|
||||||
onSuccess: () => { |
|
||||||
setFieldInConfigState({ |
|
||||||
fieldName: 'twitter', |
|
||||||
value: postValue, |
|
||||||
path: 'notifications', |
|
||||||
}); |
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.')); |
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
|
||||||
}, |
|
||||||
onError: (message: string) => { |
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message)); |
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
|
||||||
}, |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Title>Twitter</Title> |
|
||||||
<p className="description reduced-margins"> |
|
||||||
Let your Twitter followers know each time you go live. |
|
||||||
</p> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<p className="description reduced-margins"> |
|
||||||
<a href="https://owncast.online/docs/notifications" target="_blank" rel="noreferrer"> |
|
||||||
Read how to configure your Twitter account |
|
||||||
</a>{' '} |
|
||||||
to support posting from Owncast. |
|
||||||
</p> |
|
||||||
<p className="description reduced-margins"> |
|
||||||
<a |
|
||||||
href="https://developer.twitter.com/en/portal/dashboard" |
|
||||||
target="_blank" |
|
||||||
rel="noreferrer" |
|
||||||
> |
|
||||||
And then get your Twitter developer credentials |
|
||||||
</a>{' '} |
|
||||||
to fill in below. |
|
||||||
</p> |
|
||||||
</div> |
|
||||||
|
|
||||||
<ToggleSwitch |
|
||||||
apiPath="" |
|
||||||
fieldName="enabled" |
|
||||||
label="Enable Twitter" |
|
||||||
onChange={handleSwitchChange} |
|
||||||
checked={formDataValues.enabled} |
|
||||||
/> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<TextField |
|
||||||
{...TWITTER_CONFIG_FIELDS.apiKey} |
|
||||||
required |
|
||||||
value={formDataValues.apiKey} |
|
||||||
onChange={handleFieldChange} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<TextField |
|
||||||
{...TWITTER_CONFIG_FIELDS.apiSecret} |
|
||||||
type={TEXTFIELD_TYPE_PASSWORD} |
|
||||||
required |
|
||||||
value={formDataValues.apiSecret} |
|
||||||
onChange={handleFieldChange} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<TextField |
|
||||||
{...TWITTER_CONFIG_FIELDS.accessToken} |
|
||||||
required |
|
||||||
value={formDataValues.accessToken} |
|
||||||
onChange={handleFieldChange} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<TextField |
|
||||||
{...TWITTER_CONFIG_FIELDS.accessTokenSecret} |
|
||||||
type={TEXTFIELD_TYPE_PASSWORD} |
|
||||||
required |
|
||||||
value={formDataValues.accessTokenSecret} |
|
||||||
onChange={handleFieldChange} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<TextField |
|
||||||
{...TWITTER_CONFIG_FIELDS.bearerToken} |
|
||||||
required |
|
||||||
value={formDataValues.bearerToken} |
|
||||||
onChange={handleFieldChange} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}> |
|
||||||
<TextField |
|
||||||
{...TWITTER_CONFIG_FIELDS.goLiveMessage} |
|
||||||
type={TEXTFIELD_TYPE_TEXT} |
|
||||||
required |
|
||||||
value={formDataValues.goLiveMessage} |
|
||||||
onChange={handleFieldChange} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<Button |
|
||||||
type="primary" |
|
||||||
onClick={save} |
|
||||||
style={{ |
|
||||||
display: enableSaveButton ? 'inline-block' : 'none', |
|
||||||
position: 'relative', |
|
||||||
marginLeft: 'auto', |
|
||||||
right: '0', |
|
||||||
marginTop: '20px', |
|
||||||
}} |
|
||||||
> |
|
||||||
Save |
|
||||||
</Button> |
|
||||||
<FormStatusIndicator status={submitStatus} /> |
|
||||||
</> |
|
||||||
); |
|
||||||
}; |
|
||||||
export default ConfigNotify; |
|
Loading…
Reference in new issue