├── inc ├── .gitignore ├── images │ ├── def.gif │ ├── Thumbs.db │ ├── def2.gif │ ├── logo.gif │ ├── calendar.png │ ├── reg_ap.jpg │ ├── reg_ap2.jpg │ ├── reg_ap3.jpg │ ├── tab_bg.gif │ ├── ajax-loader.gif │ ├── delete_icon.gif │ ├── refresh_icon.gif │ ├── twitter_sign_in.jpg │ └── sign-in-with-twitter-button.png ├── include_bottom.php ├── common │ ├── footer.php │ └── header.php ├── content │ └── english │ │ ├── mysql_error.php │ │ ├── ajax.not_logged_in.php │ │ ├── multi_account_functions.php │ │ ├── not_logged_in.php │ │ ├── log_settings.php │ │ ├── follow_settings.php │ │ ├── tweet_settings.php │ │ ├── install_tables.php │ │ ├── ajax.index_inc.php │ │ ├── ajax.multi_account_functions_inc.php │ │ ├── index.php │ │ ├── cron_instructions.php │ │ ├── lang.php │ │ ├── ajax.log_settings_inc.php │ │ └── ajax.tweet_settings_inc.php ├── csv │ └── example.csv ├── include_top.php ├── config-sample.php ├── class │ ├── class.mainfuncs.php │ ├── twitteroauth.php │ ├── class.mysql.php │ └── class.cron.php ├── ajax │ ├── ajax.log_settings.php │ ├── ajax.pagination_inc.php │ ├── ajax.tweet_settings.php │ ├── ajax.index.php │ ├── ajax.multi_account_functions.php │ └── ajax.follow_settings.php ├── style │ └── style.css └── scripts │ ├── ajax_scripts.js │ └── anytime.c.css ├── favicon.ico ├── multi_account_functions.php ├── log_settings.php ├── follow_settings.php ├── cron_instructions.php ├── redirect.php ├── tweet_settings.php ├── index.php ├── callback.php ├── cron_tweet.php ├── install_tables.php ├── license.txt ├── README.md ├── cron_follow.php └── LICENSE /inc/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/favicon.ico -------------------------------------------------------------------------------- /inc/images/def.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/def.gif -------------------------------------------------------------------------------- /inc/images/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/Thumbs.db -------------------------------------------------------------------------------- /inc/images/def2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/def2.gif -------------------------------------------------------------------------------- /inc/images/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/logo.gif -------------------------------------------------------------------------------- /inc/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/calendar.png -------------------------------------------------------------------------------- /inc/images/reg_ap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/reg_ap.jpg -------------------------------------------------------------------------------- /inc/images/reg_ap2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/reg_ap2.jpg -------------------------------------------------------------------------------- /inc/images/reg_ap3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/reg_ap3.jpg -------------------------------------------------------------------------------- /inc/images/tab_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/tab_bg.gif -------------------------------------------------------------------------------- /inc/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/ajax-loader.gif -------------------------------------------------------------------------------- /inc/images/delete_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/delete_icon.gif -------------------------------------------------------------------------------- /inc/images/refresh_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/refresh_icon.gif -------------------------------------------------------------------------------- /inc/images/twitter_sign_in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/twitter_sign_in.jpg -------------------------------------------------------------------------------- /inc/images/sign-in-with-twitter-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psegovias/twando/HEAD/inc/images/sign-in-with-twitter-button.png -------------------------------------------------------------------------------- /inc/include_bottom.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /inc/common/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /inc/content/english/mysql_error.php: -------------------------------------------------------------------------------- 1 | 7 |
8 | 9 |
7 |
8 | You're not logged in or your session has timed out. Please refresh the page and log in again. 9 |
10 | 11 | -------------------------------------------------------------------------------- /inc/csv/example.csv: -------------------------------------------------------------------------------- 1 | "2013-12-25 13:00","You want me to come over, I got an excuse" 2 | "2013-12-25 13:10","Might be holding your hand, but I'm holding it loose" 3 | "2013-12-25 13:20","Go to talk, then we choke it's like our necks in a noose" 4 | "2013-12-25 13:30","Avoid the obvious, we should be facing the truth" 5 | "2013-12-25 13:40","RT @twando_com And you get the idea now, right?" 6 | 7 | -------------------------------------------------------------------------------- /multi_account_functions.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /log_settings.php: -------------------------------------------------------------------------------- 1 | get_user_data($_GET['id']); 21 | } 22 | 23 | mainFuncs::print_html($page_select); 24 | 25 | include('inc/include_bottom.php'); 26 | ?> 27 | -------------------------------------------------------------------------------- /follow_settings.php: -------------------------------------------------------------------------------- 1 | get_user_data($_GET['id']); 21 | } 22 | 23 | mainFuncs::print_html($page_select); 24 | 25 | include('inc/include_bottom.php'); 26 | ?> 27 | -------------------------------------------------------------------------------- /inc/include_top.php: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /cron_instructions.php: -------------------------------------------------------------------------------- 1 | query("UPDATE " . DB_PREFIX . "cron_status SET cron_state = '0', last_updated = '" . $db->prep(date("Y-m-d H:i:s")) . "' WHERE cron_name = '" . $db->prep($_GET['cron_type']) . "'"); 22 | 23 | //To prevent the URL being refreshed accidentally by the user, redirect to page without query string 24 | Header("Location: " . $return_url); 25 | } 26 | } 27 | } 28 | 29 | mainFuncs::print_html($page_select); 30 | 31 | include('inc/include_bottom.php'); 32 | ?> 33 | -------------------------------------------------------------------------------- /redirect.php: -------------------------------------------------------------------------------- 1 | get_ap_creds(); 18 | 19 | //Create request connection 20 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret']); 21 | $request_token = $connection->getRequestToken(BASE_LINK_URL . 'callback.php'); 22 | $_SESSION['oauth_token'] = $token = $request_token['oauth_token']; 23 | $_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret']; 24 | 25 | //Redirect based on response code 26 | switch ($connection->http_code) { 27 | case 200: 28 | $url = $connection->getAuthorizeURL($token); 29 | header('Location: ' . $url); 30 | break; 31 | default: 32 | header('Location: ' . BASE_LINK_URL . '?msg=3'); 33 | break; 34 | } 35 | 36 | } 37 | 38 | include('inc/include_bottom.php'); 39 | -------------------------------------------------------------------------------- /inc/config-sample.php: -------------------------------------------------------------------------------- 1 | 52 | -------------------------------------------------------------------------------- /inc/class/class.mainfuncs.php: -------------------------------------------------------------------------------- 1 | 3) { 18 | $logged_in = true; 19 | } else { 20 | $logged_in = false; 21 | } 22 | 23 | return $logged_in; 24 | } 25 | 26 | /* 27 | Basic HTML page printing - no need to pass page titles, meta data 28 | and so on for this script 29 | */ 30 | 31 | public static function print_html($content_id) { 32 | 33 | include('inc/common/header.php'); 34 | include('inc/content/' . TWANDO_LANG . '/' . $content_id . '.php'); 35 | include('inc/common/footer.php'); 36 | } 37 | 38 | /* 39 | Error or success message output 40 | */ 41 | 42 | public static function push_response($msg_id) { 43 | 44 | global $response_msgs; 45 | 46 | if ($response_msgs[$msg_id]['text']) { 47 | return '' . htmlentities($response_msgs[$msg_id]['text']) . '
' . "\n"; 48 | } 49 | } 50 | 51 | 52 | } 53 | ?> 54 | -------------------------------------------------------------------------------- /inc/content/english/multi_account_functions.php: -------------------------------------------------------------------------------- 1 | 12 |

Multi Account Functions

13 | query("SELECT * FROM " . DB_PREFIX . "authed_users WHERE 1"); 17 | if ($db->num_rows($qcheck) < 2) { 18 | echo mainFuncs::push_response(29); 19 | } else { 20 | //List all options here 21 | ?> 22 |
23 |
24 | Cross Follow Accounts 25 |
26 | All Follow / Unfollow 27 |
28 | Multi Tweet 29 |
30 |
31 | 32 |
33 |
34 |   35 |
36 | 37 | 41 |
42 | Return to main admin screen 43 | 44 | -------------------------------------------------------------------------------- /inc/content/english/not_logged_in.php: -------------------------------------------------------------------------------- 1 | 12 |

Login

13 | '; 16 | } else { 17 | echo mainFuncs::push_response(6) . '
'; 18 | } 19 | 20 | if (!empty($_POST['username_login'])) { 21 | $username = $_POST['username_login']; 22 | } else { 23 | $username = ""; 24 | } 25 | if (!empty($_POST['password_login'])) { 26 | $password = $_POST['password_login']; 27 | } else { 28 | $password = ""; 29 | } 30 | 31 | 32 | 33 | ?> 34 |
35 | Username:
36 | 37 |
38 | Password:
39 | 40 |
41 | 42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /inc/common/header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | Twando 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 30 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | 40 |
41 | Twando 42 |
43 | 44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /inc/content/english/log_settings.php: -------------------------------------------------------------------------------- 1 | 12 |

Log Settings for

13 | 19 |
20 |
21 | Log Settings 22 |
23 | View Follow Logs 24 |
25 | View Tweet Logs 26 |
27 | Purge Log History 28 |
29 |
30 | 31 |
32 |
33 |   34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 | 45 |
46 | Return to main admin screen 47 | 48 | -------------------------------------------------------------------------------- /inc/content/english/follow_settings.php: -------------------------------------------------------------------------------- 1 | 12 |

Follow / Unfollow Settings for

13 | 18 |
19 | 20 |
25 | Follow Exclusions 26 |
27 | Auto DM Message 28 |
29 | Search to Follow 30 |
31 |
32 | 33 |
34 |
35 |   36 |
37 | 41 |
42 | Return to main admin screen 43 | 44 | -------------------------------------------------------------------------------- /tweet_settings.php: -------------------------------------------------------------------------------- 1 | ' . "\n" . '' . "\n"; 21 | 22 | //Get data here 23 | $q1a = $db->get_user_data($_GET['id']); 24 | if (empty($_POST['a'])) 25 | { $_POST['a'] = ""; } 26 | if ($_POST['a'] == 'csv_upload') { 27 | //Bulk CSV upload 28 | $header_info['on_load'] = "ajax_tweet_settings_tab('tab4');"; 29 | 30 | if ($_FILES['csv_file']['name']) { 31 | 32 | //Not ideal, but saves reminding user to chmod a directory 33 | $handle = @fopen($_FILES['csv_file']['tmp_name'],'r'); 34 | $valid_rows = 0; 35 | while (($data = @fgetcsv($handle, 1000, ",")) !== FALSE) { 36 | if (count($data) == 2) { 37 | $valid_rows ++; 38 | $db->query("INSERT INTO " . DB_PREFIX . "scheduled_tweets (owner_id, tweet_content, time_to_post) 39 | VALUES ('" . $db->prep($q1a['id']) . "','" . $db->prep($data[1]) . "','" . $db->prep($data[0]) . "')"); 40 | } 41 | } 42 | @fclose($handle); 43 | 44 | //Check valid rows 45 | if ($valid_rows == 0) { 46 | $pass_msg = 19; 47 | } else { 48 | $pass_msg = 20; 49 | } 50 | 51 | } 52 | 53 | } 54 | 55 | 56 | 57 | } 58 | 59 | mainFuncs::print_html($page_select); 60 | 61 | include('inc/include_bottom.php'); 62 | ?> 63 | -------------------------------------------------------------------------------- /inc/content/english/tweet_settings.php: -------------------------------------------------------------------------------- 1 | 12 |

Tweet Settings for

13 | 19 |
20 |
21 | Post Quick Tweet 22 |
23 | Scheduled Tweets 24 |
25 | Schedule a Tweet 26 |
29 |
30 | 31 |
32 |
33 |   34 |
35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 |
48 | 49 | 50 |
51 | 52 | 56 |
57 | Return to main admin screen 58 | 59 | -------------------------------------------------------------------------------- /inc/content/english/install_tables.php: -------------------------------------------------------------------------------- 1 | 12 |

Install MySQL Tables

13 | Checking tables....
14 | check_table_exists(DB_PREFIX . $this_table)) { 20 | echo 'Table ' . DB_PREFIX . $this_table . ' created.
' . "\n"; 21 | } else { 22 | echo 'Table ' . DB_PREFIX . $this_table . ' could not be created.
' . "\n"; 23 | $table_error = true; 24 | } 25 | } 26 | if ($table_error == true) { 27 | echo '
One or more tables could not be created. Please check your MySQL settings and try again.
' . "\n"; 28 | } 29 | ?> 30 |

Config Check

31 | Checking config file for basic issues....
32 | Warning: You have not changed the default username and password in config.php.
' . "\n"; 37 | $config_error = true; 38 | } 39 | if (CRON_KEY == 'abc123') { 40 | echo 'Warning: You have not changed the default cron key value in config.php.
' . "\n"; 41 | $config_error = true; 42 | } 43 | if (strlen(DB_PREFIX) > 10) { 44 | echo 'Warning: Your database prefix setting in config.php is unusually long. This could cause issues.
' . "\n"; 45 | $config_error = true; 46 | } 47 | if ($config_error == false) { 48 | echo 'No basic issues found in your config.php file.
' . "\n"; 49 | } 50 | ?> 51 |
52 | Return to main admin screen 53 | -------------------------------------------------------------------------------- /inc/ajax/ajax.log_settings.php: -------------------------------------------------------------------------------- 1 | $_REQUEST['twitter_id'], 21 | 'log_data' => (int)$_REQUEST['log_data'], 22 | 'last_updated' => date("Y-m-d H:i:s") 23 | ); 24 | 25 | $db->store_authed_user($tw_user); 26 | $response_msg = mainFuncs::push_response(8); 27 | break; 28 | case 'tab4': 29 | 30 | if ($_REQUEST['a'] == 'deletelogs') { 31 | //Set query 32 | $base_query = "DELETE FROM " . DB_PREFIX . "cron_logs WHERE owner_id='" . $db->prep($_REQUEST['twitter_id']) . "'"; 33 | if ((int)$_REQUEST['log_time'] == 2) { 34 | $base_query .= " AND last_updated < '" . date("Y-m-d H:i:s",strtotime('-30 days')) . "'"; 35 | } elseif ((int)$_REQUEST['log_time'] == 3) { 36 | $base_query .= " AND last_updated < '" . date("Y-m-d H:i:s",strtotime('-90 days')) . "'"; 37 | } 38 | if ((int)$_REQUEST['log_type'] > 0) { 39 | $base_query .= " AND type = " . (int)$_REQUEST['log_type']; 40 | } 41 | 42 | $db->query($base_query); 43 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "cron_logs"); 44 | 45 | if ((int)$_REQUEST['empty_cache'] == 1) { 46 | $db->query("TRUNCATE TABLE " . DB_PREFIX . "user_cache"); 47 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "user_cache"); 48 | } 49 | $response_msg = mainFuncs::push_response(22); 50 | } 51 | 52 | break; 53 | //End of tab switch 54 | } 55 | 56 | //End of data update POST 57 | } 58 | 59 | //Get account details 60 | if (!$q1a) { 61 | $q1a = $db->get_user_data($_REQUEST['twitter_id']); 62 | } 63 | 64 | 65 | include('../content/' . TWANDO_LANG . '/ajax.log_settings_inc.php'); 66 | 67 | 68 | 69 | //End of is logged in 70 | } 71 | 72 | 73 | include('../include_bottom.php'); 74 | ?> 75 | -------------------------------------------------------------------------------- /inc/ajax/ajax.pagination_inc.php: -------------------------------------------------------------------------------- 1 | query($q2_base); 16 | list($total_items) = $db->fetch_row($db->query("SELECT FOUND_ROWS();")); 17 | 18 | if ($total_items > 0) { 19 | 20 | //Page calculations 21 | $total_pages = 0; 22 | $mod_pages = $total_items % $per_page; 23 | if ($mod_pages != 0) { 24 | $total_pages++; 25 | } 26 | $total_pages += ($total_items - $mod_pages) / $per_page; 27 | 28 | if ($page > $total_pages) {$page = 1;} 29 | 30 | $next_page = $page + 1; 31 | $back_page = $page - 1; 32 | if ($back_page < 1) {$back_page = 0;} 33 | 34 | //Do back links 35 | $back_string = "« "; 36 | if ($back_page == 0) {$back_string = "«";} 37 | 38 | $next_string = " »"; 39 | if ($next_page > $total_pages) {$next_string = "»";} 40 | 41 | //List pages 42 | $page_list_string = ""; 43 | 44 | for ($ij = $page - 2; $ij <= $page +2; $ij++) { 45 | if ( ($ij > 0) and ($ij <= $total_pages) ) { 46 | if ($ij == $page) { 47 | $page_list_string .= "
" . $ij . "
"; 48 | } 49 | else { 50 | $page_list_string .= ""; 51 | } 52 | } 53 | } 54 | 55 | if (($page + 2) < $total_pages) { 56 | $page_list_string .= '
...
"; 57 | } 58 | if (($page - 2) > 1) { 59 | $page_list_string = '
' . "1
" . '
...
' . $page_list_string; 60 | } 61 | 62 | } 63 | ?> 64 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | query("SELECT * FROM " . DB_PREFIX . "ap_settings WHERE id='twando'"); 60 | if ($db->num_rows($q1) == 0) { 61 | //Insert 62 | $db->query("INSERT INTO " . DB_PREFIX . "ap_settings (id,consumer_key,consumer_secret) VALUES 63 | ('twando','" . $db->prep($_POST['consumer_key']) . "','" . $db->prep($_POST['consumer_secret']) . "')"); 64 | 65 | } else { 66 | //Update 67 | $db->query("UPDATE " . DB_PREFIX . "ap_settings SET consumer_key='" . $db->prep($_POST['consumer_key']) . "', consumer_secret='" . $db->prep($_POST['consumer_secret']) . "' 68 | WHERE id='twando'"); 69 | } 70 | 71 | //End of keys update 72 | } 73 | 74 | 75 | } 76 | 77 | 78 | //Check if we need to load twitter api box here 79 | $ap_creds = @$db->get_ap_creds(); 80 | if ( ($ap_creds['consumer_key']) and ($ap_creds['consumer_secret']) ) { 81 | $header_info['on_load'] = "ajax_index_update('0','0');"; 82 | } 83 | 84 | mainFuncs::print_html($page_select); 85 | 86 | include('inc/include_bottom.php'); 87 | ?> 88 | -------------------------------------------------------------------------------- /inc/ajax/ajax.tweet_settings.php: -------------------------------------------------------------------------------- 1 | get_ap_creds(); 16 | $q1a = $db->get_user_data($_REQUEST['twitter_id']); 17 | 18 | if ($_REQUEST['update_type'] == 'update_data') { 19 | //Updates to be done here 20 | switch ($_REQUEST['tab_id']) { 21 | case 'tab1': 22 | 23 | if ($_REQUEST['tweet_content']) { 24 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 25 | $connection->post('statuses/update', array('status' => $_REQUEST['tweet_content'])); 26 | 27 | if ($connection->http_code == 200) { 28 | $response_msg = mainFuncs::push_response(13); 29 | } else { 30 | $response_msg = mainFuncs::push_response(14); 31 | } 32 | } 33 | 34 | break; 35 | 36 | case 'tab2': 37 | 38 | if ( ($_REQUEST['a'] == 'deletetweet') and ($_REQUEST['deltweet_id']) ) { 39 | $db->query("DELETE FROM " . DB_PREFIX . "scheduled_tweets WHERE owner_id='" . $db->prep($q1a['id']) . "' AND id='" . $db->prep($_REQUEST['deltweet_id']) . "'"); 40 | $response_msg = mainFuncs::push_response(16); 41 | } 42 | if ( ($_REQUEST['a'] == 'edittweetsave') and ($_REQUEST['edittweetsave_id']) ) { 43 | $db->query("UPDATE " . DB_PREFIX . "scheduled_tweets SET tweet_content='" . $db->prep($_REQUEST['tweet_content']) . "', time_to_post='" . $db->prep($_REQUEST['time_to_post']) . "' WHERE owner_id='" . $db->prep($q1a['id']) . "' AND id='" . $db->prep($_REQUEST['edittweetsave_id']) . "'"); 44 | $response_msg = mainFuncs::push_response(17); 45 | } 46 | break; 47 | 48 | case 'tab3': 49 | if ($_REQUEST['tweet_content']) { 50 | $db->query("INSERT INTO " . DB_PREFIX . "scheduled_tweets (owner_id, tweet_content, time_to_post) 51 | VALUES ('" . $db->prep($q1a['id']) . "','" . $db->prep($_REQUEST['tweet_content']) . "','" . $db->prep($_REQUEST['time_to_post']) . "')"); 52 | $response_msg = mainFuncs::push_response(18); 53 | } 54 | break; 55 | //End of tab switch 56 | } 57 | 58 | //End of data update POST 59 | } 60 | 61 | 62 | include('../content/' . TWANDO_LANG . '/ajax.tweet_settings_inc.php'); 63 | 64 | 65 | //End of is logged in 66 | } 67 | 68 | 69 | include('../include_bottom.php'); 70 | ?> 71 | -------------------------------------------------------------------------------- /inc/ajax/ajax.index.php: -------------------------------------------------------------------------------- 1 | get_ap_creds();} 15 | 16 | if ( ($_REQUEST['update_type'] == 'refresh') and ($_REQUEST['id']) ) { 17 | //Refresh 18 | $q1a = $db->get_user_data($_REQUEST['id']); 19 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 20 | $content = $connection->get('account/verify_credentials'); 21 | 22 | if ($connection->http_code == 200) { 23 | //Update DB 24 | $tw_user = array('id' => $q1a['id'], 25 | 'profile_image_url' => $content->profile_image_url_https, 26 | 'screen_name' => $content->screen_name, 27 | 'followers_count' => $content->followers_count, 28 | 'friends_count' => $content->friends_count, 29 | 'last_updated' => date("Y-m-d H:i:s") 30 | ); 31 | 32 | $db->store_authed_user($tw_user); 33 | $response_msg = mainFuncs::push_response(9); 34 | } else { 35 | $response_msg = mainFuncs::push_response(12); 36 | } 37 | } 38 | 39 | elseif ( ($_REQUEST['update_type'] == 'delete') and ($_REQUEST['id']) ) { 40 | //Delete 41 | $db->query("DELETE FROM " . DB_PREFIX . "authed_users WHERE id='" . $db->prep($_REQUEST['id']) . "'"); 42 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "authed_users"); 43 | $db->query("DELETE FROM " . DB_PREFIX . "follow_exclusions WHERE owner_id='" . $db->prep($_REQUEST['id']) . "'"); 44 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "follow_exclusions"); 45 | $db->query("DELETE FROM " . DB_PREFIX . "cron_logs WHERE owner_id='" . $db->prep($_REQUEST['id']) . "'"); 46 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "cron_logs"); 47 | $db->query("DELETE FROM " . DB_PREFIX . "scheduled_tweets WHERE owner_id='" . $db->prep($_REQUEST['id']) . "'"); 48 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "scheduled_tweets"); 49 | $db->query("DROP TABLE `". DB_PREFIX . "fw_" . $_REQUEST['id'] . "`"); 50 | $db->query("DROP TABLE `". DB_PREFIX . "fr_" . $_REQUEST['id'] . "`"); 51 | $response_msg = mainFuncs::push_response(4); 52 | } 53 | 54 | 55 | 56 | include('../content/' . TWANDO_LANG . '/ajax.index_inc.php'); 57 | 58 | //End of is logged in 59 | } 60 | 61 | 62 | include('../include_bottom.php'); 63 | 64 | ?> 65 | -------------------------------------------------------------------------------- /inc/content/english/ajax.index_inc.php: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 12 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | query("SELECT * FROM " . DB_PREFIX . "authed_users WHERE 1 ORDER BY screen_name ASC"); 36 | while ($q1a = $db->fetch_array($q1)) { 37 | ?> 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 61 |
IDScreen NameProfile ImageFollowingFollowersSettings & Options
FollowsTweetsLogsClick to refresh user details for this Twitter idClick to delete this account from your Twando application
You haven't yet authorized any Twitter accounts
62 | 63 | -------------------------------------------------------------------------------- /callback.php: -------------------------------------------------------------------------------- 1 | get_ap_creds(); 18 | 19 | //Connect to Twitter 20 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']); 21 | $access_token = $connection->getAccessToken($_REQUEST['oauth_verifier']); 22 | $_SESSION['access_token'] = $access_token; 23 | 24 | //Remove request tokens 25 | unset($_SESSION['oauth_token']); 26 | unset($_SESSION['oauth_token_secret']); 27 | 28 | //Check response 29 | if ($connection->http_code == 200) { 30 | //All OK, store credentials in database for this user 31 | $access_token = $_SESSION['access_token']; 32 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $access_token['oauth_token'], $access_token['oauth_token_secret']); 33 | $content = $connection->get('account/verify_credentials'); 34 | 35 | //Update DB 36 | $tw_user = array('id' => $content->id, 37 | 'oauth_token' => $access_token['oauth_token'], 38 | 'oauth_token_secret' => $access_token['oauth_token_secret'], 39 | 'profile_image_url' => $content->profile_image_url_https, 40 | 'screen_name' => $content->screen_name, 41 | 'followers_count' => $content->followers_count, 42 | 'friends_count' => $content->friends_count, 43 | 'log_data' => 1, 44 | 'last_updated' => date("Y-m-d H:i:s") 45 | ); 46 | 47 | $db->store_authed_user($tw_user); 48 | $db->create_cron_tables($tw_user['id']); 49 | 50 | /* 51 | This is probably the bit you're looking for :) Twando is a completely free 52 | script; to help us see usage, the script will (as stated on the website) follow the 53 | Twando Twitter account when you first auth an account (and only at this stage). 54 | Nothing is posted to your timeline or anything like that. Of course, we'll follow 55 | you back soon! If you think this is too much to ask, perhaps you should write 56 | your own script instead of using this one :) 57 | */ 58 | 59 | $connection->post('friendships/create', array('user_id' => 104866576)); 60 | unset($_SESSION['access_token']); 61 | header('Location: ' . BASE_LINK_URL . '?msg=1'); 62 | 63 | } else { 64 | //Something went wrong - redirect with error to index page 65 | header('Location: ' . BASE_LINK_URL . '?msg=2'); 66 | 67 | } 68 | 69 | //End of not logged in 70 | } 71 | 72 | include('inc/include_bottom.php'); 73 | -------------------------------------------------------------------------------- /cron_tweet.php: -------------------------------------------------------------------------------- 1 | get_cron_state('tweet') == 1) { 22 | echo mainFuncs::push_response(24); 23 | $run_cron = false; 24 | } 25 | } 26 | 27 | if ($run_cron == true) { 28 | 29 | /* 30 | New to 0.3 - Some people on super cheap hosting seem to get 31 | SQL errors - output them if they occur 32 | */ 33 | $db->output_error = 1; 34 | 35 | //Set cron status 36 | $cron->set_cron_state('tweet',1); 37 | 38 | //Get credentials 39 | $ap_creds = $db->get_ap_creds(); 40 | 41 | //Loop through all accounts 42 | $q1 = $db->query("SELECT * FROM " . DB_PREFIX . "authed_users ORDER BY (followers_count + friends_count) ASC"); 43 | while ($q1a = $db->fetch_array($q1)) { 44 | 45 | //Defines 46 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 47 | $cron->set_user_id($q1a['id']); 48 | $cron->set_log($q1a['log_data']); 49 | 50 | //Time can vary between PHP and server if server timezone doesn't match PHP timezone. 51 | //All scripts use PHP time rather than NOW() to avoid issues. 52 | $current_time = date("Y-m-d H:i:s"); 53 | 54 | //Get scheduled tweets for this user older than or equal to current time 55 | $q2 = $db->query("SELECT * FROM " . DB_PREFIX . "scheduled_tweets WHERE owner_id = '" . $db->prep($q1a['id']) . "' AND time_to_post != '0000-00-00 00:00:00' AND time_to_post <= '" . $db->prep($current_time) . "' ORDER BY time_to_post ASC"); 56 | while ($q2a = $db->fetch_array($q2)) { 57 | 58 | //Post the tweet 59 | $connection->post('statuses/update', array('status' => $q2a['tweet_content'])); 60 | 61 | //Log result - reasons for a non 200 include duplicate tweets, too many tweets 62 | //posted in a period of time, etc etc. 63 | if ($connection->http_code == 200) { 64 | $cron->store_cron_log(2,$cron_txts[18] . $q2a['tweet_content'] . $cron_txts[19],''); 65 | } else { 66 | $cron->store_cron_log(2,$cron_txts[18] . $q2a['tweet_content'] . $cron_txts[20],''); 67 | } 68 | 69 | //Delete the tweet 70 | $db->query("DELETE FROM " . DB_PREFIX . "scheduled_tweets WHERE owner_id = '" . $db->prep($q1a['id']) . "' AND id = '" . $q2a['id'] . "'"); 71 | 72 | } 73 | 74 | //End of db loop 75 | } 76 | 77 | //Optimize tweet table 78 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "scheduled_tweets"); 79 | 80 | //Set cron status 81 | $cron->set_cron_state('tweet',0); 82 | 83 | echo mainFuncs::push_response(32); 84 | 85 | //End of run cron 86 | } 87 | 88 | include('inc/include_bottom.php'); 89 | ?> 90 | -------------------------------------------------------------------------------- /inc/content/english/ajax.multi_account_functions_inc.php: -------------------------------------------------------------------------------- 1 | 12 |

Cross Follow Accounts

13 | 14 |
15 |   all your accounts from all your other accounts?
19 | 20 |
21 | 22 | 26 |

All Follow / Unfollow

27 | 28 |
29 |   all the following    from all your Twitter accounts (enter one screen name / Twitter id per line):
36 |
37 | 38 |
39 | 40 | 44 |

Multi Tweet

45 | 46 | Post the following tweet from all your Twitter accounts at once:
47 |
48 |
49 | Characters: 50 |
51 |
52 | 53 | 54 | 55 | 62 | 63 | -------------------------------------------------------------------------------- /inc/ajax/ajax.multi_account_functions.php: -------------------------------------------------------------------------------- 1 | 0) ) { 22 | //Get ap creds 23 | $ap_creds = $db->get_ap_creds(); 24 | $ua = $db->get_all_user_data(); 25 | $ua2 = $ua; 26 | 27 | //Loop 28 | foreach ($ua as $this_id => $this_data) { 29 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $this_data['oauth_token'], $this_data['oauth_token_secret']); 30 | foreach ($ua2 as $this_id2 => $this_data2) { 31 | if ($this_id != $this_id2) { 32 | if ((int)$_REQUEST['cross_op'] == 1) { 33 | $connection->post('friendships/create',array('user_id' => $this_id2)); 34 | } elseif ((int)$_REQUEST['cross_op'] == 2) { 35 | $connection->post('friendships/destroy',array('user_id' => $this_id2)); 36 | } 37 | } 38 | } 39 | } 40 | 41 | 42 | //Response message - nothing too descriptive really required here 43 | $response_msg = mainFuncs::push_response(30); 44 | 45 | } 46 | 47 | break; 48 | case 'tab2': 49 | 50 | if ( ($_REQUEST['a'] == 'allfollow_go') and ($_REQUEST['twitter_ids_list']) ) { 51 | 52 | //Get data 53 | $screen_names = explode("\n",str_replace("\r",'',$_REQUEST['twitter_ids_list'])); 54 | $ap_creds = $db->get_ap_creds(); 55 | $ua = $db->get_all_user_data(); 56 | 57 | //Set options 58 | if ((int)$_REQUEST['cross_op'] == 1) {$twt_op = 'friendships/create';} 59 | elseif ((int)$_REQUEST['cross_op'] == 2) {$twt_op = 'friendships/destroy';} 60 | if ((int)$_REQUEST['cross_type'] == 1) {$usr_op = 'screen_name';} 61 | elseif ((int)$_REQUEST['cross_type'] == 2) {$usr_op = 'user_id';} 62 | 63 | //Loop 64 | foreach ($ua as $this_id => $this_data) { 65 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $this_data['oauth_token'], $this_data['oauth_token_secret']); 66 | foreach ($screen_names as $this_name) { 67 | $this_name = trim($this_name); 68 | $connection->post($twt_op, array($usr_op => $this_name)); 69 | } 70 | } 71 | 72 | //Response message - nothing too descriptive really required here 73 | $response_msg = mainFuncs::push_response(30); 74 | 75 | } 76 | 77 | break; 78 | case 'tab3': 79 | 80 | if ( ($_REQUEST['a'] == 'quicktweet') and ($_REQUEST['tweet_content']) ) { 81 | //Get Data 82 | $ap_creds = $db->get_ap_creds(); 83 | $ua = $db->get_all_user_data(); 84 | foreach ($ua as $this_id => $this_data) { 85 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $this_data['oauth_token'], $this_data['oauth_token_secret']); 86 | $connection->post('statuses/update', array('status' => $_REQUEST['tweet_content'])); 87 | } 88 | 89 | //Response message - nothing too descriptive really required here 90 | $response_msg = mainFuncs::push_response(31); 91 | 92 | } 93 | 94 | break; 95 | //End of tab switch 96 | } 97 | 98 | //End of data update POST 99 | } 100 | 101 | include('../content/' . TWANDO_LANG . '/ajax.multi_account_functions_inc.php'); 102 | 103 | 104 | //End of is logged in 105 | } 106 | 107 | 108 | include('../include_bottom.php'); 109 | ?> 110 | -------------------------------------------------------------------------------- /inc/content/english/index.php: -------------------------------------------------------------------------------- 1 | 0) { 15 | $response_msg = mainFuncs::push_response((int)$_GET['msg']); 16 | } 17 | } else { 18 | $response_msg = ""; 19 | } 20 | ?> 21 | 24 |

Register Your Application

25 | Before you can start using Twando, you must first register your application with Twitter. 26 |
    27 |
  1. First, click here to install the MySQL tables if you have not done so already.
  2. 28 |
  3. Next, visit https://dev.twitter.com/apps/new; you will need to sign in to your Twitter account to register an app.
  4. 29 |
  5. Enter the values as demonstrated in this picture; your application URL is ; your callback URL is .
  6. 30 |
  7. You will then be given a consumer key and consumer secret (example). Enter these in the boxes below to complete the setup of your Twitter app.
  8. 31 |
  9. You must also make sure your application is set with read/write access on the settings page; click here to view.
  10. 32 |
33 |
34 | Consumer Key:
35 | 36 |
37 | Consumer Secret:
38 | 39 |
40 | 41 | 42 |
43 | 46 |

Application Details

47 | Your application has been registered with a consumer key and a consumer secret. If you would like to update 48 | these credentials, please click here. 49 | 61 |

Authorized Twitter Accounts

62 | ' . $response_msg . '
'; 65 | } 66 | ?> 67 |
68 |   69 |
70 | To authorize another account, make sure you are either signed out of all accounts or signed into the account you want to authorize on Twitter before clicking the button below. 71 |
72 | Sign in with Twitter 73 |

Further Options

74 | Multi account functions
75 | Cron job instructions
76 | 80 | -------------------------------------------------------------------------------- /inc/style/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Twando.com Free PHP Twitter Application 3 | http://www.twando.com/ 4 | */ 5 | 6 | body { 7 | margin: 0px; 8 | padding: 0px 0px 10px 0px; 9 | border: 0px; 10 | font-size: 14px; 11 | font-family: Arial; 12 | background: #CCCCCC; 13 | line-height: 1.3em; 14 | } 15 | 16 | body a:link {color : #0000d1; text-decoration:none;} 17 | body a:visited{color : #0000d1; text-decoration:none;} 18 | body a:active {color : #0000d1; text-decoration:none;} 19 | body a:hover {color : #ff0000; text-decoration:underline;} 20 | 21 | img {border: 0;} 22 | form {display: inline;} 23 | ul {list-style-type:square; list-style-position:outside;} 24 | td {padding: 1px;} 25 | .success {color: #45A70F; font-weight: bold;} 26 | .error {color: #FF0000; font-weight: bold;} 27 | .input_box_style {border: 1px solid #616161; padding: 2px; font-family: Arial; width: 250px; margin: 2px 0px 2px 0px; background: #fff;} 28 | .submit_button_style {font-size: 16px; padding: 6px; cursor: pointer; margin: 6px 0px 0px 0px;} 29 | h2 {font-size: 16px; font-weight: bold;} 30 | 31 | #container { 32 | position:relative; 33 | width: 960px; 34 | margin-left: auto; 35 | margin-right: auto; 36 | overflow: hidden; 37 | } 38 | 39 | #header_main { 40 | float: left; 41 | width: 100%; 42 | height: 65px; 43 | margin: 5px 0px 5px 0px; 44 | text-align: center; 45 | } 46 | 47 | #centre_main { 48 | float: left; 49 | width: 100%; 50 | margin: 5px 0px 5px 0px; 51 | } 52 | 53 | #centre_main #cred_update_div { 54 | width: 100%; 55 | clear: both; 56 | margin: 5px 0px 5px 0px; 57 | } 58 | 59 | #centre_main .follow_ex_block { 60 | float: left; 61 | width: 460px; 62 | } 63 | 64 | #centre_main .follow_scroll_block { 65 | float: left; 66 | width: 450px; 67 | height: 260px; 68 | overflow-y: auto; 69 | overflow-x: hidden; 70 | } 71 | 72 | #centre_main .tab_row { 73 | float: left; 74 | width: 100%; 75 | height: 30px; 76 | background: url(../images/tab_bg.gif) bottom repeat-x; 77 | } 78 | 79 | #centre_main .tab_main { 80 | float: left; 81 | font-weight: bold; 82 | border: 1px solid #33ccff; 83 | text-align: center; 84 | padding: 5px 15px 5px 15px; 85 | margin: 0px 10px 0px 0px; 86 | background: #fff; 87 | height: 18px; 88 | } 89 | 90 | #centre_main .affuser { 91 | float: left; 92 | width: 24px; 93 | height: 24px; 94 | margin: 0px 2px 2px 0px; 95 | } 96 | 97 | #centre_main .cron_row { 98 | float: left; 99 | width: 100%; 100 | clear: both; 101 | } 102 | 103 | #centre_main .cron_left { 104 | float: left; 105 | width: 150px; 106 | margin: 3px 0px 0px 0px; 107 | } 108 | 109 | #centre_main .cron_right { 110 | float: left; 111 | width: 805px; 112 | } 113 | 114 | 115 | 116 | .page_nav { 117 | float: left; 118 | width: 948px; 119 | padding: 5px; 120 | border: 1px solid #33ccff; 121 | margin: 6px 0px 6px 0px; 122 | font-size: 12px; 123 | } 124 | 125 | .page_nav .left_side { 126 | float: left; 127 | padding: 3px 0px 0px 0px; 128 | } 129 | 130 | .page_nav .right_side { 131 | float: right; 132 | text-align: right; 133 | } 134 | 135 | .page_nav .box { 136 | float: left; 137 | border: 1px solid #33ccff; 138 | padding: 2px; 139 | margin: 0px 0px 0px 3px; 140 | } 141 | 142 | .page_nav .boxw { 143 | float: left; 144 | border: 1px solid #9AE4E8; 145 | padding: 2px; 146 | margin: 0px 0px 0px 3px; 147 | } 148 | 149 | 150 | #footer { 151 | float: left; 152 | width: 100%; 153 | border-top: 2px solid #fff; 154 | text-align: center; 155 | margin: 10px 0px 10px 0px; 156 | padding: 5px 0px 0px 0px; 157 | } 158 | 159 | table.data_table { 160 | width: 100%; 161 | border-top: 1px solid #616161; 162 | border-left: 1px solid #616161; 163 | border-collapse: collapse; 164 | text-align: left; 165 | margin: 6px 0px 6px 0px; 166 | } 167 | 168 | table.data_table td { 169 | padding: 2px 5px 2px 5px; 170 | border-bottom: 1px solid #616161; 171 | border-right: 1px solid #616161; 172 | } 173 | 174 | table.data_table td.heading { 175 | font-weight: bold; 176 | font-size: 16px; 177 | background: #fff; 178 | color: #333; 179 | } 180 | 181 | -------------------------------------------------------------------------------- /install_tables.php: -------------------------------------------------------------------------------- 1 | query(" 24 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "ap_settings` ( 25 | `id` varchar(10) NOT NULL, 26 | `consumer_key` varchar(255) NOT NULL, 27 | `consumer_secret` varchar(255) NOT NULL, 28 | PRIMARY KEY (`id`) 29 | ); 30 | "); 31 | 32 | 33 | $db->query(" 34 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "authed_users` ( 35 | `id` varchar(48) NOT NULL, 36 | `oauth_token` varchar(255) NOT NULL, 37 | `oauth_token_secret` varchar(255) NOT NULL, 38 | `profile_image_url` text NOT NULL, 39 | `screen_name` varchar(32) NOT NULL, 40 | `followers_count` int(11) NOT NULL, 41 | `friends_count` int(11) NOT NULL, 42 | `auto_follow` tinyint(1) NOT NULL default '0', 43 | `auto_unfollow` tinyint(1) NOT NULL default '0', 44 | `auto_dm` tinyint(1) NOT NULL default '0', 45 | `auto_dm_msg` text NOT NULL, 46 | `log_data` tinyint(1) NOT NULL default '0', 47 | `fr_fw_fp` tinyint(1) NOT NULL default '0', 48 | `last_updated` datetime NOT NULL, 49 | PRIMARY KEY (`id`) 50 | ); 51 | "); 52 | 53 | $db->query(" 54 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "cron_logs` ( 55 | `id` int(11) NOT NULL auto_increment, 56 | `owner_id` varchar(48) NOT NULL, 57 | `type` tinyint(1) NOT NULL default '0', 58 | `log_text` text NOT NULL, 59 | `affected_users` longtext NOT NULL, 60 | `last_updated` datetime NOT NULL, 61 | PRIMARY KEY (`id`), 62 | KEY `owner_id` (`owner_id`), 63 | KEY `type` (`type`) 64 | ); 65 | "); 66 | 67 | $db->query(" 68 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "cron_status` ( 69 | `cron_name` varchar(10) NOT NULL default '', 70 | `cron_state` tinyint(1) NOT NULL default '0', 71 | `last_updated` datetime NOT NULL default '0000-00-00 00:00:00', 72 | PRIMARY KEY (`cron_name`) 73 | ); 74 | "); 75 | 76 | $db->query(" 77 | INSERT INTO `" . DB_PREFIX . "cron_status` (`cron_name`, `cron_state`, `last_updated`) VALUES 78 | ('follow', 0, '0000-00-00 00:00:00'), 79 | ('tweet', 0, '0000-00-00 00:00:00'); 80 | "); 81 | 82 | $db->query(" 83 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "follow_exclusions` ( 84 | `type` tinyint(1) NOT NULL default '0', 85 | `owner_id` varchar(48) NOT NULL, 86 | `twitter_id` varchar(48) NOT NULL, 87 | `screen_name` varchar(32) NOT NULL, 88 | `profile_image_url` text NOT NULL, 89 | `last_updated` datetime NOT NULL, 90 | PRIMARY KEY (`type`,`owner_id`,`twitter_id`), 91 | KEY `type` (`type`), 92 | KEY `owner_id` (`owner_id`) 93 | ); 94 | "); 95 | 96 | $db->query(" 97 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "scheduled_tweets` ( 98 | `id` int(11) NOT NULL auto_increment, 99 | `owner_id` varchar(48) NOT NULL, 100 | `tweet_content` text NOT NULL, 101 | `time_to_post` datetime NOT NULL, 102 | PRIMARY KEY (`id`), 103 | KEY `owner_id` (`owner_id`), 104 | KEY `time_to_post` (`time_to_post`) 105 | ); 106 | "); 107 | 108 | $db->query(" 109 | CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "user_cache` ( 110 | `twitter_id` varchar(48) NOT NULL, 111 | `screen_name` varchar(32) NOT NULL, 112 | `actual_name` varchar(64) NOT NULL, 113 | `profile_image_url` text NOT NULL, 114 | `followers_count` int(11) NOT NULL default '0', 115 | `friends_count` int(11) NOT NULL default '0', 116 | `last_updated` datetime NOT NULL, 117 | PRIMARY KEY (`twitter_id`), 118 | KEY `screen_name` (`screen_name`) 119 | ); 120 | "); 121 | 122 | } 123 | 124 | mainFuncs::print_html($page_select); 125 | 126 | include('inc/include_bottom.php'); 127 | ?> 128 | -------------------------------------------------------------------------------- /inc/content/english/cron_instructions.php: -------------------------------------------------------------------------------- 1 | 13 |

Cron Job Instructions

14 | In order to fully use the Twando system, there are two cron jobs you must set up. Usually you can set up these cron jobs very easily via your hosting control panel (or directly in SSH if you have access). However if for 15 | any reason you don't have local cron job access, you can also call these files remotely. 16 |

Follow Cron Job

17 | This cron is used to execute the auto follow/unfollow/DM commands. You should run it at a time when you are not using API requests (e.g. during the night). We recommend running the cron once per day, but you can run it more 18 | frequently if you wish. Even if you don't have auto follow or unfollow enabled, you should still run this cron job as it will record who followed and unfollowed each account. 19 |

20 |
21 |
Local Command:
22 |
23 |
24 |
25 |
Remote URL:
26 |
27 |
28 |
29 |

Tweet Cron Job

30 | This cron is used to post your scheduled tweets. When the cron file is executed, tweets scheduled for that time or before will be posted to Twitter, so how often you run this cron really depends how often you need to post scheduled tweets. Calling the cron 31 | every five minutes is probably reasonable. If you never schedule any tweets you don't need to set up this cron. 32 |

33 |
34 |
Local Command:
35 |
36 |
37 |
38 |
Remote URL:
39 |
40 |
41 |
42 |

Cron Status

43 | It's important you don't stop the cron jobs while they are running as logs will then be lost for that pass and other unexpected issues can occur. If you do for some unavoidable reason stop a cron job while it is processing, the system will think the cron is still running 44 | and you will not be able to run the cron again (you will receive a "cron already running" error). You can reset the cron running status below if this happens. 45 |

46 | query("SELECT cron_state,last_updated FROM " . DB_PREFIX . "cron_status WHERE cron_name = '" . $this_cron_type . "'"); 51 | $q2a = $db->fetch_array($q2); 52 | 53 | //Set Date 54 | $output_date = ""; 55 | if ($q2a['last_updated'] == '0000-00-00 00:00:00') { 56 | $output_date = "Cron has never been run"; 57 | } else { 58 | $ouput_date = 'Cron state logged on ' . date(TIMESTAMP_FORMAT,strtotime($q2a['last_updated'])); 59 | } 60 | 61 | //Output status 62 | echo '
' . "\n"; 63 | echo '
' . ucwords($this_cron_type) . ' cron job state:
'; 64 | echo '
'; 65 | if ($q2a['cron_state'] == 0) {echo 'Not running'; $ouput_date = "";} 66 | elseif ($q2a['cron_state'] == 1) {echo 'Running';} 67 | echo '   (' . $ouput_date . ')'; 68 | echo '
' . "\n"; 69 | echo '
' . "\n"; 70 | 71 | } 72 | ?> 73 |
74 |
75 | Return to main admin screen 76 | -------------------------------------------------------------------------------- /inc/content/english/lang.php: -------------------------------------------------------------------------------- 1 | 'Account authorized sucessfully','type'=>'success'); 10 | $response_msgs[2] = array('text'=>'There was an error while trying to authorize that account','type'=>'error'); 11 | $response_msgs[3] = array('text'=>'Couldn\'t connect to Twitter. Please check your consumer key and consumer secret values','type'=>'error'); 12 | $response_msgs[4] = array('text'=>'Account removed','type'=>'success'); 13 | $response_msgs[5] = array('text'=>'Your username/password combination is incorrect. Please try again','type'=>'error'); 14 | $response_msgs[6] = array('text'=>'You must be signed in to access this page!','type'=>'error'); 15 | $response_msgs[7] = array('text'=>'Invalid Twitter ID specified. Unable to set options','type'=>'error'); 16 | $response_msgs[8] = array('text'=>'Settings saved','type'=>'success'); 17 | $response_msgs[9] = array('text'=>'User details (screen name, profile image, follower counts etc) refreshed','type'=>'success'); 18 | $response_msgs[10] = array('text'=>'Selected Twitter IDs deleted','type'=>'success'); 19 | $response_msgs[11] = array('text'=>'Selected Twitter users added to list','type'=>'success'); 20 | $response_msgs[12] = array('text'=>'Unable to connect to Twitter to refresh details','type'=>'error'); 21 | $response_msgs[13] = array('text'=>'Tweet posted sucessfully','type'=>'success'); 22 | $response_msgs[14] = array('text'=>'Unable to post tweet','type'=>'error'); 23 | $response_msgs[15] = array('text'=>'Could not connect to MySQL database. Please check your connection settings.','type'=>'error'); 24 | $response_msgs[16] = array('text'=>'Scheduled tweet deleted','type'=>'success'); 25 | $response_msgs[17] = array('text'=>'Scheduled tweet edited','type'=>'success'); 26 | $response_msgs[18] = array('text'=>'Tweet scheduled','type'=>'success'); 27 | $response_msgs[19] = array('text'=>'No CSV rows extracted successfully for scheduled tweets','type'=>'error'); 28 | $response_msgs[20] = array('text'=>'CSV upload successful. Tweets added to database','type'=>'success'); 29 | $response_msgs[21] = array('text'=>'Auto DM message updated','type'=>'success'); 30 | $response_msgs[22] = array('text'=>'Cron logs purged','type'=>'success'); 31 | $response_msgs[23] = array('text'=>'Invalid cron key','type'=>'error'); 32 | $response_msgs[24] = array('text'=>'Cron already running','type'=>'error'); 33 | $response_msgs[25] = array('text'=>'No users found for that search. You could already be following all results, or you might have exceeded your API limits.','type'=>'error'); 34 | $response_msgs[26] = array('text'=>'All selected users followed successfully','type'=>'success'); 35 | $response_msgs[27] = array('text'=>'Some selected users followed successfully, but the API rejected some follow requests','type'=>'success'); 36 | $response_msgs[28] = array('text'=>'All follow requests rejected by the API','type'=>'error'); 37 | $response_msgs[29] = array('text'=>'At least 2 authed Twitter accounts are required to perform multi account functions','type'=>'error'); 38 | $response_msgs[30] = array('text'=>'Follow/unfollow requests sent to API','type'=>'success'); 39 | $response_msgs[31] = array('text'=>'Tweet posts sent to API','type'=>'success'); 40 | $response_msgs[32] = array('text'=>'Cron run complete','type'=>'success'); 41 | $response_msgs[33] = array('text'=>'Twando was unable to look up those users. This could be a temporary issue with the Twitter API, or the users might not exist.','type'=>'error'); 42 | 43 | //English cron text 44 | $cron_txts = array(); 45 | $cron_txts[1] = 'API error. All follow operations disabled for this pass.'; 46 | $cron_txts[2] = 'follower'; 47 | $cron_txts[3] = 'followers'; 48 | $cron_txts[4] = 'friend'; 49 | $cron_txts[5] = 'friends'; 50 | $cron_txts[6] = ' found on first pass.'; 51 | $cron_txts[7] = ' new users followed this account since last update.'; 52 | $cron_txts[8] = ' users unfollowed this account since last update.'; 53 | $cron_txts[9] = ' new users followed by this account since last update.'; 54 | $cron_txts[10] = ' users unfollowed by this account since last update.'; 55 | $cron_txts[11] = 'users'; 56 | $cron_txts[12] = 'user'; 57 | $cron_txts[13] = ' unfollowed automatically.'; 58 | $cron_txts[14] = ' followed back automatically.'; 59 | $cron_txts[15] = 'DM'; 60 | $cron_txts[16] = 'DMs'; 61 | $cron_txts[17] = ' automatically sent to new followers.'; 62 | $cron_txts[18] = 'Tweet "'; 63 | $cron_txts[19] = '" posted succesfully.'; 64 | $cron_txts[20] = '" failed to post.'; 65 | 66 | ?> 67 | -------------------------------------------------------------------------------- /inc/scripts/ajax_scripts.js: -------------------------------------------------------------------------------- 1 | /* 2 | Twando.com Free PHP Twitter Application 3 | http://www.twando.com/ 4 | */ 5 | 6 | //Basic defines 7 | $.ajaxSetup ({cache: false}); 8 | var ajax_load = '
Loading...
'; 9 | 10 | //Index page method for refresh and delete 11 | function ajax_index_update(this_update_type,twitter_id) { 12 | $("#twitter_user_table").html(ajax_load).load('inc/ajax/ajax.index.php?update_type=' + this_update_type + '&id=' + twitter_id); 13 | }; 14 | 15 | //Tab select content 16 | function tab_highlight(tab_id) { 17 | $('.tab_main').css('background', '#fff'); 18 | $('.tab_main').css('border-bottom', '1px solid #33ccff'); 19 | $('#' + tab_id).css('background', 'none'); 20 | $('#' + tab_id).css('border-bottom', '1px solid #9AE4E8'); 21 | } 22 | 23 | //Content selector follow settings page 24 | function ajax_follow_settings_tab(tab_id) { 25 | tab_highlight(tab_id); 26 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.follow_settings.php?update_type=show_tab&tab_id=' + tab_id + '&twitter_id=' + $('#twitter_id').val()); 27 | } 28 | 29 | //Update data follow settings page 30 | function ajax_follow_settings_update(tab_id,form_id) { 31 | var form_data = $("#" + form_id).serialize() + '&update_type=update_data&form_id=' + form_id + '&tab_id=' + tab_id + '&twitter_id=' + $('#twitter_id').val(); 32 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.follow_settings.php',form_data); 33 | } 34 | 35 | //Content selector follow settings page 36 | function ajax_tweet_settings_tab(tab_id) { 37 | tab_highlight(tab_id); 38 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.tweet_settings.php?update_type=show_tab&tab_id=' + tab_id + '&twitter_id=' + $('#twitter_id').val() + '&pass_msg=' + $('#pass_msg').val()); 39 | } 40 | 41 | //Update data tweet settings page 42 | function ajax_tweet_settings_update(tab_id,form_id) { 43 | var form_data = $("#" + form_id).serialize() + '&update_type=update_data&form_id=' + form_id + '&tab_id=' + tab_id + '&page_id=' + $('#page_id').val() + '&twitter_id=' + $('#twitter_id').val(); 44 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.tweet_settings.php',form_data); 45 | } 46 | 47 | //Tweet settings page pagination 48 | function ajax_tweet_settings_set_page(tab_id,set_page) { 49 | $("#page_id").val(set_page); 50 | ajax_tweet_settings_update(tab_id,''); 51 | } 52 | 53 | //Delete scheduled tweet 54 | function ajax_tweet_settings_del_tweet(tweet_id,page_pass) { 55 | $("#page_id").val(page_pass); 56 | $("#deltweet_id").val(tweet_id); 57 | ajax_tweet_settings_update('tab2','deltweet'); 58 | } 59 | 60 | //Edit scheduled tweet 61 | function ajax_tweet_settings_edit_tweet_load(tweet_id) { 62 | $("#edittweet_id").val(tweet_id); 63 | ajax_tweet_settings_update('tab2','edittweet'); 64 | } 65 | 66 | //Content selector follow settings page 67 | function ajax_log_settings_tab(tab_id) { 68 | tab_highlight(tab_id); 69 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.log_settings.php?update_type=show_tab&tab_id=' + tab_id + '&twitter_id=' + $('#twitter_id').val()); 70 | } 71 | 72 | //Update data log settings page 73 | function ajax_log_settings_update(tab_id,form_id) { 74 | var form_data = $("#" + form_id).serialize() + '&update_type=update_data&form_id=' + form_id + '&tab_id=' + tab_id + '&page_id=' + $('#page_id').val() + '&twitter_id=' + $('#twitter_id').val(); 75 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.log_settings.php',form_data); 76 | } 77 | 78 | //Log page pagination 79 | function ajax_log_settings_set_page(tab_id,set_page) { 80 | $("#page_id").val(set_page); 81 | ajax_log_settings_update(tab_id,''); 82 | } 83 | 84 | //Select All Checkboxes 85 | function ajax_check_all() { 86 | $('.dcheck').prop('checked','checked'); 87 | } 88 | 89 | //Multi account functions page 90 | function ajax_multi_account_tab(tab_id) { 91 | tab_highlight(tab_id); 92 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.multi_account_functions.php?update_type=show_tab&tab_id=' + tab_id); 93 | } 94 | 95 | //Update data multi account functions page 96 | function ajax_multi_account_update(tab_id,form_id) { 97 | var form_data = $("#" + form_id).serialize() + '&update_type=update_data&form_id=' + form_id + '&tab_id=' + tab_id + '&page_id=' + $('#page_id').val() + '&twitter_id=' + $('#twitter_id').val(); 98 | $("#update_div").html(ajax_load).load('inc/ajax/ajax.multi_account_functions.php',form_data); 99 | } 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /inc/scripts/anytime.c.css: -------------------------------------------------------------------------------- 1 | /* anytimec.css 4.1112L (anytime.css 4.1112L) 2 | Copyright 2008-2012 Andrew M. Andrews III 3 | License: http://creativecommons.org/licenses/by-nc-sa/3.0/ 4 | Any+Time is a trademark of Andrew M. Andrews III. */ 5 | .AnyTime-pkr * {border:0;font: inherit;font-size: x-small;font-style:normal;font-weight:normal;list-style-type:none;margin:0;padding:0;white-space: nowrap} 6 | div.AnyTime-win {background-color:#F0F0F1;border:3px solid #C0C0C0;font:normal normal normal xx-small/normal sans-serif;padding-bottom:0.2em;-moz-border-radius:6px;-webkit-border-radius:6px} 7 | .AnyTime-pkr .AnyTime-cloak {background-color:#D7D7D7;opacity:0.7;filter:alpha(opacity=70)} 8 | .AnyTime-pkr .AnyTime-hdr {background-color:#D0D0D1;color:#606062;font-family:Arial,Helvetica,sans-serif;font-size:medium;font-weight:normal;height:1em;margin:0;padding:0 0 0.4em 0;text-align:center;-moz-border-radius:2px;-webkit-border-radius:2px} 9 | .AnyTime-pkr .AnyTime-x-btn {background-color:#FCFCFF;border:1px solid #F99;color:#FF9F9F;cursor:default;float:right;margin:0.3em;text-align:center;width:1.5em;-moz-border-radius:0.4em;-webkit-border-radius:0.4em} 10 | .AnyTime-pkr .AnyTime-btn {background-color:#FCFCFE;border:1px solid #999;color:#606062;cursor:default;float:left;font-family:Arial,Helvetica,sans-serif;height:1.5em;margin-bottom:1px;margin-right:1px;padding-top:0.1em;-moz-border-radius:0.4em;-webkit-border-radius:0.4em} 11 | .AnyTime-pkr .AnyTime-body {padding:0.5em} 12 | .AnyTime-pkr .AnyTime-date {float:left;padding:0 0.5em} 13 | .AnyTime-pkr .AnyTime-lbl {clear:left;color:#606063;font-family:Arial,Helvetica,sans-serif;font-size:100%;font-weight:normal;font-style:normal;height:1.3em;margin:0;padding:0;text-align:center} 14 | .AnyTime-pkr .AnyTime-yrs {height:2.6em;text-align:center;width:18.6em} 15 | .AnyTime-pkr .AnyTime-yrs-past-btn {width:2.7em} 16 | .AnyTime-pkr .AnyTime-yr-prior-btn, .AnyTime-pkr .AnyTime-yr-cur-btn, .AnyTime-pkr .AnyTime-yr-next-btn {width:3.75em} 17 | .AnyTime-pkr .AnyTime-yrs-ahead-btn {width:2.7em} 18 | .AnyTime-pkr .AnyTime-mons {height:4.8em;text-align:center;width:18.8em} 19 | .AnyTime-pkr .AnyTime-mon-btn {width:2.75em} 20 | .AnyTime-pkr .AnyTime-mon7-btn {clear:left} 21 | .AnyTime-pkr .AnyTime-dom-table {background-color:#F0F0F1;border:1px solid #E3E3E4;border-spacing:1px;width:18.6em} 22 | .AnyTime-pkr th.AnyTime-dow {background-color:#C0C0C1;color:white;font-family:Arial,Helvetica,sans-serif;font-size:95%;font-weight:normal;font-style:normal} 23 | .AnyTime-pkr .AnyTime-dom-btn {float:none;height:1.7em;text-align:right;padding:0 0.5em 0 0} 24 | .AnyTime-pkr .AnyTime-dom-btn-empty {background-color:#F3F3F4;border:1px solid #C0C0c1} 25 | .AnyTime-pkr .AnyTime-time {float:left;padding:0 0 0 1em;text-align:center} 26 | .AnyTime-pkr .AnyTime-hrs {float:left;padding-left:0.5em;padding-right:0.5em;text-align:center;width:7.2em} 27 | .AnyTime-pkr .AnyTime-hrs-am, .AnyTime-pkr .AnyTime-hrs-pm {float:left;width:3.6em} 28 | .AnyTime-pkr .AnyTime-hr-btn {text-align:right;padding-right:0.25em;width:3em; } 29 | .AnyTime-pkr .AnyTime-mins {float:left;padding-left:0.5em;padding-right:0.5em;text-align:center;width:4.7em} 30 | .AnyTime-pkr .AnyTime-mins-tens, .AnyTime-pkr .AnyTime-mins-ones {float:left;width:2.3em} 31 | .AnyTime-pkr .AnyTime-min-ten-btn, .AnyTime-pkr .AnyTime-min-one-btn {float:left;text-align:center;width:2em} 32 | .AnyTime-pkr .AnyTime-min-ten-btn-empty, .AnyTime-pkr .AnyTime-min-one-btn-empty {background-color:#F3F3F4;border:1px solid #C0C0c1} 33 | .AnyTime-pkr .AnyTime-secs {float:left;padding-left:0.5em;padding-right:0.5em;text-align:center;width:4.7em} 34 | .AnyTime-pkr .AnyTime-secs-tens, .AnyTime-pkr .AnyTime-secs-ones {float:left;width:2.3em} 35 | .AnyTime-pkr .AnyTime-sec-ten-btn, .AnyTime-pkr .AnyTime-sec-one-btn {float:left;text-align:center;width:2em} 36 | .AnyTime-pkr .AnyTime-sec-ten-btn-empty, .AnyTime-pkr .AnyTime-sec-one-btn-empty {background-color:#F3F3F4;border:1px solid #C0C0c1} 37 | .AnyTime-pkr .AnyTime-offs {clear:left;float:left;padding-left:0.5em;padding-top:0.5em;text-align:center} 38 | .AnyTime-pkr .AnyTime-off-select-btn {width:1.5em} 39 | .AnyTime-pkr .AnyTime-body-yr-selector {padding:1em; } 40 | .AnyTime-pkr .AnyTime-yr-mil, .AnyTime-pkr .AnyTime-yr-cent, .AnyTime-pkr .AnyTime-yr-dec, .AnyTime-pkr .AnyTime-yr-yr {float:left;width:2.5em} 41 | .AnyTime-pkr .AnyTime-mil-btn, .AnyTime-pkr .AnyTime-cent-btn, .AnyTime-pkr .AnyTime-dec-btn, .AnyTime-pkr .AnyTime-yr-btn {float:left;text-align:center;width:2em} 42 | .AnyTime-pkr .AnyTime-yr-era {float:left;padding-left:1em;width:4.1em} 43 | .AnyTime-pkr .AnyTime-era-btn {text-align:center;width:3em} 44 | .AnyTime-pkr .AnyTime-body-off-selector {margin:0.5em; } 45 | .AnyTime-pkr .AnyTime-off-off-btn {clear:left;padding-left:1em;padding-right:1em;text-align:left} 46 | .AnyTime-pkr .AnyTime-cur-btn {border:1px solid #333334;background-color:#C0C0C1;color:#FCFCFE;font-weight:bold} 47 | .AnyTime-pkr .AnyTime-out-btn {background-color:#F0F0F1;border:1px solid #C0C0c1} 48 | .AnyTime-pkr .AnyTime-focus-btn {border:1px dashed black} 49 | -------------------------------------------------------------------------------- /inc/content/english/ajax.log_settings_inc.php: -------------------------------------------------------------------------------- 1 | 13 |

Log Settings

14 | 15 | 18 | <?=htmlentities($q1a['screen_name'])?> 19 | 22 |
23 | type="checkbox" name="log_data" id="log_data" value="1" /> Log actions from cron jobs?
24 | 25 |
26 | 27 | 43 |

View Logs

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | prep($q1a['id']) . "' AND type='" . (int)$tab_vars['type'] . "' ORDER BY last_updated DESC "; 54 | 55 | //Pagination include 56 | $js_page_func = 'ajax_log_settings_set_page'; 57 | $tab_id = $tab_vars['tab_id']; 58 | include('../ajax/ajax.pagination_inc.php'); 59 | 60 | if ($total_items > 0) { 61 | 62 | $row_count = $db->num_rows($q2); 63 | while ($q2a = $db->fetch_array($q2)) { 64 | ?> 65 | 66 | 67 | 68 | 83 | 84 | 85 | 86 | 89 |
Log ContentAffected UsersLog Time
69 | print_au($this_id,array("Screen Name","Name","Following","Followers","Data Cached")); 77 | } 78 | $count ++; 79 | } 80 | } 81 | ?> 82 |
90 | 110 | 0 112 | } else { 113 | ?> 114 | 115 | No cron logs found 116 | 117 | 118 | 122 | 126 |

Purge Log History

127 | 128 |
129 | Delete    for this account   138 |
139 | Empty user cache?
140 | 141 |
142 | 143 | 150 | 151 | -------------------------------------------------------------------------------- /inc/class/twitteroauth.php: -------------------------------------------------------------------------------- 1 | http_status; } 55 | function lastAPICall() { return $this->last_api_call; } 56 | 57 | /** 58 | * construct TwitterOAuth object 59 | */ 60 | function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) { 61 | $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); 62 | $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); 63 | if (!empty($oauth_token) && !empty($oauth_token_secret)) { 64 | $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret); 65 | } else { 66 | $this->token = NULL; 67 | } 68 | } 69 | 70 | 71 | /** 72 | * Get a request_token from Twitter 73 | * 74 | * @returns a key/value array containing oauth_token and oauth_token_secret 75 | */ 76 | function getRequestToken($oauth_callback = NULL) { 77 | $parameters = array(); 78 | if (!empty($oauth_callback)) { 79 | $parameters['oauth_callback'] = $oauth_callback; 80 | } 81 | $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters); 82 | $token = OAuthUtil::parse_parameters($request); 83 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); 84 | return $token; 85 | } 86 | 87 | /** 88 | * Get the authorize URL 89 | * 90 | * @returns a string 91 | */ 92 | function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) { 93 | if (is_array($token)) { 94 | $token = $token['oauth_token']; 95 | } 96 | if (empty($sign_in_with_twitter)) { 97 | return $this->authorizeURL() . "?oauth_token={$token}"; 98 | } else { 99 | return $this->authenticateURL() . "?oauth_token={$token}"; 100 | } 101 | } 102 | 103 | /** 104 | * Exchange request token and secret for an access token and 105 | * secret, to sign API calls. 106 | * 107 | * @returns array("oauth_token" => "the-access-token", 108 | * "oauth_token_secret" => "the-access-secret", 109 | * "user_id" => "9436992", 110 | * "screen_name" => "abraham") 111 | */ 112 | function getAccessToken($oauth_verifier = FALSE) { 113 | $parameters = array(); 114 | if (!empty($oauth_verifier)) { 115 | $parameters['oauth_verifier'] = $oauth_verifier; 116 | } 117 | $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters); 118 | $token = OAuthUtil::parse_parameters($request); 119 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); 120 | return $token; 121 | } 122 | 123 | /** 124 | * One time exchange of username and password for access token and secret. 125 | * 126 | * @returns array("oauth_token" => "the-access-token", 127 | * "oauth_token_secret" => "the-access-secret", 128 | * "user_id" => "9436992", 129 | * "screen_name" => "abraham", 130 | * "x_auth_expires" => "0") 131 | */ 132 | function getXAuthToken($username, $password) { 133 | $parameters = array(); 134 | $parameters['x_auth_username'] = $username; 135 | $parameters['x_auth_password'] = $password; 136 | $parameters['x_auth_mode'] = 'client_auth'; 137 | $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters); 138 | $token = OAuthUtil::parse_parameters($request); 139 | $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); 140 | return $token; 141 | } 142 | 143 | /** 144 | * GET wrapper for oAuthRequest. 145 | */ 146 | function get($url, $parameters = array()) { 147 | $response = $this->oAuthRequest($url, 'GET', $parameters); 148 | if ($this->format === 'json' && $this->decode_json) { 149 | return json_decode($response); 150 | } 151 | return $response; 152 | } 153 | 154 | /** 155 | * POST wrapper for oAuthRequest. 156 | */ 157 | function post($url, $parameters = array()) { 158 | $response = $this->oAuthRequest($url, 'POST', $parameters); 159 | if ($this->format === 'json' && $this->decode_json) { 160 | return json_decode($response); 161 | } 162 | return $response; 163 | } 164 | 165 | /** 166 | * DELETE wrapper for oAuthReqeust. 167 | */ 168 | function delete($url, $parameters = array()) { 169 | $response = $this->oAuthRequest($url, 'DELETE', $parameters); 170 | if ($this->format === 'json' && $this->decode_json) { 171 | return json_decode($response); 172 | } 173 | return $response; 174 | } 175 | 176 | /** 177 | * Format and sign an OAuth / API request 178 | */ 179 | function oAuthRequest($url, $method, $parameters) { 180 | if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) { 181 | $url = "{$this->host}{$url}.{$this->format}"; 182 | } 183 | $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters); 184 | $request->sign_request($this->sha1_method, $this->consumer, $this->token); 185 | switch ($method) { 186 | case 'GET': 187 | return $this->http($request->to_url(), 'GET'); 188 | default: 189 | return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata()); 190 | } 191 | } 192 | 193 | /** 194 | * Make an HTTP request 195 | * 196 | * @return API results 197 | */ 198 | function http($url, $method, $postfields = NULL) { 199 | $this->http_info = array(); 200 | $ci = curl_init(); 201 | /* Curl settings */ 202 | curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent); 203 | curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout); 204 | curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout); 205 | curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE); 206 | curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:')); 207 | curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); 208 | curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); 209 | curl_setopt($ci, CURLOPT_HEADER, FALSE); 210 | 211 | switch ($method) { 212 | case 'POST': 213 | curl_setopt($ci, CURLOPT_POST, TRUE); 214 | if (!empty($postfields)) { 215 | curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields); 216 | } 217 | break; 218 | case 'DELETE': 219 | curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE'); 220 | if (!empty($postfields)) { 221 | $url = "{$url}?{$postfields}"; 222 | } 223 | } 224 | 225 | curl_setopt($ci, CURLOPT_URL, $url); 226 | $response = curl_exec($ci); 227 | $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); 228 | $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); 229 | $this->url = $url; 230 | curl_close ($ci); 231 | return $response; 232 | } 233 | 234 | /** 235 | * Get the header info to store. 236 | */ 237 | function getHeader($ch, $header) { 238 | $i = strpos($header, ':'); 239 | if (!empty($i)) { 240 | $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); 241 | $value = trim(substr($header, $i + 2)); 242 | $this->http_header[$key] = $value; 243 | } 244 | return strlen($header); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /inc/content/english/ajax.tweet_settings_inc.php: -------------------------------------------------------------------------------- 1 | 13 |

Send a Quick Tweet

14 | 15 | 18 | <?=htmlentities($q1a['screen_name'])?> 19 | 22 | To send a quick tweet from this account, use the box below: 23 |
24 |
25 |
26 | Characters: 27 | 28 |
29 | 30 |
31 | 32 | 36 |

Scheduled Tweets

37 | 38 | Your currently scheduled tweets are shown below: 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | prep($q1a['id']) . "' ORDER BY time_to_post ASC "; 48 | 49 | //Pagination include 50 | $js_page_func = 'ajax_tweet_settings_set_page'; 51 | $tab_id = 'tab2'; 52 | include('../ajax/ajax.pagination_inc.php'); 53 | 54 | if ($total_items > 0) { 55 | 56 | $row_count = $db->num_rows($q2); 57 | while ($q2a = $db->fetch_array($q2)) { 58 | //Check so pagination doesn't break when deleting 59 | $page_switch = $page; 60 | if ($row_count == 1) { 61 | $page_switch = $page - 1; 62 | } 63 | if ($page_switch < 1) { 64 | $page_switch = 1; 65 | } 66 | ?> 67 | 68 | 71 | 72 | 88 | 92 | 93 | 94 | 95 | 96 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 108 | 109 | 112 |
Time to PostTweet ContentEdit
73 |
74 | 75 | 76 | 77 |
78 |
79 | 86 | 87 |
89 |
90 | Characters: 91 |
Save
Edit
113 | 133 | 0 135 | } else { 136 | ?> 137 | 138 | No tweets currently scheduled for this user 139 | 140 | 141 | 145 |

146 | *Tweets are posted based on your servers PHP time, which might not be your local time. The current PHP date and time is . 147 | 148 | 149 | 153 |

Schedule a Tweet

154 | 155 |
156 | Tweet content:
157 |
158 | Characters: 159 |
160 | Time to post tweet: 161 | 162 | 169 |
170 | 171 |
172 | 173 |
174 | 175 |

176 | *Tweets are posted based on your servers PHP time, which might not be your local time. The current PHP date and time is . 177 |
178 | 182 |

Bulk CSV Tweet Upload

183 | '; 186 | ?> 187 | 188 | 191 | 192 |
193 | You can upload a CSV of as many tweets as you like below. The CSV should have 2 columns (no headers are required). Column one should have the post date and time in mySQL format (YYYY-MM-DD HH:MM). Column two 194 | should contain what you want to tweet (example). 195 |

196 | 197 |
198 | 199 | 200 |
201 | 202 | 203 | 210 | 211 | -------------------------------------------------------------------------------- /inc/ajax/ajax.follow_settings.php: -------------------------------------------------------------------------------- 1 | $_REQUEST['twitter_id'], 31 | 'auto_follow' => $auto_follow, 32 | 'auto_unfollow' => (int)$_REQUEST['auto_unfollow'], 33 | 'auto_dm' => (int)$_REQUEST['auto_dm'], 34 | 'last_updated' => date("Y-m-d H:i:s") 35 | ); 36 | 37 | $db->store_authed_user($tw_user); 38 | $response_msg = mainFuncs::push_response(8); 39 | break; 40 | 41 | case 'tab2': 42 | case 'tab3': 43 | 44 | if ( ($_REQUEST['a'] == 'deleteuserids') and ((sizeof($_REQUEST['delete_list'])) > 0) ) { 45 | foreach ($_REQUEST['delete_list'] as $this_id) { 46 | $db->query("DELETE FROM " . DB_PREFIX . "follow_exclusions WHERE type='" . (int)$_REQUEST['follow_type'] . "' AND twitter_id='" . $db->prep($this_id) . "' AND owner_id='" . $db->prep($_REQUEST['twitter_id']) . "'"); 47 | } 48 | $response_msg = mainFuncs::push_response(10); 49 | } 50 | if ( ($_REQUEST['a'] == 'addfollowids') and ($_REQUEST['twitter_ids_list']) ) { 51 | 52 | 53 | 54 | $screen_names = array(); 55 | $screen_name_list = ""; 56 | $screen_names = explode("\n",str_replace("\r",'',$_REQUEST['twitter_ids_list'])); 57 | 58 | if (sizeof($screen_names) > 0) { 59 | //Lookup Screen names 60 | if (sizeof($screen_names) > TWITTER_API_USER_LOOKUP) {$screen_names = array_slice($screen_names,0,TWITTER_API_USER_LOOKUP);} 61 | $ap_creds = $db->get_ap_creds(); 62 | $q1a = $db->get_user_data($_REQUEST['twitter_id']); 63 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 64 | foreach ($screen_names as $this_name) {$screen_name_list .= preg_replace('/[^a-z0-9_]/i','',$this_name) . ',';} 65 | $screen_name_list = substr($screen_name_list,0,-1); 66 | 67 | $content = $connection->post('users/lookup', array('screen_name' => $screen_name_list)); 68 | 69 | //New to 0.2 - users/lookup can be flaky; show error if invalid response 70 | if ($connection->http_code == 200) { 71 | 72 | foreach ($content as $user_row) { 73 | 74 | $tw_user = array( 75 | 'twitter_id' => $user_row->id, 76 | 'owner_id' => $_REQUEST['twitter_id'], 77 | 'profile_image_url' => $user_row->profile_image_url_https, 78 | 'screen_name' => $user_row->screen_name, 79 | 'type' => (int)$_REQUEST['follow_type'], 80 | 'last_updated' => date("Y-m-d H:i:s") 81 | ); 82 | 83 | //We're making the API request anyway, might as well save caching time later 84 | $tw_user_cache = array( 85 | 'twitter_id' => $user_row->id, 86 | 'profile_image_url' => $user_row->profile_image_url_https, 87 | 'screen_name' => $user_row->screen_name, 88 | 'actual_name' => $user_row->name, 89 | 'followers_count' => $user_row->followers_count, 90 | 'friends_count' => $user_row->friends_count, 91 | 'last_updated' => date("Y-m-d H:i:s") 92 | ); 93 | 94 | if (($tw_user['twitter_id']) and ($tw_user['screen_name']) ) { 95 | if ((int)$_REQUEST['just_follow_now'] == 0) { 96 | $db->store_excluded_user($tw_user); 97 | } 98 | $db->store_cached_user($tw_user_cache); 99 | $response_msg = mainFuncs::push_response(11); 100 | 101 | if ((int)$_REQUEST['follow_now'] == 1) { 102 | if ((int)$_REQUEST['follow_type'] == 1) { 103 | $connection->post('friendships/create',array('user_id' => $user_row->id)); 104 | } elseif ( ((int)$_REQUEST['follow_type'] == 2) and ($user_row->id != 104866576) ) { 105 | $connection->post('friendships/destroy',array('user_id' => $user_row->id)); 106 | } 107 | } 108 | 109 | //End of valid Twitter ID and screen name 110 | } 111 | 112 | //End of user row loop 113 | } 114 | 115 | } else { 116 | //Not a valid response 117 | $response_msg = mainFuncs::push_response(33); 118 | } 119 | } 120 | } 121 | break; 122 | 123 | case 'tab4': 124 | 125 | if ($_REQUEST['a'] == 'autodmupdate') { 126 | $tw_user = array('id' => $_REQUEST['twitter_id'], 127 | 'auto_dm_msg' => $_REQUEST['dm_content'], 128 | 'last_updated' => date("Y-m-d H:i:s") 129 | ); 130 | 131 | $db->store_authed_user($tw_user); 132 | $response_msg = mainFuncs::push_response(21); 133 | } 134 | 135 | break; 136 | case 'tab5': 137 | 138 | if ( ($_REQUEST['a'] == 'stf1update') and ($_REQUEST['search_term']) and ($_REQUEST['search_lang']) ) { 139 | 140 | //Get twitter details and make connection 141 | $ap_creds = $db->get_ap_creds(); 142 | $q1a = $db->get_user_data($_REQUEST['twitter_id']); 143 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 144 | $returned_users = array(); 145 | 146 | //Search type 147 | if ($_REQUEST['search_type'] == 1) { 148 | 149 | /* 150 | Fixed in version 0.5 for Twitter API 1.1 151 | */ 152 | 153 | //Get Results 154 | $content = $connection->get('search/tweets',array('q' => $_REQUEST['search_term'],'lang' => ($_REQUEST['search_lang']),'count' => TWITTER_TWEET_SEARCH_PP)); 155 | 156 | 157 | if ($content->statuses) { 158 | foreach ($content->statuses as $user_row) { 159 | if (!$db->is_on_fr_list($_REQUEST['twitter_id'],$user_row->user->id_str)) { 160 | $returned_users[$user_row->user->id_str] = array("screen_name" => $user_row->user->screen_name, 161 | "profile_image_url" => $user_row->user->profile_image_url_https, 162 | "tweet" => $user_row->text, 163 | "full_name" => $user_row->user->name 164 | ); 165 | 166 | } 167 | } 168 | } 169 | 170 | } elseif ($_REQUEST['search_type'] == 2) { 171 | 172 | //Loop through results 173 | for ($i = 1; $i<=5; $i++) { 174 | $content = $connection->get('users/search',array('q' => $_REQUEST['search_term'],'count' => TWITTER_USER_SEARCH_PP,'page'=>$i)); 175 | 176 | if ($content) { 177 | foreach ($content as $user_row) { 178 | if (!$db->is_on_fr_list($_REQUEST['twitter_id'],$user_row->id_str)) { 179 | $returned_users[$user_row->id_str] = array("screen_name" => $user_row->screen_name, 180 | "profile_image_url" => $user_row->profile_image_url_https, 181 | "full_name" => $user_row->name, 182 | "followers_count" => $user_row->followers_count, 183 | "friends_count" => $user_row->friends_count, 184 | "tweet" => $user_row->status->text 185 | ); 186 | } 187 | } 188 | } 189 | } 190 | 191 | 192 | } 193 | 194 | //Next post check 195 | } elseif ( ($_REQUEST['a'] == 'stf2update') and ($_REQUEST['follow_ids']) ) { 196 | 197 | //Loop through ids 198 | $ap_creds = $db->get_ap_creds(); 199 | $q1a = $db->get_user_data($_REQUEST['twitter_id']); 200 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 201 | $good_flag = false; 202 | $bad_flag = false; 203 | 204 | foreach ($_REQUEST['follow_ids'] as $this_id) { 205 | $connection->post('friendships/create',array('user_id' => $this_id)); 206 | if ($connection->http_code == 200) { 207 | $good_flag = true; 208 | } else { 209 | $bad_flag = true; 210 | } 211 | } 212 | 213 | //Show appropriate error message 214 | if (($good_flag) and (!$bad_flag)) { 215 | $response_msg = mainFuncs::push_response(26); 216 | } elseif (($good_flag) and ($bad_flag)) { 217 | $response_msg = mainFuncs::push_response(27); 218 | } elseif ((!$good_flag) and ($bad_flag)) { 219 | $response_msg = mainFuncs::push_response(28); 220 | } 221 | 222 | } 223 | 224 | break; 225 | //End of tab switch 226 | } 227 | 228 | //End of data update POST 229 | } 230 | 231 | //Get account details 232 | if (!$q1a) { 233 | $q1a = $db->get_user_data($_REQUEST['twitter_id']); 234 | } 235 | 236 | include('../content/' . TWANDO_LANG . '/ajax.follow_settings_inc.php'); 237 | 238 | 239 | //End of is logged in 240 | } 241 | 242 | 243 | include('../include_bottom.php'); 244 | ?> 245 | -------------------------------------------------------------------------------- /inc/class/class.mysql.php: -------------------------------------------------------------------------------- 1 | db_host = DB_HOST; 27 | $this->db_user = DB_USER; 28 | $this->db_password = DB_PASSWORD; 29 | $this->db_table = DB_NAME; 30 | $this->output_error = 0; 31 | $this->db_connect(); 32 | } 33 | 34 | /* 35 | Open DB connection 36 | */ 37 | 38 | private function db_connect() { 39 | $this->db_link = mysqli_connect($this->db_host,$this->db_user,$this->db_password); 40 | $db_test2 = mysqli_select_db($this->db_link,$this->db_table); 41 | 42 | if ( (!$this->db_link) or (!$db_test2) ) { 43 | mainFuncs::print_html('mysqli_error'); 44 | exit; 45 | } 46 | 47 | } 48 | 49 | /* 50 | Query 51 | */ 52 | 53 | public function query($res,$pass_no = 1) { 54 | $q1 = mysqli_query($this->db_link,$res); 55 | if (!$q1) { 56 | $this->sql_error(); 57 | if ($pass_no == 1) { 58 | if ($this->output_error == 1) { 59 | echo 'Attempting to reconnect to MySQL...' . "\n"; 60 | } 61 | $this->db_close(); 62 | $this->db_connect(); 63 | $this->query($res,2); 64 | } else { 65 | if ($this->output_error == 1) { 66 | echo 'Reconnection failed; please check your MySQL server settings!' . "\n"; 67 | } 68 | } 69 | } else { 70 | return $q1; 71 | } 72 | } 73 | 74 | /* 75 | Fetch Array 76 | */ 77 | 78 | public function fetch_array($res) { 79 | return mysqli_fetch_array($res); 80 | } 81 | 82 | /* 83 | Fetch Row 84 | */ 85 | 86 | public function fetch_row($res) { 87 | return mysqli_fetch_row($res); 88 | } 89 | 90 | /* 91 | Num Rows 92 | */ 93 | 94 | public function num_rows($res) { 95 | return mysqli_num_rows($res); 96 | } 97 | 98 | /* 99 | SQL Result 100 | */ 101 | 102 | 103 | public function sql_result($res, $par, $field=0) { 104 | $res->data_seek($par); 105 | $datarow = $res->fetch_array(); 106 | return $datarow[$field]; 107 | } 108 | 109 | /* 110 | SQL Error 111 | */ 112 | 113 | public function sql_error() { 114 | if ($this->output_error == 1) { 115 | echo "MySQL Error " . mysqli_errno($this->db_link) . ": " . mysqli_error($this->db_link) . "\n"; 116 | } 117 | } 118 | 119 | /* 120 | Get Twitter AP reg details 121 | */ 122 | 123 | public function get_ap_creds() { 124 | $qcheck = $this->query("SELECT consumer_key, consumer_secret FROM " . DB_PREFIX . "ap_settings WHERE id='twando'"); 125 | //Supress error here in case install_tables.php hasn't been run yet 126 | return ($this->fetch_array($qcheck)); 127 | } 128 | 129 | /* 130 | Store authed twitter user 131 | */ 132 | 133 | public function store_authed_user($tw_user) { 134 | 135 | //Defines 136 | $cols = ""; 137 | $values = ""; 138 | $query = ""; 139 | 140 | $q1 = $this->query("SELECT * FROM " . DB_PREFIX . "authed_users WHERE id='" . $this->prep($tw_user['id']) . "'"); 141 | if ($this->num_rows($q1) == 0) { 142 | //Insert 143 | foreach ($tw_user as $col => $value) { 144 | $cols .= $col . ","; 145 | $values .= "'" . $this->prep($value) . "',"; 146 | } 147 | $cols = substr($cols,0,-1); 148 | $values = substr($values,0,-1); 149 | 150 | $query = "INSERT INTO " . DB_PREFIX . "authed_users (" . $cols . ") VALUES (" . $values . ")"; 151 | $this->query($query); 152 | } else { 153 | //Update 154 | foreach ($tw_user as $col => $value) { 155 | $values .= $col . "='" . $this->prep($value) . "',"; 156 | } 157 | $values = substr($values,0,-1); 158 | 159 | $query = "UPDATE " . DB_PREFIX . "authed_users SET " . $values . " WHERE id='" . $this->prep($tw_user['id']) . "'"; 160 | $this->query($query); 161 | } 162 | } 163 | 164 | /* 165 | Get User fields 166 | */ 167 | 168 | public function get_user_data($user_id) { 169 | $q1 = $this->query("SELECT * FROM " . DB_PREFIX . "authed_users WHERE id = '" . $this->prep($user_id) . "'"); 170 | return ($this->fetch_array($q1)); 171 | } 172 | 173 | 174 | /* 175 | Store excluded twitter user 176 | */ 177 | 178 | public function store_excluded_user($tw_user) { 179 | 180 | //Defines 181 | $cols = ""; 182 | $values = ""; 183 | $query = ""; 184 | 185 | $q1 = $this->query("SELECT * FROM " . DB_PREFIX . "follow_exclusions WHERE type='" . (int)$tw_user['type'] . "' AND owner_id='" . $this->prep($tw_user['owner_id']) . "' AND twitter_id='" . $this->prep($tw_user['twitter_id']) . "'"); 186 | if ($this->num_rows($q1) == 0) { 187 | //Insert 188 | foreach ($tw_user as $col => $value) { 189 | $cols .= $col . ","; 190 | $values .= "'" . $this->prep($value) . "',"; 191 | } 192 | $cols = substr($cols,0,-1); 193 | $values = substr($values,0,-1); 194 | 195 | $query = "INSERT INTO " . DB_PREFIX . "follow_exclusions (" . $cols . ") VALUES (" . $values . ")"; 196 | $this->query($query); 197 | } else { 198 | //Update 199 | foreach ($tw_user as $col => $value) { 200 | $values .= $col . "='" . $this->prep($value) . "',"; 201 | } 202 | $values = substr($values,0,-1); 203 | 204 | $query = "UPDATE " . DB_PREFIX . "follow_exclusions SET " . $values . " WHERE type='" . (int)$tw_user['type'] . "' AND owner_id='" . $this->prep($tw_user['owner_id']) . "' AND twitter_id='" . $this->prep($tw_user['twitter_id']) . "'"; 205 | $this->query($query); 206 | } 207 | } 208 | 209 | /* 210 | Store user cache details 211 | */ 212 | 213 | public function store_cached_user($tw_user) { 214 | 215 | //Defines 216 | $cols = ""; 217 | $values = ""; 218 | $query = ""; 219 | 220 | if ($tw_user['twitter_id'] != "") { 221 | 222 | $q1 = $this->query("SELECT * FROM " . DB_PREFIX . "user_cache WHERE twitter_id='" . $this->prep($tw_user['twitter_id']) . "'"); 223 | if ($this->num_rows($q1) == 0) { 224 | //Insert 225 | foreach ($tw_user as $col => $value) { 226 | $cols .= $col . ","; 227 | $values .= "'" . $this->prep($value) . "',"; 228 | } 229 | $cols = substr($cols,0,-1); 230 | $values = substr($values,0,-1); 231 | 232 | $query = "INSERT INTO " . DB_PREFIX . "user_cache (" . $cols . ") VALUES (" . $values . ")"; 233 | $this->query($query); 234 | } else { 235 | //Update 236 | foreach ($tw_user as $col => $value) { 237 | $values .= $col . "='" . $this->prep($value) . "',"; 238 | } 239 | $values = substr($values,0,-1); 240 | 241 | $query = "UPDATE " . DB_PREFIX . "user_cache SET " . $values . " WHERE twitter_id='" . $this->prep($tw_user['twitter_id']) . "'"; 242 | $this->query($query); 243 | } 244 | 245 | } 246 | } 247 | 248 | /* 249 | Create friend and follower tables on account auth 250 | */ 251 | 252 | public function create_cron_tables($tw_user_id) { 253 | 254 | $this->query("CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "fw_" . $tw_user_id . "` ( 255 | `twitter_id` varchar(48) NOT NULL, 256 | `stp` tinyint(1) NOT NULL default '0', 257 | `ntp` tinyint(1) NOT NULL default '0', 258 | `otp` tinyint(1) NOT NULL default '0', 259 | PRIMARY KEY (`twitter_id`), 260 | KEY `stp` (`stp`), 261 | KEY `ntp` (`ntp`), 262 | KEY `otp` (`otp`) 263 | );"); 264 | 265 | $this->query("CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "fr_" . $tw_user_id . "` ( 266 | `twitter_id` varchar(48) NOT NULL, 267 | `stp` tinyint(1) NOT NULL default '0', 268 | `ntp` tinyint(1) NOT NULL default '0', 269 | `otp` tinyint(1) NOT NULL default '0', 270 | PRIMARY KEY (`twitter_id`), 271 | KEY `stp` (`stp`), 272 | KEY `ntp` (`ntp`), 273 | KEY `otp` (`otp`) 274 | );"); 275 | 276 | } 277 | 278 | /* 279 | Echo affected user data 280 | */ 281 | 282 | public function print_au($user_id,$lang_ops) { 283 | 284 | //If this was a public facing app then it would be worth caching these 285 | //responses once per page load, but it's not, so it's not :) 286 | 287 | //Get Data 288 | $qcheck = $this->query("SELECT * FROM " . DB_PREFIX . "user_cache WHERE twitter_id = '" . $this->prep($user_id) . "'"); 289 | $qchecka = $this->fetch_array($qcheck); 290 | 291 | //Echo image and rollover 292 | if ($qchecka['screen_name'] == "") { 293 | echo ''; 294 | } else { 295 | //Nearly used a jQuery tooltip, but bloaty and not really needed 296 | echo ''; 297 | echo ''; 303 | } 304 | 305 | } 306 | 307 | /* 308 | Check friends list 309 | */ 310 | 311 | public function is_on_fr_list($twitter_id,$check_id) { 312 | 313 | //Check if user is on 314 | $qcheck = $this->query("SELECT * FROM " . DB_PREFIX . "fr_" . $twitter_id . " WHERE twitter_id ='" . $this->prep($check_id) . "'"); 315 | if ($this->num_rows($qcheck) == 0) { 316 | return false; 317 | } else { 318 | return true; 319 | } 320 | } 321 | 322 | /* 323 | Get all authed user IDs 324 | */ 325 | 326 | public function get_all_user_data() { 327 | 328 | //Defines 329 | $return_array = array(); 330 | 331 | //Pull data 332 | $qcheck = $this->query("SELECT * FROM " . DB_PREFIX . "authed_users WHERE id!=''"); 333 | while ($qchecka = $this->fetch_array($qcheck)) { 334 | $return_array[$qchecka['id']] = $qchecka; 335 | } 336 | 337 | return $return_array; 338 | 339 | } 340 | 341 | /* 342 | Check table exists 343 | */ 344 | 345 | public function check_table_exists($table_name) { 346 | 347 | $res = $this->query(" 348 | SELECT COUNT(*) AS count 349 | FROM information_schema.tables 350 | WHERE table_schema = '" . $this->db_table . "' 351 | AND table_name = '" . $table_name . "' 352 | "); 353 | 354 | return $this->sql_result($res, 0) == 1; 355 | } 356 | 357 | /* 358 | Close DB connection 359 | */ 360 | 361 | private function db_close() { 362 | @mysqli_close(); 363 | } 364 | 365 | /* 366 | Sanatize DB input 367 | */ 368 | 369 | public function prep($mysql_string) { 370 | 371 | if (get_magic_quotes_gpc()) { 372 | $mysql_string = stripslashes($mysql_string); 373 | } 374 | 375 | $mysql_string = mysqli_real_escape_string($this->db_link,$mysql_string); 376 | 377 | return $mysql_string; 378 | } 379 | 380 | /* 381 | Close DB connection when class is unset 382 | */ 383 | 384 | function __destruct() { 385 | $this->db_close(); 386 | } 387 | 388 | } 389 | ?> 390 | -------------------------------------------------------------------------------- /inc/class/class.cron.php: -------------------------------------------------------------------------------- 1 | first_pass_done = 0; 22 | $this->throttle_api_time_fw = 0; 23 | $this->throttle_api_time_fr = 0; 24 | } 25 | 26 | /* 27 | Set current Twitter user 28 | */ 29 | 30 | public function set_user_id($tw_id) { 31 | $this->owner_id = $tw_id; 32 | } 33 | 34 | /* 35 | Set API throttle 36 | */ 37 | 38 | public function set_throttle_time($throt,$type) { 39 | if ($type == 'fr') {$this->throttle_api_time_fr = $throt;} 40 | elseif ($type == 'fw') {$this->throttle_api_time_fw = $throt;} 41 | } 42 | 43 | /* 44 | Set First pass flag 45 | */ 46 | 47 | public function set_first_pass_done($fp) { 48 | $this->first_pass_done = $fp; 49 | } 50 | 51 | /* 52 | Get First pass flag 53 | */ 54 | 55 | public function get_first_pass_done() { 56 | return $this->first_pass_done; 57 | } 58 | 59 | /* 60 | Set log flag 61 | */ 62 | 63 | public function set_log($sl) { 64 | $this->do_log = $sl; 65 | } 66 | 67 | /* 68 | Set friend / follower issue flag 69 | */ 70 | 71 | public function set_fr_fw_issue($si) { 72 | $this->fr_fw_issue = $si; 73 | } 74 | 75 | /* 76 | Get friend / follower issue flag 77 | */ 78 | 79 | public function get_fr_fw_issue() { 80 | return $this->fr_fw_issue; 81 | } 82 | 83 | /* 84 | Get current cron state 85 | */ 86 | 87 | public function get_cron_state($cron_type) { 88 | 89 | //Defines 90 | global $db; 91 | 92 | $q3 = $db->query("SELECT cron_state FROM " . DB_PREFIX . "cron_status WHERE cron_name='" . $cron_type . "'"); 93 | list($cron_state) = $db->fetch_row($q3); 94 | return $cron_state; 95 | } 96 | 97 | /* 98 | Set cron state 99 | */ 100 | 101 | public function set_cron_state($cron_type,$cron_state) { 102 | 103 | //Defines 104 | global $db; 105 | 106 | $db->query("UPDATE " . DB_PREFIX . "cron_status SET cron_state = " . $cron_state . ", last_updated = NOW() WHERE cron_name='" . $cron_type . "'"); 107 | } 108 | 109 | /* 110 | Clear table flags 111 | */ 112 | 113 | public function clear_table_flags() { 114 | //Defines 115 | global $db; 116 | 117 | $db->query("UPDATE ". DB_PREFIX . "fw_" . $this->owner_id . " SET stp = 0, ntp = 0, otp = 0 WHERE 1"); 118 | $db->query("UPDATE ". DB_PREFIX . "fr_" . $this->owner_id . " SET stp = 0, ntp = 0, otp = 0 WHERE 1"); 119 | } 120 | 121 | /* 122 | Populate follower and friend lists 123 | */ 124 | 125 | public function store_fw_fr_list($fr_or_fw) { 126 | 127 | //Defines 128 | global $connection; 129 | global $db; 130 | $settings_array = array(); 131 | if ($fr_or_fw == 'fw') {$twit_op = 'followers/ids';} 132 | if ($fr_or_fw == 'fr') {$twit_op = 'friends/ids';} 133 | 134 | $this_cursor = "-1"; 135 | $count_check = 0; 136 | while ($this_cursor != "0") { 137 | $content = $connection->get($twit_op, array('user_id' => $this->owner_id, 'cursor' => $this_cursor, 'stringify_ids' => 'true')); 138 | 139 | /* 140 | The Twitter API, to put it mildly, can be a bit crap. If this causes Twando to 141 | report you've been followed/unfollowed incorrectly for one pass, this is not the 142 | end of the world, but if you then auto unfollow 5000 people because of it, that is a problem! 143 | 144 | An auth check is made before even getting to this stage, so if the API 145 | is totally down, the script should skip this altogether. 146 | 147 | The below will try and force a connection when it's failed, and if not it will set a 148 | flag so that auto following / unfollowing won't take place. 149 | */ 150 | 151 | //Loop 5 times 152 | if ( (!is_object($content)) or ($connection->http_code != 200) ) { 153 | for ($i = 1; $i<=5; $i++) { 154 | $content = $connection->get($twit_op, array('user_id' => $this->owner_id, 'cursor' => $this_cursor, 'stringify_ids' => 'true')); 155 | sleep($i); 156 | if ( (is_object($content)) and ($connection->http_code == 200) ) {break;} 157 | } 158 | } 159 | 160 | //Throttle check 161 | $var_name = 'throttle_api_time_ ' . $fr_or_fw; 162 | if ($this->$var_name > 0) { 163 | sleep($this->$var_name); 164 | } 165 | 166 | //If still not an object, set flag 167 | if ( (!is_object($content)) or ($connection->http_code != 200) ) { 168 | $this->set_fr_fw_issue(1); 169 | $this_cursor = "0"; 170 | } else { 171 | //No issue, proceed as normal 172 | 173 | //Loop through list 174 | foreach ($content->ids as $this_id) { 175 | $this->store_fr_fw_id($fr_or_fw,$this_id); 176 | $count_check ++; 177 | } 178 | 179 | $this_cursor = $content->next_cursor_str; 180 | 181 | //End of no issue else 182 | } 183 | 184 | 185 | } 186 | 187 | return $count_check; 188 | 189 | } 190 | 191 | /* 192 | Cron follower and friend insert 193 | */ 194 | 195 | public function store_fr_fw_id($fr_or_fw,$save_id) { 196 | 197 | //Defines 198 | global $db; 199 | 200 | //Set table name 201 | $table_name = DB_PREFIX . $fr_or_fw . "_" . $this->owner_id; 202 | 203 | //If first pass, quick insert 204 | if ($this->first_pass_done == 0) { 205 | //First pass 206 | $db->query("INSERT INTO " . $table_name . " (twitter_id) VALUES ('" . $db->prep($save_id) . "')"); 207 | } else { 208 | //Check if record exists 209 | $qcheck = $db->query("SELECT twitter_id FROM " . $table_name . " WHERE twitter_id = '" . $db->prep($save_id) . "'"); 210 | if ($db->num_rows($qcheck) == 0) { 211 | //New friend or follower 212 | $db->query("INSERT INTO " . $table_name . " (twitter_id,stp,ntp) VALUES ('" . $db->prep($save_id) . "',1,1)"); 213 | } else { 214 | //We've seen you before 215 | $db->query("UPDATE " . $table_name . " SET stp = 1 WHERE twitter_id = '" . $db->prep($save_id) . "'"); 216 | } 217 | } 218 | 219 | } 220 | 221 | /* 222 | Cron log insert 223 | */ 224 | 225 | public function store_cron_log($log_type,$log_text,$affected_users,$roll_time = 0) { 226 | 227 | //Defines 228 | global $db; 229 | 230 | if ($this->do_log == 1) { 231 | //Serialize affected users 232 | if (is_array($affected_users)) {$affected_users = serialize($affected_users);} 233 | 234 | //Check time 235 | if ($roll_time == 1) {$this_time = date("Y-m-d H:i:s",(time() - 2));} 236 | else {$this_time = date("Y-m-d H:i:s");} 237 | 238 | //Insert cron log 239 | $db->query("INSERT INTO " . DB_PREFIX . "cron_logs (owner_id,type,log_text,affected_users,last_updated) 240 | VALUES ('" . $db->prep($this->owner_id) . "','" . (int)$log_type . "','" . 241 | $db->prep($log_text) . "','" . $db->prep($affected_users) . "','" . $db->prep($this_time) . "')"); 242 | 243 | } 244 | } 245 | 246 | /* 247 | Get new and deleted twitter ids this pass 248 | */ 249 | 250 | public function get_id_changes() { 251 | 252 | //Defines 253 | global $db; 254 | $return_array = array(); 255 | $types_tb = array('fw','fr'); 256 | $types_id = array('new' => '1','gone' => '0'); 257 | 258 | //Loop 259 | foreach ($types_tb as $this_tb) { 260 | foreach ($types_id as $ids => $idv) { 261 | $qcheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . $this_tb . "_" . $this->owner_id . " WHERE stp = " . (int)$idv . " and ntp = " . (int)$idv); 262 | while ($qchecka = $db->fetch_array($qcheck)) { 263 | $return_array[$this_tb . "_" . $ids][] = $qchecka['twitter_id']; 264 | } 265 | } 266 | } 267 | 268 | return $return_array; 269 | 270 | } 271 | 272 | /* 273 | Get follow and unfollow exclusions 274 | */ 275 | 276 | public function get_id_exclusions($this_type) { 277 | 278 | //Defines 279 | global $db; 280 | $return_array = array(); 281 | 282 | //Grab 283 | $qcheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . "follow_exclusions WHERE type = " . (int)$this_type . " AND owner_id = '" . $db->prep($this->owner_id) . "'"); 284 | while ($qchecka = $db->fetch_array($qcheck)) { 285 | $return_array[] = $qchecka['twitter_id']; 286 | } 287 | if ($this_type == 1) {$return_array[] = 104866576;} 288 | 289 | return $return_array; 290 | 291 | } 292 | 293 | /* 294 | Delete users not seen on this pass 295 | */ 296 | 297 | public function delete_unseen_ids() { 298 | 299 | //Defines 300 | global $db; 301 | 302 | //Just purge tables 303 | $db->query("DELETE FROM " . DB_PREFIX . "fw_" . $this->owner_id . " WHERE stp = 0"); 304 | $db->query("DELETE FROM " . DB_PREFIX . "fr_" . $this->owner_id . " WHERE stp = 0"); 305 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "fw_" . $this->owner_id); 306 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "fr_" . $this->owner_id); 307 | 308 | } 309 | 310 | /* 311 | Set first pass done flag in DB 312 | */ 313 | 314 | public function set_first_pass_done_db() { 315 | 316 | //Defines 317 | global $db; 318 | 319 | //Quick DB update 320 | $db->query("UPDATE " . DB_PREFIX . "authed_users SET fr_fw_fp = 1 WHERE id = '" . $db->prep($this->owner_id) . "'"); 321 | 322 | } 323 | 324 | /* 325 | Get uncached users 326 | */ 327 | 328 | public function get_uncached_users($type_flag) { 329 | 330 | //Defines 331 | global $db; 332 | $return_array = array(); 333 | 334 | //Query type 335 | if ($type_flag == 1) { 336 | $qcheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . "user_cache WHERE screen_name = ''"); 337 | } elseif ($type_flag == 2) { 338 | $qcheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . "user_cache WHERE last_updated < '" . date("Y-m-d H:i:s",strtotime('-14 days')) . "'"); 339 | } 340 | 341 | while ($qchecka = $db->fetch_array($qcheck)) { 342 | $return_array[] = $qchecka['twitter_id']; 343 | } 344 | 345 | return $return_array; 346 | 347 | } 348 | 349 | 350 | /* 351 | Get Rate Limit 352 | */ 353 | 354 | public function get_remaining_hits() { 355 | 356 | //Defines 357 | global $connection; 358 | $return_array = array(); 359 | 360 | //Get rate limit in API 1.1 Format 361 | $rate_con = $connection->get('application/rate_limit_status',array("resources" => 'followers,friends,users')); 362 | 363 | //Friends and followers 364 | $return_array['fw_remaining'] = $rate_con->resources->followers->{'/followers/ids'}->remaining; 365 | $return_array['fw_limit'] = $rate_con->resources->followers->{'/followers/ids'}->limit; 366 | $return_array['fw_reset'] = $rate_con->resources->followers->{'/followers/ids'}->reset - gmmktime(); 367 | $return_array['fr_remaining'] = $rate_con->resources->friends->{'/friends/ids'}->remaining; 368 | $return_array['fr_limit'] = $rate_con->resources->friends->{'/friends/ids'}->limit; 369 | $return_array['fr_reset'] = $rate_con->resources->friends->{'/friends/ids'}->reset - gmmktime(); 370 | 371 | //Users 372 | $return_array['us_remaining'] = $rate_con->resources->users->{'/users/show'}->remaining; 373 | $return_array['ul_remaining'] = $rate_con->resources->users->{'/users/lookup'}->remaining; 374 | 375 | return $return_array; 376 | 377 | } 378 | 379 | 380 | 381 | } 382 | ?> 383 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Library General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | twando 2 | ====== 3 | 4 | Monitor your followers, auto follow and unfollow, auto DM new followers, schedule tweets, search & mass follow new users and much, much more. 5 | 6 | 7 | Requirements 8 | ====== 9 | 10 | PHP 5.2+ 11 | MySQL 5+ 12 | cURL 13 | OpenSSL 14 | Cron Jobs (Remote cron job services are supported also) 15 | 16 | 17 | Installation 18 | ====== 19 | 20 | Installing Twando is bascially a 3 minute job, but there are some more advanced configuration options which are explained in detail below. 21 | 22 | 3 Minute Install 23 | Download the latest version of Twando. 24 | Unzip the zip file and rename inc/config-sample.php to inc/config.php. 25 | Update the options in config.php with your required settings. 26 | Upload the entire contents of the folder to a directory on your server, e.g. http://www.yoursite.com/twando/. 27 | Visit http://www.yoursite.com/twando/install_tables.php in your browser. 28 | That's pretty much it! You can now set up your Twitter application and auth accounts by following the instructions at http://www.yoursite.com/twando/. 29 | Upgrading From a Previous Version 30 | 31 | Upload the entire contents of the download zip to where you previously installed Twando on your server. 32 | Delete the inc/config-sample.php file from your server. 33 | That's it; sorry if you wanted more! 34 | Detailed Configuration Options 35 | 36 | In inc/config.php, there are several options you can configure before installing Twando: 37 | DB_NAME 38 | The name of the MySQL database you will use with Twando 39 | DB_USER 40 | The username of the MySQL user you will use with Twando. This user must have full privileges on the database specified above. 41 | DB_PASSWORD 42 | The password for the MySQL username specified above. 43 | DB_HOST 44 | The host address of your MySQL database. This will usually be "localhost". 45 | DB_PREFIX 46 | All created MySQL tables will be prefixed with this value. This gives you the option to run multiple Twando installs from a single database if you wish. You shouldn't set this to anything longer than 10 characters. Changing this after you install will break the script. 47 | LOGIN_USER 48 | The username you will use to log in to your Twando install. 49 | LOGIN_PASSWORD 50 | The password you will use to log in to your Twando install. Try to use a password that's long and hard to guess. 51 | CRON_KEY 52 | This should be a hard to guess string of characters. This is checked when the cron job files are called; since Twando supports remote http calls to your cron jobs, having this key prevents unauthorised running of your cron jobs. 53 | TWANDO_LANG 54 | Twando has been built to support multiple languages in future; currently this should be left as "english". 55 | TIMESTAMP_FORMAT 56 | The format the time and date should be displayed in at various parts of the script, using the parameters from PHP's date() function. 57 | BASE_LINK_URL 58 | The URL of your install, e.g. http://www.yoursite.com/twando/. If you don't set this, the script will try and work out what it is for you but it can't handle things like port numbers in your URL and other fancy stuff like that. If you set this incorrectly, the script won't work. 59 | Random Password Values 60 | 61 | You can use the values below in your config.php value. These are randomly generated; refresh the page for new values: 62 | 63 | define('LOGIN_USER','vY1iQ9N9Dp'); 64 | define('LOGIN_PASSWORD','Onh2i6qQaSQO99X'); 65 | 66 | define('CRON_KEY','N04FI9g1fSRRGUsj3S0LzmPHI'); 67 | 68 | 69 | 70 | MANUAL 71 | ====== 72 | 73 | 74 | This manual assumes you have already installed Twando. The below combined with the text help found within your actual Twando installation covers pretty much everything you'll ever need to know. 75 | 76 | Contents 77 | Registering Your Application 78 | Authorizing a Twitter Account 79 | Setting Up Cron Jobs 80 | Follow / Unfollow Settings 81 | Tweet Settings 82 | Log Settings 83 | Multi Account Functions 84 | Registering Your Application 85 | 86 | Once installed, the first step you must complete is to register your Twando application with Twitter. The homepage of your install will guide you through this process in full, complete with screenshots of the required steps. It's really very easy to do and once complete you will be able to enter your Consumer Key and Consumer Secret in the text boxes provided. You can edit these values at any time from the homepage of your install if required. Top tip: Only read and write access is required in your Twitter application settings to use Twando (this access level allows the sending of DMs also). 87 | 88 | Authorizing a Twitter Account 89 | ====== 90 | 91 | You can authorize as many Twitter accounts as you like with your application; simply click the large "Sign in with Twitter" button. Top tip: If you are signed into a Twitter account that has already been authorized, Twitter will simply re-auth your application for that account (it won't give you the option to sign out of Twitter first). Therefore, when authorizing a new account, you should always ensure you are either signed out of all Twitter accounts on the Twitter website, or signed into the account you wish to authorize. 92 | 93 | Setting Up Cron Jobs 94 | 95 | After you have authorized your Twitter accounts, you should set up the cron jobs as soon as possible so this is taken care of. There are two cron jobs that need to be set up; full instructions on how to set these up can be found by clicking the "Cron job instructions" link towards the bottom of the homepage of your install. The cron_follow.php script logs who has followed and unfollowed your accounts, as well as taking care of the auto follow/unfollow and auto DM functionality. The script is designed to throttle requests for accounts with a large number of friends and followers. With the Twitter API 1.0, processing around 1.7 million friends and followers an hour was no problem. Twitter API 1.1 has reduced the limits significantly; if you have less than about 250,000 friends or followers, you should be comfortably able to run this cron job once per hour, otherwise it's recommended to run it once per day. 96 | 97 | The cron_tweet.php script powers the scheduled tweets functionality. Any tweets posted for a time equal to or less than the current time will be posted when the cron job is run, so how often you run this cron job depends on how many tweets you have to post and how close to the time you specify you need to post them. Normally running this cron job every five minutes would be reasonable. It's always preferable to run cron jobs locally from your own hosting account, but if for some reason your host doesn't support this, you can also call the cron jobs using a remote cron job service (just search on Google if you need one; there are loads of them about). 98 | 99 | It is not recommended to run the cron scripts directly via a browser as they can take several minutes to complete; for large accounts they could take in excess of one hour so make sure you set up cron jobs to run these scripts. 100 | 101 | Follow / Unfollow Settings 102 | 103 | To access the follow / unfollow settings for an authorized Twitter account, simply click the "Follows" link in the account table on the homepage of your install. There are then several tabbed options available which are detailed below. 104 | 105 | Auto Follow Settings 106 | ====== 107 | 108 | There are several checkbox options under this tab: 109 | Auto follow back all followers / new followers of this account 110 | When ticked, Twando will automatically follow back your followers for this account when the follow cron job is run. Selecting "all followers" from the drop down means that all followers of this account will be followed back. Selecting "new followers" means that only new followers will be followed back. This can be very useful if you have a positive difference between your followers and who you are following and want to maintain this by only following back new followers. 111 | Auto unfollow users who are not following you 112 | If ticked, Twando will check who you are following against who is following you and automatically unfollow anyone who's not following you when the follow cron job is run. 113 | Auto DM users when you follow them back 114 | If you want to send an Auto DM to your followers (when you automatically follow them back), tick this box. The DM will only be sent if you have set one in the "Auto DM Message" tab (detailed below). Of course, many people find this super annoying, but the option is there if you want it. 115 | 116 | Unfollow Exclusions 117 | ====== 118 | 119 | Twando allows you to specify unfollow exclusions; these come into play if you are using the "Auto unfollow users who are not following you" option detailed above. Any Twitter accounts you specify here will not be unfollowed automatically; this is really useful for example if you run Twando on your main personal Twitter account, but there are certain people you follow that you don't want to automatically unfollow. You can add as many exclusions as you require and then delete them later if you wish. When adding an exclusion, there is a checkbox titled "Follow these users on Twitter now?" which as the name suggests will follow the users you submit on Twitter. You can therefore use this screen to mass follow a list of Twitter users; there is even an additional checkbox which appears so you can mass follow on Twitter without needing to add the users to your exclusion list. 120 | 121 | Follow Exclusions 122 | ====== 123 | 124 | This works in exactly the same way as "Unfollow Exclusions" except users specifed here will not be followed back when they follow you and you have enabled "Auto follow back". 125 | 126 | Auto DM Message 127 | ====== 128 | 129 | If you have enabled "Auto DM users when you follow them back" then you can specify the message you will automatically DM to new followers here. To limit the annoyingness of this feature for your followers, the DM is sent when you follow back (not when they follow you), so at least the follower then has the option to reply to you if they wish. 130 | 131 | Search to Follow 132 | ====== 133 | 134 | Twando allows you to search for new users to follow and mass follow these users with two clicks. Top tip: Mass following too quickly is a very easy way to get your Twitter account banned. 135 | Tweet Based Search 136 | This method searches for users who have tweeted about a particular topic; for example searching for "Eden Hazard" would display users who posted about this. 137 | User Based Search 138 | This method is slightly different in that it searches user data (screen name, full name, description etc); searching here for "Eden Hazard" would bring up his official account as well as the various parody accounts using his name and so on. 139 | Once the search is complete, tick the checkbox underneath the users you want to follow (there is a "Select all users" link above the results to select them all) and click the "Follow Selected Users" button to follow the selected users. Top tip: When following new users, you should untick the "Auto unfollow users who are not following you" from the "Auto Follow Settings" tab; a possible approach might be to disable auto unfollowing, follow some users and then re-enable auto unfollow after a week or so. 140 | 141 | Tweet Settings 142 | ====== 143 | 144 | To access the tweet settings for an authorized Twitter account, simply click the "Tweets" link in the account table on the homepage of your install. There are then several tabbed options available which are detailed below. 145 | 146 | Post Quick Tweet 147 | ====== 148 | 149 | Although Twando is certainly not intended as a replacement for your Twitter client of choice, or even the Twitter web interface, the ability to simply post a quick tweet from your account is included. This is also useful for checking your account is working as expected. 150 | 151 | Scheduled Tweets 152 | ====== 153 | 154 | This screen lists all the tweets currently scheduled for the selected account. You can edit any scheduled tweet by clicking the "Edit" link; click the bin (trashcan) icon to delete a scheduled tweet. 155 | 156 | Schedule a Tweet 157 | ====== 158 | 159 | Here you can schedule a tweet to be posted from the selected Twitter account. The time and date must be specified in MySQL format (YYYY-MM-DD HH:MM) but this is easily set by clicking the calendar icon which will bring up a time and date selector window. Tweets are posted based on the PHP time set in your hosting account; this may not be the same as your local time. The scheduled tweet cron posts scheduled tweets from before or exactly the time the cron is run, so the post time may not be exactly the same as the time you specify depending on how often you run the tweet cron job. 160 | 161 | Bulk CSV Tweet Upload 162 | ====== 163 | 164 | This system allows you to upload a CSV of scheduled tweets. The CSV should contain just two columns; column 1 should contain the tweet time in MySQL format (YYYY-MM-DD HH:MM), column 2 the tweet text. There is an example csv included with your install. The maximum size of the CSV you can upload depends on your host; typically shared hosts limit file uploads to 2mb but you may be able to upload larger files. Top tip: Spreadsheet programs such as Microsoft Excel will sometimes convert the date into an incorrect format; it's therefore recommended to create your csv in a text editor such as Notepad if you are doing this manually. 165 | 166 | Log Settings 167 | ====== 168 | 169 | To access the log settings for an authorized Twitter account, simply click the "Logs" link in the account table on the homepage of your install. There are then several tabbed options available which are detailed below. 170 | 171 | Log Settings 172 | ====== 173 | 174 | This checkbox simple sets if you would like to log the actions for this account when either the follow or tweet cron job is run. By default this is enabled when you authorize a new account. We would recommend enabling logging for all accounts as the logs provide useful information. 175 | 176 | View Follow Logs 177 | ====== 178 | 179 | This table displays the logs from the follow cron job. The information here shows who has unfollowed your account, who you have followed and consequently which users Twando auto followed or unfollowed for you. This screen also logs when automatic DMs are sent. In the "Affected Users" column you will see the Twitter profile images of the relevant users affected by a particular log entry. Hovering over the image will show additional useful information about their account (name, screen name, follower counts etc); clicking the image will open a new window link to their profile on Twitter.com. 180 | 181 | View Tweet Logs 182 | ====== 183 | 184 | This table shows your scheduled tweet history and shows both the time the tweet was posted and also whether or not the tweet posting was sucessful. 185 | 186 | Purge Log History 187 | ====== 188 | 189 | As the name suggests, this tab allows you to delete log history for a particular account if you wish. There is also an option to "Empty user cache"; the user cache is shared between all your Twitter accounts and is used to display more information about Twitter users in the "View Follow Logs" section. If one of your accounts is being followed by 1000 new users a day, the cache could get quite large quite quickly so you may want to empty it periodically. If you are proficient with phpMyAdmin, you can truncate the (tw_)user_cache table yourself at any time. This will not affect the log records; it just means that the additional information about a user (name, screen name, follower counts etc) will not be displayed in the "Affected Users" column in the "View Follow Logs" section. 190 | 191 | Multi Account Functions 192 | ====== 193 | 194 | Once you have authorized two or more Twitter accounts, you can then make use of the multi account functions Twando provides. These can be accessed by clicking the "Multi account functions" link towards the bottom of the homepage of your install. There are then several tabbed options available which are detailed below. 195 | 196 | Cross Follow Accounts 197 | ====== 198 | 199 | This option is extremely useful if you have a lot of Twitter accounts authorized with your Twando install and want to increase the number of followers to all of them. With one click, Twando will follow all your Twitter accounts from all your other Twitter accounts. You can also do the reverse and cross unfollow to remove the connection between accounts. 200 | 201 | All Follow / Unfollow 202 | ====== 203 | 204 | This section allows you to follow or unfollow a list of screen names or Twitter id's from all your accounts. For example, if you had 100 Twitter accounts authorized and wanted them all to follow a particular user, you could perform this operation very easily here. 205 | 206 | Multi Tweet 207 | ====== 208 | 209 | As the name suggests, you can enter a Tweet here which will be posted by all your Twitter accounts simultaneously. -------------------------------------------------------------------------------- /cron_follow.php: -------------------------------------------------------------------------------- 1 | get_cron_state('follow') == 1) { 22 | echo mainFuncs::push_response(24); 23 | $run_cron = false; 24 | } 25 | } 26 | 27 | if ($run_cron == true) { 28 | 29 | /* 30 | New to 0.3 - Some people on super cheap hosting seem to get 31 | SQL errors - output them if they occur 32 | */ 33 | $db->output_error = 1; 34 | 35 | //Set cron status 36 | $cron->set_cron_state('follow',1); 37 | 38 | //Get credentials 39 | $ap_creds = $db->get_ap_creds(); 40 | 41 | //Loop through all accounts 42 | $q1 = $db->query("SELECT * FROM " . DB_PREFIX . "authed_users ORDER BY (followers_count + friends_count) ASC"); 43 | while ($q1a = $db->fetch_array($q1)) { 44 | 45 | //Defines 46 | $connection = new TwitterOAuth($ap_creds['consumer_key'], $ap_creds['consumer_secret'], $q1a['oauth_token'], $q1a['oauth_token_secret']); 47 | $cron->set_user_id($q1a['id']); 48 | $cron->set_first_pass_done($q1a['fr_fw_fp']); 49 | $cron->set_log($q1a['log_data']); 50 | $cron->set_throttle_time(0,'fr'); 51 | $cron->set_throttle_time(0,'fw'); 52 | $cron->set_fr_fw_issue(0); 53 | 54 | //Refresh details 55 | $content = $connection->get('account/verify_credentials'); 56 | if ($connection->http_code == 200) { 57 | //Update DB 58 | $tw_user = array('id' => $q1a['id'], 59 | 'profile_image_url' => $content->profile_image_url_https, 60 | 'screen_name' => $content->screen_name, 61 | 'followers_count' => $content->followers_count, 62 | 'friends_count' => $content->friends_count, 63 | 'last_updated' => date("Y-m-d H:i:s") 64 | ); 65 | 66 | $db->store_authed_user($tw_user); 67 | 68 | /* 69 | API Version 1.1 has really killed the limits here; from 350 per hour to 15 per 70 | 15 minutes. Also Follower and Friend lists are now separately rate limited; code 71 | adjusted below to set individual throttle rates. Thanks Twitter, real helpful (not). 72 | 73 | On the plus side, these limits are now not used up elsewhere in the application 74 | so you can tweet away while this cron is running. 75 | 76 | Rate limit checking on every request is still very slow and most sensible 77 | users would never need it, hence just setting a throttle value once per account. 78 | */ 79 | 80 | //Work out if to throttle 81 | $ops_array = array('fw' => 'followers_count','fr' => 'friends_count'); 82 | $rate_con = $cron->get_remaining_hits(); 83 | $this_limit = 0; 84 | 85 | foreach ($ops_array as $this_op => $this_op_var) { 86 | 87 | if ((int)$rate_con[$this_op . '_reset'] == 0) { 88 | //Either bang on the reset or we couldn't get a connection 89 | $this_limit = TWITTER_API_LIMIT; 90 | } elseif ($rate_con[$this_op . '_remaining'] >= $rate_con[$this_op . '_limit']) { 91 | //This should never happen 92 | $this_limit = $rate_con[$this_op . '_limit']; 93 | } elseif ((int)$rate_con[$this_op . '_remaining'] < 3) { 94 | //Really don't want to attempt with so few requests remaining. Sleep until reset 95 | if ((int)$rate_con[$this_op . '_reset'] > 0) { 96 | sleep($rate_con[$this_op . '_reset']); 97 | } 98 | $this_limit = $rate_con[$this_op . '_limit']; 99 | } else { 100 | $this_limit = $rate_con[$this_op . '_remaining']; 101 | } 102 | 103 | //Work out throttle 104 | if ($content->$this_op_var > ($this_limit * TWITTER_API_LIST_FW)) { 105 | //Work out worst case throttle time 106 | if ($this_limit != 0) { 107 | $throt_time = ((int)(900 / $this_limit) + 1); 108 | } else { 109 | $throt_time = 1; 110 | } 111 | $cron->set_throttle_time($throt_time,$this_op); 112 | } 113 | 114 | //End of foreach op type 115 | } 116 | 117 | //Set table flags 118 | $cron->clear_table_flags(); 119 | $c1 = $cron->store_fw_fr_list('fw'); 120 | $c2 = $cron->store_fw_fr_list('fr'); 121 | 122 | if ($cron->get_fr_fw_issue() == 1) { 123 | $cron->store_cron_log(1,$cron_txts[1],''); 124 | $cron->set_first_pass_done(666); 125 | } 126 | 127 | } else { 128 | //Can't connect to this account so set flag to skip stages below 129 | $cron->set_first_pass_done(666); 130 | } 131 | 132 | //Log number of followers on first pass 133 | if ($cron->get_first_pass_done() == 0) { 134 | 135 | //Correct text - don't just add s to help multi language support later 136 | if ($c1 == 1) {$f_txt1 = $cron_txts[2];} else {$f_txt1 = $cron_txts[3];} 137 | if ($c2 == 1) {$f_txt2 = $cron_txts[4];} else {$f_txt2 = $cron_txts[5];} 138 | 139 | $cron->store_cron_log(1,$c1 . ' ' . $f_txt1 . $cron_txts[6],''); 140 | $cron->store_cron_log(1,$c2 . ' ' . $f_txt2 . $cron_txts[6],''); 141 | 142 | //Update user to indicate first pass done 143 | $cron->set_first_pass_done_db(); 144 | 145 | } elseif ( ($cron->get_first_pass_done() == 1) and ($cron->get_fr_fw_issue() == 0) ) { 146 | 147 | /* 148 | Decided in the end that if there is an API error, not doing anything at all is probably 149 | safest. Disabling auto-follow alone is fine, but for a large account an API connection 150 | failure for the friends and followers list could mean a lot of entries being added to 151 | the user cache. 152 | */ 153 | 154 | //Get array of new users 155 | $fw_fr_types = array( 156 | 'fw_new' => $cron_txts[7], 157 | 'fw_gone' => $cron_txts[8], 158 | 'fr_new' => $cron_txts[9], 159 | 'fr_gone' => $cron_txts[10] 160 | ); 161 | $data_array = array(); 162 | $data_array = $cron->get_id_changes(); 163 | 164 | //Loop through and log data 165 | foreach ($fw_fr_types as $key => $text) { 166 | if ((sizeof($data_array[$key])) > 0) { 167 | //Log row 168 | if ((sizeof($data_array[$key])) == 1) {$text = str_replace($cron_txts[11],$cron_txts[12],$text);} 169 | $cron->store_cron_log(1,sizeof($data_array[$key]) . $text,$data_array[$key],1); 170 | 171 | //Store users for lookup later 172 | foreach ($data_array[$key] as $this_twitter_id) { 173 | $tw_user_cache = array('twitter_id' => $this_twitter_id); 174 | $db->store_cached_user($tw_user_cache); 175 | } 176 | } 177 | } 178 | 179 | //Update flags to indicate cross matches 180 | $db->query("UPDATE " . DB_PREFIX . "fw_" . $q1a['id'] . " fw, " . DB_PREFIX . "fr_" . $q1a['id'] . " fr SET fw.otp = 1, fr.otp = 1 WHERE fw.twitter_id = fr.twitter_id AND fw.stp = 1 AND fr.stp = 1"); 181 | 182 | //Unfollow people who aren't following 183 | if ( ((int)$q1a['auto_unfollow'] == 1) and ($cron->get_fr_fw_issue() == 0) ) { 184 | 185 | //Get array of ids not to unfollow 186 | $never_unfollow = array(); 187 | $never_unfollow = $cron->get_id_exclusions(1); 188 | 189 | //Loop through IDs we're following, but that aren't in followers table 190 | $unfollow_now = array(); 191 | $qheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . "fr_" . $q1a['id'] . " WHERE otp = 0 AND stp = 1"); 192 | while ($qchecka = $db->fetch_array($qheck)) { 193 | if (!in_array($qchecka['twitter_id'],$never_unfollow)) { 194 | $unfollow_now[] = (string)$qchecka['twitter_id']; 195 | } 196 | } 197 | 198 | //If we have people to unfollow, do so now 199 | if ((sizeof($unfollow_now)) > 0) { 200 | foreach ($unfollow_now as $this_id) { 201 | 202 | //Execute unfollow - not rate limited 203 | $content = $connection->post('friendships/destroy', array('user_id' => $this_id)); 204 | 205 | 206 | if ($connection->http_code == 200) { 207 | 208 | //Make use of the fact friendship create/destroy returns said user 209 | $tw_user_cache = array( 210 | 'twitter_id' => $content->id, 211 | 'profile_image_url' => $content->profile_image_url_https, 212 | 'screen_name' => $content->screen_name, 213 | 'actual_name' => $content->name, 214 | 'followers_count' => $content->followers_count, 215 | 'friends_count' => $content->friends_count, 216 | 'last_updated' => date("Y-m-d H:i:s") 217 | ); 218 | $db->store_cached_user($tw_user_cache); 219 | 220 | //Delete from table; no point in logging x people unfollowed on next pass 221 | //if logging it here anyway 222 | $db->query("DELETE FROM " . DB_PREFIX . "fr_" . $q1a['id'] . " WHERE otp = 0 AND twitter_id = '" . $db->prep($this_id) . "'"); 223 | 224 | } else { 225 | $key = ""; 226 | $key = array_search($this_id,$unfollow_now); 227 | unset($unfollow_now[$key]); 228 | } 229 | 230 | } 231 | 232 | //Log now 233 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "fr_" . $q1a['id']); 234 | 235 | if ((sizeof($unfollow_now)) > 0) { 236 | if ((sizeof($unfollow_now)) == 1) {$u_txt = $cron_txts[12];} else {$u_txt = $cron_txts[11];} 237 | $cron->store_cron_log(1,sizeof($unfollow_now) . ' ' . $u_txt . $cron_txts[13],$unfollow_now); 238 | } 239 | 240 | } 241 | 242 | 243 | //End of auto unfollow 244 | } 245 | 246 | //Follow back users 247 | if ( ((int)$q1a['auto_follow'] > 0) and ($cron->get_fr_fw_issue() == 0) ) { 248 | 249 | //Get array of ids not to follow 250 | $never_follow = array(); 251 | $never_follow = $cron->get_id_exclusions(2); 252 | 253 | //Loop through IDs who are following us, but that aren't in friends table 254 | $follow_now = array(); 255 | if ((int)$q1a['auto_follow'] == 1 ){ 256 | //Any followers we're not following yet 257 | $qheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . "fw_" . $q1a['id'] . " WHERE otp = 0 AND stp = 1"); 258 | } elseif ((int)$q1a['auto_follow'] == 2) { 259 | //Just new followers 260 | $qheck = $db->query("SELECT twitter_id FROM " . DB_PREFIX . "fw_" . $q1a['id'] . " WHERE otp = 0 AND stp = 1 AND ntp = 1"); 261 | } 262 | 263 | while ($qchecka = $db->fetch_array($qheck)) { 264 | if (!in_array($qchecka['twitter_id'],$never_follow)) { 265 | $follow_now[] = (string)$qchecka['twitter_id']; 266 | } 267 | } 268 | 269 | //If we have people to follow, do so now 270 | if ((sizeof($follow_now)) > 0) { 271 | $dm_count = 0; 272 | foreach ($follow_now as $this_id) { 273 | 274 | //Execute follow - not rate limited 275 | $content = $connection->post('friendships/create', array('user_id' => $this_id)); 276 | 277 | /* 278 | OK, if it's a protected account, don't bother doing anything apart from sending the initial 279 | follow request. $content->protected sometimes returns 1 not true as listed in the API guide. 280 | If already followed, should return 403 but check error message just in case 200 is returned 281 | */ 282 | 283 | $protected_acc = 0; 284 | if ( ($content->protected) or (preg_match("/already requested to follow/i",$content->error)) ) { 285 | $protected_acc = 1; 286 | } 287 | 288 | if ( ($connection->http_code == 200) and ($protected_acc == 0) ) { 289 | 290 | //Make use of the fact friendship create/destroy returns said user 291 | $tw_user_cache = array( 292 | 'twitter_id' => $content->id, 293 | 'profile_image_url' => $content->profile_image_url_https, 294 | 'screen_name' => $content->screen_name, 295 | 'actual_name' => $content->name, 296 | 'followers_count' => $content->followers_count, 297 | 'friends_count' => $content->friends_count, 298 | 'last_updated' => date("Y-m-d H:i:s") 299 | ); 300 | $db->store_cached_user($tw_user_cache); 301 | 302 | //If Auto DM, send it here 303 | if ( ((int)$q1a['auto_dm'] == 1) and ($q1a['auto_dm_msg'] != "") ) { 304 | //Send DM here - not rate limited 305 | $connection->post('direct_messages/new', array('user_id' => $this_id, 'text' => $q1a['auto_dm_msg'])); 306 | if ($connection->http_code == 200) { 307 | $dm_count ++; 308 | } 309 | } 310 | 311 | //Insert into friends now that we're following, rather than appearing in 312 | //logs on next pass. 313 | $db->query("INSERT INTO " . DB_PREFIX . "fr_" . $q1a['id'] . " (twitter_id,stp,otp) VALUES ('" . $db->prep($this_id) . "',1,1)"); 314 | 315 | } else { 316 | $key = ""; 317 | $key = array_search($this_id,$follow_now); 318 | unset($follow_now[$key]); 319 | } 320 | 321 | } 322 | 323 | //Log now 324 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "fw_" . $q1a['id']); 325 | 326 | if ((sizeof($follow_now)) > 0) { 327 | if ((sizeof($follow_now)) == 1) {$u_txt = $cron_txts[12];} else {$u_txt = $cron_txts[11];} 328 | $cron->store_cron_log(1,sizeof($follow_now) . ' ' . $u_txt . $cron_txts[14],$follow_now); 329 | } 330 | 331 | if ($dm_count > 0) { 332 | if ($dm_count == 1) {$d_txt = $cron_txts[15];} else {$d_txt = $cron_txts[16];} 333 | $cron->store_cron_log(1,$dm_count . ' ' . $d_txt . $cron_txts[17],''); 334 | } 335 | } 336 | 337 | //End of auto follow back 338 | } 339 | 340 | //Now logging is complete, delete any users not seen to prevent them being 341 | //logged on next run 342 | $cron->delete_unseen_ids(); 343 | 344 | //Try and lookup cached user ids we've stored 345 | 346 | //Check remaining API requests 347 | $rate_con = $cron->get_remaining_hits(); 348 | $req_total = $rate_con['ul_remaining']; 349 | 350 | for ($i=1; $i<=2; $i++) { 351 | 352 | //Get users 353 | $uncached_users = array(); 354 | $uncached_users = $cron->get_uncached_users($i); 355 | $user_total = sizeof($uncached_users); 356 | 357 | //Loop and build 358 | if ( ($req_total > 0) and ($user_total > 0) ) { 359 | $uncached_users_split = array(); 360 | $uncached_users_split = array_chunk($uncached_users,TWITTER_API_USER_LOOKUP,true); 361 | 362 | foreach ($uncached_users_split as $this_user_array) { 363 | $pass_ids = ""; 364 | foreach ($uncached_users as $this_id) { 365 | $pass_ids .= $this_id . ','; 366 | } 367 | $pass_ids = substr($pass_ids,0,-1); 368 | 369 | //Run lookup now 370 | $content = ""; 371 | $content = $connection->post('users/lookup', array('user_id' => $pass_ids)); 372 | if ($content) { 373 | foreach ($content as $user_row) { 374 | 375 | //Cache user 376 | $tw_user_cache = array( 377 | 'twitter_id' => $user_row->id, 378 | 'profile_image_url' => $user_row->profile_image_url_https, 379 | 'screen_name' => $user_row->screen_name, 380 | 'actual_name' => $user_row->name, 381 | 'followers_count' => $user_row->followers_count, 382 | 'friends_count' => $user_row->friends_count, 383 | 'last_updated' => date("Y-m-d H:i:s") 384 | ); 385 | 386 | //Save user 387 | $db->store_cached_user($tw_user_cache); 388 | } 389 | } 390 | 391 | $req_total --; 392 | if ($req_total <= 0) {break;} 393 | } 394 | } 395 | 396 | //End of $i 1/2 loop 397 | } 398 | 399 | //Next, check again in case any Twitter IDs no longer exist and are still in 400 | //the DB cache 401 | $rate_con = $cron->get_remaining_hits(); 402 | $req_total = $rate_con['us_remaining']; 403 | 404 | //Get users 405 | $uncached_users = array(); 406 | $uncached_users = $cron->get_uncached_users(1); 407 | $user_total = sizeof($uncached_users); 408 | 409 | //Loop and build 410 | if ( ($req_total > 0) and ($user_total > 0) ) { 411 | 412 | foreach ($uncached_users as $this_id) { 413 | //Get user 414 | $content = $connection->get('users/show', array('user_id' => $this_id)); 415 | 416 | //If not a 200 code (i.e. 404) delete row from table 417 | if ($connection->http_code != 200) { 418 | $db->query("DELETE FROM " . DB_PREFIX . "user_cache WHERE twitter_id='" . $db->prep($this_id) . "'"); 419 | } 420 | 421 | $req_total --; 422 | if ($req_total <= 0) {break;} 423 | } 424 | 425 | //Optimise table 426 | $db->query("OPTIMIZE TABLE " . DB_PREFIX . "user_cache"); 427 | 428 | } 429 | 430 | 431 | //End of first pass check 432 | } 433 | 434 | //End of db loop 435 | } 436 | 437 | //Set cron status 438 | $cron->set_cron_state('follow',0); 439 | 440 | echo mainFuncs::push_response(32); 441 | 442 | //End of run cron 443 | } 444 | 445 | include('inc/include_bottom.php'); 446 | ?> 447 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------