184 lines
3.9 KiB
Go
184 lines
3.9 KiB
Go
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 ""
|
|
}
|