Add Go consent tool and Gitea build workflow
This commit is contained in:
183
VRWT_Tool/VRC_OSC/internal/consenttool/tool.go
Normal file
183
VRWT_Tool/VRC_OSC/internal/consenttool/tool.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package consenttool
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Run(configPath string, mode string) error {
|
||||
cfg, err := LoadConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseDir := filepath.Dir(configPath)
|
||||
|
||||
switch mode {
|
||||
case "generate-json":
|
||||
return generateJSON(cfg, baseDir)
|
||||
case "extract-log":
|
||||
return extractLog(cfg, baseDir)
|
||||
default:
|
||||
return fmt.Errorf("unknown mode %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func generateJSON(cfg *Config, baseDir string) error {
|
||||
inputPath := resolvePath(baseDir, cfg.Consent.InputCSV)
|
||||
outputPath := resolvePath(baseDir, cfg.Consent.OutputJSON)
|
||||
|
||||
rows, err := readCSVRows(inputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names := collectConsentedNames(rows, cfg.Consent)
|
||||
body, err := json.MarshalIndent(names, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(outputPath, append(body, '\n'), 0o644)
|
||||
}
|
||||
|
||||
func extractLog(cfg *Config, baseDir string) error {
|
||||
inputPath := resolvePath(baseDir, cfg.Log.InputPath)
|
||||
outputPath := resolvePath(baseDir, cfg.Log.OutputPath)
|
||||
|
||||
data, err := os.ReadFile(inputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(cfg.Log.IncludeRegex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var kept []string
|
||||
for _, line := range strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") {
|
||||
if re.MatchString(line) {
|
||||
kept = append(kept, line)
|
||||
continue
|
||||
}
|
||||
if cfg.Log.KeepNonMatch {
|
||||
kept = append(kept, line)
|
||||
}
|
||||
}
|
||||
|
||||
body := strings.Join(kept, "\n")
|
||||
if body != "" {
|
||||
body += "\n"
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(outputPath, []byte(body), 0o644)
|
||||
}
|
||||
|
||||
func readCSVRows(path string) ([]map[string]string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
r := csv.NewReader(file)
|
||||
headers, err := r.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, header := range headers {
|
||||
headers[i] = strings.TrimPrefix(strings.TrimSpace(header), "\ufeff")
|
||||
}
|
||||
|
||||
var rows []map[string]string
|
||||
for {
|
||||
record, err := r.Read()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
row := make(map[string]string, len(headers))
|
||||
for i, header := range headers {
|
||||
if i < len(record) {
|
||||
row[strings.TrimSpace(header)] = strings.TrimPrefix(strings.TrimSpace(record[i]), "\ufeff")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func collectConsentedNames(rows []map[string]string, cfg ConsentConfig) []string {
|
||||
displayFields := cfg.DisplayFields
|
||||
if len(displayFields) == 0 {
|
||||
displayFields = []string{"display_name", "vrchat_name", "name"}
|
||||
}
|
||||
consentFields := cfg.ConsentFields
|
||||
if len(consentFields) == 0 {
|
||||
consentFields = []string{"consent", "agree", "agreed", "checked"}
|
||||
}
|
||||
accepted := make(map[string]struct{})
|
||||
var names []string
|
||||
|
||||
for _, row := range rows {
|
||||
if !rowIsConsented(row, consentFields, cfg.ConsentValues) {
|
||||
continue
|
||||
}
|
||||
name := firstNonEmpty(row, displayFields)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
key := name
|
||||
if cfg.NormalizeCase {
|
||||
key = strings.ToLower(key)
|
||||
}
|
||||
if !cfg.AllowDuplicates {
|
||||
if _, ok := accepted[key]; ok {
|
||||
continue
|
||||
}
|
||||
accepted[key] = struct{}{}
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func rowIsConsented(row map[string]string, fields []string, values []string) bool {
|
||||
if len(values) == 0 {
|
||||
values = []string{"1", "true", "yes", "y", "on", "checked"}
|
||||
}
|
||||
allowed := make(map[string]struct{}, len(values))
|
||||
for _, v := range values {
|
||||
allowed[strings.ToLower(strings.TrimSpace(v))] = struct{}{}
|
||||
}
|
||||
for _, field := range fields {
|
||||
if v, ok := row[field]; ok {
|
||||
if _, ok := allowed[strings.ToLower(strings.TrimSpace(v))]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func firstNonEmpty(row map[string]string, fields []string) string {
|
||||
for _, field := range fields {
|
||||
if v := strings.TrimSpace(row[field]); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user