15 changed files with 0 additions and 477 deletions
@ -1,78 +0,0 @@
@@ -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 @@
@@ -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