From fc4c4c2dc8781a7d77dabcec82a7d83643ff3520 Mon Sep 17 00:00:00 2001 From: Maksim Redzkin Date: Thu, 25 Jun 2026 12:45:26 +0300 Subject: [PATCH] Add searchJobs and fetchJob operations --- nodes/LinkedApi/LinkedApi.node.ts | 6 + .../LinkedApi/operations/webhook/FetchJob.ts | 16 ++ .../operations/webhook/SearchJobs.ts | 234 ++++++++++++++++++ nodes/LinkedApi/operations/webhook/index.ts | 2 + nodes/LinkedApi/shared/AvailableActions.ts | 14 ++ nodes/LinkedApi/shared/SharedParameters.ts | 10 + 6 files changed, 282 insertions(+) create mode 100644 nodes/LinkedApi/operations/webhook/FetchJob.ts create mode 100644 nodes/LinkedApi/operations/webhook/SearchJobs.ts diff --git a/nodes/LinkedApi/LinkedApi.node.ts b/nodes/LinkedApi/LinkedApi.node.ts index 658c84c..52bf2ba 100644 --- a/nodes/LinkedApi/LinkedApi.node.ts +++ b/nodes/LinkedApi/LinkedApi.node.ts @@ -26,6 +26,7 @@ import { FetchCompany, FetchPerson, FetchPost, + FetchJob, ReactToPost, RemoveConnection, RetrieveConnections, @@ -33,6 +34,7 @@ import { RetrievePerformance, SearchCompanies, SearchPeople, + SearchJobs, SendConnectionRequest, SendMessage, SyncConversation, @@ -61,6 +63,7 @@ const operations: Record = { fetchPerson: new FetchPerson(), fetchCompany: new FetchCompany(), fetchPost: new FetchPost(), + fetchJob: new FetchJob(), reactToPost: new ReactToPost(), removeConnection: new RemoveConnection(), retrieveConnections: new RetrieveConnections(), @@ -68,6 +71,7 @@ const operations: Record = { retrievePerformance: new RetrievePerformance(), searchCompanies: new SearchCompanies(), searchPeople: new SearchPeople(), + searchJobs: new SearchJobs(), sendConnectionRequest: new SendConnectionRequest(), sendMessage: new SendMessage(), syncConversation: new SyncConversation(), @@ -146,6 +150,7 @@ export class LinkedApi implements INodeType { ...operations.fetchPerson.operationFields, ...operations.fetchCompany.operationFields, ...operations.fetchPost.operationFields, + ...operations.fetchJob.operationFields, ...operations.reactToPost.operationFields, ...operations.removeConnection.operationFields, ...operations.retrieveConnections.operationFields, @@ -153,6 +158,7 @@ export class LinkedApi implements INodeType { ...operations.retrievePerformance.operationFields, ...operations.searchCompanies.operationFields, ...operations.searchPeople.operationFields, + ...operations.searchJobs.operationFields, ...operations.sendConnectionRequest.operationFields, ...operations.sendMessage.operationFields, ...operations.syncConversation.operationFields, diff --git a/nodes/LinkedApi/operations/webhook/FetchJob.ts b/nodes/LinkedApi/operations/webhook/FetchJob.ts new file mode 100644 index 0000000..9ae90f3 --- /dev/null +++ b/nodes/LinkedApi/operations/webhook/FetchJob.ts @@ -0,0 +1,16 @@ +import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow'; +import { createParameterWithDisplayOptions, jobUrlParameter } from '../../shared/SharedParameters'; +import { StandardLinkedApiOperation } from '../../shared/LinkedApiOperation'; +import { AVAILABLE_ACTION } from '../../shared/AvailableActions'; + +export class FetchJob extends StandardLinkedApiOperation { + operationName = AVAILABLE_ACTION.fetchJob; + + fields: INodeProperties[] = [createParameterWithDisplayOptions(jobUrlParameter, this.show)]; + + public body(context: IExecuteFunctions): Record { + return { + jobUrl: this.stringParameter(context, 'jobUrl'), + }; + } +} diff --git a/nodes/LinkedApi/operations/webhook/SearchJobs.ts b/nodes/LinkedApi/operations/webhook/SearchJobs.ts new file mode 100644 index 0000000..5223abc --- /dev/null +++ b/nodes/LinkedApi/operations/webhook/SearchJobs.ts @@ -0,0 +1,234 @@ +/* eslint-disable n8n-nodes-base/node-param-options-type-unsorted-items */ +/* eslint-disable n8n-nodes-base/node-param-multi-options-type-unsorted-items */ +import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow'; +import { + createParameterWithDisplayOptions, + searchTermParameter, + limitParameter, + customSearchUrlParameter, +} from '../../shared/SharedParameters'; +import { StandardLinkedApiOperation } from '../../shared/LinkedApiOperation'; +import { AVAILABLE_ACTION } from '../../shared/AvailableActions'; + +export class SearchJobs extends StandardLinkedApiOperation { + operationName = AVAILABLE_ACTION.searchJobs; + + fields: INodeProperties[] = [ + createParameterWithDisplayOptions( + { ...searchTermParameter, placeholder: 'product manager' }, + this.show, + ), + createParameterWithDisplayOptions(limitParameter, this.show), + { + displayName: 'Advanced Filter', + name: 'advancedFilter', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: this.show, + }, + options: [ + { + ...customSearchUrlParameter, + placeholder: 'https://www.linkedin.com/jobs/search/?keywords=product%20manager', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + placeholder: 'San Francisco, California, United States', + description: 'Free-form location to filter by', + }, + { + displayName: 'Date Posted', + name: 'datePosted', + type: 'options', + default: 'anyTime', + description: 'Filter by when the job was posted', + options: [ + { name: 'Any Time', value: 'anyTime' }, + { name: 'Past 24 Hours', value: 'past24Hours' }, + { name: 'Past Week', value: 'pastWeek' }, + { name: 'Past Month', value: 'pastMonth' }, + ], + }, + { + displayName: 'Experience Levels', + name: 'experienceLevels', + type: 'multiOptions', + default: [], + description: 'Filter by experience level', + options: [ + { name: 'Internship', value: 'internship' }, + { name: 'Entry Level', value: 'entryLevel' }, + { name: 'Associate', value: 'associate' }, + { name: 'Mid-Senior Level', value: 'midSeniorLevel' }, + { name: 'Director', value: 'director' }, + { name: 'Executive', value: 'executive' }, + ], + }, + { + displayName: 'Employment Types', + name: 'employmentTypes', + type: 'multiOptions', + default: [], + description: 'Filter by employment type', + options: [ + { name: 'Full Time', value: 'fullTime' }, + { name: 'Part Time', value: 'partTime' }, + { name: 'Contract', value: 'contract' }, + { name: 'Temporary', value: 'temporary' }, + { name: 'Volunteer', value: 'volunteer' }, + { name: 'Internship', value: 'internship' }, + { name: 'Other', value: 'other' }, + ], + }, + { + displayName: 'Workplace Types', + name: 'workplaceTypes', + type: 'multiOptions', + default: [], + description: 'Filter by workplace type', + options: [ + { name: 'On-Site', value: 'onSite' }, + { name: 'Remote', value: 'remote' }, + { name: 'Hybrid', value: 'hybrid' }, + ], + }, + { + displayName: 'Companies', + name: 'companies', + type: 'string', + default: '', + placeholder: 'Google; Microsoft', + description: 'Company names separated by semicolons', + }, + { + displayName: 'Industries', + name: 'industries', + type: 'string', + default: '', + placeholder: 'Software Development; Technology', + description: 'Industries separated by semicolons', + }, + { + displayName: 'Job Functions', + name: 'jobFunctions', + type: 'string', + default: '', + placeholder: 'Engineering; Product Management', + description: 'Job functions separated by semicolons', + }, + { + displayName: 'Easy Apply', + name: 'easyApply', + type: 'boolean', + default: false, + description: 'Whether to only return jobs with Easy Apply', + }, + { + displayName: 'Has Verifications', + name: 'hasVerifications', + type: 'boolean', + default: false, + description: 'Whether to only return jobs with verification signals', + }, + { + displayName: 'Under 10 Applicants', + name: 'under10Applicants', + type: 'boolean', + default: false, + description: 'Whether to only return jobs with fewer than 10 applicants', + }, + { + displayName: 'In Your Network', + name: 'inYourNetwork', + type: 'boolean', + default: false, + description: 'Whether to only return jobs from your network', + }, + { + displayName: 'Fair Chance Employer', + name: 'fairChanceEmployer', + type: 'boolean', + default: false, + description: 'Whether to only return fair chance employer jobs', + }, + ], + }, + ]; + + public body(context: IExecuteFunctions): Record { + const filter: Record = {}; + const advancedFilter = context.getNodeParameter('advancedFilter', this.itemIndex, {}) as { + customSearchUrl?: string; + location?: string; + datePosted?: string; + experienceLevels?: string[]; + employmentTypes?: string[]; + workplaceTypes?: string[]; + companies?: string; + industries?: string; + jobFunctions?: string; + easyApply?: boolean; + hasVerifications?: boolean; + under10Applicants?: boolean; + inYourNetwork?: boolean; + fairChanceEmployer?: boolean; + }; + + const { + location, + datePosted, + experienceLevels, + employmentTypes, + workplaceTypes, + companies, + industries, + jobFunctions, + easyApply, + hasVerifications, + under10Applicants, + inYourNetwork, + fairChanceEmployer, + } = advancedFilter; + + if (location) filter.location = location; + if (datePosted) filter.datePosted = datePosted; + if (experienceLevels && experienceLevels.length > 0) filter.experienceLevels = experienceLevels; + if (employmentTypes && employmentTypes.length > 0) filter.employmentTypes = employmentTypes; + if (workplaceTypes && workplaceTypes.length > 0) filter.workplaceTypes = workplaceTypes; + if (companies) { + filter.companies = companies + .split(';') + .map((s) => s.trim()) + .filter((s) => s); + } + if (industries) { + filter.industries = industries + .split(';') + .map((s) => s.trim()) + .filter((s) => s); + } + if (jobFunctions) { + filter.jobFunctions = jobFunctions + .split(';') + .map((s) => s.trim()) + .filter((s) => s); + } + if (easyApply) filter.easyApply = true; + if (hasVerifications) filter.hasVerifications = true; + if (under10Applicants) filter.under10Applicants = true; + if (inYourNetwork) filter.inYourNetwork = true; + if (fairChanceEmployer) filter.fairChanceEmployer = true; + + return { + term: this.stringParameter(context, 'searchTerm') || undefined, + limit: this.numberParameter(context, 'limit'), + customSearchUrl: advancedFilter.customSearchUrl || undefined, + filter, + }; + } +} diff --git a/nodes/LinkedApi/operations/webhook/index.ts b/nodes/LinkedApi/operations/webhook/index.ts index f1ce089..92b47ae 100644 --- a/nodes/LinkedApi/operations/webhook/index.ts +++ b/nodes/LinkedApi/operations/webhook/index.ts @@ -5,6 +5,7 @@ export * from './CustomWorkflow'; export * from './FetchCompany'; export * from './FetchPerson'; export * from './FetchPost'; +export * from './FetchJob'; export * from './NvFetchCompany'; export * from './NvFetchPerson'; export * from './NvSearchCompanies'; @@ -19,6 +20,7 @@ export * from './RetrievePerformance'; export * from './RetrieveSSI'; export * from './SearchCompanies'; export * from './SearchPeople'; +export * from './SearchJobs'; export * from './SendConnectionRequest'; export * from './SendMessage'; export * from './SyncConversation'; diff --git a/nodes/LinkedApi/shared/AvailableActions.ts b/nodes/LinkedApi/shared/AvailableActions.ts index 50b0e86..18be011 100644 --- a/nodes/LinkedApi/shared/AvailableActions.ts +++ b/nodes/LinkedApi/shared/AvailableActions.ts @@ -9,6 +9,7 @@ export const AVAILABLE_ACTION = { fetchCompany: 'fetchCompany', fetchPerson: 'fetchPerson', fetchPost: 'fetchPost', + fetchJob: 'fetchJob', reactToPost: 'reactToPost', removeConnection: 'removeConnection', retrieveConnections: 'retrieveConnections', @@ -17,6 +18,7 @@ export const AVAILABLE_ACTION = { retrieveSSI: 'retrieveSSI', searchCompanies: 'searchCompanies', searchPeople: 'searchPeople', + searchJobs: 'searchJobs', sendConnectionRequest: 'sendConnectionRequest', sendMessage: 'sendMessage', syncConversation: 'syncConversation', @@ -93,6 +95,12 @@ export const availableStandardOperations: INodeProperties = { description: 'Retrieve detailed information about a LinkedIn post', action: 'Fetch post', }, + { + name: 'Fetch Job', + value: AVAILABLE_ACTION.fetchJob, + description: 'Fetch detailed information about a LinkedIn job', + action: 'Fetch job', + }, { name: 'React to Post', value: AVAILABLE_ACTION.reactToPost, @@ -142,6 +150,12 @@ export const availableStandardOperations: INodeProperties = { description: 'Search for people on LinkedIn', action: 'Search people', }, + { + name: 'Search Jobs', + value: AVAILABLE_ACTION.searchJobs, + description: 'Search for jobs on LinkedIn', + action: 'Search jobs', + }, { name: 'Send Connection Request', value: AVAILABLE_ACTION.sendConnectionRequest, diff --git a/nodes/LinkedApi/shared/SharedParameters.ts b/nodes/LinkedApi/shared/SharedParameters.ts index 88d21d3..9799b91 100644 --- a/nodes/LinkedApi/shared/SharedParameters.ts +++ b/nodes/LinkedApi/shared/SharedParameters.ts @@ -385,6 +385,16 @@ export const postUrlParameter: INodeProperties = { description: 'LinkedIn URL of the post', }; +export const jobUrlParameter: INodeProperties = { + displayName: 'Job URL', + name: 'jobUrl', + type: 'string', + required: true, + default: '', + placeholder: 'https://www.linkedin.com/jobs/view/4416248954/', + description: 'LinkedIn URL of the job', +}; + export const commentTextParameter: INodeProperties = { displayName: 'Comment Text', name: 'commentText',