├── .gitattributes ├── .gitignore ├── LICENSE └── src ├── app └── Main.java └── tools ├── ArrayBuilder.java ├── DifferenceFinder.java ├── ResultStringBuilder.java ├── SourceReader.java ├── TxtFileManager.java ├── UserNameFinder.java └── UserNumbersGetter.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | *.iml 26 | *.idea 27 | *.DS_Store 28 | out/production/ 29 | /Github-Checker.iml 30 | out 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 R. Cem Kahveci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/app/Main.java: -------------------------------------------------------------------------------- 1 | package app; 2 | 3 | import tools.*; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.URISyntaxException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.util.ArrayList; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | public class Main extends Thread { 17 | private static final long startTime = System.nanoTime(); 18 | static String userToCheck = "khvci"; 19 | private static int followersPageNumber; 20 | private static int followingPageNumber; 21 | 22 | public static void setFollowersPageNumber(int num) { 23 | followersPageNumber = num; 24 | } 25 | 26 | public static void setFollowingPageNumber(int num) { 27 | followingPageNumber = num; 28 | } 29 | 30 | public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException { 31 | 32 | UserNumbersGetter.readProfilePageSource(userToCheck); 33 | 34 | TxtFileManager txtFileManager = new TxtFileManager(); 35 | File followersTxtFile = txtFileManager.create("followers.txt"); 36 | File followingTxtFile = txtFileManager.create("following.txt"); 37 | 38 | InputStream followerStream = Files.newInputStream(Path.of("src/followers.txt")); 39 | InputStream followingStream = Files.newInputStream(Path.of("src/following.txt")); 40 | 41 | Main secondThread = new Main(); 42 | secondThread.start(); 43 | 44 | SourceReader sourceReader = new SourceReader( 45 | userToCheck, followersPageNumber, "followers"); 46 | sourceReader.read(); 47 | 48 | secondThread.join(); 49 | 50 | List followers = new ArrayList<>(); 51 | List following = new ArrayList<>(); 52 | 53 | ArrayBuilder arrayBuilder = new ArrayBuilder(); 54 | arrayBuilder.arrayBuilder(followers, followerStream); 55 | arrayBuilder.arrayBuilder(following, followingStream); 56 | 57 | DifferenceFinder differenceFinder = new DifferenceFinder(); 58 | differenceFinder.findDifference(followers, following); 59 | 60 | ResultStringBuilder resultStringBuilder = new ResultStringBuilder(); 61 | String resultToReturn = resultStringBuilder.CreateResultString(followers, 62 | following, 63 | differenceFinder.getDontFollowYou(), 64 | differenceFinder.getYouDontFollow()); 65 | System.out.println(resultToReturn); 66 | 67 | txtFileManager.delete(followersTxtFile); 68 | txtFileManager.delete(followingTxtFile); 69 | 70 | System.out.printf( 71 | "\nTotal runtime: %d ms.\n", 72 | (System.nanoTime() - startTime) / 1000000); 73 | 74 | 75 | } 76 | 77 | @Override 78 | public void run() { 79 | try { 80 | SourceReader sourceReader2 = new SourceReader( 81 | userToCheck, followingPageNumber, "following"); 82 | sourceReader2.read(); 83 | } catch (IOException e) { 84 | System.out.println("second thread problem"); 85 | } catch (InterruptedException e) { 86 | System.out.println("InterruptedException"); 87 | interrupt(); 88 | } catch (URISyntaxException e) { 89 | System.out.println("URISyntaxException"); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/tools/ArrayBuilder.java: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | import java.io.InputStream; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.List; 6 | import java.util.Scanner; 7 | import java.util.Set; 8 | 9 | public class ArrayBuilder { 10 | public void arrayBuilder( 11 | List groupArray, 12 | InputStream inputStream) { 13 | 14 | try (Scanner scannerFollowing = new Scanner( 15 | inputStream, StandardCharsets.UTF_8)) { 16 | while (scannerFollowing.hasNextLine()) { 17 | String followingLine = scannerFollowing.nextLine(); 18 | if (followingLine.startsWith( 19 | " dontFollowYou = new ArrayList<>(); 10 | private final List youDontFollow = new ArrayList<>(); 11 | 12 | public void findDifference( 13 | List followersArray, 14 | List followingArray) { 15 | 16 | // Find users who follow you but you don't follow back 17 | for (String follower : followersArray) { 18 | if (!followingArray.contains(follower)) { 19 | youDontFollow.add(follower); 20 | } 21 | } 22 | 23 | // Find users who you follow but they don't follow you back 24 | for (String followingTo : followingArray) { 25 | if (!followersArray.contains(followingTo)) { 26 | dontFollowYou.add(followingTo); 27 | } 28 | } 29 | } 30 | 31 | //getters 32 | public List getDontFollowYou() { 33 | return dontFollowYou; 34 | } 35 | 36 | public List getYouDontFollow() { 37 | return youDontFollow; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/tools/ResultStringBuilder.java: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | public class ResultStringBuilder { 7 | public String CreateResultString(List followersSet, 8 | List followingSet, 9 | List dontFollowYou, 10 | List youDontFollow) { 11 | 12 | StringBuilder result = new StringBuilder(); 13 | result 14 | .append("\nFollowers: ").append(followersSet.size()) 15 | .append(" | Following: ").append(followingSet.size()) 16 | .append("\n\nWho does not follow you back: ").append(dontFollowYou.size()); 17 | 18 | for (Object s : dontFollowYou) { 19 | result.append("\n https://github.com/").append(s); 20 | } 21 | 22 | result 23 | .append("\n\nWho you do not follow back: ") 24 | .append(youDontFollow.size()).append("\n\n") 25 | .append(youDontFollow); 26 | 27 | return result.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/tools/SourceReader.java: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.net.URLConnection; 6 | import java.net.URI; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Scanner; 12 | 13 | 14 | public class SourceReader { 15 | private final String userName; 16 | private final int numberOfPages; 17 | private final String group; 18 | 19 | public SourceReader(String userName, int numberOfPages, String group) { 20 | this.userName = userName; 21 | this.numberOfPages = numberOfPages; 22 | this.group = group; 23 | } 24 | 25 | public void read() throws IOException, InterruptedException, URISyntaxException { 26 | List linkList = generateLinkList(); 27 | StringBuilder content = new StringBuilder(); 28 | 29 | for (int i = 0; i < linkList.size(); i++) { 30 | 31 | readSource(linkList, i, content); 32 | } 33 | 34 | Path path = Path.of(String.format("src/%s.txt", group)); 35 | Files.writeString(path, content); 36 | } 37 | 38 | private ArrayList generateLinkList() { 39 | ArrayList linkList = new ArrayList<>(); 40 | for (int i = 1; i <= numberOfPages; i++) { 41 | linkList.add(String.format( 42 | "https://github.com/%s?page=%d&tab=%s", 43 | userName, i, group)); 44 | } 45 | return linkList; 46 | } 47 | 48 | private void readSource(List linkList, int i, StringBuilder content) 49 | throws InterruptedException, IOException, URISyntaxException { 50 | URLConnection connection; 51 | try { 52 | connection = new URI(linkList.get(i)).toURL() 53 | .openConnection(); 54 | scanConnection(content, connection); 55 | } catch (Exception ex) { 56 | System.out.printf("%d pages has been read, sleeping for 1 min to prevent from HTTP 429 (too many requests).%n", i); 57 | Thread.sleep(60000); 58 | 59 | connection = new URI(linkList.get(i)).toURL().openConnection(); 60 | scanConnection(content, connection); 61 | } 62 | } 63 | 64 | private void scanConnection(StringBuilder content, URLConnection connection) throws IOException { 65 | Scanner scanner = new Scanner(connection.getInputStream()); 66 | scanner.useDelimiter("\\Z"); 67 | content.append(scanner.next()); 68 | scanner.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/tools/TxtFileManager.java: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | public class TxtFileManager { 7 | public File create(String txtFileName) { 8 | File file = new File("src/" + txtFileName); 9 | 10 | try { 11 | file.createNewFile(); 12 | } catch (IOException e) { 13 | e.printStackTrace(); 14 | } 15 | 16 | return file; 17 | } 18 | 19 | public void delete(File file) { 20 | file.delete(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/tools/UserNameFinder.java: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | public class UserNameFinder { 4 | public static String getUserName(String str) { 5 | return str 6 | .split("user\" data-hovercard-url=\"/users/")[1] 7 | .split("/hovercard\" data-octo-click=")[0]; 8 | } 9 | 10 | public static String getCompanyName(String str) { 11 | return str 12 | .split("data-hovercard-url=\"/orgs/")[1] 13 | .split("/hovercard\" data-octo-click")[0]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tools/UserNumbersGetter.java: -------------------------------------------------------------------------------- 1 | package tools; 2 | 3 | import app.Main; 4 | 5 | import java.io.IOException; 6 | import java.net.MalformedURLException; 7 | import java.net.URL; 8 | import java.net.URLConnection; 9 | import java.util.Scanner; 10 | 11 | public class UserNumbersGetter { 12 | private static final StringBuilder profilePageSourceCode = new StringBuilder(); 13 | 14 | public static void readProfilePageSource(String userToCheck) throws NumberFormatException { 15 | fetchDataFromUserProfile(userToCheck); 16 | getFollowerPageNumber(); 17 | getFollowingPageNumber(); 18 | } 19 | 20 | private static void fetchDataFromUserProfile(String userToCheck) { 21 | Scanner scanner; 22 | URLConnection connection; 23 | try { 24 | connection = new URL("https://github.com/" + userToCheck).openConnection(); 25 | scanner = new Scanner(connection.getInputStream()); 26 | scanner.useDelimiter("\\Z"); 27 | profilePageSourceCode.append(scanner.next()); 28 | } catch (Exception e) { 29 | System.out.println("problem to fetch data from user profile: " + userToCheck); 30 | } 31 | } 32 | 33 | private static void getFollowerPageNumber() { 34 | String followersPageNumberAsString = profilePageSourceCode.substring( 35 | profilePageSourceCode.indexOf("\n" + 36 | " ") + 58, 37 | profilePageSourceCode.indexOf("\n followers")); 38 | 39 | try { 40 | Main.setFollowersPageNumber((Integer.parseInt(followersPageNumberAsString) / 50 + 1)); 41 | } catch (Exception ex) { 42 | System.out.println("WARNING: followersPageNumberAsString: "+followersPageNumberAsString); 43 | String[] temp1 = followersPageNumberAsString.split("\\."); 44 | followersPageNumberAsString = temp1.length < 2 ? 45 | temp1[0].split("k")[0] + "000" : 46 | temp1[0] + temp1[1].charAt(0) + "00"; 47 | 48 | Main.setFollowersPageNumber(((Integer.parseInt(followersPageNumberAsString) + 100) / 50 + 1)); 49 | } 50 | } 51 | 52 | private static void getFollowingPageNumber() { 53 | String followingPageNumberAsString = profilePageSourceCode.substring( 54 | profilePageSourceCode.indexOf( 55 | "?tab=following\">\n ") + 68, 56 | profilePageSourceCode.indexOf("\n following")); 57 | 58 | try { 59 | Main.setFollowingPageNumber((Integer.parseInt(followingPageNumberAsString) / 50 + 1)); 60 | } catch (Exception ex) { 61 | System.out.println("WARNING: followingPageNumberAsString: "+followingPageNumberAsString); 62 | String[] temp2 = followingPageNumberAsString.split("\\."); 63 | followingPageNumberAsString = temp2.length < 2 ? 64 | temp2[0].split("k")[0] + "000" : 65 | temp2[0] + temp2[1].charAt(0) + "00"; 66 | 67 | Main.setFollowingPageNumber(((Integer.parseInt(followingPageNumberAsString) + 100) / 50 + 1)); 68 | } 69 | } 70 | } 71 | --------------------------------------------------------------------------------