Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Google Content Safety API library (#18)

authored by

Bernardo Pires and committed by
GitHub
424ef491 d924892a

+144
+40
server/services/signalsService/signals/third_party_signals/google/content_safety/fetchUtils.ts
··· 1 + export async function fetchWithTimeout( 2 + resource: string, 3 + options: RequestInit, 4 + timeoutMs: number, 5 + ): Promise<Response> { 6 + const controller = new AbortController(); 7 + const id = setTimeout(() => controller.abort(), timeoutMs); 8 + 9 + try { 10 + const response = await fetch(resource, { 11 + ...options, 12 + signal: controller.signal, 13 + }); 14 + return response; 15 + } finally { 16 + clearTimeout(id); 17 + } 18 + } 19 + 20 + export async function fetchImage( 21 + url: string, 22 + timeoutMs: number, 23 + ): Promise<Buffer> { 24 + try { 25 + const response = await fetchWithTimeout(url, {method: 'GET'}, timeoutMs); 26 + 27 + if (!response.ok) { 28 + throw new Error( 29 + `Failed to fetch image: ${response.status} ${response.statusText}`, 30 + ); 31 + } 32 + 33 + const arrayBuffer = await response.arrayBuffer(); 34 + return Buffer.from(arrayBuffer); 35 + } catch (error: unknown) { 36 + const msg = error instanceof Error ? error.message : String(error); 37 + throw new Error(`Failed to download image for classification: ${msg}`); 38 + } 39 + } 40 +
+104
server/services/signalsService/signals/third_party_signals/google/content_safety/googleContentSafetyLib.ts
··· 1 + import {type ReadonlyDeep} from 'type-fest'; 2 + import {fetchWithTimeout} from './fetch_utils'; 3 + 4 + export const GOOGLE_CONTENT_SAFETY_PRIORITIES = [ 5 + 'VERY_LOW', 6 + 'LOW', 7 + 'MEDIUM', 8 + 'HIGH', 9 + 'VERY_HIGH', 10 + ] as const; 11 + 12 + export type GoogleContentSafetyPriority = 13 + (typeof GOOGLE_CONTENT_SAFETY_PRIORITIES)[number]; 14 + 15 + export interface GoogleContentSafetyOptions { 16 + apiKey: string; 17 + /** Timeout for requests to the API in milliseconds. */ 18 + timeoutMs?: number; 19 + } 20 + 21 + export interface ClassificationResult { 22 + reviewPriorities: GoogleContentSafetyPriority[]; 23 + modelVersion?: string; 24 + } 25 + 26 + export class GoogleContentSafetyClient { 27 + private readonly apiKey: string; 28 + private readonly timeoutMs: number; 29 + private readonly baseUrl = 30 + 'https://contentsafety.googleapis.com/v1beta1/images:classify'; 31 + 32 + constructor(options: GoogleContentSafetyOptions) { 33 + if (!options.apiKey) { 34 + throw new Error('Google Content Safety API key is required.'); 35 + } 36 + this.apiKey = options.apiKey; 37 + this.timeoutMs = options.timeoutMs ?? 10_000; 38 + } 39 + 40 + /** 41 + * Classifies a single raw image (Buffer or Uint8Array). 42 + * 43 + * Prefer `classifyImages` when classifying multiple images. 44 + */ 45 + public async classifyImage( 46 + image: Buffer | Uint8Array, 47 + ): Promise<GoogleContentSafetyPriority | undefined> { 48 + const priorities = await this.classifyImages([image]); 49 + return priorities[0]; 50 + } 51 + 52 + /** 53 + * Classifies a list of raw images (Buffer or Uint8Array). 54 + * 55 + * This method is preferred over calling `classifyImage` multiple times, as it 56 + * batches the images into a single API request. 57 + */ 58 + public async classifyImages( 59 + images: (Buffer | Uint8Array)[], 60 + ): Promise<ReadonlyDeep<GoogleContentSafetyPriority[]>> { 61 + const url = `${this.baseUrl}?key=${this.apiKey}`; 62 + 63 + const reqBody = { 64 + images: images.map((image) => { 65 + if (Buffer.isBuffer(image)) { 66 + return image.toString('base64'); 67 + } 68 + return Buffer.from(image).toString('base64'); 69 + }), 70 + }; 71 + 72 + try { 73 + const response = await fetchWithTimeout( 74 + url, 75 + { 76 + method: 'POST', 77 + headers: {'Content-Type': 'application/json'}, 78 + body: JSON.stringify(reqBody), 79 + }, 80 + this.timeoutMs, 81 + ); 82 + 83 + if (!response.ok) { 84 + throw new Error( 85 + `Google Content Safety API request failed with status ${response.status}: ${response.statusText}`, 86 + ); 87 + } 88 + 89 + const responseJson = (await response.json()) as { 90 + reviewPriorities: GoogleContentSafetyPriority[]; 91 + model_version: string; 92 + }; 93 + 94 + return responseJson.reviewPriorities; 95 + } catch (error: unknown) { 96 + // Map known errors or rethrow 97 + if (error instanceof Error) { 98 + throw new Error(`Google Content Safety API Error: ${error.message}`); 99 + } 100 + throw error; 101 + } 102 + } 103 + } 104 +