mirror of
https://github.com/renbaoshuo/UOJ-Luogu-RemoteJudge.git
synced 2024-11-27 14:56:29 +00:00
feat: luogu problem basic info fetcher
This commit is contained in:
parent
ca821e1d62
commit
03cffac030
18
web/app/libs/uoj-html-lib.php
Normal file
18
web/app/libs/uoj-html-lib.php
Normal 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;
|
||||
}
|
75
web/app/libs/uoj-luogu-lib.php
Normal file
75
web/app/libs/uoj-luogu-lib.php
Normal 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']);
|
||||
}
|
5
web/app/libs/uoj-validate-lib.php
Normal file
5
web/app/libs/uoj-validate-lib.php
Normal 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
249
web/app/models/Curl.php
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user