use integer score, add announcements

This commit is contained in:
pluja 2024-02-10 13:48:41 +01:00
parent d6e6592171
commit f0b85c80e3
4 changed files with 214 additions and 285 deletions

View File

@ -1,201 +1,6 @@
package database package database
type Attribute struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Rating int `json:"rating"`
}
type AttributeMap struct {
Attributes map[string]AttributeNew `json:"attributes"`
}
const ( const (
AttributeAccountNeeded = "account-needed" AttributeOpenSource = "l0p2hlenm8cm7jp"
AttributeAPIAvailable = "api" AttributeP2P = "5bcqjb2xzbel9g9"
AttributeBlockFundsIfFlagged = "blocks-if-flagged"
AttributeCustodialWallet = "custodial-wallet"
AttributeJavaScriptNeeded = "needs-js"
AttributeKYCForSomeFeatures = "kyc-features"
AttributeKYCIfObligedByLaw = "kyc-law"
AttributeMobileAppAvailable = "mobile-app"
AttributeNoCustomerSupport = "no-customer-support"
AttributeNoJavaScriptNeeded = "no-js"
AttributeNonCustodialWallet = "non-custodial"
AttributeNoPersonalInfoNeeded = "no-personal-info"
AttributeNoRegistrationNeeded = "no-registration"
AttributeOpenSource = "open-source"
AttributeP2P = "p2p"
AttributePartnersMayEnforceKYC = "partners-kyc"
AttributePrivateSourceCode = "private-code"
AttributeRefundNoKYC = "refunds-no-kyc"
AttributeRefundRequiresKYC = "refunds-kyc"
AttributeRiskPreventionSystem = "risk-prevention-system"
AttributeSellerWalletCustodial = "seller-wallet-custodial"
AttributeStrictNoKYCPolicy = "strict-no-kyc"
AttributeStrictNoLogPolicy = "strict-no-log"
AttributeTelegramBotAvailable = "telegram-bot"
AttributeToSNotScrapable = "tos-not-scrapable"
AttributeUnclearRefundPolicy = "unclear-refunds"
) )
var ServiceAttributes = AttributeMap{
Attributes: map[string]AttributeNew{
AttributeRiskPreventionSystem: {
Title: "Automated Risk-Prevention System",
Description: "This service scans transactions automatically in search for suspicious transactions. If the trade is flagged suspicious by the system, the user might be required a KYC procedure in order to complete the trade or get a refund.",
Rating: "bad",
},
AttributeKYCForSomeFeatures: {
Title: "KYC is mandatory to use some features",
Description: "Some features offered by this service need KYC for the user to use them.",
Rating: "bad",
},
AttributeKYCIfObligedByLaw: {
Title: "May require KYC/SOF by policy/law",
Description: "If obliged to do so by the law or in accordance with the service's internal policy, it may at any time introduce or amend mandatory identification / verification procedures and require the user to complete identification and/or verification and may also require to submit identification documents (KYC) or provide Source of Funds (SOF) information.",
Rating: "bad",
},
AttributeCustodialWallet: {
Title: "Custodial wallet",
Description: "A custodial wallet is a type of crypto wallet where a third party holds and manages the private keys to your wallet and your assets in custody. The custodian is responsible for safeguarding your funds, and you entrust them with your private keys. Custodial wallets are usually provided by centralized crypto exchanges like Coinbase or Binance. Using a custodial wallet requires a great deal of trust in the institution.",
Rating: "warn",
},
AttributeAccountNeeded: {
Title: "Account needed to use the service",
Description: "Users require creating and maintaining an account with the service in order to access and use its features. ",
Rating: "info",
},
AttributePrivateSourceCode: {
Title: "Source code is private",
Description: "The source code of the service is not publicly available. This means that the service cannot be audited by the community. This is not necessarily bad, but it is something to keep in mind.",
Rating: "info",
},
AttributeNoCustomerSupport: {
Title: "Poor or no customer support",
Description: "The service does not offer customer support, or the quality of service provided by the support team is inadequate or unsatisfactory.",
Rating: "warn",
},
AttributeBlockFundsIfFlagged: {
Title: "May block 'suspicious' transactions",
Description: "User funds may pass through an analysis system that may flag the source of the funds as suspicious. If this happens, the user may be refunded or may be required a KYC procedure.",
Rating: "warn",
},
AttributeUnclearRefundPolicy: {
Title: "Unclear refund policy",
Description: "This service has an unclear or nonexistent statement about how they manage user refunds.",
Rating: "warn",
},
AttributeSellerWalletCustodial: {
Title: "The seller wallet is custodial",
Description: "The seller wallet is custodial, which means that the seller does not have control over the funds. This is a common practice in P2P exchanges, where the service needs to have control over the funds in order to be able to refund the buyer if the trade enters a dispute.",
Rating: "warn",
},
AttributeRefundRequiresKYC: {
Title: "Refunds may require KYC",
Description: "In certain cases, the refund process of these services may require the completion of a Know Your Customer (KYC) procedure or the disclosure of personal information. Some services, such as aggregators, usually don't control the KYC procedures of their partners so they fall in this category.",
Rating: "warn",
},
AttributeToSNotScrapable: {
Title: "ToS page is not possible to scrape",
Description: "The ToS page has some mechanism that makes it impossible for KYCNOT.me to scrape its content. These methods include, for example, using a canvas and rendering the text on it.",
Rating: "warn",
},
AttributePartnersMayEnforceKYC: {
Title: "Partners may enforce KYC policies",
Description: "The service has partners that may enforce or implement KYC policies. This is common in aggregators, where the service does not control the KYC policies of their partners.",
Rating: "warn",
},
AttributeNonCustodialWallet: {
Title: "Non-custodial wallet",
Description: "A non-custodial wallet is a type of crypto wallet where the user holds and manages the private keys to the wallet and the assets in custody. The user is responsible for safeguarding their funds, and they are the only ones with access to their private keys. Using a non-custodial wallet requires no trust in any institution.",
Rating: "good",
},
AttributeOpenSource: {
Title: "Open source code",
Description: "The source code of the service is publicly available. This means that the service can be audited by the community. This is not necessarily good, but it is something to keep in mind.",
Rating: "good",
},
AttributeP2P: {
Title: "Peer to peer",
Description: "Peer-to-peer marketplaces are online platforms where people can trade goods, cryptocurrencies and services directly from each other, without the need for intermediaries like traditional retailers or service providers. In this case, the service is based on such type of market.",
Rating: "good",
},
AttributeNoRegistrationNeeded: {
Title: "No registration needed",
Description: "Users can access and use the service without creating an account. This enables a faster and more convenient user experience while also offering enhanced privacy and anonymity.",
Rating: "good",
},
AttributeNoPersonalInfoNeeded: {
Title: "No personal information needed",
Description: "Users can create an account with these services without having to provide any personal information such as their name, address, or identification documents. This offers a high degree of privacy and anonymity to users who prefer to keep their personal information private.",
Rating: "good",
},
AttributeStrictNoKYCPolicy: {
Title: "Strict no-KYC policy",
Description: "The service has a strict no-KYC policy, which means that it does not require users to complete any KYC procedure in order to access and use its features.",
Rating: "good",
},
AttributeRefundNoKYC: {
Title: "Refunds do not require KYC",
Description: "The refund process of these services does not require the completion of a Know Your Customer (KYC) procedure or the disclosure of personal information.",
Rating: "good",
},
AttributeStrictNoLogPolicy: {
Title: "Strict no-log policy",
Description: "The service has a strict no-log policy, which means that it does not collect or store any information about its users.",
Rating: "good",
},
AttributeMobileAppAvailable: {
Title: "Mobile app available",
Description: "The service has a mobile app available for download.",
Rating: "info",
},
AttributeNoJavaScriptNeeded: {
Title: "No JavaScript needed",
Description: "The service does not require the user to enable JavaScript in order to access and use its features.",
Rating: "info",
},
AttributeTelegramBotAvailable: {
Title: "Telegram bot available",
Description: "The service has a Telegram bot available.",
Rating: "info",
},
AttributeAPIAvailable: {
Title: "API available",
Description: "The service has an API available.",
Rating: "info",
},
AttributeJavaScriptNeeded: {
Title: "JavaScript needed",
Description: "The service requires the user to enable JavaScript in order to access and use its features.",
Rating: "info",
},
},
}

View File

@ -1,36 +1,36 @@
package database package database
type Service struct { type Service struct {
Category string `json:"category"` Category string `json:"category"`
CollectionID string `json:"collectionId"` CollectionID string `json:"collectionId"`
CollectionName string `json:"collectionName"` CollectionName string `json:"collectionName"`
Comments []string `json:"comments"` Comments []string `json:"comments"`
Created string `json:"created"` Created string `json:"created"`
Description string `json:"description"` Description string `json:"description"`
Expand map[string][]AttributeNew `json:"expand"` Expand map[string][]Attribute `json:"expand"`
Attributes []string `json:"attributes"` Attributes []string `json:"attributes"`
ID string `json:"id"` ID string `json:"id"`
KycLevel int `json:"kyc_level"` KycLevel int `json:"kyc_level"`
Listed bool `json:"listed"` Listed bool `json:"listed"`
LogoURL string `json:"logo_url"` LogoURL string `json:"logo_url"`
Name string `json:"name"` Name string `json:"name"`
OnionUrls []string `json:"onion_urls"` OnionUrls []string `json:"onion_urls"`
Pending bool `json:"pending"` Pending bool `json:"pending"`
Score string `json:"score"` Score int `json:"score"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
TosReviews []TosReview `json:"tos_reviews"` TosReviews []TosReview `json:"tos_reviews"`
LastTosReview string `json:"last_tos_review"` LastTosReview string `json:"last_tos_review"`
TosUrls []string `json:"tos_urls"` TosUrls []string `json:"tos_urls"`
Type string `json:"type"` Type string `json:"type"`
Referral string `json:"referral"` Referral string `json:"referral"`
Updated string `json:"updated"` Updated string `json:"updated"`
Urls []string `json:"urls"` Urls []string `json:"urls"`
Verified bool `json:"verified"` Verified bool `json:"verified"`
Xmr bool `json:"xmr"` Xmr bool `json:"xmr"`
Btc bool `json:"btc"` Btc bool `json:"btc"`
Fiat bool `json:"fiat"` Fiat bool `json:"fiat"`
Cash bool `json:"cash"` Cash bool `json:"cash"`
Lightning bool `json:"lightning"` Lightning bool `json:"lightning"`
} }
type TosReview struct { type TosReview struct {
@ -41,7 +41,7 @@ type TosReview struct {
Reference string `json:"reference"` Reference string `json:"reference"`
} }
type AttributeNew struct { type Attribute struct {
CollectionID string `json:"collectionId"` CollectionID string `json:"collectionId"`
CollectionName string `json:"collectionName"` CollectionName string `json:"collectionName"`
Created string `json:"created"` Created string `json:"created"`
@ -51,3 +51,11 @@ type AttributeNew struct {
Title string `json:"title"` Title string `json:"title"`
Updated string `json:"updated"` Updated string `json:"updated"`
} }
type Announcement struct {
Title string
Body string
Link string
Warning bool
Active bool
}

View File

@ -111,8 +111,8 @@ func (p *PbClient) UpdateService(id string, service Service) error {
return nil return nil
} }
func (p *PbClient) GetAttribute(id string) (*AttributeNew, error) { func (p *PbClient) GetAttribute(id string) (*Attribute, error) {
collection := pocketbase.CollectionSet[AttributeNew](p.Client, "attributes") collection := pocketbase.CollectionSet[Attribute](p.Client, "attributes")
response, err := collection.One(id) response, err := collection.One(id)
if err != nil { if err != nil {
@ -122,8 +122,8 @@ func (p *PbClient) GetAttribute(id string) (*AttributeNew, error) {
return &response, nil return &response, nil
} }
func (p *PbClient) GetAttributes(filters, sort string) ([]AttributeNew, error) { func (p *PbClient) GetAttributes(filters, sort string) ([]Attribute, error) {
collection := pocketbase.CollectionSet[AttributeNew](p.Client, "attributes") collection := pocketbase.CollectionSet[Attribute](p.Client, "attributes")
params := pocketbase.ParamsList{ params := pocketbase.ParamsList{
Page: 1, Page: 1,
Size: 250, Size: 250,
@ -145,8 +145,8 @@ func (p *PbClient) GetAttributes(filters, sort string) ([]AttributeNew, error) {
return response.Items, nil return response.Items, nil
} }
func (p *PbClient) CreateAttribute(attribute AttributeNew) error { func (p *PbClient) CreateAttribute(attribute Attribute) error {
collection := pocketbase.CollectionSet[AttributeNew](p.Client, "attributes") collection := pocketbase.CollectionSet[Attribute](p.Client, "attributes")
_, err := collection.Create(attribute) _, err := collection.Create(attribute)
if err != nil { if err != nil {
@ -155,3 +155,19 @@ func (p *PbClient) CreateAttribute(attribute AttributeNew) error {
return nil return nil
} }
func (p *PbClient) GetAnnouncement() *Announcement {
collection := pocketbase.CollectionSet[Announcement](p.Client, "announcements")
params := pocketbase.ParamsList{
Page: 1,
Size: 1,
Filters: "active=true",
}
response, err := collection.List(params)
if err != nil {
return nil
}
return &response.Items[0]
}

View File

@ -1,33 +1,47 @@
package utils package utils
import ( import (
"fmt"
"math" "math"
"github.com/rs/zerolog/log"
"pluja.dev/kycnot.me/database" "pluja.dev/kycnot.me/database"
) )
func ComputeScore(s *database.Service) string { func ComputeScore(s *database.Service) int {
const ( const (
baseScore = 100 baseScore = 10
goodRating = 0 maxScore = 10
warningRating = 10 minScore = 0
badRating = 20 bonusLow = 15
maxScore = 100 bonusMedium = 20
minScore = 0 bonusHigh = 25
bonusLow = 15
bonusMedium = 20
bonusHigh = 25
) )
grade := float64(baseScore) grade := float64(baseScore)
// Attribute Ratings // KYC Level Adjustment
switch s.KycLevel {
case 0:
grade = 8
case 1:
grade = 7
case 2:
grade = 6
case 3:
grade = 5
}
// Attributes
for _, attr := range s.Expand["attributes"] { for _, attr := range s.Expand["attributes"] {
switch attr.Rating { switch attr.Rating {
case "good":
grade += 0.1
case "warn": case "warn":
grade -= bonusLow grade -= 0.25
case "bad": case "bad":
grade -= bonusHigh grade -= 0.5
} }
} }
@ -36,65 +50,151 @@ func ComputeScore(s *database.Service) string {
s.TosReviews = []database.TosReview{} s.TosReviews = []database.TosReview{}
} }
trPenalty := 0 trPenalty := 0.0
for _, tr := range s.TosReviews { for _, tr := range s.TosReviews {
if tr.Warning { if tr.Warning {
trPenalty -= 2 trPenalty -= 0.1
} }
} }
if trPenalty > bonusHigh { if trPenalty > 3 {
trPenalty = bonusHigh trPenalty = 3
} }
grade += float64(trPenalty) grade += float64(trPenalty)
// Cash/Monero Bonus // Cash/Monero Bonus
if s.Cash || s.Xmr { if s.Cash || s.Xmr || s.Btc {
grade += bonusLow grade += 0.5
} if s.Xmr {
grade += 0.25
// KYC Level Adjustment }
switch s.KycLevel {
case 1:
grade -= 5
case 2:
grade -= 10
case 3:
grade -= 15
} }
// TODO: Manage bonuses // TODO: Manage bonuses
// P2P/OpenSource Bonus // P2P/OpenSource Bonus
/*if strings.Contains(s.Attributes, database.AttributeP2P) || strings.Contains(s.Attributes, database.AttributeOpenSource) { isP2P := false
grade += bonusLow isOpenSource := false
}*/ for _, attr := range s.Attributes {
if attr == database.AttributeP2P {
isP2P = true
}
if attr == database.AttributeOpenSource {
isOpenSource = true
}
}
// Tor URL Bonus // Tor URL Bonus
if len(s.OnionUrls) == 0 || s.OnionUrls[0] == "" { if len(s.OnionUrls) > 0 && s.OnionUrls[0] != "" {
grade -= bonusLow grade += 0.5
}
if s.Verified {
if grade < 7.5 {
grade += 0.5
}
}
if !isP2P && grade > 9.5 {
grade = 9
}
if isP2P || isOpenSource && grade < 8 {
grade += 0.5
} }
// Normalize the grade to be within 0-10 bounds // Normalize the grade to be within 0-10 bounds
grade = math.Min(maxScore, math.Max(minScore, grade)) grade = math.Min(maxScore, math.Max(minScore, grade))
switch { log.Debug().Msgf("Computed score for %s: %f", s.Name, math.RoundToEven(grade))
case grade >= 95:
return "A" return int(math.RoundToEven(grade))
case grade >= 85: }
return "A"
case grade >= 75: func ScoreSummary(s *database.Service) string {
return "B" summary := fmt.Sprintf("SCORE BREAKDOWN - %s:\n", s.Name)
case grade >= 65: const leftPad = 10 // Adjust this based on your longest line
return "B"
case grade >= 55: // KYC Level Adjustment
return "C" switch s.KycLevel {
case grade >= 45: case 0:
return "C" summary += fmt.Sprintf("\n%-*s--> base score for KYC Level 0", leftPad, "|| 8")
case grade >= 35: case 1:
return "D" summary += fmt.Sprintf("\n%-*s--> base score for KYC Level 1", leftPad, "|| 7")
default: case 2:
return "F" summary += fmt.Sprintf("\n%-*s--> base score for KYC Level 2", leftPad, "|| 6")
case 3:
summary += fmt.Sprintf("\n%-*s--> base score for KYC Level 3", leftPad, "|| 5")
} }
// Attributes
for _, attr := range s.Expand["attributes"] {
switch attr.Rating {
case "good":
summary += fmt.Sprintf("\n%-*s--> good attribute.", leftPad, "|| +0.1")
case "warn":
summary += fmt.Sprintf("\n%-*s--> warn attribute.", leftPad, "|| -0.25")
case "bad":
summary += fmt.Sprintf("\n%-*s--> bad attribute.", leftPad, "|| -0.5")
}
}
// Tos Highlights Penalty (Corrected to reflect actual penalty logic)
trPenalty := 0.0
wrns := 0
for _, tr := range s.TosReviews {
if tr.Warning {
trPenalty -= 0.1
wrns++
}
}
if trPenalty < -3 {
trPenalty = -3
}
if trPenalty != 0 {
summary += fmt.Sprintf("\n|| %-*f--> %d terms of service warnings.", leftPad, trPenalty, wrns)
}
// Cash/Monero Bonus
if s.Cash || s.Xmr || s.Btc {
summary += fmt.Sprintf("\n%-*s--> accepting Cash/BTC.", leftPad, "|| +0.5")
if s.Xmr {
summary += fmt.Sprintf("\n%-*s--> monero bonus.", leftPad, "|| +0.25")
}
}
// Tor URL Bonus
if len(s.OnionUrls) > 0 && s.OnionUrls[0] != "" {
summary += fmt.Sprintf("\n%-*s--> having a Tor URL.", leftPad, "|| +0.5")
}
// Verified Bonus
if s.Verified {
summary += fmt.Sprintf("\n%-*s--> being verified (applies only if score was below 7.5 at this point).", leftPad, "|| +0.5")
}
// P2P/OpenSource Bonus
// Note: Since the original ComputeScore doesn't show the exact bonus amount for P2P/OpenSource,
// it's assumed to follow the similar logic as in ComputeScore for simplicity.
isP2P := false
isOpenSource := false
for _, attr := range s.Attributes {
if attr == database.AttributeP2P {
isP2P = true
}
if attr == database.AttributeOpenSource {
isOpenSource = true
}
}
if isP2P || isOpenSource {
summary += fmt.Sprintf("\n%-*s--> P2P/OpenSource bonus (applies if score was below 8).", leftPad, "|| +0.5")
}
summary += "\n||"
summary += fmt.Sprintf("\n|| Total: %v", ComputeScore(s)) // Call ComputeScore to get the actual score
summary += "\n(final score is rounded to even number)"
return summary
} }
/*func UpdateScore(s *ent.Service) error { /*func UpdateScore(s *ent.Service) error {