├── .gitignore
├── include
├── .htaccess
├── utility.php
├── twitteroauth.php
├── OAuth.php
└── simple_html_dom.php
├── oauth
├── .htaccess
└── index.html
├── .htaccess
├── README.md
├── config-example.php
├── filters
├── 1_1__statuses__NUMBER__activity__summary_json.php
├── oauth__access_token.php
├── _default.php
├── 1_1__statuses__update_with_media_json.php
└── 1_1__timeline__home_json.php
├── index.php
├── index.html
├── getapi.php
├── rewrite_rules.md
├── image_proxy.php
├── style.css
├── oauth_proxy.php
├── oauth.php
└── twip.php
/.gitignore:
--------------------------------------------------------------------------------
1 | config.php
2 | oauth/
3 | debug
4 | log
5 |
--------------------------------------------------------------------------------
/include/.htaccess:
--------------------------------------------------------------------------------
1 | Order deny,allow
2 | Deny from all
3 |
--------------------------------------------------------------------------------
/oauth/.htaccess:
--------------------------------------------------------------------------------
1 | Order deny,allow
2 | Deny from all
3 |
--------------------------------------------------------------------------------
/oauth/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine On
3 | #RewriteBase /twip
4 | RewriteCond %{REQUEST_FILENAME} !-f
5 | RewriteCond %{REQUEST_FILENAME} !-d
6 | RewriteRule . index.php [L]
7 |
8 | DirectoryIndex index.html
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # twip, a twitter API proxy in PHP
2 |
3 | ## Prerequisite
4 |
5 | - PHP >= 5.3.0 with curl support.
6 | - Proper HTTP server rewrite rules. Apache/nginx/lighttpd is supported
7 | for now. Please contribute more rules if you happened to run twip on
8 | an unsupported HTTP server.
9 |
--------------------------------------------------------------------------------
/config-example.php:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/filters/1_1__statuses__NUMBER__activity__summary_json.php:
--------------------------------------------------------------------------------
1 | filters[$filterName] = function($args) {
6 | // TODO: should we try to parse some official API and get the correct response?
7 | return '{"retweeters_count":"0","retweeters":[],"repliers_count":"0","repliers":[],"favoriters":[],"favoriters_count":"0"}';
8 | };
9 |
--------------------------------------------------------------------------------
/filters/oauth__access_token.php:
--------------------------------------------------------------------------------
1 | filters[$filterName] = function($args) {
6 | return sprintf(
7 | "oauth_token=%s&oauth_token_secret=%s&user_id=%s&screen_name=%s&x_auth_expires=0\n",
8 | $args['self']->access_token['oauth_token'],
9 | $args['self']->access_token['oauth_token_secret'],
10 | $args['self']->access_token['user_id'],
11 | $args['self']->access_token['screen_name']
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/filters/_default.php:
--------------------------------------------------------------------------------
1 | filters[$filterName] = function($args) {
6 | if (substr($args['path'], 0, 4) != '1.1/') {
7 | $url = sprintf("https://api.twitter.com/1.1/%s", $args['path']);
8 | } else {
9 | $url = sprintf("https://api.twitter.com/%s", $args['path']);
10 | }
11 | if ($args['method'] === 'POST') {
12 | return $args['self']->connection->post($url, $args['params']);
13 | } else {
14 | return $args['self']->connection_get->get($url, $args['params']);
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/include/utility.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/filters/1_1__statuses__update_with_media_json.php:
--------------------------------------------------------------------------------
1 | filters[$filterName] = function($args) {
6 | $url = sprintf("https://api.twitter.com/%s", $args['path']);
7 | $headers = OAuthUtil::get_headers();
8 | // Check actually media uplaod
9 | if(strpos(@$headers['Content-Type'], 'multipart/form-data') === FALSE
10 | or count($_FILES) == 0 or !isset($_FILES['media'])) {
11 | header('HTTP/1.0 400 Bad Request');
12 | return;
13 | }
14 |
15 | $auth_headers = $args['self']->connection->getOAuthRequest(
16 | $url, $args['method'], null)->to_header();
17 | $forwarded_headers = array(
18 | "Host: api.twitter.com",
19 | $auth_headers,
20 | "Expect:");
21 | $parameters = preg_replace('/^@/', "\0@", $_POST);
22 |
23 | $media = $_FILES['media'];
24 | $fn = is_array($media['tmp_name']) ? $media['tmp_name'][0] : $media['tmp_name'];
25 | $parameters["media[]"] = '@' . $fn;
26 |
27 | $ch = curl_init($url);
28 | curl_setopt($ch, CURLOPT_HTTPHEADER, $forwarded_headers);
29 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($args['self'],'headerfunction'));
30 | curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters);
31 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
32 | $ret = curl_exec($ch);
33 | return $ret;
34 | };
35 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Twip 4 - Configuration
6 |
7 |
8 |
9 |
10 |
11 | Twitter API Proxy, redefined.
12 |
13 |
Twip 使用说明
14 |
什么是 Twip?
15 |
Twip 是一个运行在 LAMP 主机上的 PHP 程序,用于绕过GFW的限制在各种Twitter客户端里访问Twitter。
16 |
什么是 T 模式?
17 |
T模式 ( Transparent ) 会透明转发客户端的HTTP请求,能完整保留客户端的Source信息。
18 |
什么是 O 模式?
19 |
O模式 ( Override ) 会使用自定义OAuth Consumer Key/Secret重新对客户端HTTP请求进行签名,无法保留客户端Source信息。
20 |
开始使用 Twip
21 |
22 | 使用 T 模式
23 | 使用 O 模式
24 |
25 |
FAQ 及反馈
26 |
27 | 更多安装说明和问题解决,请访问项目 Wiki:
https://github.com/twip/twip/wiki
28 |
29 |
30 | 反馈 BUG 请在此提交:
31 | https://github.com/twip/twip/issues
32 |
33 |
34 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/getapi.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | Twip 4 - Configuration
9 |
10 |
11 |
12 |
13 |
14 | Twitter API Proxy, redefined.
15 |
16 |
17 |
你的 API Proxy 地址
18 |
19 |
20 |
21 |
22 |
23 |
24 |
你的 Image Proxy 地址
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 友情提醒:请不要随意泄漏你的 API 地址。Twip 默认会保护你的 API 地址不被搜索引擎爬取。
33 |
34 |
35 |
38 |
39 | 注意: O 模式下每次提交认证都会生成新的随机 API 地址!
40 |
41 |
44 |
45 |
46 | 返回首页
47 |
48 |
49 |
50 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/filters/1_1__timeline__home_json.php:
--------------------------------------------------------------------------------
1 | filters[$filterName] = function($args) {
6 | $url = "https://api.twitter.com/1.1/statuses/home_timeline.json";
7 | if ($args['method'] === 'POST') {
8 | $ret = $args['self']->connection->post($url, $args['params']);
9 | } else {
10 | $ret = $args['self']->connection_get->get($url, $args['params']);
11 | }
12 | $raw_obj = json_decode($ret);
13 | $ret = array(
14 | "twitter_objects" => array(
15 | "users" => array(),
16 | "tweets" => array(),
17 | "event_summaries" => array(),
18 | ),
19 | "response" => array(
20 | "timeline" => array(),
21 | ),
22 | );
23 | $tweets = &$ret['twitter_objects']['tweets'];
24 | $users = &$ret['twitter_objects']['users'];
25 | $timeline = &$ret['response']['timeline'];
26 | foreach($raw_obj as $tweet) {
27 | $user = $tweet->user;
28 | $users[$user->id_str] = $user;
29 | unset($tweet->user);
30 | $tweet->user = array(
31 | "id" => $user->id,
32 | "id_str" => $user->id_str,
33 | );
34 | $tweets[strval($tweet->id)] = $tweet;
35 | $timeline[] = array(
36 | 'tweet' => array(
37 | 'id' => strval($tweet->id),
38 | ),
39 | 'entity_id' => array(
40 | 'type' => 'tweet',
41 | 'ids' => array(
42 | strval($tweet->id),
43 | ),
44 | ),
45 | );
46 | }
47 | return json_encode($ret);
48 | };
49 |
--------------------------------------------------------------------------------
/rewrite_rules.md:
--------------------------------------------------------------------------------
1 | # Rewrite Rules
2 |
3 |
4 | ## Apache
5 |
6 | Just set `AllowOverride` in you Host, Apache will follow `.htaccess` rules provided in twip.
7 |
8 | ## Nginx(with php-fpm)
9 |
10 | Example:
11 |
12 | ```nginx
13 | server {
14 | listen 443 ssl spdy;
15 | server_name m.example.net;
16 | ssl on;
17 | ssl_certificate /path/to/cert.crt;
18 | ssl_certificate_key /path/to/privkey.pem;
19 | ssl_prefer_server_ciphers on;
20 | client_max_body_size 8m;
21 | gzip on;
22 | index index.php;
23 |
24 | root /srv/http/twitter;
25 | location /twip/oauth { deny all; }
26 | location /twip/ { try_files $uri /twip/index.php; }
27 | location ~ \.php$ {
28 | try_files $uri =404;
29 | include fastcgi_params;
30 | fastcgi_pass unix:/var/run/php-fpm/php-cgi.socket;
31 | }
32 | }
33 | ```
34 |
35 | Note:
36 |
37 | * In the example, twip source is located at: `/srv/http/twitter/twip`, and twip is working at `https://m.example.net/twip`.
38 | * If you want to change to some other directory, change `root` / `location /twip/oauth` / `location /twip/`
39 | * Remember to protect `/twip/oauth/` from leaking info when you're adjusting rules.
40 |
41 |
42 | ## lightTPD
43 |
44 | (Sorry I'm not familar with lightTPD, can't provide a working example here, text below was provided by someone other. If you want to help other lightTPD users, fire an issue at github with a full example.)
45 |
46 |
47 | For lightTPD users, please use the following rules:
48 |
49 | ```
50 | url.rewrite-if-not-file += ( "^/(.*)$" => "/index.php/$1" )
51 | ```
52 |
53 | _Provided by kk198_
54 |
55 |
56 | Just a reminder, please specify the index-file to "index.php"
57 |
58 | ```
59 | ^/twip/(.*)$ /twip/index.php
60 | ```
61 |
62 | instead of
63 |
64 | ```
65 | ^/(.*)$. /index.php
66 |
67 | index-file.names = ( "index.php", "index.html",
68 | "index.htm", "default.htm" )
69 | ```
70 |
--------------------------------------------------------------------------------
/image_proxy.php:
--------------------------------------------------------------------------------
1 | empty($_POST['message']) ? '' : $_POST['message'],
9 | 'media' => "@$image",
10 | );
11 | $signingurl = 'https://api.twitter.com/1/account/verify_credentials.json';
12 | $consumer = new OAuthConsumer($oauth_key, $oauth_secret);
13 | $token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
14 | $sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
15 | $request = OAuthRequest::from_consumer_and_token($consumer, $token, 'GET', $signingurl, array());
16 | $request->sign_request($sha1_method, $consumer, $token);
17 | // header
18 | $header = $request->to_header("http://api.twitter.com/");
19 |
20 | /**** request method ****/
21 | $url = 'http://img.ly/api/2/upload.json';
22 | $ch = curl_init($url);
23 | curl_setopt($ch, CURLOPT_POST, true);
24 | curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
25 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('X-Auth-Service-Provider: '.$signingurl,'X-Verify-Credentials-'.$header));
26 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
27 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
28 | curl_setopt($ch, CURLOPT_TIMEOUT, 60);
29 |
30 | $response = curl_exec($ch);
31 | $response_info=curl_getinfo($ch);
32 | curl_close($ch);
33 |
34 | if ($response_info['http_code'] == 200) {
35 | if(preg_match('/^Twitter\/[^ ]+ CFNetwork\/[^ ]+ Darwin\/[^ ]+$/',$_SERVER['HTTP_USER_AGENT'])){
36 | $data = json_decode($response);
37 | return empty($data) ? '' : ''.$data->{'url'}.'';
38 | }else{
39 | header('Content-Type: application/json');
40 | return $response;
41 | }
42 | } else {
43 | return 'error '.$response_info['http_code'];
44 | }
45 | }
46 |
47 | ?>
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | Styles for Twip 4
3 | Author: disinfeqt
4 | Website: www.zdxia.com
5 | E-mail: disinfeqt@gmail.com
6 | */
7 | *{margin:0;padding:0;font-size:1em;border:0;outline:none;list-style:none;text-decoration:none;}
8 | .clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0;}
9 | .clearfix:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0;}
10 | * html .clearfix,:first-child+html .clearfix{zoom:1;}
11 | body{background:#5B686A;background:rgba(0,24,26,0.7);color:#143C3F;font:13px/1.8 "Helvetica Neue",Helvetica,Arial,sans-serif;}
12 | a:link,a:visited{color:#043034;border-bottom:1px dotted #334648;}
13 | a:hover,a:focus{border-bottom:1px solid #334648;color:#334648;}
14 | a:active,input[type=submit]:active{position:relative;bottom:-1px;}
15 | p{margin:0 0 10px;}
16 | small{font-size:12px;}
17 | div{margin:0 auto;width:100%;text-align:left;background:#eee;width:340px;padding:5px 20px 20px;}
18 | div#footer{margin-top:10px;background:none;text-align:center;padding:0;color:#DDD;font-size:10px;font-weight:100;letter-spacing:1px;text-shadow:0 1px 0 #314446;text-transform:uppercase;}
19 | div#footer a{color:#ddd;border:none;}
20 | div#footer a:hover{color:#fff;}
21 | h1,h2{width:100%;text-align:center;color:#fff;}
22 | h1{font-size:40px;text-transform:lowercase;margin-top:20px;line-height:1.2;}
23 | h1 sup{font-size:20px;}
24 | h1 a{color:#fff!important;border:none!important;}
25 | h1 a:hover{color:#eee!important;}
26 | h2{margin-bottom:20px;}
27 | h3{border-left:5px solid #B4BABB;font-size:20px;line-height:1;padding-left:10px;margin:15px 0;text-transform:uppercase;font-weight:100;}
28 | h4{font-size:14px;font-weight:700;}
29 | a.button{background:#143C3F;border:medium none;color:#FFF;display:block;float:left;padding:4px 5px;text-align:center;width:150px;}
30 | a.last{margin-left:20px;}
31 | input[type=text],input[type=password]{border:1px solid #B4BABB;font-family:Helvetica;font-size:13px;padding:5px;width:328px;}
32 | input.half {width:156px;}
33 | input[type=text]:focus,input[type=password]:focus{border:1px solid #143C3F;}
34 | input[type=submit]{background:none repeat scroll 0 0 #143C3F;border:1px solid #143C3F;color:#FFF;font-family:Helvetica;font-size:13px;padding:5px;cursor:pointer;}
35 | label{display:block}
36 | div ul{border-bottom:2px solid #143C3F;margin-bottom:10px;}
37 | ul li a{display:block;float:left;background:#B4BABB;color:#fff!important;border:none!important;padding:2px 10px;text-align:center;width:150px;}
38 | ul li a.active{background:#143C3F;color:#fff;}
--------------------------------------------------------------------------------
/oauth_proxy.php:
--------------------------------------------------------------------------------
1 | 抓
11 | oauth_token -> 抓
12 | session[username_or_email] -> twitterAccount
13 | session[password] -> twitterPassword
14 | */
15 | /* After this page, we should validate the returning page.
16 | Statud 403 -> No longer valid / wrong password.
17 | Status 200 ->
18 | if (contain_Allow) {
19 | post_allow;
20 | get_oauth_strings;
21 | post_oauth_strings_to_oauth.php;
22 | } else {
23 | get_oauth_strings;
24 | ...
25 | }
26 | */
27 | $page_auth = file_get_html($oAuthEntryPage);
28 | if($page_auth === FALSE){
29 | echo "Cannot load http resource using file_get_contents";
30 | exit();
31 | }
32 | $oauth_token = $page_auth->find('input[name=oauth_token]', 0)->attr['value'];
33 | $authenticity_token = $page_auth->find('input[name=authenticity_token]', 0)->attr['value'];
34 | $login_fields = Array(
35 | 'oauth_token' => urlencode($oauth_token),
36 | 'authenticity_token' => urlencode($authenticity_token),
37 | 'session[username_or_email]' => urlencode($twitterAccount),
38 | 'session[password]' => urlencode($twitterPassword)
39 | );
40 | foreach($login_fields as $key=>$value) {
41 | $login_string .= $key.'='.$value.'&';
42 | }
43 | $ckfile = tempnam ("/tmp", "CURLCOOKIE");
44 | $ch = curl_init();
45 | curl_setopt($ch, CURLOPT_URL, 'https://api.twitter.com/oauth/authorize');
46 | curl_setopt($ch, CURLOPT_COOKIEJAR, $ckfile);
47 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
48 | curl_setopt($ch, CURLOPT_POST, count($login_fields));
49 | curl_setopt($ch, CURLOPT_POSTFIELDS, $login_string);
50 | $login_result = curl_exec($ch);
51 | curl_close($ch);
52 | $login_obj = str_get_html($login_result);
53 | $login_error = $login_obj->find('div[class=error notice] p', 0)->innertext;
54 | if(strlen($login_error) > 8) {
55 | /* This is a workaround coz oauth_errors can be " " */
56 | echo "There must be something wrong with your user account and password combination.
";
57 | echo "Twitter said: $login_error\n";
58 | die(-1);
59 | }
60 | $code = $login_obj->find('code', 0)->innertext;
61 | return $code;
62 | }
63 |
--------------------------------------------------------------------------------
/oauth.php:
--------------------------------------------------------------------------------
1 | getRequestToken(BASE_URL.'oauth.php');
24 |
25 | /* Save request token to session */
26 | $_SESSION['oauth_token'] = $request_token['oauth_token'];
27 | $_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];
28 |
29 | if ($connection->http_code != 200) {
30 | http_error($connection->http_code);
31 | }
32 |
33 | /* Build authorize URL */
34 | $url = $connection->getAuthorizeURL($_SESSION['oauth_token'],FALSE);
35 | header('HTTP/1.1 302 Found');
36 | header('Status: 302 Found');
37 | header('Location: ' . $url);
38 | }
39 | elseif ($_GET['type'] == 2) {
40 | function oob($oauth_key, $oauth_secret, $suff = '') {
41 | $connection = new TwitterOAuth($oauth_key, $oauth_secret);
42 | $request_token = $connection->getRequestToken('oob');
43 |
44 | /* Save request token to session */
45 | $_SESSION['oauth_token' . $suff] = $request_token['oauth_token'];
46 | $_SESSION['oauth_token_secret' . $suff] = $request_token['oauth_token_secret'];
47 |
48 | if ($connection->http_code != 200) {
49 | http_error($connection->http_code);
50 | }
51 |
52 | $url = $connection->getAuthorizeURL($request_token['oauth_token'], FALSE);
53 | $oauth_verifier = oauth_proxy($url, $_POST['username'], $_POST['password']);
54 | return "&oauth_token$suff=" . $request_token['oauth_token'] . "&oauth_verifier$suff=" . $oauth_verifier;
55 | }
56 |
57 | $url = oob(OAUTH_KEY, OAUTH_SECRET);
58 | $url .= oob(OAUTH_KEY_GET, OAUTH_SECRET_GET, '_get');
59 |
60 | header('HTTP/1.1 302 Found');
61 | header('Status: 302 Found');
62 | header('Location: ' . BASE_URL . 'oauth.php?' . $url);
63 | }
64 | exit();
65 | }
66 | if(isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])){
67 | $connection = new TwitterOAuth(OAUTH_KEY, OAUTH_SECRET, $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']);
68 | $access_token = $connection->getAccessToken($_GET['oauth_verifier']);
69 | if(isset($_GET['oauth_token_get']) && isset($_GET['oauth_verifier_get'])) {
70 | // XXX: really need to be refactored with previous part
71 | $connection = new TwitterOAuth(OAUTH_KEY_GET, OAUTH_SECRET_GET, $_SESSION['oauth_token_get'], $_SESSION['oauth_token_secret_get']);
72 | $access_token_get = $connection->getAccessToken($_GET['oauth_verifier_get']);
73 | $access_token['oauth_token_get'] = $access_token_get['oauth_token'];
74 | $access_token['oauth_token_secret_get'] = $access_token_get['oauth_token_secret'];
75 | }
76 | if($connection->http_code == 200){
77 | $old_tokens = glob('oauth/*.'.$access_token['screen_name']);
78 | if(!empty($old_tokens)){
79 | foreach($old_tokens as $file){
80 | unlink($file);
81 | }
82 | }
83 | if($_SESSION['url_suffix']==''){
84 | for ($i=0; $i<6; $i++) {
85 | $d=rand(1,30)%2;
86 | $suffix_string .= $d ? chr(rand(65,90)) : chr(rand(48,57));
87 | }
88 | }
89 | else{
90 | $suffix_string = $_SESSION['url_suffix'];
91 | }
92 | if(file_put_contents('oauth/'.$suffix_string.'.'.$access_token['screen_name'],serialize($access_token)) === FALSE){
93 | echo 'Error failed to write access_token file.Please check if you have write permission to oauth/ directory'."\n";
94 | exit();
95 | }
96 | $url = BASE_URL.'o/'.$suffix_string;
97 | header('HTTP/1.1 302 Found');
98 | header('Status: 302 Found');
99 | header('Location: getapi.php?api='.$url);
100 | }
101 | else {
102 | echo 'Error '.$connection->http_code."\n";
103 | print_r($connection);
104 | }
105 | exit();
106 | }
107 | ?>
108 |
109 |
110 |
111 |
112 | Twip 4 - Configuration
113 |
114 |
115 |
116 |
117 |
118 | Twitter API Proxy, redefined.
119 |
122 |
123 |
124 |
Twip 配置
125 |
126 |
144 |
145 |
148 |
149 |
150 |
154 |
155 |
156 |
Twip 配置
157 |
158 |
186 |
187 |
188 |
191 |