Compare commits

..

1 Commits

Author SHA1 Message Date
Ali BARIN
69ae448d64 feat: new find project merge requests action in GitLab 2022-05-07 20:47:37 +02:00
619 changed files with 14545 additions and 26199 deletions

View File

@@ -3,7 +3,6 @@ dist
build
coverage
packages/docs/*
packages/e2e-tests
.eslintrc.js
husky.config.js

9
.gitignore vendored
View File

@@ -74,15 +74,6 @@ web_modules/
.env.test
.env.production
# cypress environment variables file
cypress.env.json
# cypress screenshots
packages/e2e-tests/cypress/screenshots
# cypress videos
packages/e2e-tests/cypress/videos/
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

View File

@@ -1,4 +1,3 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.formatOnSave": true
}

View File

@@ -1,76 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
Demonstrating empathy and kindness toward other people
Being respectful of differing opinions, viewpoints, and experiences
Giving and gracefully accepting constructive feedback
Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
The use of sexualized language or imagery, and sexual attention or advances of any kind
Trolling, insulting or derogatory comments, and personal or political attacks
Public or private harassment
Publishing others private information, such as a physical or email address, without their explicit permission
Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at ali@automatisch.io. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozillas code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

View File

@@ -1,51 +0,0 @@
# Automatisch - Open Source Zapier Alternative
![Automatisch - Screenshot](https://user-images.githubusercontent.com/2501931/191562539-e42f6c34-03c7-4dc4-bcf9-7f9473a9c64f.png)
🧐 Automatisch is a business automation tool that lets you connect different services like Twitter, Slack, and more to automate your business processes.
💸 Automating your workflows doesn't have to be a difficult or expensive process. You also don't need any programming knowledge to use Automatisch.
## Advantages
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
✅ The most significant advantage of having Automatisch is keeping your data on your own servers. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
💙 No vendor lock-in. If you ever decide that Automatisch is no longer helpful for your business, you can switch to any other provider, which will be easier than switching from the one cloud provider to another since you have all data and flexibility.
## Documentation
The official documentation can be found here: [https://automatisch.io/docs](https://automatisch.io/docs)
## Installation
```bash
# Clone the repository
git clone git@github.com:automatisch/automatisch.git
# Go to the repository folder
cd automatisch/docker/compose
# Start
docker compose up
```
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. You can also change your email and password later on from the settings page.
## Community Links
- [Discord](https://discord.gg/dJSah9CVrC)
- [Twitter](https://twitter.com/automatischio)
## Support
If you have any questions or problems, please visit our GitHub discussions page, and we'll try to help you as soon as possible.
[https://github.com/automatisch/automatisch/discussions](https://github.com/automatisch/automatisch/discussions)
## License
Automatisch is an open-source software with the [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).

View File

@@ -1,46 +0,0 @@
version: '3.9'
services:
main:
build:
context: ../images/main
network: host
ports:
- '3000:3000'
depends_on:
- postgres
- redis
environment:
- HOST=localhost
- PROTOCOL=http
- PORT=3000
- APP_ENV=production
- REDIS_HOST=redis
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
volumes:
- automatisch_storage:/automatisch/storage
worker:
build:
context: ../images/worker
network: host
depends_on:
- main
environment:
- APP_ENV=production
- REDIS_HOST=redis
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
volumes:
- automatisch_storage:/automatisch/storage
postgres:
image: 'postgres:14.5'
environment:
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: automatisch
POSTGRES_USER: automatisch_user
redis:
image: 'redis:7.0.4'
volumes:
automatisch_storage:

View File

@@ -1,15 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:16
WORKDIR /automatisch
RUN apt-get update && apt-get install -y postgresql-client
COPY ./wait-for-postgres.sh /automatisch/wait-for-postgres.sh
RUN mkdir -p /automatisch/storage
RUN touch /automatisch/storage/.env
RUN echo "ENCRYPTION_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
RUN echo "APP_SECRET_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
RUN yarn global add @automatisch/cli@0.1.5
EXPOSE 3000
CMD sh /automatisch/wait-for-postgres.sh automatisch start --env-file=/automatisch/storage/.env

View File

@@ -1,11 +0,0 @@
#!/bin/sh
set -e
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
>&2 echo "Waiting for Postgres to be ready..."
sleep 1
done
>&2 echo "Postgres is up - executing command"
exec "$@"

View File

@@ -1,10 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:16
WORKDIR /automatisch
RUN apt-get update && apt-get install -y postgresql-client
COPY ./wait-for-postgres.sh /automatisch/wait-for-postgres.sh
RUN yarn global add @automatisch/cli@0.1.5
CMD sh /automatisch/wait-for-postgres.sh automatisch start-worker --env-file /automatisch/storage/.env

View File

@@ -1,11 +0,0 @@
#!/bin/sh
set -e
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
>&2 echo "Waiting for Postgres to be ready..."
sleep 1
done
>&2 echo "Postgres is up - executing command"
exec "$@"

View File

@@ -2,10 +2,14 @@
"packages": [
"packages/*"
],
"version": "0.2.0",
"private": true,
"version": "independent",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
"publish": {
"registry": "http://localhost:5000"
},
"add": {
"exact": true
}

View File

@@ -1,6 +1,5 @@
{
"name": "@automatisch/root",
"license": "AGPL-3.0",
"private": true,
"scripts": {
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
@@ -32,6 +31,6 @@
"prettier": "^2.5.1"
},
"publishConfig": {
"access": "public"
"registry": "http://localhost:5000"
}
}

View File

@@ -1,7 +1,7 @@
HOST=localhost
PROTOCOL=http
PORT=3000
WEB_APP_URL=http://localhost:3001
WEB_APP_URL=https://localhost:3001
APP_ENV=development
POSTGRES_DATABASE=automatisch_development
POSTGRES_PORT=5432
@@ -13,5 +13,3 @@ ENCRYPTION_KEY=sample-encryption-key
APP_SECRET_KEY=sample-app-secret-key
REDIS_PORT=6379
REDIS_HOST=127.0.0.1
ENABLE_BULLMQ_DASHBOARD=false
SERVE_WEB_APP_SEPARATELY=true

View File

@@ -1,4 +1,11 @@
# `backend`
The open source Zapier alternative. Build workflow automation without spending
time and money.
> TODO: description
## Usage
```
const backend = require('backend');
// TODO: DEMONSTRATE API
```

View File

@@ -4,6 +4,6 @@ const client = new Client({
host: 'localhost',
user: 'postgres',
port: 5432,
});
})
export default client;

View File

@@ -4,10 +4,7 @@ import client from './client';
import User from '../../src/models/user';
import '../../src/config/orm';
export async function createUser(
email = 'user@automatisch.io',
password = 'sample'
) {
export async function createUser(email = 'user@automatisch.io', password = 'sample') {
const UNIQUE_VIOLATION_CODE = '23505';
const userParams = {
email,
@@ -15,14 +12,8 @@ export async function createUser(
};
try {
const userCount = await User.query().resultSize();
if (userCount === 0) {
const user = await User.query().insertAndFetch(userParams);
logger.info(`User has been saved: ${user.email}`);
} else {
logger.info('No need to seed a user.');
}
} catch (err) {
if ((err as any).nativeError.code !== UNIQUE_VIOLATION_CODE) {
throw err;
@@ -32,17 +23,14 @@ export async function createUser(
}
}
export const createDatabaseAndUser = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
export const createDatabaseAndUser = async (database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => {
await client.connect();
await createDatabase(database);
await createDatabaseUser(user);
await grantPrivileges(database, user);
await client.end();
};
}
export const createDatabase = async (database = appConfig.postgresDatabase) => {
const DUPLICATE_DB_CODE = '42P04';
@@ -57,7 +45,7 @@ export const createDatabase = async (database = appConfig.postgresDatabase) => {
logger.info(`Database: ${database} already exists!`);
}
};
}
export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
const DUPLICATE_OBJECT_CODE = '42710';
@@ -74,25 +62,25 @@ export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
logger.info(`Database User: ${user} already exists!`);
}
};
}
export const grantPrivileges = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
database = appConfig.postgresDatabase, user = appConfig.postgresUsername
) => {
await client.query(
`GRANT ALL PRIVILEGES ON DATABASE ${database} TO ${user};`
);
logger.info(`${user} has granted all privileges on ${database}!`);
};
logger.info(
`${user} has granted all privileges on ${database}!`
);
}
export const dropDatabase = async () => {
if (appConfig.appEnv != 'development' && appConfig.appEnv != 'test') {
const errorMessage =
'Drop database command can be used only with development or test environments!';
const errorMessage = 'Drop database command can be used only with development or test environments!'
logger.error(errorMessage);
logger.error(errorMessage)
return;
}
@@ -100,15 +88,13 @@ export const dropDatabase = async () => {
await dropDatabaseAndUser();
await client.end();
};
}
export const dropDatabaseAndUser = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
export const dropDatabaseAndUser = async(database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => {
await client.query(`DROP DATABASE IF EXISTS ${database}`);
logger.info(`Database: ${database} removed!`);
await client.query(`DROP USER IF EXISTS ${user}`);
logger.info(`Database User: ${user} removed!`);
};
}

View File

@@ -1 +0,0 @@
export * from './dist/bin/database/utils';

View File

@@ -1,2 +0,0 @@
/* eslint-disable */
module.exports = require('./dist/bin/database/utils');

View File

@@ -1 +1,2 @@
export * from './dist/src/config/database';
export * as utils from './dist/bin/database/utils';
export * as database from './dist/src/config/database';

View File

@@ -1,2 +1,3 @@
/* eslint-disable */
module.exports = require('./dist/src/config/database');
module.exports.utils = require('./dist/bin/database/utils');
module.exports.database = require('./dist/src/config/database');

View File

@@ -10,7 +10,7 @@ const knexConfig = {
user: appConfig.postgresUsername,
password: appConfig.postgresPassword,
database: appConfig.postgresDatabase,
ssl: appConfig.postgresEnableSsl,
ssl: appConfig.postgresEnableSsl
},
pool: { min: 0, max: 20 },
migrations: {
@@ -20,7 +20,7 @@ const knexConfig = {
},
seeds: {
directory: __dirname + '/src/db/seeds',
},
};
}
}
export default knexConfig;

View File

@@ -1,10 +1,9 @@
{
"name": "@automatisch/backend",
"version": "0.2.0",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"version": "0.1.0",
"description": "> TODO: description",
"scripts": {
"dev": "ts-node-dev --exit-child src/server.ts",
"dev": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec 'ts-node' src/server.ts --ext ts,json",
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
"build": "tsc && yarn copy-statics",
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
@@ -17,30 +16,32 @@
"db:migration:create": "knex migrate:make",
"db:rollback": "knex migrate:rollback",
"db:migrate": "knex migrate:latest",
"copy-statics": "copyfiles src/**/*.{graphql,json,svg} dist",
"prepack": "yarn build",
"prebuild": "rm -rf ./dist"
"copy-statics": "copyfiles src/**/*.{graphql,json,svg} dist"
},
"dependencies": {
"@automatisch/web": "^0.2.0",
"@automatisch/web": "0.1.0",
"@bull-board/express": "^3.10.1",
"@gitbeaker/node": "^35.6.0",
"@graphql-tools/graphql-file-loader": "^7.3.4",
"@graphql-tools/load": "^7.5.2",
"@octokit/oauth-methods": "^1.2.6",
"@rudderstack/rudder-sdk-node": "^1.1.2",
"@slack/bolt": "3.10.0",
"@types/luxon": "^2.3.1",
"ajv-formats": "^2.1.1",
"axios": "0.24.0",
"bcrypt": "^5.0.1",
"bullmq": "^3.0.0",
"bullmq": "^1.76.1",
"copyfiles": "^2.4.1",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"debug": "~2.6.9",
"discord.js": "13.2.0",
"dotenv": "^10.0.0",
"express": "~4.16.1",
"express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0",
"fast-xml-parser": "^4.0.11",
"flickr-sdk": "3.10.0",
"googleapis": "89.0.0",
"graphql-middleware": "^6.1.15",
"graphql-shield": "^7.5.0",
"graphql-tools": "^8.2.0",
@@ -52,9 +53,12 @@
"luxon": "2.3.1",
"morgan": "^1.10.0",
"nodemailer": "6.7.0",
"oauth-1.0a": "^2.2.6",
"objection": "^3.0.0",
"octokit": "^1.7.1",
"pg": "^8.7.1",
"twilio": "3.70.0",
"twitch-js": "2.0.0-beta.42",
"twitter-api-v2": "1.6.0",
"winston": "^3.7.1"
},
"contributors": [
@@ -71,19 +75,14 @@
"test": "__tests__"
},
"files": [
"dist",
"bin",
"src",
"server.js",
"server.d.ts",
"worker.js",
"worker.d.ts",
"logger.js",
"logger.d.ts",
"database.js",
"database.d.ts",
"database-utils.js",
"database-utils.d.ts"
"database.d.ts"
],
"repository": {
"type": "git",
@@ -93,7 +92,7 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@automatisch/types": "^0.2.0",
"@automatisch/types": "0.1.0",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",
@@ -110,8 +109,7 @@
"ava": "^3.15.0",
"nodemon": "^2.0.13",
"sinon": "^11.1.2",
"ts-node": "^10.2.1",
"ts-node-dev": "^1.1.8"
"ts-node": "^10.2.1"
},
"ava": {
"files": [
@@ -123,8 +121,5 @@
"require": [
"ts-node/register"
]
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -1,3 +1,4 @@
import appConfig from './config/app';
import createError from 'http-errors';
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
@@ -14,11 +15,15 @@ import {
} from './helpers/create-bull-board-handler';
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
if (appConfig.appEnv === 'development') {
createBullBoardHandler(serverAdapter);
}
const app = express();
if (appConfig.appEnv === 'development') {
injectBullBoardHandler(app, serverAdapter);
}
appAssetsHandler(app);

View File

@@ -1,3 +0,0 @@
import translateText from './translate-text';
export default [translateText];

View File

@@ -1,77 +0,0 @@
import qs from 'qs';
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Translate Text',
key: 'translateText',
description: 'Translates text from one language to another.',
arguments: [
{
label: 'Text',
key: 'text',
type: 'string' as const,
required: true,
description: 'Text to be translated.',
variables: true,
},
{
label: 'Target Language',
key: 'targetLanguage',
type: 'dropdown' as const,
required: true,
description: 'Language to translate the text to.',
variables: false,
value: '',
options: [
{ label: 'Bulgarian', value: 'BG' },
{ label: 'Chinese (simplified)', value: 'ZH' },
{ label: 'Czech', value: 'CS' },
{ label: 'Danish', value: 'DA' },
{ label: 'Dutch', value: 'NL' },
{ label: 'English', value: 'EN' },
{ label: 'English (American)', value: 'EN-US' },
{ label: 'English (British)', value: 'EN-GB' },
{ label: 'Estonian', value: 'ET' },
{ label: 'Finnish', value: 'FI' },
{ label: 'French', value: 'FR' },
{ label: 'German', value: 'DE' },
{ label: 'Greek', value: 'EL' },
{ label: 'Hungarian', value: 'HU' },
{ label: 'Indonesian', value: 'ID' },
{ label: 'Italian', value: 'IT' },
{ label: 'Japanese', value: 'JA' },
{ label: 'Latvian', value: 'LV' },
{ label: 'Lithuanian', value: 'LT' },
{ label: 'Polish', value: 'PL' },
{ label: 'Portuguese', value: 'PT' },
{ label: 'Portuguese (Brazilian)', value: 'PT-BR' },
{
label:
'Portuguese (all Portuguese varieties excluding Brazilian Portuguese)',
value: 'PT-PT',
},
{ label: 'Romanian', value: 'RO' },
{ label: 'Russian', value: 'RU' },
{ label: 'Slovak', value: 'SK' },
{ label: 'Slovenian', value: 'SL' },
{ label: 'Spanish', value: 'ES' },
{ label: 'Swedish', value: 'SV' },
{ label: 'Turkish', value: 'TR' },
{ label: 'Ukrainian', value: 'UK' },
],
},
],
async run($) {
const stringifiedBody = qs.stringify({
text: $.step.parameters.text,
target_lang: $.step.parameters.targetLanguage,
});
const response = await $.http.post('/v2/translate', stringifiedBody);
$.setActionItem({
raw: response.data,
});
},
});

View File

@@ -1,39 +0,0 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<metadata id="metadata4177">image/svg+xml</metadata>
<defs>
<clipPath id="clipPath4187" clipPathUnits="userSpaceOnUse">
<path id="path4189" d="m70.850157,393.069981l708.661,0l0,-425.197l-708.661,0l0,425.197z"/>
</clipPath>
</defs>
<g>
<title>background</title>
<rect fill="#ffffff" id="canvas_background" height="130" width="130" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<g fill="#FFF" transform="matrix(1.36265596065192,0,0,-1.3395561298863794,-403.63320244005325,311.0674198374575) " id="g4179">
<g fill="#FFF" id="g4183">
<g fill="#FFF" clip-path="url(#clipPath4187)" id="g4185">
<g fill="#FFF" id="g4191">
<path fill="#000000" fill-rule="nonzero" id="path4193" d="m411.83557,185.452083c0,-2.425 -0.463,-4.637 -1.3,-6.226c-1.009,-1.905 -2.173,-3.234 -3.366,-3.843c-1.641,-0.835 -3.707,-1.69 -6.586,-1.69l-4.044,0l0,22.753l3.221,0c4.28,0 7.213,-0.841 8.971,-2.572c2.09,-2.059 3.104,-4.814 3.104,-8.422m-1.89,15.206c-2.434,1.24 -6.053,1.24 -10.633,1.24l-7.069,0c-1.158,0 -2.097,-0.939 -2.097,-2.097l0,-29.463c0,-1.157 0.939,-2.096 2.097,-2.096l6.662,0c3.78,0 7.209,0.393 9.411,1.079c1.585,0.491 3.181,1.529 5.022,3.27c1.559,1.472 2.785,3.317 3.642,5.484c0.886,2.243 1.318,4.609 1.318,7.232c0,3.66 -0.771,6.809 -2.359,9.622c-1.441,2.558 -3.344,4.377 -5.994,5.729"/>
</g>
<g fill="#FFF" id="g4195">
<path fill="#000000" fill-rule="nonzero" id="path4197" d="m533.882353,173.693278l-10.436,0l0,27.36c0,0.467 -0.38,0.845 -0.845,0.845l-4.702,0c-0.466,0 -0.845,-0.378 -0.845,-0.845l0,-31.966c0,-0.467 0.379,-0.846 0.845,-0.846l15.983,0c0.465,0 0.848,0.379 0.848,0.846l0,3.759c0,0.469 -0.383,0.847 -0.848,0.847"/>
</g>
<g fill="#FFF" id="g4199">
<path fill="#000000" fill-rule="nonzero" id="path4201" d="m506.182158,181.465785c0,-2.225 -0.685,-4.08 -2.034,-5.516c-1.35,-1.437 -3.039,-2.167 -5.017,-2.167c-2.038,0 -3.751,0.718 -5.096,2.13c-1.34,1.415 -2.022,3.291 -2.022,5.576c0,2.242 0.682,4.081 2.024,5.471c1.343,1.391 3.056,2.096 5.094,2.096c2.026,0 3.724,-0.716 5.05,-2.132c1.326,-1.412 2.001,-3.249 2.001,-5.458m-5.813,13.324c-1.536,0 -3.091,-0.305 -4.385,-0.862c-1.508,-0.646 -2.894,-1.553 -3.903,-2.554l-0.601,-0.48l-2.708,2.543l-2.843,0c-0.465,0 -0.842,-0.377 -0.842,-0.842l0,-32.911c0,-0.467 0.378,-0.845 0.845,-0.845l4.701,0c0.467,0 0.847,0.379 0.847,0.847l0,12.166l0.598,-0.455c1.287,-1.204 2.95,-2.034 4.125,-2.52c1.249,-0.516 2.621,-0.78 4.076,-0.78c3.306,0 6.085,1.248 8.499,3.812c2.408,2.557 3.581,5.661 3.581,9.488c0,3.886 -1.176,7.13 -3.505,9.64c-2.302,2.491 -5.157,3.753 -8.485,3.753"/>
</g>
<g fill="#FFF" id="g4203">
<path fill="#000000" fill-rule="nonzero" id="path4205" d="m460.078642,184.036586l0.149,0.387c0.847,1.866 1.627,3.045 2.381,3.604c1.364,1.009 2.965,1.522 4.765,1.522c1.543,0 3.024,-0.499 4.396,-1.485c1.209,-0.865 2.09,-2.13 2.533,-3.616l0.101,-0.412l-14.325,0zm6.978,10.753c-3.801,0 -6.871,-1.255 -9.39,-3.836c-2.52,-2.584 -3.745,-5.741 -3.745,-9.651c0,-3.815 1.228,-6.902 3.757,-9.439c2.526,-2.535 5.709,-3.766 9.73,-3.766c2.584,0 4.824,0.454 6.656,1.348c1.543,0.752 2.915,1.843 4.079,3.243c0.167,0.203 0.233,0.472 0.172,0.728c-0.056,0.257 -0.23,0.472 -0.469,0.583l-3.782,1.739c-0.3,0.138 -0.656,0.089 -0.907,-0.128c-1.691,-1.459 -3.641,-2.2 -5.797,-2.2c-2.156,0 -3.793,0.489 -5.008,1.493c-1.29,1.072 -2.003,2.191 -2.332,3.679l19.696,0c0.466,0 0.842,0.375 0.844,0.841l0.007,0.835c0,4.359 -1.369,8.256 -3.757,10.695c-2.529,2.581 -5.718,3.836 -9.754,3.836"/>
</g>
<g fill="#FFF" id="g4207">
<path fill="#000000" fill-rule="nonzero" id="path4209" d="m428.942961,184.036586l0.148,0.386c0.854,1.871 1.635,3.05 2.387,3.605c1.36,1.009 2.963,1.522 4.763,1.522c1.542,0 3.022,-0.5 4.396,-1.485c1.208,-0.865 2.088,-2.13 2.533,-3.615l0.099,-0.413l-14.326,0zm6.979,10.753c-3.798,0 -6.871,-1.255 -9.39,-3.836c-2.521,-2.584 -3.745,-5.741 -3.745,-9.651c0,-3.816 1.227,-6.904 3.757,-9.439c2.526,-2.535 5.709,-3.766 9.73,-3.766c2.584,0 4.824,0.454 6.656,1.348c1.544,0.752 2.915,1.843 4.079,3.244c0.168,0.202 0.231,0.471 0.172,0.728c-0.056,0.256 -0.231,0.471 -0.469,0.582l-3.782,1.739c-0.298,0.138 -0.655,0.089 -0.907,-0.128c-1.688,-1.459 -3.639,-2.2 -5.797,-2.2c-2.156,0 -3.793,0.489 -5.005,1.494c-1.29,1.071 -2.006,2.189 -2.335,3.678l19.696,0c0.465,0 0.844,0.375 0.847,0.841l0.004,0.835c0,4.358 -1.369,8.254 -3.757,10.695c-2.526,2.581 -5.715,3.836 -9.754,3.836"/>
</g>
<g fill="#FFF" id="g4211">
<path fill="#000000" fill-rule="nonzero" id="path4213" d="m355.595244,180.065883c-2.797,0 -5.063,2.266 -5.063,5.063c0,0.295 0.025,0.584 0.075,0.865l-12.108,6.972c-0.881,-0.744 -2.02,-1.193 -3.263,-1.193c-2.797,0 -5.064,2.267 -5.064,5.063c0,2.796 2.267,5.063 5.064,5.063c2.797,0 5.064,-2.267 5.064,-5.063c0,-0.322 -0.033,-0.637 -0.091,-0.943l12.064,-6.946c0.889,0.775 2.051,1.245 3.322,1.245c2.795,0 5.062,-2.267 5.062,-5.063c0,-2.797 -2.267,-5.063 -5.062,-5.063m-15.357,-6.724c0,-2.796 -2.266,-5.063 -5.063,-5.063c-2.795,0 -5.062,2.267 -5.062,5.063c0,2.797 2.267,5.064 5.062,5.064c1.257,0 2.405,-0.459 3.29,-1.217l8.827,5.073c0.377,-1.089 0.963,-2.081 1.708,-2.929l-8.843,-5.083c0.052,-0.295 0.081,-0.598 0.081,-0.908m30.444,30.899l-24.042,13.738c-1.637,0.935 -3.647,0.935 -5.284,0l-24.04,-13.738c-1.66,-0.948 -2.684,-2.714 -2.684,-4.624l0,-27.946c0,-1.902 1.015,-3.661 2.663,-4.612l41.387,-23.889l0.007,16.968l12.01,6.921c1.65,0.951 2.666,2.71 2.666,4.614l0,27.944c0,1.91 -1.024,3.676 -2.683,4.624"/>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -1,33 +0,0 @@
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'authenticationKey',
label: 'Authentication Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'DeepL authentication key of your account.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -1,9 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import verifyCredentials from './verify-credentials';
const isStillVerified = async ($: IGlobalVariable) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -1,11 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const verifyCredentials = async ($: IGlobalVariable) => {
await $.http.get('/v2/usage');
await $.auth.set({
screenName: $.auth.data.screenName,
});
};
export default verifyCredentials;

View File

@@ -1,12 +0,0 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if ($.auth.data?.authenticationKey) {
const authorizationHeader = `DeepL-Auth-Key ${$.auth.data.authenticationKey}`;
requestConfig.headers.Authorization = authorizationHeader;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,18 +0,0 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import actions from './actions';
export default defineApp({
name: 'DeepL',
key: 'deepl',
iconUrl: '{BASE_URL}/apps/deepl/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/deepl/connection',
supportsConnections: true,
baseUrl: 'https://deepl.com',
apiBaseUrl: 'https://api.deepl.com',
primaryColor: '0d2d45',
beforeRequest: [addAuthHeader],
auth,
actions,
});

View File

@@ -1,3 +0,0 @@
import sendMessageToChannel from './send-message-to-channel';
export default [sendMessageToChannel];

View File

@@ -1,47 +0,0 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Send a message to channel',
key: 'sendMessageToChannel',
description: 'Sends a message to a specific channel you specify.',
arguments: [
{
label: 'Channel',
key: 'channel',
type: 'dropdown' as const,
required: true,
description: 'Pick a channel to send the message to.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listChannels',
},
],
},
},
{
label: 'Message text',
key: 'message',
type: 'string' as const,
required: true,
description: 'The content of your new message.',
variables: true,
},
],
async run($) {
const data = {
content: $.step.parameters.message as string,
};
const response = await $.http?.post(
`/channels/${$.step.parameters.channel}/messages`,
data
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,22 +0,0 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import scopes from '../common/scopes';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.consumerKey as string,
redirect_uri: callbackUrl,
response_type: 'code',
permissions: '2146958591',
scope: scopes.join(' '),
});
const url = `${$.app.apiBaseUrl}/oauth2/authorize?${searchParams.toString()}`;
await $.auth.set({ url });
}

View File

@@ -1,61 +0,0 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/discord/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/discord#oauth-redirect-url',
clickToCopy: true,
},
{
key: 'consumerKey',
label: 'Consumer Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/discord#consumer-key',
clickToCopy: false,
},
{
key: 'consumerSecret',
label: 'Consumer Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/discord#consumer-secret',
clickToCopy: false,
},
{
key: 'botToken',
label: 'Bot token',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/discord#bot-token',
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

@@ -1,10 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
await getCurrentUser($);
return true;
};
export default isStillVerified;

View File

@@ -1,53 +0,0 @@
import { IGlobalVariable, IField } from '@automatisch/types';
import { URLSearchParams } from 'url';
import scopes from '../common/scopes';
import getCurrentUser from '../common/get-current-user';
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = oauthRedirectUrlField.value as string;
const params = new URLSearchParams({
client_id: $.auth.data.consumerKey as string,
redirect_uri: callbackUrl,
response_type: 'code',
scope: scopes.join(' '),
client_secret: $.auth.data.consumerSecret as string,
code: $.auth.data.code as string,
grant_type: 'authorization_code',
});
const { data: verifiedCredentials } = await $.http.post(
'/oauth2/token',
params.toString()
);
const {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn,
scope: scope,
token_type: tokenType,
guild: { id: guildId, name: guildName },
} = verifiedCredentials;
await $.auth.set({
accessToken,
refreshToken,
expiresIn,
scope,
tokenType,
});
const user = await getCurrentUser($);
await $.auth.set({
userId: user.id,
screenName: user.username,
email: user.email,
guildId,
guildName,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,99 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { URLSearchParams } from 'url';
import axios, { AxiosInstance } from 'axios';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: AxiosInstance = axios.create({
baseURL: 'https://discord.com/api/',
});
scope: string[] = ['identify', 'email'];
constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData;
this.connectionData = connectionData;
}
get oauthRedirectUrl() {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData() {
const searchParams = new URLSearchParams({
client_id: this.connectionData.consumerKey as string,
redirect_uri: this.oauthRedirectUrl,
response_type: 'code',
scope: this.scope.join(' '),
});
const url = `https://discord.com/api/oauth2/authorize?${searchParams.toString()}`;
return { url };
}
async verifyCredentials() {
const params = new URLSearchParams({
client_id: this.connectionData.consumerKey as string,
redirect_uri: this.oauthRedirectUrl,
response_type: 'code',
scope: this.scope.join(' '),
client_secret: this.connectionData.consumerSecret as string,
code: this.connectionData.oauthVerifier as string,
grant_type: 'authorization_code',
});
const { data: verifiedCredentials } = await this.client.post(
'/oauth2/token',
params.toString()
);
const {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn,
scope: scope,
token_type: tokenType,
} = verifiedCredentials;
const { data: user } = await this.client.get('/users/@me', {
headers: {
Authorization: `${tokenType} ${accessToken}`,
},
});
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken,
refreshToken,
expiresIn,
scope,
tokenType,
userId: user.id,
screenName: user.username,
email: user.email,
};
}
async isStillVerified() {
try {
await this.client.get('/users/@me', {
headers: {
Authorization: `${this.connectionData.tokenType} ${this.connectionData.accessToken}`,
},
});
return true;
} catch {
return false;
}
}
}

View File

@@ -1,12 +0,0 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const { tokenType, botToken } = $.auth.data;
if (tokenType && botToken) {
requestConfig.headers.Authorization = `Bot ${botToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,10 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await $.http.get('/users/@me');
const currentUser = response.data;
return currentUser;
};
export default getCurrentUser;

View File

@@ -1,3 +0,0 @@
const scopes = ['bot', 'identify'];
export default scopes;

View File

@@ -1,3 +0,0 @@
import listChannels from './list-channels';
export default [listChannels];

View File

@@ -1,34 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List channels',
key: 'listChannels',
async run($: IGlobalVariable) {
const channels: {
data: IJSONObject[];
error: IJSONObject | null;
} = {
data: [],
error: null,
};
const response = await $.http.get(
`/guilds/${$.auth.data.guildId}/channels`
);
channels.data = response.data
.filter((channel: IJSONObject) => {
// filter in text channels only
return channel.type === 0;
})
.map((channel: IJSONObject) => {
return {
value: channel.id,
name: channel.name,
};
});
return channels;
},
};

View File

@@ -1,22 +1,15 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import dynamicData from './dynamic-data';
import actions from './actions';
import triggers from './triggers';
import Authentication from './authentication';
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
export default defineApp({
name: 'Discord',
key: 'discord',
iconUrl: '{BASE_URL}/apps/discord/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/discord/connection',
supportsConnections: true,
baseUrl: 'https://discord.com',
apiBaseUrl: 'https://discord.com/api',
primaryColor: '5865f2',
beforeRequest: [addAuthHeader],
auth,
dynamicData,
triggers,
actions,
});
export default class Discord implements IService {
authenticationClient: IAuthentication;
constructor(appData: IApp, connectionData: IJSONObject) {
this.authenticationClient = new Authentication(appData, connectionData);
}
}

View File

@@ -0,0 +1,218 @@
{
"name": "Discord",
"key": "discord",
"iconUrl": "{BASE_URL}/apps/discord/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/discord",
"primaryColor": "5865f2",
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/discord/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/discord#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/discord#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/discord#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
]
}

View File

@@ -1 +0,0 @@
export default [];

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="351px" viewBox="0 0 256 351" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<defs>
<path d="M1.25273437,280.731641 L2.85834533,277.600858 L102.211177,89.0833546 L58.0613266,5.6082033 C54.3920011,-1.28304578 45.0741245,0.473674398 43.8699203,8.18789086 L1.25273437,280.731641 Z" id="path-1"></path>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-2">
<feGaussianBlur stdDeviation="17.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
<feOffset dx="0" dy="0" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
</filter>
<path d="M134.417103,148.974235 L166.455722,116.161738 L134.417104,55.1546874 C131.374828,49.3635911 123.983911,48.7568362 120.973828,54.5646483 L103.26875,88.6738296 L102.739423,90.4175473 L134.417103,148.974235 Z" id="path-3"></path>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-4">
<feGaussianBlur stdDeviation="3.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
<feOffset dx="1" dy="-9" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
</filter>
</defs>
<g>
<path d="M0,282.99762 L2.12250746,280.0256 L102.527363,89.5119284 L102.739423,87.4951323 L58.478806,4.35817711 C54.7706269,-2.60604179 44.3313035,-0.845245771 43.1143483,6.95065473 L0,282.99762 Z" fill="#FFC24A"></path>
<g>
<use fill="#FFA712" fill-rule="evenodd" xlink:href="#path-1"></use>
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
</g>
<path d="M135.004975,150.380704 L167.960199,116.629461 L134.995423,53.6993114 C131.866109,47.7425353 123.128817,47.7253411 120.032618,53.6993112 L102.421015,87.2880848 L102.421015,90.1487443 L135.004975,150.380704 Z" fill="#F4BD62"></path>
<g>
<use fill="#FFA50E" fill-rule="evenodd" xlink:href="#path-3"></use>
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
</g>
<polygon fill="#F6820C" points="0 282.99762 0.962097168 282.030396 4.45771144 280.60956 132.935323 152.60956 134.563025 148.178595 102.513123 87.1048584"></polygon>
<path d="M139.120971,347.551268 L255.395916,282.703666 L222.191698,78.2093373 C221.153051,71.8112478 213.303658,69.2818149 208.724314,73.8694368 L0.000254726368,282.997875 L115.608454,347.545536 C122.914643,351.624979 131.812872,351.62689 139.120971,347.551268" fill="#FDE068"></path>
<path d="M254.354084,282.159837 L221.401937,79.2179369 C220.371175,72.8684188 213.843792,70.2409553 209.299213,74.79375 L1.28945312,282.600785 L115.627825,346.509458 C122.878548,350.557931 131.709226,350.559827 138.961846,346.515146 L254.354084,282.159837 Z" fill="#FCCA3F"></path>
<path d="M139.120907,345.64082 C131.812808,349.716442 122.914579,349.714531 115.60839,345.635089 L0.93134768,282.014551 L0.000191044776,282.997875 L115.60839,347.545536 C122.914579,351.624979 131.812808,351.62689 139.120907,347.551268 L255.395853,282.703666 L255.111196,280.951785 L139.120907,345.64082 Z" fill="#EEAB37"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,89 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { google as GoogleApi } from 'googleapis';
import { OAuth2Client } from 'google-auth-library';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: OAuth2Client;
scopes: string[] = [
'https://www.googleapis.com/auth/datastore',
'https://www.googleapis.com/auth/firebase',
'https://www.googleapis.com/auth/user.emails.read',
'profile',
];
constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData;
this.connectionData = connectionData;
this.client = new GoogleApi.auth.OAuth2(
connectionData.consumerKey as string,
connectionData.consumerSecret as string,
this.oauthRedirectUrl
);
GoogleApi.options({ auth: this.client });
}
get oauthRedirectUrl() {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData() {
const url = this.client.generateAuthUrl({
access_type: 'offline',
scope: this.scopes,
});
return { url };
}
async verifyCredentials() {
const { tokens } = await this.client.getToken(
this.connectionData.oauthVerifier as string
);
this.client.setCredentials(tokens);
const people = GoogleApi.people('v1');
const { data } = await people.people.get({
resourceName: 'people/me',
personFields: 'emailAddresses',
});
const { emailAddresses, resourceName: userId } = data;
const primaryEmailAddress = emailAddresses.find(
(emailAddress) => emailAddress.metadata.primary
);
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
tokenType: tokens.token_type,
expiryDate: tokens.expiry_date,
scope: tokens.scope,
screenName: primaryEmailAddress.value,
userId,
};
}
async isStillVerified() {
try {
await this.client.getTokenInfo(this.connectionData.accessToken as string);
return true;
} catch {
return false;
}
}
}

View File

@@ -0,0 +1,15 @@
import Authentication from './authentication';
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
export default class Firebase implements IService {
authenticationClient: IAuthentication;
constructor(appData: IApp, connectionData: IJSONObject) {
this.authenticationClient = new Authentication(appData, connectionData);
}
}

View File

@@ -0,0 +1,218 @@
{
"name": "Firebase",
"key": "firebase",
"iconUrl": "{BASE_URL}/apps/firebase/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/firebase",
"primaryColor": "ffca28",
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/firebase/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Firebase OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/firebase#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/firebase#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/firebase#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
]
}

View File

@@ -1,21 +0,0 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = oauthRedirectUrlField.value;
const requestPath = '/oauth/request_token';
const data = { oauth_callback: callbackUrl };
const response = await $.http.post(requestPath, data);
const responseData = Object.fromEntries(new URLSearchParams(response.data));
await $.auth.set({
url: `${$.app.apiBaseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}&perms=delete`,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
});
}

View File

@@ -1,48 +0,0 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/flickr/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Flickr OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/flickr#oauth-redirect-url',
clickToCopy: true,
},
{
key: 'consumerKey',
label: 'Consumer Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/flickr#consumer-key',
clickToCopy: false,
},
{
key: 'consumerSecret',
label: 'Consumer Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/flickr#consumer-secret',
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

@@ -1,13 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const isStillVerified = async ($: IGlobalVariable) => {
const params = {
method: 'flickr.test.login',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
return !!response.data.user.id;
};
export default isStillVerified;

View File

@@ -1,22 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
const verifyCredentials = async ($: IGlobalVariable) => {
const response = await $.http.post(
`/oauth/access_token?oauth_verifier=${$.auth.data.oauth_verifier}&oauth_token=${$.auth.data.accessToken}`,
null
);
const responseData = Object.fromEntries(new URLSearchParams(response.data));
await $.auth.set({
consumerKey: $.auth.data.consumerKey,
consumerSecret: $.auth.data.consumerSecret,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
userId: responseData.user_nsid,
screenName: responseData.fullname,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,82 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import FlickrApi from 'flickr-sdk';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: typeof FlickrApi;
oauthClient: typeof FlickrApi;
constructor(appData: IApp, connectionData: IJSONObject) {
this.oauthClient = new FlickrApi.OAuth(
connectionData.consumerKey,
connectionData.consumerSecret
);
if (connectionData.accessToken && connectionData.accessSecret) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
}
this.connectionData = connectionData;
this.appData = appData;
}
async createAuthData() {
const appFields = this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = appFields.value;
const oauthData = (await this.oauthClient.request(callbackUrl)).body;
const url = await this.oauthClient.authorizeUrl(
oauthData.oauth_token,
'delete'
);
return {
accessToken: oauthData.oauth_token,
accessSecret: oauthData.oauth_token_secret,
url: url,
};
}
async verifyCredentials() {
const verifiedCredentials = (
await this.oauthClient.verify(
this.connectionData.accessToken,
this.connectionData.oauthVerifier,
this.connectionData.accessSecret
)
).body;
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: verifiedCredentials.oauth_token,
accessSecret: verifiedCredentials.oauth_token_secret,
userId: verifiedCredentials.user_nsid,
screenName: verifiedCredentials.fullname,
};
}
async isStillVerified() {
try {
await this.client.test.login();
return true;
} catch {
return false;
}
}
}

View File

@@ -1,41 +0,0 @@
import { Token } from 'oauth-1.0a';
import { IJSONObject, TBeforeRequest } from '@automatisch/types';
import oauthClient from './oauth-client';
type RequestDataType = {
url: string;
method: string;
data?: IJSONObject;
};
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const { url, method, data, params } = requestConfig;
const token: Token = {
key: $.auth.data?.accessToken as string,
secret: $.auth.data?.accessSecret as string,
};
const requestData: RequestDataType = {
url: `${requestConfig.baseURL}${url}`,
method,
};
if (url === '/oauth/request_token') {
requestData.data = data;
}
if (method === 'get') {
requestData.data = params;
}
const authHeader = oauthClient($).toHeader(
oauthClient($).authorize(requestData, token)
);
requestConfig.headers.Authorization = authHeader.Authorization;
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,23 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import crypto from 'crypto';
import OAuth from 'oauth-1.0a';
const oauthClient = ($: IGlobalVariable) => {
const consumerData = {
key: $.auth.data.consumerKey as string,
secret: $.auth.data.consumerSecret as string,
};
return new OAuth({
consumer: consumerData,
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto
.createHmac('sha1', key)
.update(base_string)
.digest('base64');
},
});
};
export default oauthClient;

View File

@@ -0,0 +1,10 @@
import { IJSONObject } from '@automatisch/types';
import ListAlbums from './data/list-albums';
export default class Data {
listAlbums: ListAlbums;
constructor(connectionData: IJSONObject) {
this.listAlbums = new ListAlbums(connectionData);
}
}

View File

@@ -0,0 +1,39 @@
import FlickrApi from 'flickr-sdk';
import type { IJSONObject } from '@automatisch/types';
export default class ListAlbums {
client?: typeof FlickrApi;
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
}
}
async run() {
const { photosets } = (await this.client.photosets.getList()).body;
const allPhotosets = [...photosets.photoset];
for (let page = photosets.page + 1; page <= photosets.pages; page++) {
const { photosets } = (await this.client.photosets.getList({ page, })).body;
allPhotosets.push(...photosets.photoset);
}
return allPhotosets.map((photoset) => ({
value: photoset.id,
name: photoset.title._content,
}));
}
}

View File

@@ -1,3 +0,0 @@
import listAlbums from './list-albums';
export default [listAlbums];

View File

@@ -1,56 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
type TResponse = {
data: IJSONObject[];
error?: IJSONObject;
};
type TPhotoset = {
id: string;
title: {
_content: string;
};
};
export default {
name: 'List albums',
key: 'listAlbums',
async run($: IGlobalVariable) {
const params = {
page: 1,
per_page: 500,
user_id: $.auth.data.userId,
method: 'flickr.photosets.getList',
format: 'json',
nojsoncallback: 1,
};
let response = await $.http.get('/rest', { params });
const aggregatedResponse: TResponse = {
data: [...response.data.photosets.photoset],
};
while (response.data.photosets.page < response.data.photosets.pages) {
response = await $.http.get('/rest', {
params: {
...params,
page: response.data.photosets.page,
},
});
aggregatedResponse.data.push(...response.data.photosets.photoset);
}
aggregatedResponse.data = aggregatedResponse.data.map(
(photoset: TPhotoset) => {
return {
value: photoset.id,
name: photoset.title._content,
} as IJSONObject;
}
);
return aggregatedResponse;
},
};

View File

@@ -0,0 +1 @@
declare module 'flickr-sdk';

View File

@@ -1,21 +1,25 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import Authentication from './authentication';
import Triggers from './triggers';
import Data from './data';
export default defineApp({
name: 'Flickr',
key: 'flickr',
iconUrl: '{BASE_URL}/apps/flickr/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/flickr/connection',
docUrl: 'https://automatisch.io/docs/flickr',
primaryColor: '000000',
supportsConnections: true,
baseUrl: 'https://www.flickr.com/',
apiBaseUrl: 'https://www.flickr.com/services',
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});
export default class Flickr implements IService {
authenticationClient: IAuthentication;
triggers: Triggers;
data: Data;
constructor(
appData: IApp,
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.authenticationClient = new Authentication(appData, connectionData);
this.data = new Data(connectionData);
this.triggers = new Triggers(connectionData, parameters);
}
}

View File

@@ -0,0 +1,303 @@
{
"name": "Flickr",
"key": "flickr",
"iconUrl": "{BASE_URL}/apps/flickr/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/flickr",
"primaryColor": "000000",
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/flickr/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Flickr OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/flickr#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/flickr#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/flickr#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.oauth_verifier}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.oauth_verifier}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
],
"triggers": [
{
"name": "New favorite photo",
"key": "newFavoritePhoto",
"description": "Triggers when you favorite a photo.",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New photo in album",
"key": "newPhotoInAlbum",
"description": "Triggers when you add a new photo in an album.",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Album",
"key": "album",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listAlbums"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New photo",
"key": "newPhoto",
"description": "Triggers when you add a new photo.",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New album",
"key": "newAlbum",
"description": "Triggers when you create a new album.",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
}
]
}

View File

@@ -0,0 +1,19 @@
import { IJSONObject } from '@automatisch/types';
import NewFavoritePhoto from './triggers/new-favorite-photo';
import NewPhotoInAlbum from './triggers/new-photo-in-album';
import NewPhoto from './triggers/new-photo';
import NewAlbum from './triggers/new-album';
export default class Triggers {
newFavoritePhoto: NewFavoritePhoto;
newPhotoInAlbum: NewPhotoInAlbum;
newPhoto: NewPhoto;
newAlbum: NewAlbum;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.newFavoritePhoto = new NewFavoritePhoto(connectionData);
this.newPhotoInAlbum = new NewPhotoInAlbum(connectionData, parameters);
this.newPhoto = new NewPhoto(connectionData);
this.newAlbum = new NewAlbum(connectionData);
}
}

View File

@@ -1,6 +0,0 @@
import newAlbums from './new-albums';
import newFavoritePhotos from './new-favorite-photos';
import newPhotos from './new-photos';
import newPhotosInAlbums from './new-photos-in-album';
export default [newAlbums, newFavoritePhotos, newPhotos, newPhotosInAlbums];

View File

@@ -0,0 +1,103 @@
import { DateTime } from 'luxon';
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewAlbum {
client?: typeof FlickrApi;
connectionData?: IJSONObject;
primaryPhotoExtraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
this.connectionData = connectionData;
}
}
async getAlbums(options: { perPage?: number, page?: number } = {}) {
const { perPage, page } = options;
const payload = {
page,
per_page: perPage,
primary_photo_extras: this.primaryPhotoExtraFields,
};
const { photosets } = (await this.client.photosets.getList(payload)).body;
return photosets;
}
async run(startTime: Date) {
const albums = await this.getAlbums({ page: 1 });
const allAlbums = [...albums.photoset];
const newAlbums = [];
let page = 1;
for (const album of allAlbums) {
const createdAtInSeconds = DateTime.fromSeconds(parseInt(album.date_create, 10));
const createdAt = createdAtInSeconds.toMillis();
if (createdAt <= startTime.getTime()) {
break;
}
newAlbums.push(album);
const currentIndex = allAlbums.indexOf(album);
const totalAlbums = allAlbums.length;
const isLastItem = currentIndex + 1 === totalAlbums;
if (isLastItem && page < albums.pages) {
page = page + 1;
const { photoset } = await this.getAlbums({ page, });
allAlbums.push(...photoset.photoset);
}
}
return newAlbums;
}
async testRun() {
const { photoset } = await this.getAlbums({ perPage: 1 });
return photoset;
}
}

View File

@@ -1,13 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newAlbums from './new-albums';
export default defineTrigger({
name: 'New albums',
pollInterval: 15,
key: 'newAlbums',
description: 'Triggers when you create a new album.',
async run($) {
await newAlbums($);
},
});

View File

@@ -1,55 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_m',
'url_o',
].join(',');
const newAlbums = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 500,
user_id: $.auth.data.userId,
extras: extraFields,
method: 'flickr.photosets.getList',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photosets = response.data.photosets;
page = photosets.page + 1;
pages = photosets.pages;
for (const photoset of photosets.photoset) {
$.pushTriggerItem({
raw: photoset,
meta: {
internalId: photoset.id as string,
},
});
}
} while (page <= pages);
};
export default newAlbums;

View File

@@ -0,0 +1,62 @@
import { DateTime } from 'luxon';
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewFavoritePhoto {
client?: typeof FlickrApi;
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
}
}
async run(startTime: Date) {
const { photos } = (await this.client.favorites.getList()).body;
const favPhotos = [...photos.photo];
const newFavPhotos = [];
let page = 1;
for (const photo of favPhotos) {
const markedFavoriteAt = DateTime.fromSeconds(parseInt(photo.date_faved, 10));
const markedFavoriteAtInMillis = markedFavoriteAt.toMillis();
if (markedFavoriteAtInMillis <= startTime.getTime()) {
break;
}
newFavPhotos.push(photo);
const currentIndex = favPhotos.indexOf(photo);
const totalFavPhotos = favPhotos.length;
const isLastItem = currentIndex + 1 === totalFavPhotos;
if (isLastItem && page < photos.pages) {
page = page + 1;
const { photos } = (await this.client.favorites.getList({ page, })).body;
favPhotos.push(...photos.photo);
}
}
return newFavPhotos;
}
async testRun() {
const { photos } = (await this.client.favorites.getList({ per_page: 1, })).body;
return photos.photo;
}
}

View File

@@ -1,13 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newFavoritePhotos from './new-favorite-photos';
export default defineTrigger({
name: 'New favorite photos',
pollInterval: 15,
key: 'newFavoritePhotos',
description: 'Triggers when you favorite a photo.',
async run($) {
await newFavoritePhotos($);
},
});

View File

@@ -1,61 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
const newPhotos = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 500,
user_id: $.auth.data.userId,
extras: extraFields,
method: 'flickr.favorites.getList',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photos = response.data.photos;
page = photos.page + 1;
pages = photos.pages;
for (const photo of photos.photo) {
$.pushTriggerItem({
raw: photo,
meta: {
internalId: photo.date_faved as string,
},
});
}
} while (page <= pages);
};
export default newPhotos;

View File

@@ -0,0 +1,80 @@
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewPhotoInAlbum {
client?: typeof FlickrApi;
connectionData?: IJSONObject;
albumId?: string;
extraFields = [
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_m',
'url_o'
].join(',');
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
this.connectionData = connectionData;
}
if (parameters?.album) {
this.albumId = parameters.album as string;
}
}
async getAlbumPhotos(options: { perPage?: number, page?: number } = {}) {
const { perPage, page } = options;
const payload = {
page,
per_page: perPage,
photoset_id: this.albumId,
user_id: this.connectionData.userId,
extras: this.extraFields,
};
const { photoset } = (await this.client.photosets.getPhotos(payload)).body;
return photoset;
}
async run() {
// TODO: implement pagination on undated entries
const { photo } = await this.getAlbumPhotos({ page: 1 });
return photo;
}
async testRun() {
const { photo } = await this.getAlbumPhotos({ perPage: 1 });
return photo;
}
}

View File

@@ -0,0 +1,88 @@
import { DateTime } from 'luxon';
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewPhoto {
client?: typeof FlickrApi;
connectionData?: IJSONObject;
extraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
this.connectionData = connectionData;
}
}
async getPhotos(options: { perPage?: number, page?: number, minUploadDate?: string } = {}) {
const { perPage, page, minUploadDate } = options;
const payload = {
page,
per_page: perPage,
user_id: this.connectionData.userId,
extras: this.extraFields,
min_upload_date: minUploadDate,
};
const { photos } = (await this.client.photos.search(payload)).body;
return photos;
}
async run(startTime: Date) {
const minUploadDate = DateTime.fromJSDate(startTime).toSeconds().toString();
const photos = await this.getPhotos({ page: 1, minUploadDate });
const allPhotos = [...photos.photo];
for (let page = photos.page + 1; page <= photos.pages; page++) {
const photos = (await this.getPhotos({ page, minUploadDate }));
allPhotos.push(...photos.photo);
}
return allPhotos;
}
async testRun(startTime: Date) {
const { photo } = await this.getPhotos({ perPage: 1 });
return photo;
}
}

View File

@@ -1,32 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newPhotosInAlbum from './new-photos-in-album';
export default defineTrigger({
name: 'New photos in album',
pollInterval: 15,
key: 'newPhotosInAlbum',
description: 'Triggers when you add a new photo in an album.',
arguments: [
{
label: 'Album',
key: 'album',
type: 'dropdown' as const,
required: true,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listAlbums',
},
],
},
},
],
async run($) {
await newPhotosInAlbum($);
},
});

View File

@@ -1,56 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_m',
'url_o',
].join(',');
const newPhotosInAlbum = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 11,
user_id: $.auth.data.userId,
extras: extraFields,
photoset_id: $.step.parameters.album as string,
method: 'flickr.photosets.getPhotos',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photoset = response.data.photoset;
page = photoset.page + 1;
pages = photoset.pages;
for (const photo of photoset.photo) {
$.pushTriggerItem({
raw: photo,
meta: {
internalId: photo.id as string,
},
});
}
} while (page <= pages);
};
export default newPhotosInAlbum;

View File

@@ -1,13 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newPhotos from './new-photos';
export default defineTrigger({
name: 'New photos',
pollInterval: 15,
key: 'newPhotos',
description: 'Triggers when you add a new photo.',
async run($) {
await newPhotos($);
},
});

View File

@@ -1,61 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
const newPhotos = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 500,
user_id: $.auth.data.userId,
extras: extraFields,
method: 'flickr.photos.search',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photos = response.data.photos;
page = photos.page + 1;
pages = photos.pages;
for (const photo of photos.photo) {
$.pushTriggerItem({
raw: photo,
meta: {
internalId: photo.id as string,
},
});
}
} while (page <= pages);
};
export default newPhotos;

View File

@@ -1,58 +0,0 @@
import defineAction from '../../../../helpers/define-action';
import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo';
export default defineAction({
name: 'Create issue',
key: 'createIssue',
description: 'Creates a new issue.',
arguments: [
{
label: 'Repo',
key: 'repo',
type: 'dropdown' as const,
required: false,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listRepos',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string' as const,
required: true,
variables: true,
},
{
label: 'Body',
key: 'body',
type: 'string' as const,
required: true,
variables: true,
},
],
async run($) {
const repoParameter = $.step.parameters.repo as string;
const title = $.step.parameters.title as string;
const body = $.step.parameters.body as string;
if (!repoParameter) throw new Error('A repo must be set!');
if (!title) throw new Error('A title must be set!');
const { repoOwner, repo } = getRepoOwnerAndRepo(repoParameter);
const response = await $.http.post(`/repos/${repoOwner}/${repo}/issues`, {
title,
body,
});
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,3 +0,0 @@
import createIssue from './create-issue';
export default [createIssue];

View File

@@ -1,23 +0,0 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($: IGlobalVariable) {
const scopes = ['read:org', 'repo', 'user'];
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.consumerKey as string,
redirect_uri: redirectUri,
scope: scopes.join(','),
});
const url = `${
$.app.baseUrl
}/login/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -1,49 +0,0 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/github/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/github#oauth-redirect-url',
clickToCopy: true,
},
{
key: 'consumerKey',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/github#client-id',
clickToCopy: false,
},
{
key: 'consumerSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/github#client-secret',
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

@@ -1,9 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
const user = await getCurrentUser($);
return !!user.id;
};
export default isStillVerified;

View File

@@ -1,36 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const verifyCredentials = async ($: IGlobalVariable) => {
const response = await $.http.post(
`${$.app.baseUrl}/login/oauth/access_token`,
{
client_id: $.auth.data.consumerKey,
client_secret: $.auth.data.consumerSecret,
code: $.auth.data.code,
},
{
headers: {
Accept: 'application/json',
},
}
);
const data = response.data;
$.auth.data.accessToken = data.access_token;
const currentUser = await getCurrentUser($);
await $.auth.set({
consumerKey: $.auth.data.consumerKey,
consumerSecret: $.auth.data.consumerSecret,
accessToken: data.access_token,
scope: data.scope,
tokenType: data.token_type,
userId: currentUser.id,
screenName: currentUser.login,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,98 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import {
getWebFlowAuthorizationUrl,
exchangeWebFlowCode,
checkToken,
} from '@octokit/oauth-methods';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
scopes: string[] = [
'read:org',
'repo',
'user',
];
client: {
getWebFlowAuthorizationUrl: typeof getWebFlowAuthorizationUrl;
exchangeWebFlowCode: typeof exchangeWebFlowCode;
checkToken: typeof checkToken;
};
constructor(appData: IApp, connectionData: IJSONObject) {
this.connectionData = connectionData;
this.appData = appData;
this.client = {
getWebFlowAuthorizationUrl,
exchangeWebFlowCode,
checkToken,
};
}
get oauthRedirectUrl(): string {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData(): Promise<{ url: string }> {
const { url } = await this.client.getWebFlowAuthorizationUrl({
clientType: 'oauth-app',
clientId: this.connectionData.consumerKey as string,
redirectUrl: this.oauthRedirectUrl,
scopes: this.scopes,
});
return {
url: url,
};
}
async verifyCredentials() {
const { data } = await this.client.exchangeWebFlowCode({
clientType: 'oauth-app',
clientId: this.connectionData.consumerKey as string,
clientSecret: this.connectionData.consumerSecret as string,
code: this.connectionData.oauthVerifier as string,
});
this.connectionData.accessToken = data.access_token;
const tokenInfo = await this.getTokenInfo();
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: data.access_token,
scope: data.scope,
tokenType: data.token_type,
userId: tokenInfo.data.user.id,
screenName: tokenInfo.data.user.login,
};
}
async getTokenInfo() {
return this.client.checkToken({
clientType: 'oauth-app',
clientId: this.connectionData.consumerKey as string,
clientSecret: this.connectionData.consumerSecret as string,
token: this.connectionData.accessToken as string,
});
}
async isStillVerified() {
try {
await this.getTokenInfo();
return true;
} catch {
return false;
}
}
}

View File

@@ -1,11 +0,0 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if (requestConfig.headers && $.auth.data?.accessToken) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,10 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await $.http.get('/user');
const currentUser = response.data;
return currentUser;
};
export default getCurrentUser;

View File

@@ -1,17 +0,0 @@
type TRepoOwnerAndRepo = {
repoOwner?: string;
repo?: string;
};
export default function getRepoOwnerAndRepo(
repoFullName: string
): TRepoOwnerAndRepo {
if (!repoFullName) return {};
const [repoOwner, repo] = repoFullName.split('/');
return {
repoOwner,
repo,
};
}

View File

@@ -1,32 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import type { AxiosResponse } from 'axios';
import parseLinkHeader from '../../../helpers/parse-header-link';
type TResponse = {
data: IJSONObject[];
error?: IJSONObject;
};
export default async function paginateAll(
$: IGlobalVariable,
request: Promise<AxiosResponse>
) {
const response = await request;
const aggregatedResponse: TResponse = {
data: [...response.data],
};
let links = parseLinkHeader(response.headers.link);
while (links.next) {
const nextPageResponse = await $.http.request({
...response.config,
url: links.next.uri,
});
aggregatedResponse.data.push(...nextPageResponse.data);
links = parseLinkHeader(nextPageResponse.headers.link);
}
return aggregatedResponse;
}

View File

@@ -0,0 +1,13 @@
import { IJSONObject } from '@automatisch/types';
import ListRepos from './data/list-repos';
import ListBranches from './data/list-branches';
export default class Data {
listRepos: ListRepos;
listBranches: ListBranches;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.listRepos = new ListRepos(connectionData);
this.listBranches = new ListBranches(connectionData, parameters);
}
}

View File

@@ -0,0 +1,36 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class ListBranches {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
const branches = await this.client.paginate(this.client.rest.repos.listBranches, this.options);
return branches.map((branch) => ({
value: branch.name,
name: branch.name,
}));
}
}

View File

@@ -0,0 +1,23 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
export default class ListRepos {
client?: Octokit;
constructor(connectionData: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
}
async run() {
const repos = await this.client.paginate(this.client.rest.repos.listForAuthenticatedUser);
return repos.map((repo) => ({
value: repo.full_name,
name: repo.full_name,
}));
}
}

View File

@@ -1,4 +0,0 @@
import listLabels from './list-labels';
import listRepos from './list-repos';
export default [listLabels, listRepos];

View File

@@ -1,28 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo';
import paginateAll from '../../common/paginate-all';
export default {
name: 'List labels',
key: 'listLabels',
async run($: IGlobalVariable) {
const { repoOwner, repo } = getRepoOwnerAndRepo(
$.step.parameters.repo as string
);
if (!repo) return { data: [] };
const firstPageRequest = $.http.get(`/repos/${repoOwner}/${repo}/labels`);
const response = await paginateAll($, firstPageRequest);
response.data = response.data.map((repo: { name: string }) => {
return {
value: repo.name,
name: repo.name,
};
});
return response;
},
};

View File

@@ -1,21 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import paginateAll from '../../common/paginate-all';
export default {
name: 'List repos',
key: 'listRepos',
async run($: IGlobalVariable) {
const firstPageRequest = $.http.get('/user/repos');
const response = await paginateAll($, firstPageRequest);
response.data = response.data.map((repo: { full_name: string }) => {
return {
value: repo.full_name,
name: repo.full_name,
};
});
return response;
},
};

View File

@@ -1,22 +1,25 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import actions from './actions';
import dynamicData from './dynamic-data';
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import Authentication from './authentication';
import Triggers from './triggers';
import Data from './data';
export default defineApp({
name: 'Github',
key: 'github',
baseUrl: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
iconUrl: '{BASE_URL}/apps/github/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/github/connection',
primaryColor: '000000',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
triggers,
actions,
dynamicData,
});
export default class Github implements IService {
authenticationClient: IAuthentication;
triggers: Triggers;
data: Data;
constructor(
appData: IApp,
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.authenticationClient = new Authentication(appData, connectionData);
this.data = new Data(connectionData, parameters);
this.triggers = new Triggers(connectionData, parameters);
}
}

View File

@@ -0,0 +1,654 @@
{
"name": "Github",
"key": "github",
"iconUrl": "{BASE_URL}/apps/github/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/github",
"primaryColor": "000000",
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/github/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/github#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/github#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/github#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
],
"triggers": [
{
"name": "New repository",
"key": "newRepository",
"description": "Triggers when a new repository is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New organization",
"key": "newOrganization",
"description": "Triggers when a new organization is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New branch",
"key": "newBranch",
"description": "Triggers when a new branch is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New notification",
"key": "newNotification",
"description": "Triggers when a new notification is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": false,
"variables": false,
"description": "If blank, we will retrieve all notifications.",
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New pull request",
"key": "newPullRequest",
"description": "Triggers when a new pull request is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New watcher",
"key": "newWatcher",
"description": "Triggers when a new watcher is added to a repo",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New milestone",
"key": "newMilestone",
"description": "Triggers when a new milestone is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New commit comment",
"key": "newCommitComment",
"description": "Triggers when a new commit comment is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New label",
"key": "newLabel",
"description": "Triggers when a new label is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New collaborator",
"key": "newCollaborator",
"description": "Triggers when a new collaborator is added to a repo",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New release",
"key": "newRelease",
"description": "Triggers when a new release is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New commit",
"key": "newCommit",
"description": "Triggers when a new commit is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
},
{
"label": "Head",
"key": "head",
"type": "dropdown",
"description": "Branch to pull commits from. If unspecified, will use the repository's default branch (usually main or develop).",
"required": false,
"variables": false,
"dependsOn": ["parameters.repo"],
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listBranches"
},
{
"name": "parameters.repo",
"value": "{parameters.repo}"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
}
]
}

View File

@@ -0,0 +1,43 @@
import { IJSONObject } from '@automatisch/types';
import NewRepository from './triggers/new-repository';
import NewOrganization from './triggers/new-organization';
import NewBranch from './triggers/new-branch';
import NewNotification from './triggers/new-notification';
import NewPullRequest from './triggers/new-pull-request';
import NewWatcher from './triggers/new-watcher';
import NewMilestone from './triggers/new-milestone';
import NewCommit from './triggers/new-commit';
import NewCommitComment from './triggers/new-commit-comment';
import NewLabel from './triggers/new-label';
import NewCollaborator from './triggers/new-collaborator';
import NewRelease from './triggers/new-release';
export default class Triggers {
newRepository: NewRepository;
newOrganization: NewOrganization;
newBranch: NewBranch;
newNotification: NewNotification;
newPullRequest: NewPullRequest;
newWatcher: NewWatcher;
newMilestone: NewMilestone;
newCommit: NewCommit;
newCommitComment: NewCommitComment;
newLabel: NewLabel;
newCollaborator: NewCollaborator;
newRelease: NewRelease;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.newRepository = new NewRepository(connectionData);
this.newOrganization = new NewOrganization(connectionData);
this.newBranch = new NewBranch(connectionData, parameters);
this.newNotification = new NewNotification(connectionData, parameters);
this.newPullRequest = new NewPullRequest(connectionData, parameters);
this.newWatcher = new NewWatcher(connectionData, parameters);
this.newMilestone = new NewMilestone(connectionData, parameters);
this.newCommit = new NewCommit(connectionData, parameters);
this.newCommitComment = new NewCommitComment(connectionData, parameters);
this.newLabel = new NewLabel(connectionData, parameters);
this.newCollaborator = new NewCollaborator(connectionData, parameters);
this.newRelease = new NewRelease(connectionData, parameters);
}
}

View File

@@ -1,6 +0,0 @@
import newIssues from './new-issues';
import newPullRequests from './new-pull-requests';
import newStargazers from './new-stargazers';
import newWatchers from './new-watchers';
export default [newIssues, newPullRequests, newStargazers, newWatchers];

Some files were not shown because too many files have changed in this diff Show More