Merge pull request #1472 from automatisch/AUT-506
feat(disqus): add disqus integration
This commit is contained in:
16
packages/backend/src/apps/disqus/assets/favicon.svg
Normal file
16
packages/backend/src/apps/disqus/assets/favicon.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<g id="background">
|
||||||
|
<rect fill="#2E9FFF" width="200" height="200"/>
|
||||||
|
</g>
|
||||||
|
<g id="Layer_2">
|
||||||
|
</g>
|
||||||
|
<path fill="#FFFFFF" d="M102.535,167.5c-16.518,0-31.621-6.036-43.298-16.021L30.5,155.405l11.102-27.401
|
||||||
|
c-3.868-8.535-6.038-18.01-6.038-28.004c0-37.277,29.984-67.5,66.971-67.5c36.984,0,66.965,30.223,66.965,67.5
|
||||||
|
C169.5,137.284,139.52,167.5,102.535,167.5z M139.102,99.807v-0.188c0-19.479-13.736-33.367-37.42-33.367h-25.58v67.5h25.201
|
||||||
|
C125.171,133.753,139.102,119.284,139.102,99.807L139.102,99.807z M101.964,117.168h-7.482V82.841h7.482
|
||||||
|
c10.989,0,18.283,6.265,18.283,17.07v0.188C120.247,110.995,112.953,117.168,101.964,117.168z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
21
packages/backend/src/apps/disqus/auth/generate-auth-url.js
Normal file
21
packages/backend/src/apps/disqus/auth/generate-auth-url.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
import authScope from '../common/auth-scope.js';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.apiKey,
|
||||||
|
scope: authScope.join(','),
|
||||||
|
response_type: 'code',
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `https://disqus.com/api/oauth/2.0/authorize/?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
48
packages/backend/src/apps/disqus/auth/index.js
Normal file
48
packages/backend/src/apps/disqus/auth/index.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url.js';
|
||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import refreshToken from './refresh-token.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/disqus/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in Disqus, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'apiSecret',
|
||||||
|
label: 'API Secret',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
refreshToken,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.response.username;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
26
packages/backend/src/apps/disqus/auth/refresh-token.js
Normal file
26
packages/backend/src/apps/disqus/auth/refresh-token.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { URLSearchParams } from 'node:url';
|
||||||
|
import authScope from '../common/auth-scope.js';
|
||||||
|
|
||||||
|
const refreshToken = async ($) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
client_id: $.auth.data.apiKey,
|
||||||
|
client_secret: $.auth.data.apiSecret,
|
||||||
|
refresh_token: $.auth.data.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`https://disqus.com/api/oauth/2.0/access_token/`,
|
||||||
|
params.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
scope: authScope.join(','),
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default refreshToken;
|
34
packages/backend/src/apps/disqus/auth/verify-credentials.js
Normal file
34
packages/backend/src/apps/disqus/auth/verify-credentials.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
client_id: $.auth.data.apiKey,
|
||||||
|
client_secret: $.auth.data.apiSecret,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`https://disqus.com/api/oauth/2.0/access_token/`,
|
||||||
|
params.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
apiKey: $.auth.data.apiKey,
|
||||||
|
apiSecret: $.auth.data.apiSecret,
|
||||||
|
scope: $.auth.data.scope,
|
||||||
|
userId: data.user_id,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
screenName: data.username,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
15
packages/backend/src/apps/disqus/common/add-auth-header.js
Normal file
15
packages/backend/src/apps/disqus/common/add-auth-header.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
access_token: $.auth.data.accessToken,
|
||||||
|
api_key: $.auth.data.apiKey,
|
||||||
|
api_secret: $.auth.data.apiSecret,
|
||||||
|
});
|
||||||
|
|
||||||
|
requestConfig.params = params;
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
3
packages/backend/src/apps/disqus/common/auth-scope.js
Normal file
3
packages/backend/src/apps/disqus/common/auth-scope.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const authScope = ['read', 'write', 'admin', 'email'];
|
||||||
|
|
||||||
|
export default authScope;
|
10
packages/backend/src/apps/disqus/common/get-current-user.js
Normal file
10
packages/backend/src/apps/disqus/common/get-current-user.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
try {
|
||||||
|
const { data: currentUser } = await $.http.get('/3.0/users/details.json');
|
||||||
|
return currentUser;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('You are not authenticated.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
3
packages/backend/src/apps/disqus/dynamic-data/index.js
Normal file
3
packages/backend/src/apps/disqus/dynamic-data/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import listForums from './list-forums/index.js';
|
||||||
|
|
||||||
|
export default [listForums];
|
@@ -0,0 +1,36 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List forums',
|
||||||
|
key: 'listForums',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const forums = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
limit: 100,
|
||||||
|
order: 'desc',
|
||||||
|
cursor: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
let more;
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get('/3.0/users/listForums.json', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
params.cursor = data.cursor.next;
|
||||||
|
more = data.cursor.hasNext;
|
||||||
|
|
||||||
|
if (data.response?.length) {
|
||||||
|
for (const forum of data.response) {
|
||||||
|
forums.data.push({
|
||||||
|
value: forum.id,
|
||||||
|
name: forum.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (more);
|
||||||
|
|
||||||
|
return forums;
|
||||||
|
},
|
||||||
|
};
|
20
packages/backend/src/apps/disqus/index.js
Normal file
20
packages/backend/src/apps/disqus/index.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import dynamicData from './dynamic-data/index.js';
|
||||||
|
import triggers from './triggers/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Disqus',
|
||||||
|
key: 'disqus',
|
||||||
|
baseUrl: 'https://disqus.com',
|
||||||
|
apiBaseUrl: 'https://disqus.com/api',
|
||||||
|
iconUrl: '{BASE_URL}/apps/disqus/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/disqus/connection',
|
||||||
|
primaryColor: '2E9FFF',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
dynamicData,
|
||||||
|
triggers,
|
||||||
|
});
|
3
packages/backend/src/apps/disqus/triggers/index.js
Normal file
3
packages/backend/src/apps/disqus/triggers/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import newComments from './new-comments/index.js';
|
||||||
|
|
||||||
|
export default [newComments];
|
@@ -0,0 +1,92 @@
|
|||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New comments',
|
||||||
|
key: 'newComments',
|
||||||
|
pollInterval: 15,
|
||||||
|
description: 'Triggers when a new comment is posted in a forum using Disqus.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Post Types',
|
||||||
|
key: 'postTypes',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'Which posts should be considered for inclusion in the trigger?',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Type',
|
||||||
|
key: 'type',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'Unapproved Posts', value: 'unapproved' },
|
||||||
|
{ label: 'Approved Posts', value: 'approved' },
|
||||||
|
{ label: 'Spam Posts', value: 'spam' },
|
||||||
|
{ label: 'Deleted Posts', value: 'deleted' },
|
||||||
|
{ label: 'Flagged Posts', value: 'flagged' },
|
||||||
|
{ label: 'Highlighted Posts', value: 'highlighted' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Forum',
|
||||||
|
key: 'forumId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: 'Select the forum where you want comments to be triggered.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listForums',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const forumId = $.step.parameters.forumId;
|
||||||
|
const postTypes = $.step.parameters.postTypes;
|
||||||
|
const formattedCommentTypes = postTypes
|
||||||
|
.filter((type) => type.type !== '')
|
||||||
|
.map((type) => type.type);
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
limit: '100',
|
||||||
|
forum: forumId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (formattedCommentTypes.length) {
|
||||||
|
formattedCommentTypes.forEach((type) => params.append('include', type));
|
||||||
|
}
|
||||||
|
|
||||||
|
let more;
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(
|
||||||
|
`/3.0/posts/list.json?${params.toString()}`
|
||||||
|
);
|
||||||
|
params.set('cursor', data.cursor.next);
|
||||||
|
more = data.cursor.hasNext;
|
||||||
|
|
||||||
|
if (data.response?.length) {
|
||||||
|
for (const comment of data.response) {
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: comment,
|
||||||
|
meta: {
|
||||||
|
internalId: comment.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (more);
|
||||||
|
},
|
||||||
|
});
|
@@ -77,6 +77,15 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/discord/connection' },
|
{ text: 'Connection', link: '/apps/discord/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Disqus',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Triggers', link: '/apps/disqus/triggers' },
|
||||||
|
{ text: 'Connection', link: '/apps/disqus/connection' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Dropbox',
|
text: 'Dropbox',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
|
19
packages/docs/pages/apps/disqus/connection.md
Normal file
19
packages/docs/pages/apps/disqus/connection.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Disqus
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the Disqus
|
||||||
|
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
1. Login to the [Disqus](https://disqus.com/).
|
||||||
|
2. Go to the [API applications page](https://disqus.com/api/applications/) and click on the **Register new application** button.
|
||||||
|
3. Fill the **Register Application** form fields.
|
||||||
|
4. Click on the **Register my application** button.
|
||||||
|
5. Go to the **Authentication** section and select **Read, Write, and Manage Forums** option.
|
||||||
|
6. Copy **OAuth Redirect URL** from Automatisch to **Callback URL** field.
|
||||||
|
7. Click on the **Save Changes** button.
|
||||||
|
8. Go to the **Details** tab.
|
||||||
|
9. Copy **API Key** to **API Key** field on Automatisch.
|
||||||
|
10. Copy **API Secret** to **API Secret** field on Automatisch.
|
||||||
|
11. Click **Submit** button on Automatisch.
|
||||||
|
12. Congrats! Start using your new Disqus connection within the flows.
|
12
packages/docs/pages/apps/disqus/triggers.md
Normal file
12
packages/docs/pages/apps/disqus/triggers.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/disqus.svg
|
||||||
|
items:
|
||||||
|
- name: New comments
|
||||||
|
desc: Triggers when a new comment is posted in a forum using Disqus.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
@@ -7,6 +7,7 @@ The following integrations are currently supported by Automatisch.
|
|||||||
- [DeepL](/apps/deepl/actions)
|
- [DeepL](/apps/deepl/actions)
|
||||||
- [Delay](/apps/delay/actions)
|
- [Delay](/apps/delay/actions)
|
||||||
- [Discord](/apps/discord/actions)
|
- [Discord](/apps/discord/actions)
|
||||||
|
- [Disqus](/apps/disqus/triggers)
|
||||||
- [Dropbox](/apps/dropbox/actions)
|
- [Dropbox](/apps/dropbox/actions)
|
||||||
- [Filter](/apps/filter/actions)
|
- [Filter](/apps/filter/actions)
|
||||||
- [Flickr](/apps/flickr/triggers)
|
- [Flickr](/apps/flickr/triggers)
|
||||||
|
16
packages/docs/pages/public/favicons/disqus.svg
Normal file
16
packages/docs/pages/public/favicons/disqus.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||||
|
<g id="background">
|
||||||
|
<rect fill="#2E9FFF" width="200" height="200"/>
|
||||||
|
</g>
|
||||||
|
<g id="Layer_2">
|
||||||
|
</g>
|
||||||
|
<path fill="#FFFFFF" d="M102.535,167.5c-16.518,0-31.621-6.036-43.298-16.021L30.5,155.405l11.102-27.401
|
||||||
|
c-3.868-8.535-6.038-18.01-6.038-28.004c0-37.277,29.984-67.5,66.971-67.5c36.984,0,66.965,30.223,66.965,67.5
|
||||||
|
C169.5,137.284,139.52,167.5,102.535,167.5z M139.102,99.807v-0.188c0-19.479-13.736-33.367-37.42-33.367h-25.58v67.5h25.201
|
||||||
|
C125.171,133.753,139.102,119.284,139.102,99.807L139.102,99.807z M101.964,117.168h-7.482V82.841h7.482
|
||||||
|
c10.989,0,18.283,6.265,18.283,17.07v0.188C120.247,110.995,112.953,117.168,101.964,117.168z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user