first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import type { IExecuteFunctions, IDataObject, JsonObject, IRequestOptions } from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
export async function googleApiRequest(
|
||||
this: IExecuteFunctions,
|
||||
method: 'POST',
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
) {
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
body,
|
||||
uri: `https://commentanalyzer.googleapis.com${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.requestOAuth2.call(this, 'googlePerspectiveOAuth2Api', options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.googlePerspective",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Analytics", "Utility"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googleperspective/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Create a toxic language detector for Telegram in 4 step",
|
||||
"icon": "🤬",
|
||||
"url": "https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": ["Moderation"]
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
import ISO6391 from 'iso-639-1';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { googleApiRequest } from './GenericFunctions';
|
||||
import type {
|
||||
AttributesValuesUi,
|
||||
CommentAnalyzeBody,
|
||||
Language,
|
||||
RequestedAttributes,
|
||||
} from './types';
|
||||
|
||||
export class GooglePerspective implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Google Perspective',
|
||||
name: 'googlePerspective',
|
||||
icon: { light: 'file:googlePerspective.svg', dark: 'file:googlePerspective.dark.svg' },
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Consume Google Perspective API',
|
||||
subtitle: '={{$parameter["operation"]}}',
|
||||
defaults: {
|
||||
name: 'Google Perspective',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'googlePerspectiveOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Analyze Comment',
|
||||
value: 'analyzeComment',
|
||||
},
|
||||
],
|
||||
default: 'analyzeComment',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['analyzeComment'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Attributes to Analyze',
|
||||
name: 'requestedAttributesUi',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Atrribute',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['analyzeComment'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'requestedAttributesValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Attribute Name',
|
||||
name: 'attributeName',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Flirtation',
|
||||
value: 'flirtation',
|
||||
},
|
||||
{
|
||||
name: 'Identity Attack',
|
||||
value: 'identity_attack',
|
||||
},
|
||||
{
|
||||
name: 'Insult',
|
||||
value: 'insult',
|
||||
},
|
||||
{
|
||||
name: 'Profanity',
|
||||
value: 'profanity',
|
||||
},
|
||||
{
|
||||
name: 'Severe Toxicity',
|
||||
value: 'severe_toxicity',
|
||||
},
|
||||
{
|
||||
name: 'Sexually Explicit',
|
||||
value: 'sexually_explicit',
|
||||
},
|
||||
{
|
||||
name: 'Threat',
|
||||
value: 'threat',
|
||||
},
|
||||
{
|
||||
name: 'Toxicity',
|
||||
value: 'toxicity',
|
||||
},
|
||||
],
|
||||
description:
|
||||
'Attribute to analyze in the text. Details <a href="https://developers.perspectiveapi.com/s/about-the-api-attributes-and-languages">here</a>.',
|
||||
default: 'flirtation',
|
||||
},
|
||||
{
|
||||
displayName: 'Score Threshold',
|
||||
name: 'scoreThreshold',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberPrecision: 2,
|
||||
minValue: 0,
|
||||
maxValue: 1,
|
||||
},
|
||||
description:
|
||||
'Score above which to return results. At zero, all scores are returned.',
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['analyzeComment'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add option',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Language Name or ID',
|
||||
name: 'languages',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLanguages',
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Languages of the text input. If unspecified, the API will auto-detect the comment language. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available languages to display them to user so that they can
|
||||
// select them easily
|
||||
async getLanguages(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const supportedLanguages = [
|
||||
'English',
|
||||
'Spanish',
|
||||
'French',
|
||||
'German',
|
||||
'Portuguese',
|
||||
'Italian',
|
||||
'Russian',
|
||||
];
|
||||
|
||||
const languages = ISO6391.getAllNames().filter((language: string) =>
|
||||
supportedLanguages.includes(language),
|
||||
);
|
||||
for (const language of languages) {
|
||||
const languageName = language;
|
||||
const languageId = ISO6391.getCode(language);
|
||||
returnData.push({
|
||||
name: languageName,
|
||||
value: languageId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
if (operation === 'analyzeComment') {
|
||||
// https://developers.perspectiveapi.com/s/about-the-api-methods
|
||||
|
||||
const attributes = this.getNodeParameter(
|
||||
'requestedAttributesUi.requestedAttributesValues',
|
||||
i,
|
||||
[],
|
||||
) as AttributesValuesUi[];
|
||||
|
||||
if (!attributes.length) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Please enter at least one attribute to analyze.',
|
||||
{ itemIndex: i },
|
||||
);
|
||||
}
|
||||
|
||||
const requestedAttributes = attributes.reduce<RequestedAttributes>((acc, cur) => {
|
||||
return Object.assign(acc, {
|
||||
[cur.attributeName.toUpperCase()]: {
|
||||
scoreType: 'probability',
|
||||
scoreThreshold: cur.scoreThreshold,
|
||||
},
|
||||
});
|
||||
}, {});
|
||||
|
||||
const body: CommentAnalyzeBody = {
|
||||
comment: {
|
||||
type: 'PLAIN_TEXT',
|
||||
text: this.getNodeParameter('text', i) as string,
|
||||
},
|
||||
requestedAttributes,
|
||||
};
|
||||
|
||||
const { languages } = this.getNodeParameter('options', i) as { languages: Language };
|
||||
|
||||
if (languages?.length) {
|
||||
body.languages = languages;
|
||||
}
|
||||
|
||||
responseData = await googleApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
'/v1alpha1/comments:analyze',
|
||||
body,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as JsonObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 19.9564C40 24.9269 38.1538 29.7202 34.8195 33.4064C31.4852 37.0926 26.9006 39.409 21.9551 39.9061V0C26.9018 0.497246 31.4873 2.81456 34.8217 6.5022C38.1562 10.1898 40.0016 14.9848 40 19.9564ZM0 21.9685V39.9128H17.9477C17.4838 35.3111 15.4437 31.0111 12.173 27.7409C8.90223 24.4708 4.60184 22.4316 0 21.9685ZM0 17.9443H17.9477V0.00670706C13.3469 0.46952 9.04729 2.50781 5.77669 5.77658C2.5061 9.04534 0.465392 13.3438 0 17.9443Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 560 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 19.9564C40 24.9269 38.1538 29.7202 34.8195 33.4064C31.4852 37.0926 26.9006 39.409 21.9551 39.9061V0C26.9018 0.497246 31.4873 2.81456 34.8217 6.5022C38.1562 10.1898 40.0016 14.9848 40 19.9564ZM0 21.9685V39.9128H17.9477C17.4838 35.3111 15.4437 31.0111 12.173 27.7409C8.90223 24.4708 4.60184 22.4316 0 21.9685ZM0 17.9443H17.9477V0.00670706C13.3469 0.46952 9.04729 2.50781 5.77669 5.77658C2.5061 9.04534 0.465392 13.3438 0 17.9443Z" fill="#451735"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1,26 @@
|
||||
export type CommentAnalyzeBody = {
|
||||
comment: Comment;
|
||||
requestedAttributes: RequestedAttributes;
|
||||
languages?: Language;
|
||||
};
|
||||
|
||||
export type Language = 'de' | 'en' | 'fr' | 'ar' | 'es' | 'it' | 'pt' | 'ru';
|
||||
|
||||
export type Comment = {
|
||||
text?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export type RequestedAttributes = {
|
||||
[key: string]: {
|
||||
scoreType?: string;
|
||||
scoreThreshold?: {
|
||||
value: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type AttributesValuesUi = {
|
||||
attributeName: string;
|
||||
scoreThreshold: number;
|
||||
};
|
||||
Reference in New Issue
Block a user