Skip to content

Commit

Permalink
Port the Client class
Browse files Browse the repository at this point in the history
  • Loading branch information
cedx committed Nov 7, 2024
1 parent 17d8052 commit 6ec7ad8
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 244 deletions.
90 changes: 90 additions & 0 deletions lib/client.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {Blog} from "./blog.js";
import {CheckResult} from "./check_result.js";
import {Comment} from "./comment.js";

/**
* Submits comments to the [Akismet](https://akismet.com) service.
*/
export class Client {

/**
* The Akismet API key.
*/
apiKey: string;

/**
* The base URL of the remote API endpoint.
*/
baseUrl: URL;

/**
* The front page or home URL of the instance making requests.
*/
blog: Blog;

/**
* Value indicating whether the client operates in test mode.
*/
isTest: boolean;

/**
* The user agent string to use when making requests.
*/
userAgent: string;

/**
* Creates a new client.
* @param apiKey The Akismet API key.
* @param blog The front page or home URL of the instance making requests.
* @param options An object providing values to initialize this instance.
*/
constructor(apiKey: string, blog: Blog, options?: ClientOptions);

/**
* Checks the specified comment against the service database, and returns a value indicating whether it is spam.
* @param comment The comment to be checked.
* @returns A value indicating whether the specified comment is spam.
*/
checkComment(comment: Comment): Promise<CheckResult>;

/**
* Submits the specified comment that was incorrectly marked as spam but should not have been.
* @param comment The comment to be submitted.
* @returns Resolves once the comment has been submitted.
*/
submitHam(comment: Comment): Promise<void>;

/**
* Submits the specified comment that was not marked as spam but should have been.
* @param comment The comment to be submitted.
* @returns Resolves once the comment has been submitted.
*/
submitSpam(comment: Comment): Promise<void>;

/**
* Checks the API key against the service database, and returns a value indicating whether it is valid.
* @returns `true` if the specified API key is valid, otherwise `false`.
*/
verifyKey(): Promise<boolean>;
}

/**
* Defines the options of a {@link Client} instance.
*/
export type ClientOptions = Partial<{

/**
* The base URL of the remote API endpoint.
*/
baseUrl: URL|string;

/**
* Value indicating whether the client operates in test mode.
*/
isTest: boolean;

/**
* The user agent string to use when making requests.
*/
userAgent: string;
}>;
12 changes: 6 additions & 6 deletions src/author.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export class Author

# Creates a new author from the specified JSON object.
@fromJson: (json) -> new @
email: if typeof json.comment_author_email == "string" then json.comment_author_email else ""
ipAddress: if typeof json.user_ip == "string" then json.user_ip else ""
name: if typeof json.comment_author == "string" then json.comment_author else ""
role: if typeof json.user_role == "string" then json.user_role else ""
url: if typeof json.comment_author_url == "string" then json.comment_author_url else ""
userAgent: if typeof json.user_agent == "string" then json.user_agent else ""
email: if typeof json.comment_author_email is "string" then json.comment_author_email else ""
ipAddress: if typeof json.user_ip is "string" then json.user_ip else ""
name: if typeof json.comment_author is "string" then json.comment_author else ""
role: if typeof json.user_role is "string" then json.user_role else ""
url: if typeof json.comment_author_url is "string" then json.comment_author_url else ""
userAgent: if typeof json.user_agent is "string" then json.user_agent else ""

# Returns a JSON representation of this object.
toJSON: ->
Expand Down
6 changes: 3 additions & 3 deletions src/blog.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export class Blog

# Creates a new blog from the specified JSON object.
@fromJson: (json) -> new @
charset: if typeof json.blog_charset == "string" then json.blog_charset else ""
languages: if typeof json.blog_lang == "string" then json.blog_lang.split(",").map (language) -> language.trim() else []
url: if typeof json.blog == "string" then json.blog else ""
charset: if typeof json.blog_charset is "string" then json.blog_charset else ""
languages: if typeof json.blog_lang is "string" then json.blog_lang.split(",").map (language) -> language.trim() else []
url: if typeof json.blog is "string" then json.blog else ""

# Returns a JSON representation of this object.
toJSON: ->
Expand Down
76 changes: 76 additions & 0 deletions src/client.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {CheckResult} from "./check_result.js"

# Submits comments to the [Akismet](https://akismet.com) service.
export class Client

# The response returned by the "submit-ham" and "submit-spam" endpoints when the outcome is a success.
@_success = "Thanks for making the web a better place."

# The package version.
@_version = "16.2.1"

# Creates a new client.
constructor: (apiKey, blog, options = {}) ->
{baseUrl = "https://rest.akismet.com"} = options
[nodeVersion] = process.version.slice(1).split "."
url = if baseUrl instanceof URL then baseUrl.href else baseUrl

# The Akismet API key.
@apiKey = apiKey

# The base URL of the remote API endpoint.
@baseUrl = new URL(if url.endsWith("/") then url else "#{url}/")

# The front page or home URL of the instance making requests.
@blog = blog

# Value indicating whether the client operates in test mode.
@isTest = options.isTest ? no

# The user agent string to use when making requests.
@userAgent = options.userAgent ? "Node.js/#{nodeVersion} | Akismet/#{Client._version}"

# Checks the specified comment against the service database, and returns a value indicating whether it is spam.
checkComment: (comment) ->
response = await @_fetch "1.1/comment-check", comment.toJSON()
switch
when await response.text() is "false" then CheckResult.ham
when response.headers.get("x-akismet-pro-tip") is "discard" then CheckResult.pervasiveSpam
else CheckResult.spam

# Submits the specified comment that was incorrectly marked as spam but should not have been.
submitHam: (comment) ->
response = await @_fetch "1.1/submit-ham", comment.toJSON()
throw Error "Invalid server response." unless await response.text() is Client._success

# Submits the specified comment that was not marked as spam but should have been.
submitSpam: (comment) ->
response = await @_fetch "1.1/submit-spam", comment.toJSON()
throw Error "Invalid server response." unless await response.text() is Client._success

# Checks the API key against the service database, and returns a value indicating whether it is valid.
verifyKey: ->
try
response = await @_fetch "1.1/verify-key"
await response.text() is "valid"
catch
no

# Queries the service by posting the specified fields to a given end point, and returns the response.
_fetch: (endpoint, fields = {}) ->
body = new URLSearchParams {@blog.toJSON()..., api_key: @apiKey}
body.set "is_test", "1" if @isTest

for [key, value] from Object.entries fields
unless Array.isArray value then body.set key, String(value)
else
index = 0
body.set "#{key}[#{index++}]", String(item) for item from value

response = await fetch new URL(endpoint, @baseUrl), {method: "POST", headers: {"user-agent": @userAgent}, body}
throw Error "#{response.status} #{response.statusText}" unless response.ok

{headers} = response
throw Error "#{headers.get "x-akismet-alert-code"} #{headers.get "x-akismet-alert-msg"}" if headers.has "x-akismet-alert-code"
throw Error headers.get "x-akismet-debug-help" if headers.has "x-akismet-debug-help"
response
157 changes: 0 additions & 157 deletions src/client.ts

This file was deleted.

16 changes: 9 additions & 7 deletions src/comment.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Author} from "./author.js"

# Represents a comment submitted by an author.
export class Comment

Expand Down Expand Up @@ -36,14 +38,14 @@ export class Comment
hasAuthor = Object.keys(json).filter((key) -> key.startsWith "comment_author" or key.startsWith "user").length > 0
new @
author: if hasAuthor then Author.fromJson json else null
content: if typeof json.comment_content == "string" then json.comment_content else ""
content: if typeof json.comment_content is "string" then json.comment_content else ""
context: if Array.isArray json.comment_context then json.comment_context else []
date: if typeof json.comment_date_gmt == "string" then new Date json.comment_date_gmt else null
permalink: if typeof json.permalink == "string" then json.permalink else ""
postModified: if typeof json.comment_post_modified_gmt == "string" then new Date json.comment_post_modified_gmt else null
recheckReason: if typeof json.recheck_reason == "string" then json.recheck_reason else ""
referrer: if typeof json.referrer == "string" then json.referrer else ""
type: if typeof json.comment_type == "string" then json.comment_type else ""
date: if typeof json.comment_date_gmt is "string" then new Date json.comment_date_gmt else null
permalink: if typeof json.permalink is "string" then json.permalink else ""
postModified: if typeof json.comment_post_modified_gmt is "string" then new Date json.comment_post_modified_gmt else null
recheckReason: if typeof json.recheck_reason is "string" then json.recheck_reason else ""
referrer: if typeof json.referrer is "string" then json.referrer else ""
type: if typeof json.comment_type is "string" then json.comment_type else ""

# Returns a JSON representation of this object.
toJSON: ->
Expand Down
4 changes: 2 additions & 2 deletions src/usage.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ export class Usage
# Creates a new usage from the specified JSON object.
@fromJson: (json) -> new @
limit: if Number.isInteger(json.limit) then json.limit else -1
percentage: if typeof json.percentage == "number" then json.percentage else 0
throttled: if typeof json.throttled == "boolean" then json.throttled else no
percentage: if typeof json.percentage is "number" then json.percentage else 0
throttled: if typeof json.throttled is "boolean" then json.throttled else no
usage: if Number.isInteger(json.usage) then json.usage else 0
Loading

0 comments on commit 6ec7ad8

Please sign in to comment.