├── main ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── static │ └── main │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── images │ │ ├── joy.png │ │ ├── ten.png │ │ ├── hands.png │ │ ├── tongue.png │ │ ├── example1.png │ │ ├── exhibit1.png │ │ ├── exhibit2.png │ │ ├── exhibit2.psd │ │ └── bg_nature.jpg │ │ ├── simplepolllogo.png │ │ ├── simplepolllogo-colors.png │ │ └── startbootstrap │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── README.md │ │ ├── css │ │ └── one-page-wonder.css │ │ ├── LICENSE │ │ └── js │ │ ├── bootstrap.min.js │ │ └── bootstrap.js ├── templates │ └── main │ │ ├── oauthcallback.html │ │ ├── privacy-policy.html │ │ └── index.html ├── tests.py ├── models.py └── views.py ├── runtime.txt ├── simpleslackpoll ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── .gitignore ├── Procfile ├── requirements.txt ├── manage.py └── README.md /main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-2.7.9 2 | -------------------------------------------------------------------------------- /simpleslackpoll/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | staticfiles 4 | .env 5 | *.sqlite3 -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: newrelic-admin run-program gunicorn simpleslackpoll.wsgi --log-file - -------------------------------------------------------------------------------- /main/static/main/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/favicon.ico -------------------------------------------------------------------------------- /main/static/main/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/favicon.png -------------------------------------------------------------------------------- /main/static/main/images/joy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/joy.png -------------------------------------------------------------------------------- /main/static/main/images/ten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/ten.png -------------------------------------------------------------------------------- /main/static/main/images/hands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/hands.png -------------------------------------------------------------------------------- /main/static/main/images/tongue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/tongue.png -------------------------------------------------------------------------------- /main/static/main/images/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/example1.png -------------------------------------------------------------------------------- /main/static/main/images/exhibit1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/exhibit1.png -------------------------------------------------------------------------------- /main/static/main/images/exhibit2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/exhibit2.png -------------------------------------------------------------------------------- /main/static/main/images/exhibit2.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/exhibit2.psd -------------------------------------------------------------------------------- /main/static/main/simplepolllogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/simplepolllogo.png -------------------------------------------------------------------------------- /main/static/main/images/bg_nature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/images/bg_nature.jpg -------------------------------------------------------------------------------- /main/static/main/simplepolllogo-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/simplepolllogo-colors.png -------------------------------------------------------------------------------- /main/templates/main/oauthcallback.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |{{status}}
6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.8.6 2 | gunicorn==19.1.1 3 | wsgiref==0.1.2 4 | requests==2.4.3 5 | dj-database-url==0.3.0 6 | psycopg2==2.5.4 7 | whitenoise==2.0.6 8 | newrelic -------------------------------------------------------------------------------- /main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wilhelmklopp/simple-poll/HEAD/main/static/main/startbootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simpleslackpoll.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /main/templates/main/privacy-policy.html: -------------------------------------------------------------------------------- 1 | 2 |The whole premise of Simple Poll is not to store user data, and to keep most of the User Experience within Slack. The absolute minimum of data is stored for the purpose of interacting with the Slack API. This includes the access token, the team name, the team id, and the time at which the app was added to Slack. For more details check https://github.com/xoneco/simple-poll/.
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Poll 2 | 3 |  4 | The code here is no longer up to date, but feel free to continue to report issues and track progress. :smile: 5 | 6 | Create native, simple polls in Slack. 7 | 8 | Get it here: [https://simplepoll.rocks](https://simplepoll.rocks) 9 | 10 |  11 | -------------------------------------------------------------------------------- /main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | 5 | 6 | class MainViewsTestCase(TestCase): 7 | def test_index(self): 8 | resp = self.client.get("/") 9 | self.assertEqual(resp.status_code, 200) 10 | self.assertTrue("state" in resp.context) 11 | 12 | def test_poll(self): 13 | resp = self.client.get("/poll/") 14 | self.assertEqual(resp.status_code, 400) 15 | 16 | resp = self.client.post("/poll/") 17 | self.assertEqual(resp.status_code, 400) 18 | -------------------------------------------------------------------------------- /simpleslackpoll/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for simpleslackpoll project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simpleslackpoll.settings") 15 | 16 | application = get_wsgi_application() 17 | 18 | from whitenoise.django import DjangoWhiteNoise 19 | 20 | application = get_wsgi_application() 21 | application = DjangoWhiteNoise(application) 22 | -------------------------------------------------------------------------------- /main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | 6 | class Teams(models.Model): 7 | access_token = models.CharField(max_length=1000) 8 | team_name = models.CharField(max_length=1000) 9 | team_id = models.CharField(primary_key=True, max_length=1000) 10 | incoming_webhook_url = models.CharField(max_length=1000) 11 | incoming_webhook_configuration_url = models.CharField(max_length=1000) 12 | last_changed = models.DateTimeField(auto_now = True, auto_now_add = False) 13 | created = models.DateTimeField(auto_now = False, auto_now_add = True, editable=False) 14 | 15 | def __unicode__(self): 16 | return str(self.unique_uuid) 17 | -------------------------------------------------------------------------------- /main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Teams', 15 | fields=[ 16 | ('access_token', models.CharField(max_length=1000)), 17 | ('team_name', models.CharField(max_length=1000)), 18 | ('team_id', models.CharField(max_length=1000, serialize=False, primary_key=True)), 19 | ('incoming_webhook_url', models.CharField(max_length=1000)), 20 | ('incoming_webhook_configuration_url', models.CharField(max_length=1000)), 21 | ('last_changed', models.DateTimeField(auto_now=True)), 22 | ('created', models.DateTimeField(auto_now_add=True)), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /simpleslackpoll/urls.py: -------------------------------------------------------------------------------- 1 | """simpleslackpoll URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | from main import views 19 | 20 | urlpatterns = [ 21 | url(r'^$', views.index, name="index"), 22 | url(r'^oauthcallback/', views.oauthcallback, name="oauthcallback"), 23 | url(r'^poll/', views.poll, name="poll"), 24 | url(r'^privacy-policy/', views.privacy_policy, name="privacy-policy"), 25 | ] 26 | -------------------------------------------------------------------------------- /main/static/main/startbootstrap/README.md: -------------------------------------------------------------------------------- 1 | # [Start Bootstrap](http://startbootstrap.com/) - [One Page Wonder](http://startbootstrap.com/template-overviews/one-page-wonder/) 2 | 3 | [One Page Wonder](http://startbootstrap.com/template-overviews/one-page-wonder/) is a basic one page template for [Bootstrap](http://getbootstrap.com/) created by [Start Bootstrap](http://startbootstrap.com/). 4 | 5 | ## Getting Started 6 | 7 | To use this template, choose one of the following options to get started: 8 | * Download the latest release on Start Bootstrap 9 | * Fork this repository on GitHub 10 | 11 | ## Bugs and Issues 12 | 13 | Have a bug or an issue with this template? [Open a new issue](https://github.com/IronSummitMedia/startbootstrap-one-page-wonder/issues) here on GitHub or leave a comment on the [template overview page at Start Bootstrap](http://startbootstrap.com/template-overviews/one-page-wonder/). 14 | 15 | ## Creator 16 | 17 | Start Bootstrap was created by and is maintained by **David Miller**, Managing Parter at [Iron Summit Media Strategies](http://www.ironsummitmedia.com/). 18 | 19 | * https://twitter.com/davidmillerskt 20 | * https://github.com/davidtmiller 21 | 22 | Start Bootstrap is based on the [Bootstrap](http://getbootstrap.com/) framework created by [Mark Otto](https://twitter.com/mdo) and [Jacob Thorton](https://twitter.com/fat). 23 | 24 | ## Copyright and License 25 | 26 | Copyright 2013-2015 Iron Summit Media Strategies, LLC. Code released under the [Apache 2.0](https://github.com/IronSummitMedia/startbootstrap-one-page-wonder/blob/gh-pages/LICENSE) license. -------------------------------------------------------------------------------- /simpleslackpoll/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for simpleslackpoll project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "") 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = False 27 | 28 | ALLOWED_HOSTS = ["simplepoll.rocks"] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'main', 41 | ) 42 | 43 | MIDDLEWARE_CLASSES = ( 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'simpleslackpoll.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'simpleslackpoll.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Internationalization 87 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 88 | 89 | LANGUAGE_CODE = 'en-us' 90 | 91 | TIME_ZONE = 'UTC' 92 | 93 | USE_I18N = True 94 | 95 | USE_L10N = True 96 | 97 | USE_TZ = True 98 | 99 | 100 | # Static files (CSS, JavaScript, Images) 101 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 102 | 103 | STATIC_URL = '/static/' 104 | 105 | STATIC_ROOT = 'staticfiles' 106 | 107 | # Simplified static file serving. 108 | # https://warehouse.python.org/project/whitenoise/ 109 | 110 | STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' 111 | 112 | 113 | # Parse database configuration from $DATABASE_URL 114 | import dj_database_url 115 | DATABASES['default'] = dj_database_url.config() 116 | 117 | # Honor the 'X-Forwarded-Proto' header for request.is_secure() 118 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 119 | -------------------------------------------------------------------------------- /main/static/main/startbootstrap/css/one-page-wonder.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - One Page Wonder HTML Template (http://startbootstrap.com) 3 | * Code licensed under the Apache License v2.0. 4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0. 5 | */ 6 | 7 | 8 | *{ 9 | font-family: Roboto; 10 | } 11 | body { 12 | margin-top: 50px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */ 13 | } 14 | 15 | .explain { 16 | margin-top: 50px; 17 | } 18 | 19 | .logo { 20 | position: relative; 21 | width: 18%; 22 | float: left; 23 | left: 5%; 24 | } 25 | .header-image { 26 | display: block; 27 | width: 100%; 28 | text-align: center; 29 | background: url('../../images/bg_nature.jpg') no-repeat scroll; 30 | -webkit-background-size: cover; 31 | -moz-background-size: cover; 32 | background-size: cover; 33 | -o-background-size: cover; 34 | background-position: 0 75%; 35 | } 36 | 37 | .headline { 38 | padding: 120px 0; 39 | } 40 | 41 | .greeting-text { 42 | padding: 0 0 20px 0; 43 | color: #fff; 44 | } 45 | 46 | .headline h1 { 47 | font-size: 130px; 48 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; 49 | } 50 | 51 | .headline h2 { 52 | font-size: 77px; 53 | } 54 | .headline h6 { 55 | font-size: 20px; 56 | } 57 | 58 | .featurette-divider { 59 | margin: 80px 0; 60 | } 61 | 62 | .featurette { 63 | overflow: hidden; 64 | } 65 | 66 | .featurette-image.pull-left { 67 | margin-right: 40px; 68 | } 69 | 70 | .featurette-image.pull-right { 71 | margin-left: 40px; 72 | } 73 | 74 | .featurette-heading { 75 | font-size: 50px; 76 | } 77 | 78 | footer { 79 | margin: 50px 0; 80 | } 81 | 82 | @media(max-width:1200px) { 83 | .headline h1 { 84 | font-size: 140px; 85 | } 86 | 87 | .headline h2 { 88 | font-size: 63px; 89 | } 90 | 91 | .featurette-divider { 92 | margin: 50px 0; 93 | } 94 | 95 | .featurette-image.pull-left { 96 | margin-right: 20px; 97 | } 98 | 99 | .featurette-image.pull-right { 100 | margin-left: 20px; 101 | } 102 | 103 | .featurette-heading { 104 | font-size: 35px; 105 | } 106 | } 107 | 108 | @media(max-width:991px) { 109 | .headline h1 { 110 | font-size: 105px; 111 | } 112 | 113 | .headline h2 { 114 | font-size: 50px; 115 | } 116 | 117 | .featurette-divider { 118 | margin: 40px 0; 119 | } 120 | 121 | .featurette-image { 122 | max-width: 50%; 123 | } 124 | 125 | .featurette-image.pull-left { 126 | margin-right: 10px; 127 | } 128 | 129 | .featurette-image.pull-right { 130 | margin-left: 10px; 131 | } 132 | 133 | .featurette-heading { 134 | font-size: 30px; 135 | } 136 | } 137 | 138 | @media(max-width:768px) { 139 | .container { 140 | margin: 0 15px; 141 | } 142 | 143 | .featurette-divider { 144 | margin: 40px 0; 145 | } 146 | 147 | .featurette-heading { 148 | font-size: 25px; 149 | } 150 | } 151 | 152 | @media(max-width:668px) { 153 | .headline h1 { 154 | font-size: 70px; 155 | } 156 | 157 | .headline h2 { 158 | font-size: 32px; 159 | } 160 | 161 | .featurette-divider { 162 | margin: 30px 0; 163 | } 164 | } 165 | 166 | @media(max-width:640px) { 167 | .headline { 168 | padding: 75px 0 25px 0; 169 | } 170 | 171 | .headline h1 { 172 | font-size: 60px; 173 | } 174 | 175 | .headline h2 { 176 | font-size: 30px; 177 | } 178 | } 179 | 180 | @media(max-width:375px) { 181 | .featurette-divider { 182 | margin: 10px 0; 183 | } 184 | 185 | .featurette-image { 186 | max-width: 100%; 187 | } 188 | 189 | .featurette-image.pull-left { 190 | margin-right: 0; 191 | margin-bottom: 10px; 192 | } 193 | 194 | .featurette-image.pull-right { 195 | margin-bottom: 10px; 196 | margin-left: 0; 197 | } 198 | } -------------------------------------------------------------------------------- /main/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse, HttpResponseBadRequest 3 | import requests 4 | from main.models import Teams 5 | from django.core.exceptions import ObjectDoesNotExist 6 | from django.utils import timezone 7 | import string 8 | import random 9 | from django.views.decorators.csrf import csrf_exempt 10 | import os 11 | 12 | # Create your views here. 13 | 14 | 15 | def index(request): 16 | state = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(36)) 17 | context = {"state": state} 18 | # TODO: track and verify state in cookie 19 | return render(request, "main/index.html", context) 20 | 21 | 22 | client_id = "16839103392.17540259856" 23 | client_secret = os.environ.get("SLACK_CLIENT_SECRET", "") 24 | 25 | 26 | def oauthcallback(request): 27 | if "error" in request.GET: 28 | status = "Oauth authentication failed. You aborted the Authentication process. Redirecting back to the homepage..." 29 | context = {"status": status} 30 | return render(request, "main/oauthcallback.html", context) 31 | 32 | code = request.GET["code"] 33 | 34 | url = "https://slack.com/api/oauth.access" 35 | data = { 36 | "client_id": client_id, 37 | "client_secret": client_secret, 38 | "code": code, 39 | } 40 | 41 | r = requests.get(url, params=data) 42 | query_result = r.json() 43 | if query_result["ok"]: 44 | access_token = query_result["access_token"] 45 | team_name = query_result["team_name"] 46 | team_id = query_result["team_id"] 47 | 48 | try: 49 | team = Teams.objects.get(team_id=team_id) 50 | except ObjectDoesNotExist: 51 | # new Team (yay!) 52 | new_team = Teams(access_token=access_token, team_name=team_name, team_id=team_id, last_changed=timezone.now()) 53 | new_team.save() 54 | else: 55 | team.access_token = access_token 56 | team.team_name = team_name 57 | team.save() 58 | 59 | else: 60 | status = "Oauth authentication failed. Redirecting back to the homepage..." 61 | context = {"status": status} 62 | return render(request, "main/oauthcallback.html", context) 63 | 64 | status = "Oauth authentication successful! You can now start using /poll. Redirecting back to the homepage..." 65 | context = {"status": status} 66 | return render(request, "main/oauthcallback.html", context) 67 | 68 | 69 | @csrf_exempt 70 | def poll(request): 71 | verifier = os.environ.get("SLACK_POLL_VERIFIER", "") 72 | if request.method != "POST": 73 | return HttpResponseBadRequest("400 Request should be of type POST.") 74 | try: 75 | sent_token = request.POST["token"] 76 | except KeyError: 77 | return HttpResponseBadRequest("400 Request is not signed!") 78 | else: 79 | if verifier != sent_token: 80 | return HttpResponseBadRequest("400 Request is not signed correctly!") 81 | data = request.POST["text"] 82 | 83 | data = data.replace(u'\u201C', '"') 84 | data = data.replace(u'\u201D', '"') 85 | 86 | items = data.split('"') 87 | 88 | question = items[1] 89 | options = [] 90 | for i in range(1, len(items)+1): 91 | if i % 2 == 0 and i > 2: 92 | options.append(items[i-1]) 93 | # all data ready for initial message at this point 94 | 95 | numbers = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "keycap_ten"] 96 | 97 | def sendPollMessage(): 98 | 99 | text = "" 100 | text = "*" + question + "*\n\n" 101 | for option in range(0, len(options)): 102 | toAdd = ":" + numbers[option] + ": " + options[option] + "\n" 103 | text += unicode(toAdd) 104 | 105 | postMessage_url = "https://slack.com/api/chat.postMessage" 106 | postMessage_params = { 107 | "token": Teams.objects.get(team_id=request.POST["team_id"]).access_token, 108 | "text": text, 109 | "channel": request.POST["channel_id"], 110 | "username": "Simple Poll", 111 | "icon_url": "https://simplepoll.rocks/static/main/simplepolllogo-colors.png", 112 | } 113 | text_response = requests.post(postMessage_url, params=postMessage_params) 114 | 115 | return text_response.json()["ts"] # return message timestamp 116 | 117 | class ChannelDoesNotExist(Exception): 118 | def __init__(self, *args, **kwargs): 119 | Exception.__init__(self, *args, **kwargs) 120 | 121 | try: 122 | timestamp = sendPollMessage() 123 | except ChannelDoesNotExist: 124 | return HttpResponse("We cannot add reactions to the channel you posted to. You will have to add your own. Sorry!. :(") 125 | 126 | def addNumberReaction(number): 127 | 128 | reactions_url = "https://slack.com/api/reactions.add" 129 | 130 | reactions_params = { 131 | "token": Teams.objects.get(team_id=request.POST["team_id"]).access_token, 132 | "name": number, 133 | "channel": request.POST["channel_id"], 134 | "timestamp": timestamp 135 | } 136 | 137 | add_reaction = requests.get(reactions_url, params=reactions_params) 138 | 139 | for option in range(0, len(options)): 140 | addNumberReaction(numbers[option]) 141 | 142 | return HttpResponse() # Empty 200 HTTP response, to not display any additional content in Slack 143 | 144 | 145 | def privacy_policy(request): 146 | return render(request, "main/privacy-policy.html") 147 | -------------------------------------------------------------------------------- /main/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
101 | See that awesome simple poll? You can make your own one just like it by typing:
/poll "Where should we go for lunch?" "Subway" "That burrito place" "The micro kitchen"
And boooom everybody can vote on what place would be best for lunch today!
110 | Simple Poll is powered by emoji, because who doesn't love emoji?!?


112 | With Simple Poll you can have up to
different options!