1
0
mirror of https://github.com/renbaoshuo/UOJ-Luogu-RemoteJudge.git synced 2024-12-24 03:31:59 +00:00

feat: luogu problem basic info fetcher

This commit is contained in:
Baoshuo Ren 2023-03-19 21:29:15 +08:00
parent ca821e1d62
commit 03cffac030
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
4 changed files with 347 additions and 0 deletions

View File

@ -0,0 +1,18 @@
<?php
function renderMarkdown($content_md) {
$purifier = HTML::pruifier();
try {
$v8 = new V8Js();
$v8->content_md = $content_md;
$v8->executeString(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/js/marked.js'), 'marked.js');
$content = $v8->executeString('marked(PHP.content_md)');
} catch (V8JsException $e) {
throw new Exception('V8Js error: ' . $e->getMessage());
}
$content = $purifier->purify($content);
return $content;
}

View File

@ -0,0 +1,75 @@
<?php
define('LUOGU_BASE_URL', 'https://www.luogu.com.cn');
define('LUOGU_API_BASEURL', 'https://open-v1.lgapi.cn');
define('LUOGU_SUPPORTED_LANGUAGES', ['C', 'C++', 'C++11', 'Java8', 'Pascal', 'Python2', 'Python3']);
define('LUOGU_USER_AGENT', 'UniversalOJ/1.0 UOJ-Luogu-RemoteJudge/1.0 ( https://github.com/renbaoshuo/UOJ-Luogu-RemoteJudge )');
function parseLuoguProblemData($problem) {
if (!$problem) return null;
$statement = '';
if ($problem['background']) {
$statement .= "\n### 题目背景\n\n";
$statement .= $problem['background'] . "\n";
}
$statement .= "\n### 题目描述\n\n";
$statement .= $problem['description'] . "\n";
if (isset($problem['translation']) && $problem['translation']) {
$statement .= "\n### 题意翻译\n\n";
$statement .= $problem['translation'] . "\n";
}
$statement .= "\n### 输入格式\n\n";
$statement .= $problem['inputFormat'] . "\n";
$statement .= "\n### 输出格式\n\n";
$statement .= $problem['outputFormat'] . "\n";
$statement .= "\n### 输入输出样例\n\n";
foreach ($problem['samples'] as $id => $sample) {
$display_sample_id = $id + 1;
$statement .= "\n#### 样例输入 #{$display_sample_id}\n\n";
$statement .= "\n```text\n{$sample[0]}\n```\n\n";
$statement .= "\n#### 样例输出 #{$display_sample_id}\n\n";
$statement .= "\n```text\n{$sample[1]}\n```\n\n";
}
$statement .= "\n### 说明/提示\n\n";
$statement .= $problem['hint'] . "\n";
return [
'title' => "【洛谷 {$problem['pid']}{$problem['title']}",
'time_limit' => (float)max($problem['limits']['time']) / 1000.0,
'memory_limit' => (float)max($problem['limits']['memory']) / 1024.0,
'statement' => renderMarkdown($statement),
];
}
function fetchLuoguProblemBasicInfo($pid) {
// ensure validateLuoguProblemId($pid) is true
$curl = Curl::init();
$curl->set('CURLOPT_HTTPHEADER', [
'User-Agent: ' . LUOGU_USER_AGENT,
'Content-Type: application/json',
'Accept: application/json',
]);
$curl->url(LUOGU_BASE_URL . '/problem/' . $pid . '?_contentOnly=1');
if ($curl->error()) {
throw new Exception('Curl error: ' . $curl->message());
}
$data = json_decode($curl->data(), true);
return parseLuoguProblemData($data['currentData']['problem']);
}

View File

@ -0,0 +1,5 @@
<?php
function validateLuoguProblemId($str) {
return preg_match('/^P[1-9][0-9]{3,5}$/', $str);
}

249
web/app/models/Curl.php Normal file
View File

@ -0,0 +1,249 @@
<?php
// from https://github.com/wenpeng/curl
// requires php-curl module
use Exception;
class Curl {
private $post;
private $retry = 0;
private $custom = array();
private $option = array(
'CURLOPT_HEADER' => 0,
'CURLOPT_TIMEOUT' => 30,
'CURLOPT_ENCODING' => '',
'CURLOPT_IPRESOLVE' => 1,
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_SSL_VERIFYPEER' => false,
'CURLOPT_CONNECTTIMEOUT' => 10,
);
private $info;
private $data;
private $error;
private $message;
private static $instance;
/**
* Instance
* @return self
*/
public static function init() {
if (self::$instance === null) {
self::$instance = new self;
}
return self::$instance;
}
/**
* Task info
*
* @return array
*/
public function info() {
return $this->info;
}
/**
* Result Data
*
* @return string
*/
public function data() {
return $this->data;
}
/**
* Error status
*
* @return integer
*/
public function error() {
return $this->error;
}
/**
* Error message
*
* @return string
*/
public function message() {
return $this->message;
}
/**
* Set POST data
* @param array|string $data
* @param null|string $value
* @return self
*/
public function post($data, $value = null) {
if (is_array($data)) {
foreach ($data as $key => $val) {
$this->post[$key] = $val;
}
} else {
if ($value === null) {
$this->post = $data;
} else {
$this->post[$data] = $value;
}
}
return $this;
}
/**
* File upload
* @param string $field
* @param string $path
* @param string $type
* @param string $name
* @return self
*/
public function file($field, $path, $type, $name) {
$name = basename($name);
if (class_exists('CURLFile')) {
$this->set('CURLOPT_SAFE_UPLOAD', true);
$file = curl_file_create($path, $type, $name);
} else {
$file = "@{$path};type={$type};filename={$name}";
}
return $this->post($field, $file);
}
/**
* Save file
* @param string $path
* @return self
* @throws Exception
*/
public function save($path) {
if ($this->error) {
throw new Exception($this->message, $this->error);
}
$fp = @fopen($path, 'w');
if ($fp === false) {
throw new Exception('Failed to save the content', 500);
}
fwrite($fp, $this->data);
fclose($fp);
return $this;
}
/**
* Request URL
* @param string $url
* @return self
* @throws Exception
*/
public function url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
return $this->set('CURLOPT_URL', $url)->process();
}
throw new Exception('Target URL is required.', 500);
}
/**
* Set option
* @param array|string $item
* @param null|string $value
* @return self
*/
public function set($item, $value = null) {
if (is_array($item)) {
foreach ($item as $key => $val) {
$this->custom[$key] = $val;
}
} else {
$this->custom[$item] = $value;
}
return $this;
}
/**
* Set retry times
* @param int $times
* @return self
*/
public function retry($times = 0) {
$this->retry = $times;
return $this;
}
/**
* Task process
* @param int $retry
* @return self
*/
private function process($retry = 0) {
$ch = curl_init();
$option = array_merge($this->option, $this->custom);
foreach ($option as $key => $val) {
if (is_string($key)) {
$key = constant(strtoupper($key));
}
curl_setopt($ch, $key, $val);
}
if ($this->post) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->convert($this->post));
}
$this->data = (string)curl_exec($ch);
$this->info = curl_getinfo($ch);
$this->error = curl_errno($ch);
$this->message = $this->error ? curl_error($ch) : '';
curl_close($ch);
if ($this->error && $retry < $this->retry) {
$this->process($retry + 1);
}
$this->post = array();
$this->retry = 0;
return $this;
}
/**
* Convert array
* @param array $input
* @param string $pre
* @return array
*/
private function convert($input, $pre = null) {
if (is_array($input)) {
$output = array();
foreach ($input as $key => $value) {
$index = is_null($pre) ? $key : "{$pre}[{$key}]";
if (is_array($value)) {
$output = array_merge($output, $this->convert($value, $index));
} else {
$output[$index] = $value;
}
}
return $output;
}
return $input;
}
}