get() as $source) { if (!preg_match('/^[a-zA-Z0-9_-]+$/D', $source->name)) throw new RuntimeException('invalid_source_name'); $row = ['name' => $source->name]; foreach (json_decode($source->file_real ?? '[]', true, 512, JSON_THROW_ON_ERROR) as $format => $file) { if (!in_array($format, ['v2ray', 'clash'], true)) continue; if (!preg_match('/^'.date('Ymd').'-(v2ray\.txt|clash\.yaml)$/D', $file)) throw new RuntimeException('stale_source_date:'.$source->name); $path = public_path('freenode/from/'.$source->name.'/'.$file); if (!is_file($path) || filemtime($path) < $cycle) throw new RuntimeException('stale_source_cycle:'.$source->name); $row[$format] = file_get_contents($path); } if (!isset($row['v2ray'], $row['clash'])) throw new RuntimeException('incomplete_source:'.$source->name); $sources[] = $row; } $sites = []; foreach (SiteMap::whereIn('group_code', ['fn_a', 'fn_b'])->where('status', 1)->get() as $site) { $config = json_decode($site->config ?? '[]', true, 512, JSON_THROW_ON_ERROR); $sites[] = ['code' => $site->code, 'clients' => $config['freenode_client'] ?? []]; } if (!$sources || !$sites) throw new RuntimeException('no_enabled_sources_or_sites'); $sites[] = ['code' => '_admin', 'clients' => ['v2ray', 'clash', 'singbox']]; $clashTemplate = \Symfony\Component\Yaml\Yaml::parseFile(public_path('freenode/template/clash.txt')); $validator = new NativeValidator(config('freenode.singbox_binary'), config('freenode.mihomo_binary'), $clashTemplate); $result = (new Builder())->build($sources, $sites, $validator); return self::store()->stage($result, date('Y-m-d'), (float) config('freenode.minimum_ratio', 0.5)); } }