[realse] v1.0.0

This commit is contained in:
metowolf 2017-01-28 21:23:45 +08:00
parent 50fa1483be
commit 99cdf4a43f
7 changed files with 1456 additions and 0 deletions

215
Action.php Normal file
View File

@ -0,0 +1,215 @@
<?php
if(!defined('__TYPECHO_ROOT_DIR__'))exit;
if (!extension_loaded('Meting'))include_once 'include/Meting.php';
class Meting_Action extends Typecho_Widget implements Widget_Interface_Do {
public function execute(){}
public function action(){
$this->on($this->request->is('do=js'))->js();
$this->on($this->request->is('do=url'))->url();
$this->on($this->request->is('do=pic'))->pic();
$this->on($this->request->is('do=lrc'))->lrc();
$this->on($this->request->is('do=parse'))->shortcode();
}
private function shortcode(){
$urls=$this->request->get('data');
$urls=explode("\n",$urls);
echo "[Meting]\n";
foreach($urls as $url){
$url=trim($url);
if($url=="")continue;
$server='音乐平台';$id='编号';$type='类型';
if(strpos($url,'163.com')!==false){
$server='netease';
if(preg_match('/playlist\?id=(\d+)/i',$url,$id))list($id,$type)=array($id[1],'playlist');
elseif(preg_match('/album\?id=(\d+)/i',$url,$id))list($id,$type)=array($id[1],'album');
elseif(preg_match('/song\?id=(\d+)/i',$url,$id))list($id,$type)=array($id[1],'song');
elseif(preg_match('/artist\?id=(\d+)/i',$url,$id))list($id,$type)=array($id[1],'artist');
}
elseif(strpos($url,'qq.com')!==false){
$server='tencent';
if(preg_match('/playlist\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'playlist');
elseif(preg_match('/album\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'album');
elseif(preg_match('/song\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'song');
elseif(preg_match('/singer\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'artist');
}
elseif(strpos($url,'xiami.com')!==false){
$server='xiami';
if(preg_match('/collect\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'playlist');
elseif(preg_match('/album\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'album');
elseif(preg_match('/song\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'song');
elseif(preg_match('/artist\/([^\.]*)/i',$url,$id))list($id,$type)=array($id[1],'artist');
if(!preg_match('/^\d*$/i',$id,$t)){
$data=self::curl($url);
preg_match('/'.$type.'\/(\d+)/i',$data,$id);
$id=$id[1];
}
}
elseif(strpos($url,'kugou.com')!==false){
$server='kugou';
if(preg_match('/special\/single\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'playlist');
elseif(preg_match('/album\/single\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'album');
elseif(preg_match('/singer\/home\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'artist');
}
elseif(strpos($url,'baidu.com')!==false){
$server='baidu';
if(preg_match('/songlist\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'playlist');
elseif(preg_match('/album\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'album');
elseif(preg_match('/song\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'song');
elseif(preg_match('/artist\/(\d+)/i',$url,$id))list($id,$type)=array($id[1],'artist');
}
else list($id,$type)=array($url,'search');
echo '[Music server="'.$server.'" id="'.$id.'" type="'.$type.'"/]'."\n";
}
echo "[/Meting]";
}
private function curl($url){
$curl=curl_init();
curl_setopt($curl,CURLOPT_URL,$url);
curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,10);
curl_setopt($curl,CURLOPT_TIMEOUT,10);
curl_setopt($curl,CURLOPT_REFERER,$url);
$result=curl_exec($curl);
curl_close($curl);
return $result;
}
private function js(){
$PID=$this->request->get('id');
$data=$this->request->get('d');
$data=json_decode(base64_decode($data),1);
$setting=$this->request->get('s');
$setting=json_decode(base64_decode($setting),1);
$music=array();
foreach($data as $vo){
$EID=md5('js/'.$vo['server'].'/'.$vo['type'].'/'.$vo['id']);
$t=self::cacheRead($EID,60*60*24*3);
if(!$t){
$API=(new Meting($vo['server']))->format(true);
$t=call_user_func_array(array($API,$vo['type']),array($vo['id']));
$t=json_decode($t,1);
self::cacheWrite($EID,$t);
}
$music=array_merge($music,$t);
}
$player=array(
'theme' => $setting['theme']?:Typecho_Widget::widget('Widget_Options')->plugin('Meting')->theme?:'red',
'preload' => $setting['preload']?:Typecho_Widget::widget('Widget_Options')->plugin('Meting')->preload?:'auto',
'autoplay' => $setting['autoplay']?:Typecho_Widget::widget('Widget_Options')->plugin('Meting')->autoplay?:'false',
'height' => $setting['height']?:Typecho_Widget::widget('Widget_Options')->plugin('Meting')->height?:'340px',
'mode' => $setting['mode']?:Typecho_Widget::widget('Widget_Options')->plugin('Meting')->mode?:'circulation',
'music' => array(),
);
foreach($music as $vo){
$URI=Typecho_Common::url('MetingAPI?site='.$vo['source'],Helper::options()->index);
$player['music'][]=array(
'title'=>$vo['name'],
'author'=>implode(' / ',$vo['artist']),
'url'=>$URI.'&do=url&id='.$vo['url_id'],
'pic'=>$URI.'&do=pic&id='.$vo['pic_id'],
'lrc'=>$URI.'&do=lrc&id='.$vo['lyric_id'],
);
}
if(sizeof($player['music'])==1)$player['music']=$player['music'][0];
$player['music']=json_encode($player['music']);
header('content-type:application/javascript');
echo"
var Meting{$PID} = new APlayer({
element: document.getElementById('MetingPlayer'+{$PID}),
autoplay: ".$player['autoplay'].",
preload: \"".$player['preload']."\",
showlrc: 3,
mutex: true,
mode: '".$player['mode']."',
theme: \"".$player['theme']."\",
music: ".$player['music'].",
listmaxheight: '".$player['height']."',
});
";
}
private function url(){
$id=$this->request->get('id');
$site=$this->request->get('site');
$rate=Typecho_Widget::widget('Widget_Options')->plugin('Meting')->bitrate;
$cachekey="url/{$site}/{$id}";
$data=self::cacheRead($cachekey,60*15);
if(!$data){
$data=(new Meting($site))->url($id,$rate);
$data=json_decode($data,1);
self::cacheWrite($cachekey,$data);
}
if(empty($data['url']))$data['url']='https://oc1pe0tot.qnssl.com/copyright.m4a';
$this->response->redirect($data['url']);
}
private function pic(){
$id=$this->request->get('id');
$site=$this->request->get('site');
$cachekey="pic/{$site}/{$id}";
$data=self::cacheRead($cachekey,60*60*24*30);
if(!$data){
$data=(new Meting($site))->pic($id,90);
$data=json_decode($data,1);
self::cacheWrite($cachekey,$data);
}
$this->response->redirect($data['url']);
}
private function lrc(){
$id=$this->request->get('id');
$site=$this->request->get('site');
$cachekey="lrc/{$site}/{$id}";
$data=self::cacheRead($cachekey,60*60*24*10);
if(!$data){
$data=(new Meting($site))->lyric($id);
$data=json_decode($data,1);
self::cacheWrite($cachekey,$data);
}
switch($site){
case 'netease':$text=$data['lrc']['lyric'];break;
case 'tencent':$text=base64_decode($data['lyric']);break;
case 'xiami':$text=$data['lyric'];break;
case 'kugou':$text=$data['lyric'];break;
case 'baidu':$text=$data['lrcContent'];break;
}
if(strlen($text)==0)$text='[00:00.00]无歌词';
echo $text;
}
private function cacheWrite($k,$v){
if(!is_array($v))return;
$db=Typecho_Db::get();
$prefix=$db->getPrefix();
$insert=$db->insert($prefix.'meting')->rows(array('id'=>sha1($k),'value'=>serialize($v),'date'=>time()));
return $db->query($insert);
}
private function cacheRead($k,$t=60*60){
$db=Typecho_Db::get();
$prefix=$db->getPrefix();
$query= $db->select('value','date')->from($prefix.'meting')->where('id=?',sha1($k));
$result = $db->fetchAll($query);
if(sizeof($result)){
if(time()-$result[0]['date']>$t){
$delete = $db->delete('value','date')->from($prefix.'meting')->where('date<?',time()-$t);
$db->query($delete);
return false;
}
return unserialize($result[0]['value']);
}
else return false;
}
}

293
Plugin.php Normal file
View File

@ -0,0 +1,293 @@
<?php
if(!defined('__TYPECHO_ROOT_DIR__'))exit;
/**
* Meting for Typecho
*
* @package Meting
* @author METO
* @version 1.0.0
* @dependence 13.12.12-*
* @link https://github.com/metowolf/Meting
*
*/
define('METING_VERSION','1.0.0');
class Meting_Plugin extends Typecho_Widget implements Typecho_Plugin_Interface
{
protected static $PID = 0;
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function activate(){
Meting_Plugin::install();
Helper::addRoute("Meting_Route","/MetingAPI","Meting_Action",'action');
Typecho_Plugin::factory('Widget_Abstract_Contents')->filter =array('Meting_Plugin','playerFilter');
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx=array('Meting_Plugin','playerReplace');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx=array('Meting_Plugin','playerReplace');
Typecho_Plugin::factory('Widget_Archive')->header=array('Meting_Plugin','header');
Typecho_Plugin::factory('Widget_Archive')->footer=array('Meting_Plugin','footer');
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('Meting_Plugin', 'addButton');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('Meting_Plugin', 'addButton');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @static
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function deactivate(){
Meting_Plugin::uninstall();
Helper::removeRoute("Meting_Route");
}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form){
$t = new Typecho_Widget_Helper_Form_Element_Text(
'theme', null, '#e9e9e9',
_t('播放器颜色'),
_t('播放器默认的主题颜色,支持如 #372e21、#75c、red该设定会被[Meting]标签中的theme属性覆盖默认为 #e9e9e9'));
$form->addInput($t);
$t = new Typecho_Widget_Helper_Form_Element_Text(
'height', null, '340px',
_t('播放器列表最大高度'),
_t(''));
$form->addInput($t);
$t = new Typecho_Widget_Helper_Form_Element_Radio(
'autoplay', array('true'=>_t('是'),'false'=>_t('否')),'false',
_t('全局自动播放'),
_t(''));
$form->addInput($t);
$t = new Typecho_Widget_Helper_Form_Element_Radio(
'mode', array('circulation'=>_t('循环'),'single'=>_t('单曲'),'order'=>_t('列表'),'random'=>_t('随机')),'circulation',
_t('全局播放模式'),
_t(''));
$form->addInput($t);
$t= new Typecho_Widget_Helper_Form_Element_Radio(
'preload', array('auto'=>_t('自动'),'none'=>_t('不加载'),'metadata'=>_t('加载元数据')), 'auto',
_t('预加载属性'),
_t(''));
$form->addInput($t);
$t= new Typecho_Widget_Helper_Form_Element_Radio(
'bitrate', array('128'=>_t('流畅品质'),'192'=>_t('清晰品质'),'320'=>_t('高品质')), '192',
_t('默认音质'),
_t(''));
$form->addInput($t);
}
public static function personalConfig(Typecho_Widget_Helper_Form $form){}
public static function render(){}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function header(){
$dir=Helper::options()->pluginUrl.'/Meting/assets/';
echo "<!-- Meting ".METING_VERSION." Start -->\n<script type=\"text/javascript\" src=\"{$dir}APlayer.min.js\"></script>\n<!-- Meting End -->";
}
public static function footer(){}
public static function getPID(){
return ++self::$PID;
}
public static function playerFilter($data){
return $data;
}
public static function playerReplace($data,$widget,$last){
$text=$last?:$data;
if($widget instanceof Widget_Archive){
$data=$text;
$pattern=self::get_shortcode_regex(array('Meting'));
$text=preg_replace_callback("/$pattern/",array('Meting_Plugin','parseCallback'),$data);
}
return $text;
}
public static function parseCallback($matches){
$t=self::shortcode_parse_atts(htmlspecialchars_decode($matches[3]));
$setting=base64_encode(json_encode($t));
$matches[5]=htmlspecialchars_decode($matches[5]);
$pattern=self::get_shortcode_regex(array('Music'));
preg_match_all("/$pattern/",$matches[5],$all);
$data=array();
foreach($all[3] as $vo){
$t=self::shortcode_parse_atts(htmlspecialchars_decode($vo));
if(!in_array($t['server'],['netease','tencent','xiami','baidu','kugou']))continue;
if(!in_array($t['type'],['search','album','playlist','artist','song']))continue;
$data[]=$t;
}
$id=self::getPID();
$dir=Typecho_Common::url('MetingAPI',Helper::options()->index);
$data=base64_encode(json_encode($data));
return <<<EOF
<div id="MetingPlayer{$id}" class="aplayer" /></div>
<script type="text/javascript" src="{$dir}?do=js&s={$setting}&d={$data}&id={$id}" async defer></script>
EOF;
}
public static function addButton(){
$url=Typecho_Common::url('MetingAPI?do=parse',Helper::options()->index);
echo <<<EOF
<script type="text/javascript">
$(function() {
if($('#wmd-button-row').length>0)$('#wmd-button-row').append('<li class="wmd-spacer wmd-spacer1" id="wmd-spacer5"></li><li class="wmd-button" id="wmd-music-button" style="" title="插入音乐">音乐</li>');
$(document).on('click', '#wmd-music-button', function() {
$('body').append(
'<div id="MetingPanel">'+
'<div class="wmd-prompt-background" style="position: absolute; top: 0px; z-index: 1000; opacity: 0.5; height: 875px; left: 0px; width: 100%;"></div>'+
'<div class="wmd-prompt-dialog">'+
'<div>'+
'<p><b>插入音乐</b></p>'+
'<p>请在下方的输入框内输入要插入的音乐地址,如多个地址请用回车隔开'+
'<p><textarea type="text" rows="5" cols="40"></textarea></p>'+
'</div>'+
'<form>'+
'<button type="button" class="btn btn-s primary" id="ok">确定</button>'+
'<button type="button" class="btn btn-s" id="cancel">取消</button>'+
'</form>'+
'</div>'+
'</div>');
$('.wmd-prompt-dialog textarea').val('http://').select();
});
$(document).on('click','#cancel',function() {
$('#MetingPanel').remove();
$('textarea').focus();
});
$(document).on('click','#ok',function() {
callback=$.ajax({
type:'POST',
url:"{$url}",
data:{data:$('.wmd-prompt-dialog textarea').val()},
async:false
});
$('#MetingPanel').remove();
myField = document.getElementById('text');
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = callback.responseText;
myField.focus();
}
else if (myField.selectionStart || myField.selectionStart == '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
var cursorPos = startPos;
myField.value = myField.value.substring(0, startPos)
+ callback.responseText
+ myField.value.substring(endPos, myField.value.length);
cursorPos += callback.responseText.length;
myField.focus();
myField.selectionStart = cursorPos;
myField.selectionEnd = cursorPos;
}
else{
myField.value += callback.responseText;
myField.focus();
}
});
});
</script>
EOF;
}
# https://github.com/WordPress/WordPress/blob/master/wp-includes/shortcodes.php#L508
private static function shortcode_parse_atts($text) {
$atts = array();
$pattern = '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) {
foreach ($match as $m) {
if (!empty($m[1]))
$atts[strtolower($m[1])] = stripcslashes($m[2]);
elseif (!empty($m[3]))
$atts[strtolower($m[3])] = stripcslashes($m[4]);
elseif (!empty($m[5]))
$atts[strtolower($m[5])] = stripcslashes($m[6]);
elseif (isset($m[7]) && strlen($m[7]))
$atts[] = stripcslashes($m[7]);
elseif (isset($m[8]))
$atts[] = stripcslashes($m[8]);
}
foreach( $atts as &$value ) {
if ( false !== strpos( $value, '<' ) ) {
if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) {
$value = '';
}
}
}
} else {
$atts = ltrim($text);
}
return $atts;
}
# https://github.com/WordPress/WordPress/blob/master/wp-includes/shortcodes.php#L254
private static function get_shortcode_regex( $tagnames = null ) {
$tagregexp = join( '|', array_map('preg_quote', $tagnames) );
return '\[(\[?)('.$tagregexp.')(?![\w-])([^\]\/]*(?:\/(?!\])[^\]\/]*)*?)(?:(\/)\]|\](?:([^\[]*+(?:\[(?!\/\2\])[^\[]*+)*+)\[\/\2\])?)(\]?)';
}
public static function install(){
$db=Typecho_Db::get();
$prefix=$db->getPrefix();
$scripts=file_get_contents(__DIR__.'/include/install.sql');
$scripts=str_replace('typecho_',$prefix,$scripts);
$scripts=explode(';', $scripts);
try{
foreach($scripts as $script){
$script=trim($script);
if($script){
$db->query($script,Typecho_Db::WRITE);
}
}
}catch(Typecho_Db_Exception $e){
$code=$e->getCode();
if($code=='42S01')return;
throw new Typecho_Plugin_Exception('数据表建立失败,插件启用失败。错误号: '.$code);
}
}
public static function uninstall(){
$db=Typecho_Db::get();
$prefix=$db->getPrefix();
$scripts=file_get_contents(__DIR__.'/include/uninstall.sql');
$scripts=str_replace('typecho_',$prefix,$scripts);
$scripts=explode(';', $scripts);
try{
foreach($scripts as $script){
$script=trim($script);
if($script){
$db->query($script,Typecho_Db::WRITE);
}
}
}catch(Typecho_Db_Exception $e){
$code=$e->getCode();
if($code=='42S01')return;
throw new Typecho_Plugin_Exception('数据表清空失败,插件禁用失败。错误号: '.$code);
}
}
}

64
README.md Normal file
View File

@ -0,0 +1,64 @@
# Meting for Typecho
在 Typecho 中使用 APlayer 播放在线音乐吧~
[在线演示](http://demo.i-meto.com)
## 介绍
1. 支持国内五大音乐平台网易云、QQ、虾米、百度、酷狗的单曲/专辑/歌单播放
2. 简单快捷,复制音乐详情页面网址,后台自动生成播放代码
3. **支持不同音乐平台歌曲混合播放**
4. 前端 Aplayer后端 Meting 及时更新,保证兼容性及 API 高可用性
## 声明
本作品仅供个人学习研究使用,请勿将其用作商业用途。
**!!切勿使用本插件代码下载版权保护音乐!!**
## 安装
1. 在本页面右上角点击 Download ZIP 下载压缩包
2. 上传到 /usr/plugins 目录
3. **修改文件夹名为 Meting**
4. 后台启用插件
## 使用
在文章编辑页面,点击编辑器上的 **音乐** 按钮,在弹出的窗口中输入音乐地址(见支持列表),最后点击确定即可
**歌曲混合特性:** 允许添加多个音乐地址,每行一个,插件会自动将所有歌曲合并在同一个歌单进行播放。
## 支持列表
网易云音乐 http://music.163.com
- 单曲 http://music.163.com/#/song?id=424474911
- 专辑 http://music.163.com/#/album?id=34808540
- 歌手 http://music.163.com/#/artist?id=3681
- 歌单 http://music.163.com/#/playlist?id=436843836
QQ 音乐 http://y.qq.com
- 单曲 https://y.qq.com/portal/song/000jDQWP4JiB3y.html
- 专辑 https://y.qq.com/portal/album/003rytri2FHG3V.html
- 歌手 https://y.qq.com/portal/singer/003Nz2So3XXYek.html
- 歌单 https://y.qq.com/portal/playlist/1144188779.html
虾米音乐 http://www.xiami.com or http://h.xiami.com
- 单曲 http://www.xiami.com/song/bf08DNT3035f
- 专辑 http://www.xiami.com/album/ddOZW6a10eb
- 歌手 http://www.xiami.com/artist/be6yda0f8
- 歌单 http://www.xiami.com/collect/254478782
酷狗音乐 http://www.kugou.com
- 单曲 暂不支持直接解析,可直接修改短代码实现
- 专辑 http://www.kugou.com/yy/album/single/1645030.html
- 歌手 http://www.kugou.com/yy/singer/home/3520.html
- 歌单 http://www.kugou.com/yy/special/single/119859.html
百度音乐 http://music.baidu.com/
- 单曲 http://music.baidu.com/song/268275324
- 专辑 http://music.baidu.com/album/268275533
- 歌手 http://music.baidu.com/artist/1219
- 歌单 http://music.baidu.com/songlist/364201689
## FAQ
Q: 如何清除歌单、歌词缓存?
A: 为了减少服务器压力,插件设置对歌单、歌词数据进行缓存,缓存会根据时间周期自动更新管理,无需人工干预。**如果需要强制清除,可以通过禁用再启用插件实现,不影响文章中歌曲信息**
...
更多问题可以通过 issue 页面提交,或者通过 Telegram、邮件向我反馈
## LICENSE
Meting-Typecho-Plugin is under the MIT license.

3
assets/APlayer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

874
include/Meting.php Normal file
View File

@ -0,0 +1,874 @@
<?php
/*!
* Meting music framework
* https://i-meto.com
* Version 1.0.1
*
* Copyright 2016, METO Sheel <i@i-meto.com>
* Released under the MIT license
*/
class Meting
{
protected $_SITE;
protected $_TEMP;
protected $_FORMAT = false;
function __construct($v='netease'){
self::site($v);
}
public function site($v){
$this->_SITE=$v;
}
public function format($v = true){
$this->_FORMAT=$v;
return $this;
}
private function curl($API){
if(isset($API['encode']))$API=call_user_func_array(array($this,$API['encode']),array($API));
$BASE=self::curlset();
$curl=curl_init();
if($API['method']=='POST'){
if(is_array($API['body']))$API['body']=http_build_query($API['body']);
curl_setopt($curl,CURLOPT_URL,$API['url']);
curl_setopt($curl,CURLOPT_POSTFIELDS,$API['body']);
curl_setopt($curl,CURLOPT_POST,1);
}
elseif($API['method']=='GET'){
if(isset($API['body']))$API['url']=$API['url'].'?'.http_build_query($API['body']);
curl_setopt($curl,CURLOPT_URL,$API['url']);
}
curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,10);
curl_setopt($curl,CURLOPT_TIMEOUT,10);
curl_setopt($curl,CURLOPT_COOKIE,$BASE['cookie']);
curl_setopt($curl,CURLOPT_REFERER,$BASE['referer']);
curl_setopt($curl,CURLOPT_USERAGENT,$BASE['useragent']);
$result=curl_exec($curl);
curl_close($curl);
if(isset($API['decode']))$result=call_user_func_array(array($this,$API['decode']),array($result));
if($this->_FORMAT){
if(isset($API['format'])){
$result=json_decode($result,1);
$result=self::clean($result,$API['format']);
$result=json_encode($result);
}
$this->_FORMAT=false;
}
return $result;
}
private function pickup($array,$rule){
$t=explode('#',$rule);
foreach($t as $vo){
if($array==null)return null;
$array=$array[$vo];
}
return $array;
}
private function clean($raw,$rule){
if(!empty($rule))$raw=self::pickup($raw,$rule);
if($raw==null)$raw=array();
elseif(!isset($raw[0]))$raw=array($raw);
$result=array_map(array($this,'format_'.$this->_SITE),$raw);
return $result;
}
public function search($keyword,$page=1,$limit=30){
$API=array(
'netease' => array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'POST',
'params' => array(
's' => $keyword,
'type' => 1,
'limit' => $limit,
'total' => 'true',
'offset' => $page-1,
),
'url' => 'http://music.163.com/api/cloudsearch/pc',
),
'encode' => 'netease_AESECB',
'format' => 'result#songs',
),
'tencent' => array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/soso/fcgi-bin/search_cp',
'body' => array(
'p' => $page,
'n' => $limit,
'w' => $keyword,
'aggr' => 1,
'lossless' => 1,
'cr' => 1,
),
'decode' => 'jsonp2json',
'format' => 'data#song#list',
),
'xiami' => array(
'method' => 'GET',
'url' => 'http://api.xiami.com/web',
'body' => array(
'v' => '2.0',
'app_key' => '1',
'key' => $keyword,
'page' => $page,
'limit' => $limit,
'r' => 'search/songs',
),
'format' => 'data#songs',
),
'kugou' => array(
'method' => 'GET',
'url' => 'http://mobilecdn.kugou.com/api/v3/search/song',
'body' => array(
'iscorrect' => 1,
'pagesize' => $limit,
'plat' => 20,
'sver' => 3,
'showtype' => 14,
'page' => $page,
'keyword' => $keyword,
),
'format' => 'data#info',
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://tingapi.ting.baidu.com/v1/restserver/ting',
'body' => array(
'method' => 'baidu.ting.search.merge',
'isNew' => 1,
'query' => $keyword,
'page_size' => $limit,
'page_no' => $page,
'type' => 0,
'format' => 'json',
'from' => 'ios',
'channel' => '(null)',
'cuid' => 'appstore',
'from' => 'ios',
'version' => '5.9.5',
),
'format' => 'result#song_info#song_list',
),
);
return self::curl($API[$this->_SITE]);
}
public function song($id){
$API=array(
'netease' => array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'POST',
'params' => array(
'c' => '[{"id":'.$id.'}]',
),
'url' => 'http://music.163.com/api/v3/song/detail',
),
'encode' => 'netease_AESECB',
'format' => 'songs',
),
'tencent'=>array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg',
'body' => array(
'songmid' => $id,
'format' => 'json',
),
'decode' => 'tencent_singlesong',
'format' => 'data',
),
'xiami'=>array(
'method' => 'GET',
'url' => 'http://api.xiami.com/web',
'body' => array(
'v' => '2.0',
'app_key' => '1',
'id' => $id,
'r' => 'song/detail',
),
'format' => 'data#song',
),
'kugou'=>array(
'method' => 'POST',
'url' => 'http://m.kugou.com/app/i/getSongInfo.php?',
'body' => array(
"cmd" => "playInfo",
"hash" => $id,
"from" => "mkugou",
),
'format' => '',
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://tingapi.ting.baidu.com/v1/restserver/ting',
'body' => array(
'method' => 'baidu.ting.song.play',
'songid' => $id,
'format' => 'json',
'from' => 'ios',
'channel' => '(null)',
'cuid' => 'appstore',
'from' => 'ios',
'version' => '5.9.5',
),
'format' => 'songinfo',
),
);
return self::curl($API[$this->_SITE]);
}
public function album($id){
$API=array(
'netease'=>array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'GET',
'params' => array(
'id' => $id,
),
'url' => 'http://music.163.com/api/v1/album/'.$id,
),
'encode' => 'netease_AESECB',
'format' => 'songs',
),
'tencent'=>array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg',
'body' => array(
'albummid' => $id,
),
'format' => 'data#list',
),
'xiami'=>array(
'method' => 'GET',
'url' => 'http://api.xiami.com/web',
'body' => array(
'v' => '2.0',
'app_key' => '1',
'id' => $id,
'r' => 'album/detail',
),
'format' => 'data#songs',
),
'kugou'=>array(
'method' => 'GET',
'url' => 'http://mobilecdn.kugou.com/api/v3/album/song',
'body' => array(
'albumid' => $id,
'plat' => 2,
'page' => 1,
'pagesize' => -1,
'version' => 8400,
),
'format' => 'data#info',
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://tingapi.ting.baidu.com/v1/restserver/ting',
'body' => array(
'method' => 'baidu.ting.album.getAlbumInfo',
'album_id' => $id,
'format' => 'json',
'from' => 'ios',
'channel' => '(null)',
'cuid' => 'appstore',
'from' => 'ios',
'version' => '5.9.5',
),
'format' => 'songlist',
),
);
return self::curl($API[$this->_SITE]);
}
public function artist($id,$limit=50){
$API=array(
'netease'=>array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'GET',
'params' => array(
'top' => $limit,
"id" => $id,
"ext" => "true",
),
'url' => 'http://music.163.com/api/v1/artist/'.$id,
),
'encode' => 'netease_AESECB',
'format' => 'hotSongs',
),
'tencent'=>array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg',
'body' => array(
'singermid' => $id,
'begin' => 0,
'num' => $limit,
),
'format' => 'data#list',
),
'xiami' => array(
'method' => 'GET',
'url' => 'http://api.xiami.com/web',
'body' => array(
'v' => '2.0',
'app_key' => '1',
'id' => $id,
'limit' => $limit,
'page' => 1,
'r' => 'artist/hot-songs',
),
'format' => 'data',
),
'kugou'=>array(
'method' => 'GET',
'url' => 'http://mobilecdn.kugou.com/api/v3/singer/song',
'body' => array(
'singerid' => $id,
'page' => 1,
'plat' => 0,
'pagesize' => $limit,
'version' => 8400,
),
'format' => 'data#info',
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://tingapi.ting.baidu.com/v1/restserver/ting',
'body' => array(
'method' => 'baidu.ting.artist.getSongList',
'tinguid' => $id,
'limits' => $limit,
'format' => 'json',
'from' => 'ios',
'channel' => '(null)',
'cuid' => 'appstore',
'from' => 'ios',
'version' => '5.9.5',
),
'format' => 'songlist',
),
);
return self::curl($API[$this->_SITE]);
}
public function playlist($id){
$API=array(
'netease'=>array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'POST',
'params' => array(
'id' => $id,
"n" => 1000,
),
'url' => 'http://music.163.com/api/v3/playlist/detail',
),
'encode' => 'netease_AESECB',
'format' => 'playlist#tracks',
),
'tencent'=>array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg',
'body' => array(
'disstid' => $id,
'utf8' => 1,
'type' => 1,
),
'decode' => 'jsonp2json',
'format' => 'cdlist#0#songlist',
),
'xiami'=>array(
'method' => 'GET',
'url' => 'http://api.xiami.com/web',
'body' => array(
'v' => '2.0',
'app_key' => '1',
'id' => $id,
'r' => 'collect/detail',
),
'format' => 'data#songs',
),
'kugou'=>array(
'method' => 'GET',
'url' => 'http://mobilecdn.kugou.com/api/v3/special/song',
'body' => array(
'specialid' => $id,
'page' => 1,
'plat' => 2,
'pagesize' => -1,
'version' => 8400,
),
'format' => 'data#info',
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://tingapi.ting.baidu.com/v1/restserver/ting',
'body' => array(
'method' => 'baidu.ting.diy.gedanInfo',
'listid' => $id,
'format' => 'json',
'from' => 'ios',
'channel' => '(null)',
'cuid' => 'appstore',
'from' => 'ios',
'version' => '5.9.5',
),
'format' => 'content',
),
);
return self::curl($API[$this->_SITE]);
}
public function url($id,$br=320){
$API=array(
'netease'=>array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'POST',
'params' => array(
'ids' => array($id),
'br' => $br*1000,
),
'url' => 'http://music.163.com/api/song/enhance/player/url',
),
'encode' => 'netease_AESECB',
'decode' => 'netease_url',
),
'tencent'=>array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg',
'body' => array(
'songmid' => $id,
'format' => 'json',
),
'decode' => 'tencent_url',
),
'xiami'=>array(
'method' => 'GET',
'url' => 'http://www.xiami.com/song/playlist/id/'.$id.'/object_name/default/object_id/0/cat/json',
'body' => array(),
'decode' => 'xiami_url',
),
'kugou'=>array(
'method' => 'POST',
'url' => 'http://media.store.kugou.com/v1/get_res_privilege',
'body' => array(
"relate" => 1,
"userid" => 0,
"vip" => 0,
"appid" => 1390,
"token" => "",
"behavior" => "download",
"clientver" => "1",
"resource" => array(array(
"id" => 0,
"type" => "audio",
"hash" => $id,
)),
),
'encode' => 'kugou_json',
'decode' => 'kugou_url',
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://music.baidu.com/data/music/fmlink',
'body' => array(
'songIds' => $id,
'rate' => $br,
'type' => 'mp3',
),
'decode' => 'baidu_url',
),
);
$this->_temp['br']=$br;
return self::curl($API[$this->_SITE]);
}
public function lyric($id){
$API=array(
'netease'=>array(
'method' => 'POST',
'url' => 'http://music.163.com/api/linux/forward',
'body' => array(
'method' => 'POST',
'params' => array(
'id' => $id,
'os' => 'linux',
'lv' => -1,
'kv' => -1,
'tv' => -1,
),
'url' => 'http://music.163.com/api/song/lyric',
),
'encode' => 'netease_AESECB',
),
'tencent'=>array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric.fcg',
'body' => array(
'songmid' => $id,
'nobase64' => 0,
),
'decode' => 'jsonp2json',
),
'xiami'=>array(
'method' => 'GET',
'url' => 'http://api.xiami.com/web',
'body' => array(
'v' => '2.0',
'app_key' => '1',
'id' => $id,
'r' => 'song/detail',
),
'decode' => 'xiami_lyric',
),
'kugou'=>array(
'method' => 'GET',
'url' => 'http://m.kugou.com/app/i/krc.php',
'body' => array(
'keyword' => '%20-%20',
'timelength' => 1000000,
'cmd' => 100,
'hash' => $id,
),
'decode' => 'kugou_lyric'
),
'baidu' => array(
'method' => 'GET',
'url' => 'http://tingapi.ting.baidu.com/v1/restserver/ting',
'body' => array(
'method' => 'baidu.ting.song.lry',
'songid' => $id,
'format' => 'json',
'from' => 'ios',
'channel' => '(null)',
'cuid' => 'appstore',
'from' => 'ios',
'version' => '5.9.5',
),
),
);
return self::curl($API[$this->_SITE]);
}
public function pic($id,$size=300){
switch($this->_SITE){
case 'netease':
$url='https://p3.music.126.net/'.self::netease_pickey($id).'/'.$id.'.jpg?param='.$size.'z'.$size.'&quality=100';
break;
case 'tencent':
$url='https://y.gtimg.cn/music/photo_new/T002R'.$size.'x'.$size.'M000'.$id.'.jpg?max_age=2592000';
break;
case 'xiami':
$data=$this->format(false)->song($id);
$url=json_decode($data,1)['data']['song']['logo'];
$url=str_replace(['_1.','http:','img.'],['.','https:','pic.'],$url).'@'.$size.'h_'.$size.'w_90q_1c.webp';
break;
case 'kugou':
$API=array(
'method' =>'GET',
'url' => 'http://tools.mobile.kugou.com/api/v1/singer_header/get_by_hash',
'body' => array(
'hash' => $id,
'size' => 400,
'format' => 'json',
),
);
$data=$this->curl($API);
$url=json_decode($data,1)['url'];
break;
case 'baidu':
$data=self::song($id);
$data=json_decode($data,1);
$url=$data['songinfo']['pic_big']?:$data['songinfo']['pic_small'];
}
$arr=array('url'=>$url);
return json_encode($arr);
}
private function curlset(){
$BASE=array(
'netease'=>array(
'referer' => 'http://music.163.com/',
'cookie' => 'os=linux; appver=1.0.0.1026; osver=Ubuntu%2016.10; MUSIC_U=78d411095f4b022667bc8ec49e9a44cca088df057d987f5feaf066d37458e41c4a7d9447977352cf27ea9fee03f6ec4441049cea1c6bb9b6; __remember_me=true',
'useragent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.30 Safari/537.36',
),
'tencent'=>array(
'referer' => 'http://y.qq.com/portal/player.html',
'cookie' => 'qqmusic_uin=12345678; qqmusic_key=12345678; qqmusic_fromtag=30; ts_last=y.qq.com/portal/player.html;',
'useragent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.30 Safari/537.36',
),
'xiami'=>array(
'referer' => 'http://h.xiami.com/',
'cookie' => 'user_from=2;XMPLAYER_addSongsToggler=0;XMPLAYER_isOpen=0;_xiamitoken=123456789;',
'useragent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.30 Safari/537.36',
),
'kugou'=>array(
'referer' => 'http://www.kugou.com/webkugouplayer/flash/webKugou.swf',
'cookie' => '_WCMID=123456789',
'useragent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.30 Safari/537.36',
),
'baidu'=>array(
'referer' => 'http://ting.baidu.com/',
'cookie' => 'BAIDUID=123456789',
'useragent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.30 Safari/537.36',
),
);
return $BASE[$this->_SITE];
}
/**
* 乱七八糟的函数,加密解密...
* 正在努力重构这些代码 TAT
*/
private function netease_AESECB($API){
$KEY='7246674226682325323F5E6544673A51';
$body=json_encode($API['body']);
$body=openssl_encrypt($body,'aes-128-ecb',hex2bin($KEY));
$body=strtoupper(bin2hex(base64_decode($body)));
$API['body']=array(
'eparams'=>$body,
);
return $API;
}
private function kugou_json($API){
$API['body']=json_encode($API['body']);
return $API;
}
private function jsonp2json($jsonp){
if($jsonp[0] !== '[' && $jsonp[0] !== '{') {
$jsonp = substr($jsonp, strpos($jsonp, '('));
}
return trim($jsonp,'();');
}
private function tencent_singlesong($result){
$result=json_decode($result,1);
$data=$result['data'][0];
$t=array(
'songmid' => $data['mid'],
'songname' => $data['name'],
'albummid' => $data['album']['mid'],
);
foreach($t as $key=>$vo)$result['data'][0][$key]=$vo;
return json_encode($result);
}
private function netease_pickey($id){
$magic=str_split('3go8&$8*3*3h0k(2)2');
$song_id=str_split($id);
for($i=0;$i<count($song_id);$i++)$song_id[$i]=chr(ord($song_id[$i])^ord($magic[$i%count($magic)]));
$result=base64_encode(md5(implode('',$song_id),1));
$result=str_replace(['/','+'],['_','-'],$result);
return $result;
}
/**
* URL - 歌曲地址转换函数
* 用于返回不高于指定 bitRate 的歌曲地址(默认规范化)
*/
private function netease_url($result){
$data=json_decode($result,1);
$url=array(
'url' => str_replace('http:','https:',$data['data'][0]['url']),
'br' => $data['data'][0]['br']/1000,
);
return json_encode($url);
}
private function tencent_url($result){
$data=json_decode($result,1);
$GUID=mt_rand()%10000000000;
$API=array(
'method' => 'GET',
'url' => 'https://c.y.qq.com/base/fcgi-bin/fcg_musicexpress.fcg',
'body' => array(
'json' => 3,
'guid' => $GUID,
),
'decode' => 'jsonp2json',
);
$KEY=json_decode($this->curl($API),1)['key'];
$type=array(
'size_320mp3'=>array(320,'M800','mp3'),
'size_128mp3'=>array(128,'M500','mp3'),
'size_96aac'=>array(96,'C400','m4a'),
'size_48aac'=>array(48,'C200','m4a'),
);
foreach($type as $key=>$vo){
if($data['data'][0]['file'][$key]&&$vo[0]<=$this->_temp['br']){
$url=array(
'url' => 'http://dl.stream.qqmusic.qq.com/'.$vo[1].$data['data'][0]['file']['media_mid'].'.'.$vo[2].'?vkey='.$KEY.'&guid='.$GUID.'&uid=0&fromtag=30',
'br' => $vo[0],
);
break;
}
}
return json_encode($url);
}
private function xiami_url($result){
$data=json_decode($result,1);
$max=0;
foreach($data['data']['trackList'][0]['allAudios'] as $vo){
if($vo['rate']<=$this->_temp['br']&&$vo['rate']>$max){
$max=$vo['rate'];
$url=array(
'url' => str_replace('http:','https:',$vo['filePath']),
'br' => $vo['rate'],
);
}
}
return json_encode($url);
}
private function kugou_url($result){
$data=json_decode($result,1);
$max=0;
$url=array();
foreach($data['data'][0]['relate_goods'] as $vo){
if($vo['info']['bitrate']<=$this->_temp['br']&&$vo['info']['bitrate']>$max){
$API=array(
'method' => 'GET',
'url' => 'http://trackercdn.kugou.com/i/v2/',
'body' => array(
'hash' => $vo['hash'],
'key' => md5($vo['hash'].'kgcloudv2'),
'pid' => 1,
'behavior' => 'play',
'cmd' => '23',
'version' => 8400,
),
);
$t=json_decode($this->curl($API),1);
if(isset($t['url'])){
$max=$t['bitRate']/1000;
$url=array(
'url' => $t['url'],
'br' => $t['bitRate']/1000,
);
}
}
}
return json_encode($url);
}
private function baidu_url($result){
$data=json_decode($result,1);
$url=array(
'url' => $data['data']['songList'][0]['songLink'],
'br' => $data['data']['songList'][0]['rate'],
);
$url['url']=str_replace('http://yinyueshiting.baidu.com','https://gss0.bdstatic.com/y0s1hSulBw92lNKgpU_Z2jR7b2w6buu',$url['url']);
return json_encode($url);
}
/**
* 歌词处理模块
* 用于规范化歌词输出
*/
private function xiami_lyric($result){
$result=json_decode($result,1);
$API=array(
'method' => 'GET',
'url' => $result['data']['song']['lyric'],
);
$data=$this->curl($API);
$data=preg_replace('/<\d{1,8}>/','',$data);
$arr=array(
'lyric' => $data,
);
return json_encode($arr);
}
private function kugou_lyric($result){
$arr=array(
'lyric' => $result,
);
return json_encode($arr);
}
/**
* Format - 规范化函数
* 用于统一返回的参数,可用 ->format() 一次性开关开启
*/
private function format_netease($data){
$result=array(
'id' => $data['id'],
'name' => $data['name'],
'artist' => array(),
'pic_id' => $data['al']['pic_str']?:$data['al']['pic'],
'url_id' => $data['id'],
'lyric_id' => $data['id'],
'source' => 'netease',
);
if(isset($data['al']['picUrl'])){
preg_match('/\/(\d+)\./',$data['al']['picUrl'],$match);
$result['pic_id']=$match[1];
}
foreach($data['ar'] as $vo)$result['artist'][]=$vo['name'];
return $result;
}
private function format_tencent($data){
if(isset($data['musicData']))$data=$data['musicData'];
$result=array(
'id' => $data['songmid'],
'name' => $data['songname'],
'artist' => array(),
'pic_id' => $data['albummid'],
'url_id' => $data['songmid'],
'lyric_id' => $data['songmid'],
'source' => 'tencent',
);
foreach($data['singer'] as $vo)$result['artist'][]=$vo['name'];
return $result;
}
private function format_xiami($data){
$result=array(
'id' => $data['song_id'],
'name' => $data['song_name'],
'artist' => explode(';',$data['singers']?:$data['artist_name']),
'pic_id' => $data['song_id'],
'url_id' => $data['song_id'],
'lyric_id' => $data['song_id'],
'source' => 'xiami',
);
return $result;
}
private function format_kugou($data){
$result=array(
'id' => $data['hash'],
'name' => "",
'artist' => array(),
'url_id' => $data['hash'],
'pic_id' => $data['hash'],
'lyric_id' => $data['hash'],
'source' => 'kugou',
);
list($result['artist'],$result['name'])=explode(' - ',$data['filename']?:$data['fileName']);
$result['artist']=explode('、',$result['artist']);
return $result;
}
private function format_baidu($data){
$result=array(
'id' => $data['song_id'],
'name' => $data['title'],
'artist' => explode(',',$data['author']),
'pic_id' => $data['song_id'],
'url_id' => $data['song_id'],
'lyric_id' => $data['song_id'],
'source' => 'baidu',
);
return $result;
}
}

6
include/install.sql Normal file
View File

@ -0,0 +1,6 @@
CREATE TABLE typecho_meting (
id binary(40) NOT NULL,
value TEXT NOT NULL,
date int(11) NOT NULL
)ENGINE=MYISAM DEFAULT CHARSET=utf8;
ALTER TABLE typecho_meting ADD UNIQUE(id);

1
include/uninstall.sql Normal file
View File

@ -0,0 +1 @@
TRUNCATE typecho_meting;