├── .gitignore ├── requirements.txt ├── assets └── readme.png ├── .travis.yml ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── tests.py ├── app.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.21.0 2 | Discord-Webhooks==1.0.4 3 | -------------------------------------------------------------------------------- /assets/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesIves/perforce-commit-discord-bot/HEAD/assets/readme.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | cache: pip 5 | install: 6 | - pip install -r requirements.txt 7 | script: 8 | - python tests.py 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | > Provide a description of what your changes do. 3 | 4 | **Testing Instructions** 5 | > Give us step by step instructions on how to test your changes. 6 | 7 | **Additional Notes** 8 | > Anything else that will help us test the pull request. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: If you'd like to make a suggestion please fill out the form below. 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | 9 | > Please provide a clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | **Describe the solution you'd like** 12 | 13 | > Please provide a clear and concise description of what you want to happen. 14 | 15 | **Additional Comments** 16 | 17 | > Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report to help us improve the bot. 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | 9 | > Please provide a clear and concise description of what the bug is. 10 | 11 | **Reproduce** 12 | 13 | > Steps to reproduce the behavior. 14 | 15 | 16 | **Expected behavior** 17 | 18 | > Please provide a clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | 22 | > If applicable, add screenshots to help explain your problem. 23 | 24 | 25 | **Additional Comments** 26 | > Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 James Ives 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 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | from app import PerforceLogger 4 | 5 | class BaseTest(unittest.TestCase): 6 | 7 | def test_check_for_changes_save(self): 8 | logger = PerforceLogger('webhook_url') 9 | 10 | # Initial change check should save the payload. 11 | logger.check_for_changes('Change num on date hh:mm:ss by user@client [status] description') 12 | self.assertEqual(logger.global_store['latest_change'], 'Change num on date hh:mm:ss by user@client [status] description') 13 | 14 | # Additional updates should change the store. 15 | logger.check_for_changes('Payload should change') 16 | self.assertEqual(logger.global_store['latest_change'], 'Payload should change') 17 | 18 | 19 | def test_check_for_changes_return(self): 20 | logger = PerforceLogger('webhook_url') 21 | 22 | self.assertEqual(logger.check_for_changes('Payload should return'), 'Payload should return') 23 | # The payload should return empty as it's the same change as the previous one. 24 | self.assertEqual(logger.check_for_changes('Payload should return'), '') 25 | 26 | # Should properly throw out **pending** payloads. 27 | self.assertEqual(logger.check_for_changes('Payload should return empty as its **pending**'), '') 28 | self.assertEqual(logger.check_for_changes('Payload should return data as its **new**'), 'Payload should return data as its **new**') 29 | 30 | if __name__ == '__main__': 31 | unittest.main() -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import time 4 | from discord_webhooks import DiscordWebhooks 5 | 6 | class PerforceLogger(): 7 | def __init__(self, webhook_url): 8 | """ Initializes a 30 second timer used to check if commits have been made. """ 9 | self.webhook_url = webhook_url 10 | self.global_store = { 11 | 'latest_change': '' 12 | } 13 | 14 | def check_p4(self): 15 | """ Runs the p4 changes command to get the latest commits from the server. """ 16 | p4_changes = subprocess.Popen('p4 changes -t -m 1 -l', stdout=subprocess.PIPE, shell=True) 17 | return p4_changes.stdout.read().decode('ISO-8859-1') 18 | 19 | def check_for_changes(self, output): 20 | """ Figures out if the latest p4 change is new or should be thrown out. """ 21 | if output != self.global_store['latest_change']: 22 | self.global_store['latest_change'] = output 23 | 24 | if '*pending*' in output: 25 | return '' 26 | 27 | else: 28 | return output 29 | 30 | else: 31 | return '' 32 | 33 | def post_changes(self): 34 | """ Posts the changes to the Discord server via a webhook. """ 35 | output = self.check_p4() 36 | payload = self.check_for_changes(output) 37 | 38 | if payload != '': 39 | message = DiscordWebhooks(self.webhook_url) 40 | message.set_content(color=0xc8702a, description='`%s`' % (payload)) 41 | message.set_author(name='Perforce') 42 | message.set_footer(text='https://github.com/JamesIves/perforce-commit-discord-bot', ts=True) 43 | message.send() 44 | 45 | else: 46 | return 47 | 48 | if __name__ == "__main__": 49 | """ Initializes the application loop that checks Perforce for changes. """ 50 | DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL') 51 | logger = PerforceLogger(DISCORD_WEBHOOK_URL) 52 | timer = time.time() 53 | 54 | while True: 55 | logger.post_changes() 56 | time.sleep(30.0 - ((time.time() - timer) % 30.0)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perforce Commit Logger Discord Bot 🗒️ ✏️ 2 | 3 | [![Build Status](https://travis-ci.org/JamesIves/perforce-commit-discord-bot.svg?branch=master)](https://travis-ci.org/JamesIves/perforce-commit-discord-bot) [![Issues](https://img.shields.io/github/issues/JamesIves/perforce-commit-discord-bot.svg)](https://github.com/JamesIves/perforce-commit-discord-bot/issues) 4 | 5 | With this bot you're able to keep track of commits made to a [Perforce version control](https://www.perforce.com/) server within a [Discord](https://discordapp.com/) channel. 6 | 7 | ## Installation Steps 💽 8 | 9 | 1. Within your Discord server go to the settings for the channel you'd like the commit logs to be posted to and copy the webhook URL. 10 | 2. Save the webhook URL as an environment variable called `DISCORD_WEBHOOK_URL`. 11 | 3. The service requires access to the `p4 changes` command in the terminal, your bot should be installed somewhere where it can automatically perform this command without the session expiring. Once suitable access has been provided you'll need to run `$ pip install -r requirements.txt` followed by `$ python app.py` to initialize it. 12 | 4. Optionally you should consider creating a CRON script or something similar that restarts the `app.py` file on server reboot in order to keep the bot alive. 13 | 14 | --- 15 | 16 | Unit tests can be run using the `$ python tests.py` command. 17 | 18 | ## Getting Started :airplane: 19 | 20 | Every thirty seconds the bot runs a Perforce command in the terminal that checks for the most recent changes. If it finds one it stores it in memory, if the change it finds is the same as the one it gathered previously then it discards it. You'll need to provide the bot with access to your servers Perforce command line. One way of doing this is running the Python application on the server which hosts your Perforce instance. If you can type `p4 changes` yourself then the bot will be able to do its thing. 21 | 22 | ## Configuration 📁 23 | 24 | The installation will require you to enter a number of settings as environment variables. Below you'll find an explanation of each. 25 | 26 | | Key | Value Information | Required | 27 | | ------------- | ------------- | ------------- | 28 | | `DISCORD_WEBHOOK_URL` | The [Webhook URL](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks) for the Discord channel you'd like the bot to post its messages to. | **Yes** | 29 | 30 | 31 | ![Example](assets/readme.png) 32 | --------------------------------------------------------------------------------