├── .gitignore ├── .vscode └── settings.json ├── Bot.py ├── README.md ├── bin ├── activate ├── activate.csh ├── activate.fish ├── easy_install ├── easy_install-3.6 ├── pip ├── pip3 ├── pip3.6 ├── python └── python3 ├── example-subscribe.py ├── pip-selfcheck.json ├── pyvenv.cfg └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | lib/ 3 | *.log 4 | main.py -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/leejunhyung/anaconda3/bin/python", 3 | "python.formatting.provider": "yapf" 4 | } -------------------------------------------------------------------------------- /Bot.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.keys import Keys 3 | from selenium.webdriver.support.ui import WebDriverWait 4 | from selenium.webdriver.support import expected_conditions as EC 5 | from selenium.webdriver.common.by import By 6 | from selenium.common.exceptions import TimeoutException 7 | 8 | YOUTUBE_ADDRESS = 'https://www.youtube.com/' 9 | YOUTUBE_TITLE = 'YouTube' 10 | 11 | 12 | class Bot: 13 | def __init__(self, chromeDriverLocation): 14 | # Download chromedriver first: https://sites.google.com/a/chromium.org/chromedriver/downloads 15 | self.driver = webdriver.Chrome(chromeDriverLocation) 16 | 17 | def goto_youtube(self): 18 | self.driver.get(YOUTUBE_ADDRESS) 19 | assert YOUTUBE_TITLE in self.driver.title 20 | return self 21 | 22 | def like(self, videoId, watchFullVideo=False): 23 | videoUrl = f'https://www.youtube.com/watch?v={videoId}' 24 | likeButtonLocator = 'ytd-toggle-button-renderer #button.ytd-toggle-button-renderer' 25 | likeButtonWrapperLocator = 'yt-icon-button#button.style-scope.ytd-toggle-button-renderer' 26 | 27 | self.driver.get(videoUrl) 28 | 29 | try: 30 | likeButton = WebDriverWait(self.driver, 10) \ 31 | .until(EC.element_to_be_clickable((By.CSS_SELECTOR, likeButtonLocator))) 32 | likeButtonWrapper = self.driver.find_element_by_css_selector( 33 | likeButtonWrapperLocator) 34 | 35 | # aria-pressed attribute is of type string, not boolean 36 | liked = likeButtonWrapper.get_attribute('aria-pressed') == 'true' 37 | 38 | if (not liked): 39 | likeButton.click() 40 | 41 | if watchFullVideo: 42 | # TODO: integrate youtube api v3 to get video duration 43 | WebDriverWait(self.driver, 10) 44 | 45 | except TimeoutException: 46 | print(f'Error: video with id {videoId} doesn\'t exist!') 47 | 48 | return self 49 | 50 | def subscribe(self, channelId): 51 | channelUrl = f'{YOUTUBE_ADDRESS}/channel/{channelId}' 52 | subscribeButtonLocator = (By.TAG_NAME, 'ytd-subscribe-button-renderer') 53 | 54 | self.driver.get(channelUrl) 55 | 56 | try: 57 | subscribeButton = WebDriverWait(self.driver, 10) \ 58 | .until(EC.presence_of_element_located(subscribeButtonLocator)) 59 | subscribeButton.click() 60 | except TimeoutException: 61 | print('Error: channel doesn\'t exist!') 62 | 63 | return self 64 | 65 | def login(self, email, password): 66 | loginButtonLocator = 'ytd-button-renderer.style-blue-text[is-paper-button]' 67 | emailInputLocator = 'identifier' 68 | passwordInputLocator = (By.NAME, 'password') 69 | userProfileLocator = (By.TAG_NAME, 'ytd-topbar-menu-button-renderer') 70 | 71 | # go to login page 72 | loginButton = self.driver.find_element_by_css_selector( 73 | loginButtonLocator) 74 | loginButton.click() 75 | 76 | emailInput = self.driver.find_element_by_name(emailInputLocator) 77 | emailInput.send_keys(email, Keys.RETURN) 78 | 79 | try: 80 | # wait until 'password' element is interactable 81 | passwordInput = WebDriverWait(self.driver, 10) \ 82 | .until(EC.element_to_be_clickable(passwordInputLocator)) 83 | 84 | passwordInput.send_keys(password, Keys.RETURN) 85 | 86 | # wait until redirect ends so that you can see your profile image on right top corner 87 | WebDriverWait(self.driver, 10) \ 88 | .until(EC.presence_of_element_located(userProfileLocator)) 89 | except TimeoutException: 90 | print('Error: wrong email or password!') 91 | 92 | return self 93 | 94 | def end(self): 95 | self.driver.close() 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Youtube bot 2 | 3 | ## Getting Started 4 | 5 | 1. This program uses `chromedriver`. So you must install it via [link](https://sites.google.com/a/chromium.org/chromedriver/downloads). 6 | 2. Clone the repository. 7 | 8 | ```bash 9 | git clone https://github.com/leejh3224/youtube-bot.git && cd youtube-bot 10 | ``` 11 | 12 | 3. Create `main.py` inside root directory. 13 | (You can copy `example-subscribe.py`) 14 | 15 | ```bash 16 | touch main.py or 17 | cp example-subscribe.py main.py 18 | ``` 19 | 20 | 4. execute `main.py`. 21 | 22 | ```python 23 | python3 main.py 24 | ``` 25 | 26 | ## features: 27 | 28 | - [x] login to google 29 | - [x] subscribe a channel 30 | - [x] like a video 31 | 32 | > If you have any issue or code suggestion, please make an issue or pull request. 33 | -------------------------------------------------------------------------------- /bin/activate: -------------------------------------------------------------------------------- 1 | # This file must be used with "source bin/activate" *from bash* 2 | # you cannot run it directly 3 | 4 | deactivate () { 5 | # reset old environment variables 6 | if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then 7 | PATH="${_OLD_VIRTUAL_PATH:-}" 8 | export PATH 9 | unset _OLD_VIRTUAL_PATH 10 | fi 11 | if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then 12 | PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" 13 | export PYTHONHOME 14 | unset _OLD_VIRTUAL_PYTHONHOME 15 | fi 16 | 17 | # This should detect bash and zsh, which have a hash command that must 18 | # be called to get it to forget past commands. Without forgetting 19 | # past commands the $PATH changes we made may not be respected 20 | if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then 21 | hash -r 22 | fi 23 | 24 | if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then 25 | PS1="${_OLD_VIRTUAL_PS1:-}" 26 | export PS1 27 | unset _OLD_VIRTUAL_PS1 28 | fi 29 | 30 | unset VIRTUAL_ENV 31 | if [ ! "$1" = "nondestructive" ] ; then 32 | # Self destruct! 33 | unset -f deactivate 34 | fi 35 | } 36 | 37 | # unset irrelevant variables 38 | deactivate nondestructive 39 | 40 | VIRTUAL_ENV="/Users/leejunhyung/GoogleDrive/youtube_bot" 41 | export VIRTUAL_ENV 42 | 43 | _OLD_VIRTUAL_PATH="$PATH" 44 | PATH="$VIRTUAL_ENV/bin:$PATH" 45 | export PATH 46 | 47 | # unset PYTHONHOME if set 48 | # this will fail if PYTHONHOME is set to the empty string (which is bad anyway) 49 | # could use `if (set -u; : $PYTHONHOME) ;` in bash 50 | if [ -n "${PYTHONHOME:-}" ] ; then 51 | _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" 52 | unset PYTHONHOME 53 | fi 54 | 55 | if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then 56 | _OLD_VIRTUAL_PS1="${PS1:-}" 57 | if [ "x(youtube_bot) " != x ] ; then 58 | PS1="(youtube_bot) ${PS1:-}" 59 | else 60 | if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then 61 | # special case for Aspen magic directories 62 | # see http://www.zetadev.com/software/aspen/ 63 | PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" 64 | else 65 | PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" 66 | fi 67 | fi 68 | export PS1 69 | fi 70 | 71 | # This should detect bash and zsh, which have a hash command that must 72 | # be called to get it to forget past commands. Without forgetting 73 | # past commands the $PATH changes we made may not be respected 74 | if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then 75 | hash -r 76 | fi 77 | -------------------------------------------------------------------------------- /bin/activate.csh: -------------------------------------------------------------------------------- 1 | # This file must be used with "source bin/activate.csh" *from csh*. 2 | # You cannot run it directly. 3 | # Created by Davide Di Blasi . 4 | # Ported to Python 3.3 venv by Andrew Svetlov 5 | 6 | alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate' 7 | 8 | # Unset irrelevant variables. 9 | deactivate nondestructive 10 | 11 | setenv VIRTUAL_ENV "/Users/leejunhyung/GoogleDrive/youtube_bot" 12 | 13 | set _OLD_VIRTUAL_PATH="$PATH" 14 | setenv PATH "$VIRTUAL_ENV/bin:$PATH" 15 | 16 | 17 | set _OLD_VIRTUAL_PROMPT="$prompt" 18 | 19 | if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then 20 | if ("youtube_bot" != "") then 21 | set env_name = "youtube_bot" 22 | else 23 | if (`basename "VIRTUAL_ENV"` == "__") then 24 | # special case for Aspen magic directories 25 | # see http://www.zetadev.com/software/aspen/ 26 | set env_name = `basename \`dirname "$VIRTUAL_ENV"\`` 27 | else 28 | set env_name = `basename "$VIRTUAL_ENV"` 29 | endif 30 | endif 31 | set prompt = "[$env_name] $prompt" 32 | unset env_name 33 | endif 34 | 35 | alias pydoc python -m pydoc 36 | 37 | rehash 38 | -------------------------------------------------------------------------------- /bin/activate.fish: -------------------------------------------------------------------------------- 1 | # This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org) 2 | # you cannot run it directly 3 | 4 | function deactivate -d "Exit virtualenv and return to normal shell environment" 5 | # reset old environment variables 6 | if test -n "$_OLD_VIRTUAL_PATH" 7 | set -gx PATH $_OLD_VIRTUAL_PATH 8 | set -e _OLD_VIRTUAL_PATH 9 | end 10 | if test -n "$_OLD_VIRTUAL_PYTHONHOME" 11 | set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME 12 | set -e _OLD_VIRTUAL_PYTHONHOME 13 | end 14 | 15 | if test -n "$_OLD_FISH_PROMPT_OVERRIDE" 16 | functions -e fish_prompt 17 | set -e _OLD_FISH_PROMPT_OVERRIDE 18 | functions -c _old_fish_prompt fish_prompt 19 | functions -e _old_fish_prompt 20 | end 21 | 22 | set -e VIRTUAL_ENV 23 | if test "$argv[1]" != "nondestructive" 24 | # Self destruct! 25 | functions -e deactivate 26 | end 27 | end 28 | 29 | # unset irrelevant variables 30 | deactivate nondestructive 31 | 32 | set -gx VIRTUAL_ENV "/Users/leejunhyung/GoogleDrive/youtube_bot" 33 | 34 | set -gx _OLD_VIRTUAL_PATH $PATH 35 | set -gx PATH "$VIRTUAL_ENV/bin" $PATH 36 | 37 | # unset PYTHONHOME if set 38 | if set -q PYTHONHOME 39 | set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME 40 | set -e PYTHONHOME 41 | end 42 | 43 | if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" 44 | # fish uses a function instead of an env var to generate the prompt. 45 | 46 | # save the current fish_prompt function as the function _old_fish_prompt 47 | functions -c fish_prompt _old_fish_prompt 48 | 49 | # with the original prompt function renamed, we can override with our own. 50 | function fish_prompt 51 | # Save the return status of the last command 52 | set -l old_status $status 53 | 54 | # Prompt override? 55 | if test -n "(youtube_bot) " 56 | printf "%s%s" "(youtube_bot) " (set_color normal) 57 | else 58 | # ...Otherwise, prepend env 59 | set -l _checkbase (basename "$VIRTUAL_ENV") 60 | if test $_checkbase = "__" 61 | # special case for Aspen magic directories 62 | # see http://www.zetadev.com/software/aspen/ 63 | printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal) 64 | else 65 | printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal) 66 | end 67 | end 68 | 69 | # Restore the return status of the previous command. 70 | echo "exit $old_status" | . 71 | _old_fish_prompt 72 | end 73 | 74 | set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" 75 | end 76 | -------------------------------------------------------------------------------- /bin/easy_install: -------------------------------------------------------------------------------- 1 | #!/Users/leejunhyung/GoogleDrive/youtube_bot/bin/python3 2 | 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from setuptools.command.easy_install import main 8 | 9 | if __name__ == '__main__': 10 | sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /bin/easy_install-3.6: -------------------------------------------------------------------------------- 1 | #!/Users/leejunhyung/GoogleDrive/youtube_bot/bin/python3 2 | 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from setuptools.command.easy_install import main 8 | 9 | if __name__ == '__main__': 10 | sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /bin/pip: -------------------------------------------------------------------------------- 1 | #!/Users/leejunhyung/GoogleDrive/youtube_bot/bin/python3 2 | 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from pip._internal import main 8 | 9 | if __name__ == '__main__': 10 | sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /bin/pip3: -------------------------------------------------------------------------------- 1 | #!/Users/leejunhyung/GoogleDrive/youtube_bot/bin/python3 2 | 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from pip._internal import main 8 | 9 | if __name__ == '__main__': 10 | sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /bin/pip3.6: -------------------------------------------------------------------------------- 1 | #!/Users/leejunhyung/GoogleDrive/youtube_bot/bin/python3 2 | 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from pip._internal import main 8 | 9 | if __name__ == '__main__': 10 | sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 11 | sys.exit(main()) 12 | -------------------------------------------------------------------------------- /bin/python: -------------------------------------------------------------------------------- 1 | python3 -------------------------------------------------------------------------------- /bin/python3: -------------------------------------------------------------------------------- 1 | /Users/leejunhyung/anaconda3/bin/python3 -------------------------------------------------------------------------------- /example-subscribe.py: -------------------------------------------------------------------------------- 1 | from Bot import Bot 2 | 3 | # your location to chromedriver executable 4 | bot = Bot('/Users/**/Downloads/chromedriver') 5 | 6 | bot.goto_youtube() \ 7 | .login(email='your@email', password='password') \ 8 | .subscribe(channelId='uisdg22g') \ 9 | .end() -------------------------------------------------------------------------------- /pip-selfcheck.json: -------------------------------------------------------------------------------- 1 | {"last_check":"2018-11-12T15:20:41Z","pypi_version":"18.1"} -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /Users/leejunhyung/anaconda3/bin 2 | include-system-site-packages = false 3 | version = 3.6.5 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium==3.141.0 2 | urllib3>=1.24.2 3 | --------------------------------------------------------------------------------