You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
16 KiB

1 year ago
  1. <?php
  2. namespace app\lib;
  3. use Exception;
  4. use ZipArchive;
  5. class Plugins
  6. {
  7. private static function get_btapi($os){
  8. if($os == 'Windows'){
  9. $bt_url = config_get('wbt_url');
  10. $bt_key = config_get('wbt_key');
  11. }else{
  12. $bt_url = config_get('bt_url');
  13. $bt_key = config_get('bt_key');
  14. }
  15. if(!$bt_url || !$bt_key) throw new Exception('请先配置好宝塔面板接口信息');
  16. $btapi = new Btapi($bt_url, $bt_key);
  17. return $btapi;
  18. }
  19. //刷新插件列表
  20. public static function refresh_plugin_list($os = 'Linux'){
  21. $btapi = self::get_btapi($os);
  22. $result = $btapi->get_plugin_list();
  23. if($result && isset($result['list']) && isset($result['type'])){
  24. if(empty($result['list']) || empty($result['type'])){
  25. throw new Exception('获取插件列表失败:插件列表为空');
  26. }
  27. self::save_plugin_list($result, $os);
  28. }else{
  29. throw new Exception('获取插件列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
  30. }
  31. }
  32. //保存插件列表
  33. private static function save_plugin_list($data, $os){
  34. $data['ip'] = '127.0.0.1';
  35. $data['serverid'] = '';
  36. $data['beta'] = 0;
  37. $data['uid'] = 1;
  38. $data['skey'] = '';
  39. $list = [];
  40. foreach($data['list'] as $plugin){
  41. if(isset($plugin['endtime'])) $plugin['endtime'] = 0;
  42. $list[] = $plugin;
  43. }
  44. $data['list'] = $list;
  45. $data['ltd'] = strtotime('+10 year');
  46. $json_file = get_data_dir($os).'config/plugin_list.json';
  47. if(!file_put_contents($json_file, json_encode($data))){
  48. throw new Exception('保存插件列表失败,文件无写入权限');
  49. }
  50. }
  51. //获取插件列表
  52. public static function get_plugin_list($os = 'Linux'){
  53. $json_file = get_data_dir($os).'config/plugin_list.json';
  54. if(file_exists($json_file)){
  55. $data = file_get_contents($json_file);
  56. $json_arr = json_decode($data, true);
  57. if($json_arr){
  58. return $json_arr;
  59. }
  60. }
  61. return false;
  62. }
  63. //获取一个插件信息
  64. public static function get_plugin_info($name, $os = 'Linux'){
  65. $json_arr = self::get_plugin_list($os);
  66. if(!$json_arr) return null;
  67. foreach($json_arr['list'] as $plugin){
  68. if($plugin['name'] == $name){
  69. return $plugin;
  70. }
  71. }
  72. return null;
  73. }
  74. //下载插件(自动判断是否第三方)
  75. public static function download_plugin($plugin_name, $version, $os = 'Linux'){
  76. $plugin_info = Plugins::get_plugin_info($plugin_name, $os);
  77. if(!$plugin_info) throw new Exception('未找到该插件信息');
  78. if($plugin_info['type'] == 10 && isset($plugin_info['versions'][0]['download'])){
  79. if($plugin_info['price'] == 0){
  80. $btapi = self::get_btapi($os);
  81. $btapi->create_plugin_other_order($plugin_info['id']);
  82. }
  83. $fname = $plugin_info['versions'][0]['download'];
  84. $filemd5 = $plugin_info['versions'][0]['md5'];
  85. Plugins::download_plugin_other($fname, $filemd5, $os);
  86. if(isset($plugin_info['min_image']) && strpos($plugin_info['min_image'], 'fname=')){
  87. $fname = substr($plugin_info['min_image'], strpos($plugin_info['min_image'], '?fname=')+7);
  88. Plugins::download_plugin_other($fname, null, $os);
  89. }
  90. }else{
  91. Plugins::download_plugin_package($plugin_name, $version, $os);
  92. }
  93. }
  94. //下载插件包
  95. public static function download_plugin_package($plugin_name, $version, $os = 'Linux'){
  96. $filepath = get_data_dir($os).'plugins/package/'.$plugin_name.'-'.$version.'.zip';
  97. $btapi = self::get_btapi($os);
  98. $result = $btapi->get_plugin_filename($plugin_name, $version);
  99. if($result && isset($result['status'])){
  100. if($result['status'] == true){
  101. $filename = $result['filename'];
  102. self::download_file($btapi, $filename, $filepath);
  103. if(file_exists($filepath)){
  104. $zip = new ZipArchive;
  105. if ($zip->open($filepath) === true)
  106. {
  107. $zip->extractTo(get_data_dir($os).'plugins/folder/'.$plugin_name.'-'.$version);
  108. $zip->close();
  109. $main_filepath = get_data_dir($os).'plugins/folder/'.$plugin_name.'-'.$version.'/'.$plugin_name.'/'.$plugin_name.'_main.py';
  110. if(file_exists($main_filepath) && filesize($main_filepath)>10){
  111. if(!strpos(file_get_contents($main_filepath), 'import ')){ //加密py文件,需要解密
  112. self::decode_plugin_main($plugin_name, $version, $main_filepath, $os);
  113. self::noauth_plugin_main($main_filepath);
  114. $zip->open($filepath, ZipArchive::CREATE);
  115. $zip->addFile($main_filepath, $plugin_name.'/'.$plugin_name.'_main.py');
  116. $zip->close();
  117. }
  118. }
  119. }else{
  120. throw new Exception('插件包解压缩失败');
  121. }
  122. return true;
  123. }else{
  124. throw new Exception('下载插件包失败,本地文件不存在');
  125. }
  126. }else{
  127. throw new Exception('下载插件包失败:'.($result['msg']?$result['msg']:'未知错误'));
  128. }
  129. }else{
  130. throw new Exception('下载插件包失败,接口返回错误');
  131. }
  132. }
  133. //下载插件主程序文件
  134. public static function download_plugin_main($plugin_name, $version, $os = 'Linux'){
  135. $filepath = get_data_dir($os).'plugins/main/'.$plugin_name.'-'.$version.'.dat';
  136. $btapi = self::get_btapi($os);
  137. $result = $btapi->get_plugin_main_filename($plugin_name, $version);
  138. if($result && isset($result['status'])){
  139. if($result['status'] == true){
  140. $filename = $result['filename'];
  141. self::download_file($btapi, $filename, $filepath);
  142. if(file_exists($filepath)){
  143. return true;
  144. }else{
  145. throw new Exception('下载插件主程序文件失败,本地文件不存在');
  146. }
  147. }else{
  148. throw new Exception('下载插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  149. }
  150. }else{
  151. throw new Exception('下载插件主程序文件失败,接口返回错误');
  152. }
  153. }
  154. //解密并下载插件主程序文件
  155. public static function decode_plugin_main($plugin_name, $version, $main_filepath, $os = 'Linux'){
  156. if(self::decode_plugin_main_local($main_filepath, $os)) return true;
  157. $btapi = self::get_btapi($os);
  158. $result = $btapi->get_decode_plugin_main($plugin_name, $version);
  159. if($result && isset($result['status'])){
  160. if($result['status'] == true){
  161. $filename = $result['filename'];
  162. self::download_file($btapi, $filename, $main_filepath);
  163. return true;
  164. }else{
  165. throw new Exception('解密插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  166. }
  167. }else{
  168. throw new Exception('解密插件主程序文件失败,接口返回错误');
  169. }
  170. }
  171. //本地解密插件主程序文件
  172. public static function decode_plugin_main_local($main_filepath, $os = 'Linux'){
  173. $btapi = self::get_btapi($os);
  174. $userinfo = $btapi->get_user_info();
  175. if(isset($userinfo['uid'])){
  176. $src = file_get_contents($main_filepath);
  177. if($src===false)throw new Exception('文件打开失败');
  178. if(!$src || strpos($src, 'import ')!==false)return true;
  179. $uid = $userinfo['uid'];
  180. $serverid = $userinfo['serverid'];
  181. $key = md5(substr($serverid, 10, 16).$uid.$serverid);
  182. $iv = md5($key.$serverid);
  183. $key = substr($key, 8, 16);
  184. $iv = substr($iv, 8, 16);
  185. $data_arr = explode("\n", $src);
  186. $de_text = '';
  187. foreach($data_arr as $data){
  188. $data = trim($data);
  189. if(!empty($data) && strlen($data)!=24){
  190. $tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
  191. if($tmp) $de_text .= $tmp;
  192. }
  193. }
  194. if(!empty($de_text) && strpos($de_text, 'import ')!==false){
  195. file_put_contents($main_filepath, $de_text);
  196. return true;
  197. }
  198. return false;
  199. }else{
  200. throw new Exception('解密插件主程序文件失败,获取用户信息失败');
  201. }
  202. }
  203. public static function decode_module_file($filepath){
  204. $src = file_get_contents($filepath);
  205. if($src===false)throw new Exception('文件打开失败');
  206. if(!$src || strpos($src, 'import ')!==false)return 0;
  207. $key = 'Z2B87NEAS2BkxTrh';
  208. $iv = 'WwadH66EGWpeeTT6';
  209. $data_arr = explode("\n", $src);
  210. $de_text = '';
  211. foreach($data_arr as $data){
  212. $data = trim($data);
  213. if(!empty($data)){
  214. $tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
  215. if($tmp) $de_text .= $tmp;
  216. }
  217. }
  218. if(!empty($de_text) && strpos($de_text, 'import ')!==false){
  219. file_put_contents($filepath, $de_text);
  220. return 1;
  221. }
  222. return 2;
  223. }
  224. //去除插件主程序文件授权校验
  225. public static function noauth_plugin_main($main_filepath){
  226. $data = file_get_contents($main_filepath);
  227. if(!$data) return false;
  228. $data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
  229. $data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
  230. $data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
  231. $data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
  232. $data = str_replace('\'http://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
  233. $data = str_replace('\'https://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
  234. $data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
  235. $data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
  236. $data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
  237. $data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
  238. $data = str_replace('\'http://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
  239. $data = str_replace('\'https://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
  240. file_put_contents($main_filepath, $data);
  241. }
  242. //下载插件其他文件
  243. public static function download_plugin_other($fname, $filemd5 = null, $os = 'Linux'){
  244. $filepath = get_data_dir().'plugins/other/'.$fname;
  245. @mkdir(dirname($filepath), 0777, true);
  246. $btapi = self::get_btapi($os);
  247. $result = $btapi->get_plugin_other_filename($fname);
  248. if($result && isset($result['status'])){
  249. if($result['status'] == true){
  250. $filename = $result['filename'];
  251. self::download_file($btapi, $filename, $filepath);
  252. if(file_exists($filepath)){
  253. if($filemd5 && md5_file($filepath) != $filemd5){
  254. $msg = filesize($filepath) < 300 ? file_get_contents($filepath) : '插件文件MD5校验失败';
  255. @unlink($filepath);
  256. throw new Exception($msg);
  257. }
  258. return true;
  259. }else{
  260. throw new Exception('下载插件文件失败,本地文件不存在');
  261. }
  262. }else{
  263. throw new Exception('下载插件文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  264. }
  265. }else{
  266. throw new Exception('下载插件文件失败,接口返回错误');
  267. }
  268. }
  269. //下载文件
  270. private static function download_file($btapi, $filename, $filepath){
  271. try{
  272. $btapi->download($filename, $filepath);
  273. }catch(Exception $e){
  274. @unlink($filepath);
  275. //宝塔bug小文件下载失败,改用base64下载
  276. $result = $btapi->get_file($filename);
  277. if($result && isset($result['status']) && $result['status']==true){
  278. $filedata = base64_decode($result['data']);
  279. if(strlen($filedata) < 4096 && substr($filedata,0,1)=='{' && substr($filedata,-1,1)=='}'){
  280. $arr = json_decode($filedata, true);
  281. if($arr){
  282. throw new Exception('获取文件失败:'.($arr['msg']?$arr['msg']:'未知错误'));
  283. }
  284. }
  285. if(!$filedata){
  286. throw new Exception('获取文件失败:文件内容为空');
  287. }
  288. file_put_contents($filepath, $filedata);
  289. }elseif($result){
  290. throw new Exception('获取文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  291. }else{
  292. throw new Exception('获取文件失败:未知错误');
  293. }
  294. }
  295. }
  296. //刷新一键部署列表
  297. public static function refresh_deplist($os = 'Linux'){
  298. $btapi = self::get_btapi($os);
  299. $result = $btapi->get_deplist();
  300. if($result && isset($result['list']) && isset($result['type'])){
  301. if(empty($result['list']) || empty($result['type'])){
  302. throw new Exception('获取一键部署列表失败:一键部署列表为空');
  303. }
  304. $json_file = get_data_dir($os).'config/deployment_list.json';
  305. if(!file_put_contents($json_file, json_encode($result))){
  306. throw new Exception('保存一键部署列表失败,文件无写入权限');
  307. }
  308. }else{
  309. throw new Exception('获取一键部署列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
  310. }
  311. }
  312. //获取一键部署列表
  313. public static function get_deplist($os = 'Linux'){
  314. $json_file = get_data_dir($os).'config/deployment_list.json';
  315. if(file_exists($json_file)){
  316. $data = file_get_contents($json_file);
  317. $json_arr = json_decode($data, true);
  318. if($json_arr){
  319. return $json_arr;
  320. }
  321. }
  322. return false;
  323. }
  324. }