-
Notifications
You must be signed in to change notification settings - Fork 40
/
NeteaseMusicProxy.py
269 lines (237 loc) · 9.52 KB
/
NeteaseMusicProxy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
from twisted.web import proxy, http
from twisted.internet import reactor, protocol
from twisted.python import log
from twisted.python.compat import urllib_parse
from io import StringIO
import sys, gzip, os, time
from subprocess import Popen, PIPE, STDOUT
import requests
from pyquery.pyquery import PyQuery
log.startLogging(sys.stdout)
def kill_existed():
pgrep = Popen(['pgrep', '-f', 'NeteaseMusicProxy'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
existed = pgrep.communicate()[0].rstrip().split(b'\n')
for pid in existed:
if pid and int(pid) != os.getpid():
kill = Popen(['kill', '-9', pid], stderr=STDOUT)
kill.communicate()
time.sleep(3)
def py_gzip_compress(plain_content):
temp_file = StringIO.StringIO()
with gzip.GzipFile(fileobj=temp_file, mode="w") as f:
f.write(plain_content)
return temp_file.getvalue()
def py_gzip_decompress(compressed_content):
temp_file = StringIO.StringIO()
temp_file.write(compressed_content)
temp_file.seek(0)
with gzip.GzipFile(fileobj=temp_file, mode="rb") as f:
return f.read()
def sh_gzip_compress(plain_content):
p = Popen(['gzip', '-c'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
return p.communicate(input=plain_content)[0]
def sh_gzip_decompress(compressed_content):
p = Popen(['gzip', '-dc'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
res = p.communicate(input=compressed_content)[0]
if res.startswith(b'gzip'):
return None
return res
def modify_response(response):
response = response.replace(b'"st":-100', b'"st":0')
response = response.replace(b'"st":-200', b'"st":0')
response = response.replace(b'"pl":0', b'"pl":320000')
response = response.replace(b'"dl":0', b'"dl":320000')
response = response.replace(b'"fl":0', b'"fl":320000')
response = response.replace(b'"sp":0', b'"sp":7')
response = response.replace(b'"cp":0', b'"cp":1')
response = response.replace(b'"subp":0', b'"subp":1')
return response
class MainlandProxy():
def __init__(self):
self.default_ip = '123.57.215.44'
self.default_port = 32796
self.ip = ''
self.port = -1
self.failed_times = 0
self.set_proxy()
self.status = 0
self.url_request_length = b'264'
def set_proxy(self):
r = requests.get("http://cn-proxy.com/")
q = PyQuery(r.content)
trs = q("tbody tr")
if (len(trs) == 0):
self.ip = self.default_ip
self.port = self.default_port
return
tr = trs[min(self.failed_times,len(trs)-1)]
trq = PyQuery(tr)
tds = trq.children()
ip = tds.eq(0).text()
port = int(tds.eq(1).text())
if self.ip == ip and self.port == port:
self.set_to_default()
else:
self.ip = ip
self.port = port
def set_to_default(self):
self.ip, self.port = self.default_ip, self.default_port
def change(self):
self.failed_times += 1
print('bad proxy, ip: %s port: %d' % (self.ip, self.port))
if (self.failed_times > 5 and self.ip != self.default_ip):
self.failed_times = 0
self.ip = self.default_ip
self.port = self.default_port
else:
self.set_proxy()
print("using ip %s and port %d" % (self.ip, self.port))
def check(self, buffer_str):
for msg in ['HTTP Status 404', 'ERROR', 'ERR_INVALID_URL', 'Internal Server Error']:
if msg in buffer_str:
self.change()
break
class NeteaseMusicProxyClient(proxy.ProxyClient):
def __init__(self, *args, **kwargs):
self.intercept = {'song': b'/eapi/v3/song/detail/', 'search': b'/eapi/cloudsearch/pc', 'url': b'/eapi/song/enhance/player/url', 'album': b'/eapi/v1/album', 'artist': b'/eapi/v1/artist', 'playlist': b'/eapi/v3/playlist/detail', 'discovery': b'/eapi/v1/discovery/new/songs', 'linux': b'/api/linux/forward'}
self.interval = {self.intercept['song']: 10, self.intercept['search']: 100, 'default': 10}
self.temp_buffer = {self.intercept['song']: None, self.intercept['search']: None}
self.timestamp = {self.intercept['song']: time.time(), self.intercept['search']: time.time()}
proxy.ProxyClient.__init__(self, *args, **kwargs)
def check_buffer(self, buffer):
if len(buffer) != 414:
print('length of buffer:', len(buffer))
if len(buffer) == 238:
print('cannot play this song due to copyright')
else:
mainland_proxy.change()
else:
mainland_proxy.status = 0
def handleResponsePart(self, buffer):
if self.rest in [self.intercept['song'], self.intercept['search'], self.intercept['playlist'], self.intercept['discovery'], self.intercept['linux']] or self.intercept['album'] in self.rest or self.intercept['artist'] in self.rest:
print(self.headers)
if self.headers[b'content-length'] != mainland_proxy.url_request_length:
print('response intercepted: ', self.rest)
if self.rest not in self.timestamp:
self.timestamp[self.rest] = time.time()
self.temp_buffer[self.rest] = None
if time.time() - self.timestamp[self.rest] > self.interval.get(self.rest, self.interval['default']):
self.temp_buffer[self.rest] = 0
self.timestamp[self.rest] = time.time()
if self.temp_buffer[self.rest] != None:
buffer = self.temp_buffer[self.rest] + buffer
buffer_str = sh_gzip_decompress(buffer)
if buffer_str == None or b'unexpected end of file' in buffer_str:
self.temp_buffer[self.rest] = buffer
self.timestamp[self.rest] = time.time()
return
else:
del self.temp_buffer[self.rest]
del self.timestamp[self.rest]
buffer_str = modify_response(buffer_str)
#print buffer_str
buffer = sh_gzip_compress(buffer_str)
if self.rest == self.intercept['url']:
self.check_buffer(buffer)
proxy.ProxyClient.handleResponsePart(self, buffer)
class NeteaseMusicProxyClientFactory(proxy.ProxyClientFactory):
protocol = NeteaseMusicProxyClient
def clientConnectionFailed(self, connector, reason):
print(reason, 'client connection failed, changing proxy')
mainland_proxy.change()
def clientConnectionLost(self, connector, reason):
print(reason)
if self.headers[b'connection'] != b'close' and mainland_proxy.status == -1:
mainland_proxy.change()
mainland_proxy.status = 0
class NeteaseMusicProxyRequest(proxy.ProxyRequest):
protocols = {b'http': NeteaseMusicProxyClientFactory}
def process_prepare(self):
# print self.method, self.uri, self.path, self.args, self.requestHeaders, self.responseHeaders, self.received_cookies, self.protocols, self.host, self.channel, self.content, self.cookies
parsed = urllib_parse.urlparse(self.uri)
protocol = parsed[0] or 'http'
host = parsed[1].decode('ascii')
port = self.ports[protocol]
if ':' in host:
host, port = host.split(':')
port = int(port)
rest = urllib_parse.urlunparse((b'', b'') + parsed[2:])
if not rest:
rest = rest + b'/'
class_ = self.protocols[protocol]
headers = self.getAllHeaders().copy()
if b'host' not in headers:
headers[b'host'] = host.encode('ascii')
self.content.seek(0, 0)
s = self.content.read()
clientFactory = class_(self.method, rest, self.clientproto, headers, s, self)
return host, port, clientFactory
def process(self):
if self.uri == b'music.163.com:443':
if self.getHeader(b'host') == self.uri:
host, port = self.uri.split(b':')
port = int(port)
clientFactory = ConnectProxyClientFactory(host, port, self)
self.reactor.connectTCP(host, port, clientFactory)
return
print('DEBUG: Abort on request:', self.uri)
self.channel._respondToBadRequestAndDisconnect()
return
host, port, clientFactory = self.process_prepare()
print(self.uri)
if self.uri == b'http://music.163.com/eapi/song/enhance/player/url' or self.uri == b'http://music.163.com/api/linux/forward' and self.getHeader(b'content-length') == mainland_proxy.url_request_length:
print('request intercepted:', self.uri, self.getHeader(b'content-length'))
mainland_proxy.set_to_default()
mainland_proxy.status = -1
self.reactor.connectTCP(mainland_proxy.ip, mainland_proxy.port, clientFactory)
return
self.reactor.connectTCP(host, port, clientFactory)
class NeteaseMusicProxy(proxy.Proxy):
requestFactory = NeteaseMusicProxyRequest
connectedRemote = None
def requestDone(self, request):
if request.method == b'CONNECT' and self.connectedRemote is not None:
self.connectedRemote.connectedClient = self
self._handlingRequest = False
if self._savedTimeOut:
self.setTimeout(self._savedTimeOut)
data = b''.join(self._dataBuffer)
self._dataBuffer = []
self.setLineMode(data)
else:
proxy.Proxy.requestDone(self, request)
def dataReceived(self, data):
if self.connectedRemote is None:
proxy.Proxy.dataReceived(self, data)
else:
self.connectedRemote.transport.write(data)
class NeteaseMusicProxyFactory(http.HTTPFactory):
protocol = NeteaseMusicProxy
class ConnectProxyClient(protocol.Protocol):
connectedClient = None
def connectionMade(self):
self.factory.request.channel.connectedRemote = self
self.factory.request.setResponseCode(200, b'CONNECT OK')
self.factory.request.setHeader(b'X-Connected-IP', self.transport.realAddress[0])
self.factory.request.setHeader(b'Content-Length', b'0')
self.factory.request.finish()
def connectionLost(self, reason):
if self.connectedClient is not None:
self.connectedClient.transport.loseConnection()
def dataReceived(self, data):
if self.connectedClient is not None:
self.connectedClient.transport.write(data)
else:
print('Unexpected data received:', data)
class ConnectProxyClientFactory(protocol.ClientFactory):
protocol = ConnectProxyClient
def __init__(self, host, port, request):
self.request = request
self.host = host
self.port = port
def clientConnectionFailed(self, connector, reason):
self.request.fail(b'Gateway Error', str(reason))
mainland_proxy = MainlandProxy()
kill_existed()
reactor.listenTCP(32794, NeteaseMusicProxyFactory())
reactor.run()