244 lines
6.9 KiB
Go
244 lines
6.9 KiB
Go
// Package aliyun wraps Alibaba Cloud Green file moderation APIs.
|
|
package aliyun
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
|
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
|
green "github.com/alibabacloud-go/green-20220302/v3/client"
|
|
"github.com/alibabacloud-go/tea/dara"
|
|
|
|
"github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
const defaultDocumentService = "document_detection"
|
|
|
|
type GreenConfig struct {
|
|
AccessKeyID string
|
|
AccessKeySecret string
|
|
RegionID string
|
|
Endpoint string
|
|
DocumentService string
|
|
}
|
|
|
|
type OSSObject struct {
|
|
BucketName string
|
|
ObjectName string
|
|
RegionID string
|
|
DataID string
|
|
}
|
|
|
|
type SubmitResult struct {
|
|
ProviderTaskID string
|
|
RequestID string
|
|
}
|
|
|
|
type PollResult struct {
|
|
Status string
|
|
RiskLevel string
|
|
RiskLabels []string
|
|
Raw map[string]any
|
|
RequestID string
|
|
}
|
|
|
|
type GreenClient struct {
|
|
service string
|
|
client *green.Client
|
|
}
|
|
|
|
func NewGreenClient(conf GreenConfig) (*GreenClient, error) {
|
|
if strings.TrimSpace(conf.AccessKeyID) == "" {
|
|
return nil, errors.New("missing Access Key ID")
|
|
}
|
|
if strings.TrimSpace(conf.AccessKeySecret) == "" {
|
|
return nil, errors.New("missing Access Key Secret")
|
|
}
|
|
if strings.TrimSpace(conf.RegionID) == "" {
|
|
return nil, errors.New("missing Region ID")
|
|
}
|
|
service := strings.TrimSpace(conf.DocumentService)
|
|
if service == "" {
|
|
service = defaultDocumentService
|
|
}
|
|
clientConf := &openapi.Config{
|
|
AccessKeyId: dara.String(conf.AccessKeyID),
|
|
AccessKeySecret: dara.String(conf.AccessKeySecret),
|
|
RegionId: dara.String(conf.RegionID),
|
|
Protocol: dara.String("https"),
|
|
}
|
|
if endpoint := strings.TrimSpace(conf.Endpoint); endpoint != "" {
|
|
clientConf.Endpoint = dara.String(endpoint)
|
|
}
|
|
client, err := green.NewClient(clientConf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &GreenClient{service: service, client: client}, nil
|
|
}
|
|
|
|
func (c *GreenClient) SubmitOSSFile(ctx context.Context, object OSSObject) (*SubmitResult, error) {
|
|
params, err := buildOSSServiceParameters(object)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := c.client.FileModerationWithContext(ctx, &green.FileModerationRequest{
|
|
Service: dara.String(c.service),
|
|
ServiceParameters: dara.String(params),
|
|
}, &dara.RuntimeOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp == nil || resp.Body == nil || resp.Body.Code == nil || *resp.Body.Code != 200 || resp.Body.Data == nil || resp.Body.Data.TaskId == nil {
|
|
return nil, responseError("file moderation submit failed", responseCode(resp), responseMessage(resp))
|
|
}
|
|
return &SubmitResult{ProviderTaskID: *resp.Body.Data.TaskId, RequestID: responseRequestID(resp)}, nil
|
|
}
|
|
|
|
func (c *GreenClient) PollOSSFile(ctx context.Context, providerTaskID string) (*PollResult, error) {
|
|
params, err := json.Marshal(map[string]string{"taskId": strings.TrimSpace(providerTaskID)})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := c.client.DescribeFileModerationResultWithContext(ctx, &green.DescribeFileModerationResultRequest{
|
|
Service: dara.String(c.service),
|
|
ServiceParameters: dara.String(string(params)),
|
|
}, &dara.RuntimeOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp == nil || resp.Body == nil || resp.Body.Code == nil || *resp.Body.Code != 200 {
|
|
return nil, responseError("file moderation poll failed", describeResponseCode(resp), describeResponseMessage(resp))
|
|
}
|
|
raw := map[string]any{}
|
|
if data := resp.Body.Data; data != nil {
|
|
encoded, _ := json.Marshal(data)
|
|
json.Unmarshal(encoded, &raw)
|
|
riskLevel := strings.TrimSpace(dara.StringValue(data.RiskLevel))
|
|
return &PollResult{
|
|
Status: statusFromRiskLevel(riskLevel),
|
|
RiskLevel: riskLevel,
|
|
RiskLabels: labelsFromFileResult(data),
|
|
Raw: raw,
|
|
RequestID: describeResponseRequestID(resp),
|
|
}, nil
|
|
}
|
|
return &PollResult{Status: types.FileReviewStatusRunning, Raw: raw, RequestID: describeResponseRequestID(resp)}, nil
|
|
}
|
|
|
|
func buildOSSServiceParameters(object OSSObject) (string, error) {
|
|
if strings.TrimSpace(object.BucketName) == "" || strings.TrimSpace(object.ObjectName) == "" || strings.TrimSpace(object.RegionID) == "" {
|
|
return "", types.ErrMalformed
|
|
}
|
|
params := map[string]string{
|
|
"ossBucketName": strings.TrimSpace(object.BucketName),
|
|
"ossObjectName": strings.TrimSpace(object.ObjectName),
|
|
"ossRegionId": strings.TrimSpace(object.RegionID),
|
|
}
|
|
if dataID := strings.TrimSpace(object.DataID); dataID != "" {
|
|
params["dataId"] = dataID
|
|
}
|
|
encoded, err := json.Marshal(params)
|
|
return string(encoded), err
|
|
}
|
|
|
|
func statusFromRiskLevel(riskLevel string) string {
|
|
switch strings.ToLower(strings.TrimSpace(riskLevel)) {
|
|
case "":
|
|
return types.FileReviewStatusRunning
|
|
case "none", "low":
|
|
return types.FileReviewStatusClean
|
|
default:
|
|
return types.FileReviewStatusRisky
|
|
}
|
|
}
|
|
|
|
func labelsFromFileResult(data *green.DescribeFileModerationResultResponseBodyData) []string {
|
|
seen := map[string]bool{}
|
|
var labels []string
|
|
for _, page := range data.PageResult {
|
|
if page == nil {
|
|
continue
|
|
}
|
|
for _, image := range page.ImageResult {
|
|
if image == nil {
|
|
continue
|
|
}
|
|
for _, result := range image.LabelResult {
|
|
if result == nil {
|
|
continue
|
|
}
|
|
if label := strings.TrimSpace(dara.StringValue(result.Label)); label != "" && !seen[label] {
|
|
seen[label] = true
|
|
labels = append(labels, label)
|
|
}
|
|
}
|
|
}
|
|
for _, text := range page.TextResult {
|
|
if text == nil {
|
|
continue
|
|
}
|
|
for _, label := range strings.Split(dara.StringValue(text.Labels), ",") {
|
|
label = strings.TrimSpace(label)
|
|
if label != "" && !seen[label] {
|
|
seen[label] = true
|
|
labels = append(labels, label)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return labels
|
|
}
|
|
|
|
func responseError(prefix string, code int32, msg string) error {
|
|
if msg == "" {
|
|
return errors.New(prefix)
|
|
}
|
|
return errors.New(prefix + ": " + msg + " (" + strconv.FormatInt(int64(code), 10) + ")")
|
|
}
|
|
|
|
func responseCode(resp *green.FileModerationResponse) int32 {
|
|
if resp != nil && resp.Body != nil && resp.Body.Code != nil {
|
|
return *resp.Body.Code
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func responseMessage(resp *green.FileModerationResponse) string {
|
|
if resp != nil && resp.Body != nil && resp.Body.Message != nil {
|
|
return *resp.Body.Message
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func responseRequestID(resp *green.FileModerationResponse) string {
|
|
if resp != nil && resp.Body != nil && resp.Body.RequestId != nil {
|
|
return *resp.Body.RequestId
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func describeResponseCode(resp *green.DescribeFileModerationResultResponse) int32 {
|
|
if resp != nil && resp.Body != nil && resp.Body.Code != nil {
|
|
return *resp.Body.Code
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func describeResponseMessage(resp *green.DescribeFileModerationResultResponse) string {
|
|
if resp != nil && resp.Body != nil && resp.Body.Message != nil {
|
|
return *resp.Body.Message
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func describeResponseRequestID(resp *green.DescribeFileModerationResultResponse) string {
|
|
if resp != nil && resp.Body != nil && resp.Body.RequestId != nil {
|
|
return *resp.Body.RequestId
|
|
}
|
|
return ""
|
|
}
|