implement bots
This commit is contained in:
		
							
								
								
									
										94
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										94
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,88 +1,8 @@
 | 
				
			|||||||
# Created by .ignore support plugin (hsz.mobi)
 | 
					 | 
				
			||||||
.idea/**/workspace.xml
 | 
					 | 
				
			||||||
.idea/**/tasks.xml
 | 
					 | 
				
			||||||
.idea/**/usage.statistics.xml
 | 
					 | 
				
			||||||
.idea/**/dictionaries
 | 
					 | 
				
			||||||
.idea/**/shelf
 | 
					 | 
				
			||||||
.idea/**/dataSources/
 | 
					 | 
				
			||||||
.idea/**/dataSources.ids
 | 
					 | 
				
			||||||
.idea/**/dataSources.local.xml
 | 
					 | 
				
			||||||
.idea/**/sqlDataSources.xml
 | 
					 | 
				
			||||||
.idea/**/dynamic.xml
 | 
					 | 
				
			||||||
.idea/**/uiDesigner.xml
 | 
					 | 
				
			||||||
.idea/**/dbnavigator.xml
 | 
					 | 
				
			||||||
.idea/**/gradle.xml
 | 
					 | 
				
			||||||
.idea/**/libraries
 | 
					 | 
				
			||||||
cmake-build-*/
 | 
					 | 
				
			||||||
.idea/**/mongoSettings.xml
 | 
					 | 
				
			||||||
*.iws
 | 
					 | 
				
			||||||
out/
 | 
					 | 
				
			||||||
.idea_modules/
 | 
					 | 
				
			||||||
atlassian-ide-plugin.xml
 | 
					 | 
				
			||||||
.idea/replstate.xml
 | 
					 | 
				
			||||||
com_crashlytics_export_strings.xml
 | 
					 | 
				
			||||||
crashlytics.properties
 | 
					 | 
				
			||||||
crashlytics-build.properties
 | 
					 | 
				
			||||||
fabric.properties
 | 
					 | 
				
			||||||
.idea/httpRequests
 | 
					 | 
				
			||||||
__pycache__/
 | 
					 | 
				
			||||||
*.py[cod]
 | 
					 | 
				
			||||||
*$py.class
 | 
					 | 
				
			||||||
*.so
 | 
					 | 
				
			||||||
.Python
 | 
					 | 
				
			||||||
build/
 | 
					 | 
				
			||||||
develop-eggs/
 | 
					 | 
				
			||||||
dist/
 | 
					 | 
				
			||||||
downloads/
 | 
					 | 
				
			||||||
eggs/
 | 
					 | 
				
			||||||
.eggs/
 | 
					 | 
				
			||||||
lib/
 | 
					 | 
				
			||||||
lib64/
 | 
					 | 
				
			||||||
parts/
 | 
					 | 
				
			||||||
sdist/
 | 
					 | 
				
			||||||
var/
 | 
					 | 
				
			||||||
wheels/
 | 
					 | 
				
			||||||
*.egg-info/
 | 
					 | 
				
			||||||
.installed.cfg
 | 
					 | 
				
			||||||
*.egg
 | 
					 | 
				
			||||||
MANIFEST
 | 
					 | 
				
			||||||
*.manifest
 | 
					 | 
				
			||||||
*.spec
 | 
					 | 
				
			||||||
pip-log.txt
 | 
					 | 
				
			||||||
pip-delete-this-directory.txt
 | 
					 | 
				
			||||||
htmlcov/
 | 
					 | 
				
			||||||
.tox/
 | 
					 | 
				
			||||||
.coverage
 | 
					 | 
				
			||||||
.coverage.*
 | 
					 | 
				
			||||||
.cache
 | 
					 | 
				
			||||||
nosetests.xml
 | 
					 | 
				
			||||||
coverage.xml
 | 
					 | 
				
			||||||
*.cover
 | 
					 | 
				
			||||||
.hypothesis/
 | 
					 | 
				
			||||||
.pytest_cache/
 | 
					 | 
				
			||||||
*.mo
 | 
					 | 
				
			||||||
*.pot
 | 
					 | 
				
			||||||
*.log
 | 
					 | 
				
			||||||
local_settings.py
 | 
					 | 
				
			||||||
db.sqlite3
 | 
					 | 
				
			||||||
instance/
 | 
					 | 
				
			||||||
.webassets-cache
 | 
					 | 
				
			||||||
.scrapy
 | 
					 | 
				
			||||||
docs/_build/
 | 
					 | 
				
			||||||
target/
 | 
					 | 
				
			||||||
.ipynb_checkpoints
 | 
					 | 
				
			||||||
.python-version
 | 
					 | 
				
			||||||
celerybeat-schedule
 | 
					 | 
				
			||||||
*.sage.py
 | 
					 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
.venv
 | 
					db.sqlite3
 | 
				
			||||||
env/
 | 
					logs/*
 | 
				
			||||||
venv/
 | 
					*.py[co]
 | 
				
			||||||
ENV/
 | 
					__pycache__
 | 
				
			||||||
env.bak/
 | 
					!.gitkeep
 | 
				
			||||||
venv.bak/
 | 
					.idea
 | 
				
			||||||
.spyderproject
 | 
					build
 | 
				
			||||||
.spyproject
 | 
					 | 
				
			||||||
.ropeproject
 | 
					 | 
				
			||||||
/site
 | 
					 | 
				
			||||||
.mypy_cache/
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										52
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							@@ -1,52 +0,0 @@
 | 
				
			|||||||
<component name="ProjectCodeStyleConfiguration">
 | 
					 | 
				
			||||||
  <code_scheme name="Project" version="173">
 | 
					 | 
				
			||||||
    <DBN-PSQL>
 | 
					 | 
				
			||||||
      <case-options enabled="true">
 | 
					 | 
				
			||||||
        <option name="KEYWORD_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="FUNCTION_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="PARAMETER_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="DATATYPE_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="OBJECT_CASE" value="preserve" />
 | 
					 | 
				
			||||||
      </case-options>
 | 
					 | 
				
			||||||
      <formatting-settings enabled="false" />
 | 
					 | 
				
			||||||
    </DBN-PSQL>
 | 
					 | 
				
			||||||
    <DBN-SQL>
 | 
					 | 
				
			||||||
      <case-options enabled="true">
 | 
					 | 
				
			||||||
        <option name="KEYWORD_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="FUNCTION_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="PARAMETER_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="DATATYPE_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="OBJECT_CASE" value="preserve" />
 | 
					 | 
				
			||||||
      </case-options>
 | 
					 | 
				
			||||||
      <formatting-settings enabled="false">
 | 
					 | 
				
			||||||
        <option name="STATEMENT_SPACING" value="one_line" />
 | 
					 | 
				
			||||||
        <option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
 | 
					 | 
				
			||||||
        <option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
 | 
					 | 
				
			||||||
      </formatting-settings>
 | 
					 | 
				
			||||||
    </DBN-SQL>
 | 
					 | 
				
			||||||
    <DBN-PSQL>
 | 
					 | 
				
			||||||
      <case-options enabled="true">
 | 
					 | 
				
			||||||
        <option name="KEYWORD_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="FUNCTION_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="PARAMETER_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="DATATYPE_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="OBJECT_CASE" value="preserve" />
 | 
					 | 
				
			||||||
      </case-options>
 | 
					 | 
				
			||||||
      <formatting-settings enabled="false" />
 | 
					 | 
				
			||||||
    </DBN-PSQL>
 | 
					 | 
				
			||||||
    <DBN-SQL>
 | 
					 | 
				
			||||||
      <case-options enabled="true">
 | 
					 | 
				
			||||||
        <option name="KEYWORD_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="FUNCTION_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="PARAMETER_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="DATATYPE_CASE" value="lower" />
 | 
					 | 
				
			||||||
        <option name="OBJECT_CASE" value="preserve" />
 | 
					 | 
				
			||||||
      </case-options>
 | 
					 | 
				
			||||||
      <formatting-settings enabled="false">
 | 
					 | 
				
			||||||
        <option name="STATEMENT_SPACING" value="one_line" />
 | 
					 | 
				
			||||||
        <option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
 | 
					 | 
				
			||||||
        <option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
 | 
					 | 
				
			||||||
      </formatting-settings>
 | 
					 | 
				
			||||||
    </DBN-SQL>
 | 
					 | 
				
			||||||
  </code_scheme>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
							
								
								
									
										28
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							@@ -1,28 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
 | 
					 | 
				
			||||||
    <data-source source="LOCAL" name="db" uuid="c627db26-52a1-49af-829b-0c777acb061b">
 | 
					 | 
				
			||||||
      <driver-ref>sqlite.xerial</driver-ref>
 | 
					 | 
				
			||||||
      <synchronize>true</synchronize>
 | 
					 | 
				
			||||||
      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
					 | 
				
			||||||
      <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db.sqlite3</jdbc-url>
 | 
					 | 
				
			||||||
      <driver-properties>
 | 
					 | 
				
			||||||
        <property name="enable_load_extension" value="true" />
 | 
					 | 
				
			||||||
      </driver-properties>
 | 
					 | 
				
			||||||
      <libraries>
 | 
					 | 
				
			||||||
        <library>
 | 
					 | 
				
			||||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.20.1.1/sqlite-jdbc-3.20.1.1.jar</url>
 | 
					 | 
				
			||||||
        </library>
 | 
					 | 
				
			||||||
        <library>
 | 
					 | 
				
			||||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.20.1.1/xerial-sqlite-license.txt</url>
 | 
					 | 
				
			||||||
        </library>
 | 
					 | 
				
			||||||
        <library>
 | 
					 | 
				
			||||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.16.1/sqlite-jdbc-3.16.1.jar</url>
 | 
					 | 
				
			||||||
        </library>
 | 
					 | 
				
			||||||
        <library>
 | 
					 | 
				
			||||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.16.1/xerial-sqlite-license.txt</url>
 | 
					 | 
				
			||||||
        </library>
 | 
					 | 
				
			||||||
      </libraries>
 | 
					 | 
				
			||||||
    </data-source>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										4
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
								
							@@ -1,4 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="Encoding" addBOMForNewFiles="with NO BOM" />
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										17
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							@@ -1,17 +0,0 @@
 | 
				
			|||||||
<component name="InspectionProjectProfileManager">
 | 
					 | 
				
			||||||
  <profile version="1.0">
 | 
					 | 
				
			||||||
    <option name="myName" value="Project Default" />
 | 
					 | 
				
			||||||
    <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
					 | 
				
			||||||
      <option name="ignoredPackages">
 | 
					 | 
				
			||||||
        <value>
 | 
					 | 
				
			||||||
          <list size="4">
 | 
					 | 
				
			||||||
            <item index="0" class="java.lang.String" itemvalue="django-compressor-develop" />
 | 
					 | 
				
			||||||
            <item index="1" class="java.lang.String" itemvalue="django-recaptcha-develop" />
 | 
					 | 
				
			||||||
            <item index="2" class="java.lang.String" itemvalue="django-robokassa" />
 | 
					 | 
				
			||||||
            <item index="3" class="java.lang.String" itemvalue="python-dateutil" />
 | 
					 | 
				
			||||||
          </list>
 | 
					 | 
				
			||||||
        </value>
 | 
					 | 
				
			||||||
      </option>
 | 
					 | 
				
			||||||
    </inspection_tool>
 | 
					 | 
				
			||||||
  </profile>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								.idea/markdown-navigator/profiles_settings.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/markdown-navigator/profiles_settings.xml
									
									
									
										generated
									
									
									
								
							@@ -1,3 +0,0 @@
 | 
				
			|||||||
<component name="MarkdownNavigator.ProfileManager">
 | 
					 | 
				
			||||||
  <settings default="" pdf-export="" plain-text-search-scope="Project Files" />
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
							
								
								
									
										7
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							@@ -1,7 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="JavaScriptSettings">
 | 
					 | 
				
			||||||
    <option name="languageLevel" value="ES6" />
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (telegram_bots)" project-jdk-type="Python SDK" />
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							@@ -1,8 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="ProjectModuleManager">
 | 
					 | 
				
			||||||
    <modules>
 | 
					 | 
				
			||||||
      <module fileurl="file://$PROJECT_DIR$/.idea/telegram_bots.iml" filepath="$PROJECT_DIR$/.idea/telegram_bots.iml" />
 | 
					 | 
				
			||||||
    </modules>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										34
									
								
								.idea/telegram_bots.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										34
									
								
								.idea/telegram_bots.iml
									
									
									
										generated
									
									
									
								
							@@ -1,34 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<module type="PYTHON_MODULE" version="4">
 | 
					 | 
				
			||||||
  <component name="FacetManager">
 | 
					 | 
				
			||||||
    <facet type="django" name="Django">
 | 
					 | 
				
			||||||
      <configuration>
 | 
					 | 
				
			||||||
        <option name="rootFolder" value="$MODULE_DIR$" />
 | 
					 | 
				
			||||||
        <option name="settingsModule" value="config/settings.py" />
 | 
					 | 
				
			||||||
        <option name="manageScript" value="$MODULE_DIR$/manage.py" />
 | 
					 | 
				
			||||||
        <option name="environment" value="<map/>" />
 | 
					 | 
				
			||||||
        <option name="doNotUseTestRunner" value="false" />
 | 
					 | 
				
			||||||
        <option name="trackFilePattern" value="migrations" />
 | 
					 | 
				
			||||||
      </configuration>
 | 
					 | 
				
			||||||
    </facet>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
  <component name="NewModuleRootManager">
 | 
					 | 
				
			||||||
    <content url="file://$MODULE_DIR$" />
 | 
					 | 
				
			||||||
    <orderEntry type="jdk" jdkName="Python 3.7 (telegram_bots)" jdkType="Python SDK" />
 | 
					 | 
				
			||||||
    <orderEntry type="sourceFolder" forTests="false" />
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
  <component name="TemplatesService">
 | 
					 | 
				
			||||||
    <option name="TEMPLATE_CONFIGURATION" value="Django" />
 | 
					 | 
				
			||||||
    <option name="TEMPLATE_FOLDERS">
 | 
					 | 
				
			||||||
      <list>
 | 
					 | 
				
			||||||
        <option value="$MODULE_DIR$/templates" />
 | 
					 | 
				
			||||||
        <option value="$MODULE_DIR$/feeds/templates" />
 | 
					 | 
				
			||||||
        <option value="$MODULE_DIR$/cabinet/templates" />
 | 
					 | 
				
			||||||
        <option value="$MODULE_DIR$/aggregator/templates" />
 | 
					 | 
				
			||||||
      </list>
 | 
					 | 
				
			||||||
    </option>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
  <component name="TestRunnerService">
 | 
					 | 
				
			||||||
    <option name="PROJECT_TEST_RUNNER" value="Unittests" />
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</module>
 | 
					 | 
				
			||||||
							
								
								
									
										8
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							@@ -1,8 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="VcsDirectoryMappings">
 | 
					 | 
				
			||||||
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
 | 
					 | 
				
			||||||
    <mapping directory="$PROJECT_DIR$/build/modular-admin-html" vcs="Git" />
 | 
					 | 
				
			||||||
    <mapping directory="$PROJECT_DIR$/build/octopus" vcs="Git" />
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										25
									
								
								.idea/watcherTasks.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								.idea/watcherTasks.xml
									
									
									
										generated
									
									
									
								
							@@ -1,25 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="ProjectTasksOptions">
 | 
					 | 
				
			||||||
    <TaskOptions isEnabled="true">
 | 
					 | 
				
			||||||
      <option name="arguments" value="$FileName$" />
 | 
					 | 
				
			||||||
      <option name="checkSyntaxErrors" value="true" />
 | 
					 | 
				
			||||||
      <option name="description" />
 | 
					 | 
				
			||||||
      <option name="exitCodeBehavior" value="ERROR" />
 | 
					 | 
				
			||||||
      <option name="fileExtension" value="styl" />
 | 
					 | 
				
			||||||
      <option name="immediateSync" value="true" />
 | 
					 | 
				
			||||||
      <option name="name" value="Stylus" />
 | 
					 | 
				
			||||||
      <option name="output" value="$FileNameWithoutExtension$.css" />
 | 
					 | 
				
			||||||
      <option name="outputFilters">
 | 
					 | 
				
			||||||
        <array />
 | 
					 | 
				
			||||||
      </option>
 | 
					 | 
				
			||||||
      <option name="outputFromStdout" value="false" />
 | 
					 | 
				
			||||||
      <option name="program" value="stylus" />
 | 
					 | 
				
			||||||
      <option name="runOnExternalChanges" value="true" />
 | 
					 | 
				
			||||||
      <option name="scopeName" value="Project Files" />
 | 
					 | 
				
			||||||
      <option name="trackOnlyRoot" value="true" />
 | 
					 | 
				
			||||||
      <option name="workingDir" value="$FileDir$" />
 | 
					 | 
				
			||||||
      <envs />
 | 
					 | 
				
			||||||
    </TaskOptions>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										14
									
								
								.idea/webResources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								.idea/webResources.xml
									
									
									
										generated
									
									
									
								
							@@ -1,14 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="WebResourcesPaths">
 | 
					 | 
				
			||||||
    <contentEntries>
 | 
					 | 
				
			||||||
      <entry url="file://$PROJECT_DIR$">
 | 
					 | 
				
			||||||
        <entryData>
 | 
					 | 
				
			||||||
          <resourceRoots>
 | 
					 | 
				
			||||||
            <path value="file://$PROJECT_DIR$/static" />
 | 
					 | 
				
			||||||
          </resourceRoots>
 | 
					 | 
				
			||||||
        </entryData>
 | 
					 | 
				
			||||||
      </entry>
 | 
					 | 
				
			||||||
    </contentEntries>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										0
									
								
								bots/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								bots/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										3
									
								
								bots/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								bots/admin.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Register your models here.
 | 
				
			||||||
							
								
								
									
										5
									
								
								bots/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								bots/apps.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotsConfig(AppConfig):
 | 
				
			||||||
 | 
					    name = 'bots'
 | 
				
			||||||
							
								
								
									
										19
									
								
								bots/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								bots/forms.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					from django.forms import ModelForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.models import TelegramBot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotForm(ModelForm):
 | 
				
			||||||
 | 
					    prefix = 'bot'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, module, **kwargs):
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        if not hasattr(module, 'rpc_dispatcher'):
 | 
				
			||||||
 | 
					            self.fields.pop('rpc_name')
 | 
				
			||||||
 | 
					        if not hasattr(module, 'periodic_task'):
 | 
				
			||||||
 | 
					            self.fields.pop('periodic_interval')
 | 
				
			||||||
 | 
					            self.fields.pop('periodic_last_run')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = TelegramBot
 | 
				
			||||||
 | 
					        exclude = 'owner', 'config_type', 'config_id',
 | 
				
			||||||
							
								
								
									
										0
									
								
								bots/management/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								bots/management/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								bots/management/commands/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								bots/management/commands/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										53
									
								
								bots/management/commands/run_bots.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								bots/management/commands/run_bots.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sentry_sdk
 | 
				
			||||||
 | 
					from django.core.cache import cache
 | 
				
			||||||
 | 
					from django.core.management import BaseCommand
 | 
				
			||||||
 | 
					from telegram import TelegramError
 | 
				
			||||||
 | 
					from telegram.error import TimedOut
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.models import TelegramBot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Command(BaseCommand):
 | 
				
			||||||
 | 
					    def handle(self, *args, **options):
 | 
				
			||||||
 | 
					        dispatchers = []
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if not dispatchers or cache.get('bots_reset'):
 | 
				
			||||||
 | 
					                    logging.warning('Reloading dispatchers')
 | 
				
			||||||
 | 
					                    dispatchers = []
 | 
				
			||||||
 | 
					                    for bot in TelegramBot.objects.filter(active=True):
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            dispatcher = bot.build_dispatcher()
 | 
				
			||||||
 | 
					                            dispatcher.last_update_id = 0
 | 
				
			||||||
 | 
					                            dispatchers.append(dispatcher)
 | 
				
			||||||
 | 
					                        except TelegramError:
 | 
				
			||||||
 | 
					                            pass
 | 
				
			||||||
 | 
					                    cache.delete('bots_reset')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for dispatcher in dispatchers:
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        updates = dispatcher.bot.get_updates(dispatcher.last_update_id)
 | 
				
			||||||
 | 
					                    except TimedOut:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    except TelegramError as e:
 | 
				
			||||||
 | 
					                        sentry_sdk.capture_exception(e)
 | 
				
			||||||
 | 
					                        traceback.print_exc()
 | 
				
			||||||
 | 
					                        updates = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    for update in updates:
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            dispatcher.process_update(update)
 | 
				
			||||||
 | 
					                        except KeyboardInterrupt:
 | 
				
			||||||
 | 
					                            return
 | 
				
			||||||
 | 
					                        except Exception as e:
 | 
				
			||||||
 | 
					                            sentry_sdk.capture_exception(e)
 | 
				
			||||||
 | 
					                            traceback.print_exc()
 | 
				
			||||||
 | 
					                        dispatcher.last_update_id = update.update_id + 1
 | 
				
			||||||
 | 
					            except KeyboardInterrupt:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                sentry_sdk.capture_exception(e)
 | 
				
			||||||
 | 
					                traceback.print_exc()
 | 
				
			||||||
							
								
								
									
										33
									
								
								bots/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								bots/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					# Generated by Django 2.1.5 on 2019-11-12 15:15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('contenttypes', '0002_remove_content_type_name'),
 | 
				
			||||||
 | 
					        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='TelegramBot',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('title', models.CharField(max_length=32)),
 | 
				
			||||||
 | 
					                ('bot_token', models.CharField(max_length=256)),
 | 
				
			||||||
 | 
					                ('config_id', models.PositiveIntegerField()),
 | 
				
			||||||
 | 
					                ('config_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
 | 
				
			||||||
 | 
					                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterUniqueTogether(
 | 
				
			||||||
 | 
					            name='telegrambot',
 | 
				
			||||||
 | 
					            unique_together={('config_type', 'config_id')},
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										23
									
								
								bots/migrations/0002_echobotmoduleconfig.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								bots/migrations/0002_echobotmoduleconfig.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					# Generated by Django 2.1.5 on 2019-11-12 15:26
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('bots', '0001_initial'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='EchoBotModuleConfig',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('prefix', models.TextField()),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            options={
 | 
				
			||||||
 | 
					                'abstract': False,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										18
									
								
								bots/migrations/0003_telegrambot_active.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								bots/migrations/0003_telegrambot_active.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					# Generated by Django 2.1.5 on 2019-11-12 15:40
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('bots', '0002_echobotmoduleconfig'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='telegrambot',
 | 
				
			||||||
 | 
					            name='active',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										28
									
								
								bots/migrations/0004_auto_20191112_1908.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								bots/migrations/0004_auto_20191112_1908.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					# Generated by Django 2.1.5 on 2019-11-12 16:08
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('bots', '0003_telegrambot_active'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='telegrambot',
 | 
				
			||||||
 | 
					            name='periodic_interval',
 | 
				
			||||||
 | 
					            field=models.DurationField(blank=True, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='telegrambot',
 | 
				
			||||||
 | 
					            name='periodic_last_run',
 | 
				
			||||||
 | 
					            field=models.DateTimeField(blank=True, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='telegrambot',
 | 
				
			||||||
 | 
					            name='rpc_name',
 | 
				
			||||||
 | 
					            field=models.CharField(blank=True, max_length=32, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										23
									
								
								bots/migrations/0005_channelhelperbotmoduleconfig.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								bots/migrations/0005_channelhelperbotmoduleconfig.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					# Generated by Django 2.1.5 on 2019-11-12 16:15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('bots', '0004_auto_20191112_1908'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='ChannelHelperBotModuleConfig',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('chat_id', models.CharField(max_length=32)),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            options={
 | 
				
			||||||
 | 
					                'abstract': False,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										0
									
								
								bots/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								bots/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										57
									
								
								bots/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								bots/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
 | 
				
			||||||
 | 
					from django.contrib.contenttypes.models import ContentType
 | 
				
			||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from telegram import Bot
 | 
				
			||||||
 | 
					from telegram.ext import Dispatcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TelegramBot(models.Model):
 | 
				
			||||||
 | 
					    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
 | 
				
			||||||
 | 
					    active = models.BooleanField(default=True)
 | 
				
			||||||
 | 
					    title = models.CharField(max_length=32)
 | 
				
			||||||
 | 
					    bot_token = models.CharField(max_length=256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rpc_name = models.CharField(max_length=32, blank=True, null=True)
 | 
				
			||||||
 | 
					    periodic_interval = models.DurationField(blank=True, null=True)
 | 
				
			||||||
 | 
					    periodic_last_run = models.DateTimeField(blank=True, null=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    config_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
 | 
				
			||||||
 | 
					    config_id = models.PositiveIntegerField()
 | 
				
			||||||
 | 
					    config = GenericForeignKey('config_type', 'config_id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_bot(self):
 | 
				
			||||||
 | 
					        return Bot(self.bot_token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def build_dispatcher(self):
 | 
				
			||||||
 | 
					        bot = self.get_bot()
 | 
				
			||||||
 | 
					        bot.get_me()
 | 
				
			||||||
 | 
					        dispatcher = Dispatcher(bot, None, workers=0, use_context=True)
 | 
				
			||||||
 | 
					        self.config.build_dispatcher(dispatcher)
 | 
				
			||||||
 | 
					        return dispatcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return f'#{self.pk} {self.title}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        unique_together = ('config_type', 'config_id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TelegramBotModuleConfig(models.Model):
 | 
				
			||||||
 | 
					    _bot = GenericRelation(TelegramBot, content_type_field='config_type', object_id_field='config_id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MODULE_NAME = '<Not set>'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def bot(self):
 | 
				
			||||||
 | 
					        return self._bot.get()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def content_type(self):
 | 
				
			||||||
 | 
					        return ContentType.objects.get_for_model(self.__class__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def build_dispatcher(self, dispatcher: Dispatcher):
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        abstract = True
 | 
				
			||||||
							
								
								
									
										4
									
								
								bots/modules/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								bots/modules/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					from .channel_helper import ChannelHelperBotModuleConfig
 | 
				
			||||||
 | 
					from .echo import EchoBotModuleConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BOT_MODULES = [EchoBotModuleConfig, ChannelHelperBotModuleConfig]
 | 
				
			||||||
							
								
								
									
										87
									
								
								bots/modules/channel_helper.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								bots/modules/channel_helper.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					from uuid import uuid4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from telegram import Update
 | 
				
			||||||
 | 
					from telegram.ext import Dispatcher, CallbackContext, MessageHandler, Filters
 | 
				
			||||||
 | 
					from jsonrpc import Dispatcher as RPCDispatcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.models import TelegramBotModuleConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChannelHelperBotModuleConfig(TelegramBotModuleConfig):
 | 
				
			||||||
 | 
					    chat_id = models.CharField(max_length=32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MODULE_NAME = 'Channel helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rpc_dispatcher = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self.rpc_dispatcher = RPCDispatcher()
 | 
				
			||||||
 | 
					        self.rpc_dispatcher['post_photo'] = self.rpc_post_photo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def rpc_post_photo(self, photo, is_base64=False):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if is_base64:
 | 
				
			||||||
 | 
					                f = BytesIO(base64.b64decode(photo))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                resp = requests.get(photo)
 | 
				
			||||||
 | 
					                resp.raise_for_status()
 | 
				
			||||||
 | 
					                f = BytesIO(resp.content)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            raise RuntimeError('Could not load image')
 | 
				
			||||||
 | 
					        im = Image.open(f)  # type: Image.Image
 | 
				
			||||||
 | 
					        width, height = im.size
 | 
				
			||||||
 | 
					        if width > 2000 or height > 2000:
 | 
				
			||||||
 | 
					            im.thumbnail((2000, 2000))
 | 
				
			||||||
 | 
					        with tempfile.TemporaryDirectory() as d:
 | 
				
			||||||
 | 
					            fpath = os.path.join(d, '{}.jpg'.format(uuid4()))
 | 
				
			||||||
 | 
					            im.save(fpath)
 | 
				
			||||||
 | 
					            self.bot.get_bot().send_photo(self.chat_id, open(fpath, 'rb'))
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_message(self, update: Update, ctx: CallbackContext):
 | 
				
			||||||
 | 
					        m = update.effective_message
 | 
				
			||||||
 | 
					        bot = ctx.bot
 | 
				
			||||||
 | 
					        if hasattr(m, 'audio') and m.audio:
 | 
				
			||||||
 | 
					            a = m.audio
 | 
				
			||||||
 | 
					            r = bot.send_audio(self.chat_id, a.file_id, a.duration, a.performer, a.title)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'document') and m.document:
 | 
				
			||||||
 | 
					            d = m.document
 | 
				
			||||||
 | 
					            r = bot.send_document(self.chat_id, d.file_id, d.file_name)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'photo') and m.photo:
 | 
				
			||||||
 | 
					            p = m.photo
 | 
				
			||||||
 | 
					            r = bot.send_photo(self.chat_id, p[-1].file_id)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'sticker') and m.sticker:
 | 
				
			||||||
 | 
					            s = m.sticker
 | 
				
			||||||
 | 
					            r = bot.send_sticker(self.chat_id, s.file_id)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'video') and m.video:
 | 
				
			||||||
 | 
					            v = m.video
 | 
				
			||||||
 | 
					            r = bot.send_video(self.chat_id, v.file_id, v.duration)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'voice') and m.voice:
 | 
				
			||||||
 | 
					            v = m.voice
 | 
				
			||||||
 | 
					            r = bot.send_voice(self.chat_id, v.file_id, v.duration)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'video_note') and m.video_note:
 | 
				
			||||||
 | 
					            vn = m.video_note
 | 
				
			||||||
 | 
					            r = bot.send_video_note(self.chat_id, vn.file_id, vn.duration, vn.length)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'contact') and m.contact:
 | 
				
			||||||
 | 
					            c = m.contact
 | 
				
			||||||
 | 
					            r = bot.send_contact(self.chat_id, c.phone_number, c.first_name, c.last_name)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'location') and m.location:
 | 
				
			||||||
 | 
					            l = m.location
 | 
				
			||||||
 | 
					            r = bot.send_location(self.chat_id, l.latitude, l.longitude)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'venue') and m.venue:
 | 
				
			||||||
 | 
					            v = m.venue
 | 
				
			||||||
 | 
					            r = bot.send_venue(self.chat_id, v.location.latitude, v.location.longitude, v.title, v.address, v.foursquare_id)
 | 
				
			||||||
 | 
					        elif hasattr(m, 'text') and m.text:
 | 
				
			||||||
 | 
					            r = bot.send_message(self.chat_id, m.text_html, 'html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def build_dispatcher(self, dispatcher: Dispatcher):
 | 
				
			||||||
 | 
					        dispatcher.add_handler(MessageHandler(Filters.all, self.handle_message))
 | 
				
			||||||
 | 
					        return dispatcher
 | 
				
			||||||
							
								
								
									
										18
									
								
								bots/modules/echo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								bots/modules/echo.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from telegram import Update
 | 
				
			||||||
 | 
					from telegram.ext import Dispatcher, CallbackContext, MessageHandler, Filters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.models import TelegramBotModuleConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EchoBotModuleConfig(TelegramBotModuleConfig):
 | 
				
			||||||
 | 
					    prefix = models.TextField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MODULE_NAME = 'Echo'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def message_handler(self, update: Update, ctx: CallbackContext):
 | 
				
			||||||
 | 
					        update.effective_message.reply_text(self.prefix + update.effective_message.text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def build_dispatcher(self, dispatcher: Dispatcher):
 | 
				
			||||||
 | 
					        dispatcher.add_handler(MessageHandler(Filters.text, self.message_handler))
 | 
				
			||||||
 | 
					        return dispatcher
 | 
				
			||||||
							
								
								
									
										55
									
								
								bots/templates/cabinet/bots/bot_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								bots/templates/cabinet/bots/bot_form.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					{% extends 'cabinet/_internal_base.html' %}
 | 
				
			||||||
 | 
					{% load bootstrap4 %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block breadcrumbs %}
 | 
				
			||||||
 | 
					    <li><a href="{% url 'cabinet:bots:index' %}">Bot list</a></li>
 | 
				
			||||||
 | 
					    <li><span>{{ title }}</span></li>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    <form action="" method="post" class="card">
 | 
				
			||||||
 | 
					        {% csrf_token %}
 | 
				
			||||||
 | 
					        <header class="card-header">
 | 
				
			||||||
 | 
					            <h2 class="card-title">{% if feed %}Bot "{{ feed.title }}" configuration{% else %}New bot{% endif %}</h2>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <h4>General options</h4>
 | 
				
			||||||
 | 
					            {% bootstrap_form bot_form layout='horizontal' %}
 | 
				
			||||||
 | 
					            <hr>
 | 
				
			||||||
 | 
					            <h4>Module options</h4>
 | 
				
			||||||
 | 
					            {% bootstrap_form config_form layout='horizontal' %}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <footer class="card-footer text-right">
 | 
				
			||||||
 | 
					            {% if feed %}<a href="#delete-modal" class="modal-basic btn btn-danger">Delete</a>{% endif %}
 | 
				
			||||||
 | 
					            <button type="submit" class="btn btn-primary">Save</button>
 | 
				
			||||||
 | 
					        </footer>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% if feed %}
 | 
				
			||||||
 | 
					        <div id="delete-modal" class="modal-block modal-full-color modal-block-danger mfp-hide">
 | 
				
			||||||
 | 
					            <section class="card">
 | 
				
			||||||
 | 
					                <header class="card-header">
 | 
				
			||||||
 | 
					                    <h2 class="card-title">Delete bot "{{ feed.title }}"</h2>
 | 
				
			||||||
 | 
					                </header>
 | 
				
			||||||
 | 
					                <div class="card-body">
 | 
				
			||||||
 | 
					                    <div class="modal-wrapper">
 | 
				
			||||||
 | 
					                        <div class="modal-icon"><i class="fas fa-times-circle"></i></div>
 | 
				
			||||||
 | 
					                        <div class="modal-text">
 | 
				
			||||||
 | 
					                            <h4>Are you sure?</h4>
 | 
				
			||||||
 | 
					                            <p>This action cannot be undone.</p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <footer class="card-footer">
 | 
				
			||||||
 | 
					                    <div class="row">
 | 
				
			||||||
 | 
					                        <form action="{% url 'cabinet:bots:delete' pk=feed.pk %}" method="post" class="col-md-12 text-right">
 | 
				
			||||||
 | 
					                            {% csrf_token %}
 | 
				
			||||||
 | 
					                            <button type="button" class="btn btn-default modal-dismiss">Cancel</button>
 | 
				
			||||||
 | 
					                            <button type="submit" class="btn btn-danger">Delete</button>
 | 
				
			||||||
 | 
					                        </form>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </footer>
 | 
				
			||||||
 | 
					            </section>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										48
									
								
								bots/templates/cabinet/bots/bot_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								bots/templates/cabinet/bots/bot_list.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					{% extends 'cabinet/_internal_base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block breadcrumbs %}
 | 
				
			||||||
 | 
					    <li><span>Bot list</span></li>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    <section class="card">
 | 
				
			||||||
 | 
					        <header class="card-header">
 | 
				
			||||||
 | 
					            <div class="card-button-actions">
 | 
				
			||||||
 | 
					                <div class="dropdown">
 | 
				
			||||||
 | 
					                    <button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-toggle="dropdown">
 | 
				
			||||||
 | 
					                        Add new bot
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    <div class="dropdown-menu dropdown-menu-right">
 | 
				
			||||||
 | 
					                        {% for module in bot_modules %}
 | 
				
			||||||
 | 
					                            <a class="dropdown-item"
 | 
				
			||||||
 | 
					                               href="{% url 'cabinet:bots:new' content_type=module.content_type.model %}">{{ module.MODULE_NAME }}</a>
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <h2 class="card-title">Your bots</h2>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <table class="table table-hover table-responsive-md mb-0">
 | 
				
			||||||
 | 
					                <thead>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <th>Title</th>
 | 
				
			||||||
 | 
					                    <th>Module</th>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					                {% for bot in object_list %}
 | 
				
			||||||
 | 
					                <tr class="clickable-row" data-href="{% url 'cabinet:bots:edit' pk=bot.pk %}">
 | 
				
			||||||
 | 
					                    <td>{{ bot.title }}</td>
 | 
				
			||||||
 | 
					                    <td>{{ bot.config.MODULE_NAME }}</td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                {% empty %}
 | 
				
			||||||
 | 
					                <tfoot>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td colspan="4">No bots added</td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                </tfoot>
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										3
									
								
								bots/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								bots/tests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your tests here.
 | 
				
			||||||
							
								
								
									
										12
									
								
								bots/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								bots/urls.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					from django.urls import path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.views import BotListView, BotConfigEditView, BotConfigDeleteView, BotConfigCreateView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app_name = 'bots'
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('', BotListView.as_view(), name='index'),
 | 
				
			||||||
 | 
					    path('<int:pk>/', BotConfigEditView.as_view(), name='edit'),
 | 
				
			||||||
 | 
					    path('new/<content_type>/', BotConfigCreateView.as_view(), name='new'),
 | 
				
			||||||
 | 
					    path('delete/<int:pk>/', BotConfigDeleteView.as_view(), name='delete'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								bots/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								bots/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					from django.views.generic import TemplateView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.forms import BotForm
 | 
				
			||||||
 | 
					from bots.models import TelegramBot
 | 
				
			||||||
 | 
					from cabinet.utils import CabinetViewMixin
 | 
				
			||||||
 | 
					from config.utils import get_config_form
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseBotConfigView(CabinetViewMixin, TemplateView):
 | 
				
			||||||
 | 
					    template_name = 'cabinet/bots/bot_form.html'
 | 
				
			||||||
 | 
					    context_object_name = 'feed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        return TelegramBot.objects.filter(owner=self.request.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.object = self.get_object()
 | 
				
			||||||
 | 
					        return self.render_to_response(self.get_context_data())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.object = self.get_object()
 | 
				
			||||||
 | 
					        bot_form, config_form = self.get_forms()
 | 
				
			||||||
 | 
					        if bot_form.is_valid() and config_form.is_valid():
 | 
				
			||||||
 | 
					            return self.form_valid(bot_form, config_form)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            context = self.get_context_data(forms=(bot_form, config_form))
 | 
				
			||||||
 | 
					            return self.render_to_response(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_forms(self):
 | 
				
			||||||
 | 
					        bot = self.get_object()
 | 
				
			||||||
 | 
					        data = self.request.POST if self.request.method == 'POST' else None
 | 
				
			||||||
 | 
					        return BotForm(data=data, instance=bot, module=self.get_content_type().model_class()), \
 | 
				
			||||||
 | 
					               get_config_form(self.get_content_type().model_class())(data=data, instance=bot.config if bot else None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context_data(self, forms=None, **kwargs):
 | 
				
			||||||
 | 
					        ctx = super(BaseBotConfigView, self).get_context_data(**kwargs)
 | 
				
			||||||
 | 
					        ctx['bot_form'], ctx['config_form'] = self.get_forms() if forms is None else forms
 | 
				
			||||||
 | 
					        ctx['bot_module'] = self.get_content_type().model_class()
 | 
				
			||||||
 | 
					        return ctx
 | 
				
			||||||
							
								
								
									
										96
									
								
								bots/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								bots/views.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					from braces.views import AjaxResponseMixin, JSONResponseMixin
 | 
				
			||||||
 | 
					from django.contrib import messages
 | 
				
			||||||
 | 
					from django.contrib.auth.mixins import LoginRequiredMixin
 | 
				
			||||||
 | 
					from django.contrib.contenttypes.models import ContentType
 | 
				
			||||||
 | 
					from django.core.cache import cache
 | 
				
			||||||
 | 
					from django.core.exceptions import PermissionDenied
 | 
				
			||||||
 | 
					from django.http import HttpResponseRedirect, HttpRequest
 | 
				
			||||||
 | 
					from django.shortcuts import redirect, get_object_or_404
 | 
				
			||||||
 | 
					from django.utils.decorators import method_decorator
 | 
				
			||||||
 | 
					from django.views import View
 | 
				
			||||||
 | 
					from django.views.decorators.csrf import csrf_exempt
 | 
				
			||||||
 | 
					from django.views.generic import ListView
 | 
				
			||||||
 | 
					from django.views.generic.detail import SingleObjectMixin
 | 
				
			||||||
 | 
					from jsonrpc import JSONRPCResponseManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.models import TelegramBot
 | 
				
			||||||
 | 
					from bots.modules import BOT_MODULES
 | 
				
			||||||
 | 
					from bots.utils import BaseBotConfigView
 | 
				
			||||||
 | 
					from cabinet.utils import CabinetViewMixin
 | 
				
			||||||
 | 
					from config.utils import AllowCORSMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotListView(CabinetViewMixin, ListView):
 | 
				
			||||||
 | 
					    template_name = 'cabinet/bots/bot_list.html'
 | 
				
			||||||
 | 
					    title = 'Bot list'
 | 
				
			||||||
 | 
					    sidebar_section = 'bots'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        return TelegramBot.objects.filter(owner=self.request.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context_data(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        ctx = super(BotListView, self).get_context_data(*args, **kwargs)
 | 
				
			||||||
 | 
					        ctx['bot_modules'] = BOT_MODULES
 | 
				
			||||||
 | 
					        return ctx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotConfigEditView(BaseBotConfigView, SingleObjectMixin):
 | 
				
			||||||
 | 
					    title = 'Configure bot'
 | 
				
			||||||
 | 
					    sidebar_section = 'bots'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def form_valid(self, bot_form, config_form):
 | 
				
			||||||
 | 
					        bot_form.save()
 | 
				
			||||||
 | 
					        config_form.save()
 | 
				
			||||||
 | 
					        cache.set('bots_reset', '1')
 | 
				
			||||||
 | 
					        messages.success(self.request, 'Config was successfully saved')
 | 
				
			||||||
 | 
					        return HttpResponseRedirect('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_content_type(self):
 | 
				
			||||||
 | 
					        return self.get_object().config_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotConfigCreateView(BaseBotConfigView):
 | 
				
			||||||
 | 
					    title = 'Create bot'
 | 
				
			||||||
 | 
					    sidebar_section = 'bots'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_object(self):
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def form_valid(self, bots_form, config_form):
 | 
				
			||||||
 | 
					        config_form.save()
 | 
				
			||||||
 | 
					        bots_form.instance.owner = self.request.user
 | 
				
			||||||
 | 
					        bots_form.instance.config_type = self.get_content_type()
 | 
				
			||||||
 | 
					        bots_form.instance.config_id = config_form.instance.pk
 | 
				
			||||||
 | 
					        bots_form.save()
 | 
				
			||||||
 | 
					        cache.set('bots_reset', '1')
 | 
				
			||||||
 | 
					        messages.success(self.request, 'Config was successfully saved')
 | 
				
			||||||
 | 
					        return redirect('cabinet:bots:edit', pk=bots_form.instance.pk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_content_type(self):
 | 
				
			||||||
 | 
					        return get_object_or_404(ContentType, model=self.kwargs['content_type'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotConfigDeleteView(LoginRequiredMixin, SingleObjectMixin, View):
 | 
				
			||||||
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        return TelegramBot.objects.filter(owner=self.request.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        feed = self.get_object()
 | 
				
			||||||
 | 
					        messages.success(self.request, 'Bot "{}" was successfully deleted'.format(feed.title))
 | 
				
			||||||
 | 
					        feed.delete()
 | 
				
			||||||
 | 
					        return redirect('cabinet:bots:index')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BotRPCView(JSONResponseMixin, AjaxResponseMixin, AllowCORSMixin, View):
 | 
				
			||||||
 | 
					    @method_decorator(csrf_exempt)
 | 
				
			||||||
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        return super().dispatch(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def post(self, request: HttpRequest, *args, **kwargs):
 | 
				
			||||||
 | 
					        bot = get_object_or_404(TelegramBot, rpc_name=self.kwargs.get('endpoint'))
 | 
				
			||||||
 | 
					        if not hasattr(bot.config, 'rpc_dispatcher'):
 | 
				
			||||||
 | 
					            raise PermissionDenied()
 | 
				
			||||||
 | 
					        rpc_response = JSONRPCResponseManager.handle(request.body, bot.config.rpc_dispatcher)
 | 
				
			||||||
 | 
					        response = self.render_json_response(rpc_response.data)
 | 
				
			||||||
 | 
					        self.add_access_control_headers(response)
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
@@ -15,6 +15,12 @@
 | 
				
			|||||||
                            <span>Feeds</span>
 | 
					                            <span>Feeds</span>
 | 
				
			||||||
                        </a>
 | 
					                        </a>
 | 
				
			||||||
                    </li>
 | 
					                    </li>
 | 
				
			||||||
 | 
					                    <li class="{% if sidebar_section == 'bots' %}nav-active{% endif %}">
 | 
				
			||||||
 | 
					                        <a href="{% url 'cabinet:bots:index' %}">
 | 
				
			||||||
 | 
					                            <i class="fas fa-robot" aria-hidden="true"></i>
 | 
				
			||||||
 | 
					                            <span>Telegram bots</span>
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
                    <li class="{% if sidebar_section == 'aggregator' %}nav-active{% endif %}">
 | 
					                    <li class="{% if sidebar_section == 'aggregator' %}nav-active{% endif %}">
 | 
				
			||||||
                        <a href="{% url 'cabinet:aggregator:index' %}">
 | 
					                        <a href="{% url 'cabinet:aggregator:index' %}">
 | 
				
			||||||
                            <i class="fas fa-copy" aria-hidden="true"></i>
 | 
					                            <i class="fas fa-copy" aria-hidden="true"></i>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,5 +10,6 @@ urlpatterns = [
 | 
				
			|||||||
    path('logout/', LogoutView.as_view(), name='logout'),
 | 
					    path('logout/', LogoutView.as_view(), name='logout'),
 | 
				
			||||||
    path('feeds/', include('feeds.urls', namespace='feeds')),
 | 
					    path('feeds/', include('feeds.urls', namespace='feeds')),
 | 
				
			||||||
    path('aggregator/', include('aggregator.urls', namespace='aggregator')),
 | 
					    path('aggregator/', include('aggregator.urls', namespace='aggregator')),
 | 
				
			||||||
 | 
					    path('bots/', include('bots.urls', namespace='bots')),
 | 
				
			||||||
    path('admin/config/<slug>/', AdminConfigView.as_view(), name='admin_config'),
 | 
					    path('admin/config/<slug>/', AdminConfigView.as_view(), name='admin_config'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,6 @@ INSTALLED_APPS = [
 | 
				
			|||||||
    'djconfig',
 | 
					    'djconfig',
 | 
				
			||||||
    'django_celery_results',
 | 
					    'django_celery_results',
 | 
				
			||||||
    'django_celery_beat',
 | 
					    'django_celery_beat',
 | 
				
			||||||
    'config.suit_config.SuitConfig',
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    'django.contrib.admin',
 | 
					    'django.contrib.admin',
 | 
				
			||||||
    'django.contrib.auth',
 | 
					    'django.contrib.auth',
 | 
				
			||||||
@@ -35,6 +34,7 @@ INSTALLED_APPS = [
 | 
				
			|||||||
    'cabinet.apps.CabinetConfig',
 | 
					    'cabinet.apps.CabinetConfig',
 | 
				
			||||||
    'feeds.apps.FeedsConfig',
 | 
					    'feeds.apps.FeedsConfig',
 | 
				
			||||||
    'aggregator.apps.AggregatorConfig',
 | 
					    'aggregator.apps.AggregatorConfig',
 | 
				
			||||||
 | 
					    'bots.apps.BotsConfig',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MIDDLEWARE = [
 | 
					MIDDLEWARE = [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
from suit.apps import DjangoSuitConfig
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SuitConfig(DjangoSuitConfig):
 | 
					 | 
				
			||||||
    layout = 'vertical'
 | 
					 | 
				
			||||||
@@ -2,8 +2,11 @@ from django.contrib import admin
 | 
				
			|||||||
from django.urls import path, include
 | 
					from django.urls import path, include
 | 
				
			||||||
from django.views.generic import RedirectView
 | 
					from django.views.generic import RedirectView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from bots.views import BotRPCView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns = [
 | 
					urlpatterns = [
 | 
				
			||||||
    path('', RedirectView.as_view(pattern_name='cabinet:index')),
 | 
					    path('', RedirectView.as_view(pattern_name='cabinet:index')),
 | 
				
			||||||
    path('config/', admin.site.urls),
 | 
					    path('config/', admin.site.urls),
 | 
				
			||||||
    path('cabinet/', include('cabinet.urls', namespace='cabinet')),
 | 
					    path('cabinet/', include('cabinet.urls', namespace='cabinet')),
 | 
				
			||||||
 | 
					    path('bots_rpc/<str:endpoint>/', BotRPCView.as_view(), name='bots_rpc'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
from urllib.parse import urlparse
 | 
					from urllib.parse import urlparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.forms import ModelForm
 | 
				
			||||||
from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
 | 
					from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
 | 
				
			||||||
from pyrogram import Chat as PyrogramChat
 | 
					from pyrogram import Chat as PyrogramChat
 | 
				
			||||||
from pyrogram.api.types.chat import Chat as MTProtoChat
 | 
					from pyrogram.api.types.chat import Chat as MTProtoChat
 | 
				
			||||||
@@ -54,3 +55,26 @@ class TurbolinksMiddleware:
 | 
				
			|||||||
                response['Turbolinks-Location'] = loc
 | 
					                response['Turbolinks-Location'] = loc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_config_form(mdl):
 | 
				
			||||||
 | 
					    class ConfigForm(ModelForm):
 | 
				
			||||||
 | 
					        prefix = 'config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class Meta:
 | 
				
			||||||
 | 
					            model = mdl
 | 
				
			||||||
 | 
					            exclude = ()
 | 
				
			||||||
 | 
					    return ConfigForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AllowCORSMixin(object):
 | 
				
			||||||
 | 
					    def add_access_control_headers(self, response):
 | 
				
			||||||
 | 
					        response["Access-Control-Allow-Origin"] = "*"
 | 
				
			||||||
 | 
					        response["Access-Control-Allow-Methods"] = "GET, OPTIONS"
 | 
				
			||||||
 | 
					        response["Access-Control-Max-Age"] = "1000"
 | 
				
			||||||
 | 
					        response["Access-Control-Allow-Headers"] = "X-Requested-With, Content-Type"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def options(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        response = HttpResponse()
 | 
				
			||||||
 | 
					        self.add_access_control_headers(response)
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ from .models import Feed
 | 
				
			|||||||
from feeds.modules import EchoFeedModuleConfig, DankMemesFeedModuleConfig, ShittyWatercolourFeedModuleConfig,\
 | 
					from feeds.modules import EchoFeedModuleConfig, DankMemesFeedModuleConfig, ShittyWatercolourFeedModuleConfig,\
 | 
				
			||||||
    VKFeedModuleConfig, VKMusicFeedModuleConfig, WPComicFeedModuleConfig
 | 
					    VKFeedModuleConfig, VKMusicFeedModuleConfig, WPComicFeedModuleConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Feed)
 | 
					@admin.register(Feed)
 | 
				
			||||||
class FeedAdmin(admin.ModelAdmin):
 | 
					class FeedAdmin(admin.ModelAdmin):
 | 
				
			||||||
    list_display = '__str__', 'lock',
 | 
					    list_display = '__str__', 'lock',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,16 +15,6 @@ class FeedForm(ModelForm):
 | 
				
			|||||||
        exclude = 'owner', 'lock', 'config_type', 'config_id', 'last_check', 'last_id',
 | 
					        exclude = 'owner', 'lock', 'config_type', 'config_id', 'last_check', 'last_id',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_config_form(mdl):
 | 
					 | 
				
			||||||
    class ConfigForm(ModelForm):
 | 
					 | 
				
			||||||
        prefix = 'config'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        class Meta:
 | 
					 | 
				
			||||||
            model = mdl
 | 
					 | 
				
			||||||
            exclude = ()
 | 
					 | 
				
			||||||
    return ConfigForm
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class FeedsAppConfigForm(ConfigForm):
 | 
					class FeedsAppConfigForm(ConfigForm):
 | 
				
			||||||
    slug = 'feeds'
 | 
					    slug = 'feeds'
 | 
				
			||||||
    title = 'Feeds'
 | 
					    title = 'Feeds'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,8 @@ from python_anticaptcha import AnticaptchaClient, ImageToTextTask
 | 
				
			|||||||
from yaml.parser import ParserError
 | 
					from yaml.parser import ParserError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from cabinet.utils import CabinetViewMixin
 | 
					from cabinet.utils import CabinetViewMixin
 | 
				
			||||||
from feeds.forms import FeedForm, get_config_form, FeedsAppConfigForm
 | 
					from feeds.forms import FeedForm, FeedsAppConfigForm
 | 
				
			||||||
 | 
					from config.utils import get_config_form
 | 
				
			||||||
from feeds.models import Feed
 | 
					from feeds.models import Feed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,37 +1,45 @@
 | 
				
			|||||||
amqp==2.3.2
 | 
					amqp==2.3.2
 | 
				
			||||||
 | 
					asn1crypto==0.24.0
 | 
				
			||||||
async-generator==1.10
 | 
					async-generator==1.10
 | 
				
			||||||
beautifulsoup4==4.7.1
 | 
					beautifulsoup4==4.7.1
 | 
				
			||||||
billiard==3.5.0.5
 | 
					billiard==3.5.0.5
 | 
				
			||||||
 | 
					bs4==0.0.1
 | 
				
			||||||
celery==4.2.1
 | 
					celery==4.2.1
 | 
				
			||||||
celery-once==2.0.0
 | 
					celery-once==2.0.0
 | 
				
			||||||
certifi==2018.11.29
 | 
					certifi==2018.11.29
 | 
				
			||||||
 | 
					cffi==1.12.3
 | 
				
			||||||
chardet==3.0.4
 | 
					chardet==3.0.4
 | 
				
			||||||
 | 
					cryptography==2.7
 | 
				
			||||||
Django==2.1.5
 | 
					Django==2.1.5
 | 
				
			||||||
django-bootstrap4==0.0.7
 | 
					django-bootstrap4==0.0.7
 | 
				
			||||||
 | 
					django-braces==1.13.0
 | 
				
			||||||
django-celery-beat==1.4.0
 | 
					django-celery-beat==1.4.0
 | 
				
			||||||
django-celery-results==1.0.4
 | 
					django-celery-results==1.0.4
 | 
				
			||||||
django-crispy-forms==1.7.2
 | 
					django-crispy-forms==1.7.2
 | 
				
			||||||
django-djconfig==0.9.0
 | 
					django-djconfig==0.9.0
 | 
				
			||||||
django-environ==0.4.5
 | 
					django-environ==0.4.5
 | 
				
			||||||
django-extensions==2.1.4
 | 
					django-extensions==2.1.4
 | 
				
			||||||
 | 
					django-jet==1.0.8
 | 
				
			||||||
django-redis==4.10.0
 | 
					django-redis==4.10.0
 | 
				
			||||||
-e git+https://github.com/darklow/django-suit@9941211bc7d63d94478a3c6479c51926dc75db09#egg=django_suit
 | 
					 | 
				
			||||||
django-timezone-field==3.0
 | 
					django-timezone-field==3.0
 | 
				
			||||||
django-yamlfield==1.0.3
 | 
					django-yamlfield==1.0.3
 | 
				
			||||||
enum34==1.1.6
 | 
					enum34==1.1.6
 | 
				
			||||||
future==0.17.1
 | 
					future==0.17.1
 | 
				
			||||||
idna==2.8
 | 
					idna==2.8
 | 
				
			||||||
 | 
					json-rpc==1.12.1
 | 
				
			||||||
kombu==4.2.2.post1
 | 
					kombu==4.2.2.post1
 | 
				
			||||||
oauthlib==3.0.1
 | 
					oauthlib==3.0.1
 | 
				
			||||||
Pillow==5.4.1
 | 
					Pillow==5.4.1
 | 
				
			||||||
psycopg2-binary==2.7.6.1
 | 
					psycopg2-binary==2.7.6.1
 | 
				
			||||||
pyaes==1.6.1
 | 
					pyaes==1.6.1
 | 
				
			||||||
 | 
					pycparser==2.19
 | 
				
			||||||
Pyrogram==0.11.0
 | 
					Pyrogram==0.11.0
 | 
				
			||||||
PySocks==1.6.8
 | 
					PySocks==1.6.8
 | 
				
			||||||
pyTelegramBotAPI==3.6.6
 | 
					pyTelegramBotAPI==3.6.6
 | 
				
			||||||
python-anticaptcha==0.3.1
 | 
					python-anticaptcha==0.3.1
 | 
				
			||||||
python-crontab==2.3.6
 | 
					python-crontab==2.3.6
 | 
				
			||||||
python-dateutil==2.7.5
 | 
					python-dateutil==2.7.5
 | 
				
			||||||
 | 
					python-telegram-bot==12.2.0
 | 
				
			||||||
python-twitter==3.5
 | 
					python-twitter==3.5
 | 
				
			||||||
pytz==2018.9
 | 
					pytz==2018.9
 | 
				
			||||||
PyYAML==3.13
 | 
					PyYAML==3.13
 | 
				
			||||||
@@ -42,6 +50,7 @@ sentry-sdk==0.6.9
 | 
				
			|||||||
six==1.12.0
 | 
					six==1.12.0
 | 
				
			||||||
soupsieve==1.7.3
 | 
					soupsieve==1.7.3
 | 
				
			||||||
TgCrypto==1.1.1
 | 
					TgCrypto==1.1.1
 | 
				
			||||||
 | 
					tornado==6.0.3
 | 
				
			||||||
urllib3==1.24.1
 | 
					urllib3==1.24.1
 | 
				
			||||||
uWSGI==2.0.17.1
 | 
					uWSGI==2.0.17.1
 | 
				
			||||||
vine==1.2.0
 | 
					vine==1.2.0
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										341
									
								
								static/vendor/codemirror/lib/codemirror.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								static/vendor/codemirror/lib/codemirror.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,341 @@
 | 
				
			|||||||
 | 
					/* BASICS */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror {
 | 
				
			||||||
 | 
					  /* Set height, width, borders, and global font properties here */
 | 
				
			||||||
 | 
					  font-family: monospace;
 | 
				
			||||||
 | 
					  height: 300px;
 | 
				
			||||||
 | 
					  color: black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* PADDING */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-lines {
 | 
				
			||||||
 | 
					  padding: 4px 0; /* Vertical padding around content */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror pre {
 | 
				
			||||||
 | 
					  padding: 0 4px; /* Horizontal padding of content */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
 | 
				
			||||||
 | 
					  background-color: white; /* The little square between H and V scrollbars */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* GUTTER */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-gutters {
 | 
				
			||||||
 | 
					  border-right: 1px solid #ddd;
 | 
				
			||||||
 | 
					  background-color: #f7f7f7;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-linenumbers {}
 | 
				
			||||||
 | 
					.CodeMirror-linenumber {
 | 
				
			||||||
 | 
					  padding: 0 3px 0 5px;
 | 
				
			||||||
 | 
					  min-width: 20px;
 | 
				
			||||||
 | 
					  text-align: right;
 | 
				
			||||||
 | 
					  color: #999;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-guttermarker { color: black; }
 | 
				
			||||||
 | 
					.CodeMirror-guttermarker-subtle { color: #999; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* CURSOR */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-cursor {
 | 
				
			||||||
 | 
					  border-left: 1px solid black;
 | 
				
			||||||
 | 
					  border-right: none;
 | 
				
			||||||
 | 
					  width: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/* Shown when moving in bi-directional text */
 | 
				
			||||||
 | 
					.CodeMirror div.CodeMirror-secondarycursor {
 | 
				
			||||||
 | 
					  border-left: 1px solid silver;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.cm-fat-cursor .CodeMirror-cursor {
 | 
				
			||||||
 | 
					  width: auto;
 | 
				
			||||||
 | 
					  border: 0 !important;
 | 
				
			||||||
 | 
					  background: #7e7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.cm-fat-cursor div.CodeMirror-cursors {
 | 
				
			||||||
 | 
					  z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cm-animate-fat-cursor {
 | 
				
			||||||
 | 
					  width: auto;
 | 
				
			||||||
 | 
					  border: 0;
 | 
				
			||||||
 | 
					  -webkit-animation: blink 1.06s steps(1) infinite;
 | 
				
			||||||
 | 
					  -moz-animation: blink 1.06s steps(1) infinite;
 | 
				
			||||||
 | 
					  animation: blink 1.06s steps(1) infinite;
 | 
				
			||||||
 | 
					  background-color: #7e7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@-moz-keyframes blink {
 | 
				
			||||||
 | 
					  0% {}
 | 
				
			||||||
 | 
					  50% { background-color: transparent; }
 | 
				
			||||||
 | 
					  100% {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@-webkit-keyframes blink {
 | 
				
			||||||
 | 
					  0% {}
 | 
				
			||||||
 | 
					  50% { background-color: transparent; }
 | 
				
			||||||
 | 
					  100% {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@keyframes blink {
 | 
				
			||||||
 | 
					  0% {}
 | 
				
			||||||
 | 
					  50% { background-color: transparent; }
 | 
				
			||||||
 | 
					  100% {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Can style cursor different in overwrite (non-insert) mode */
 | 
				
			||||||
 | 
					.CodeMirror-overwrite .CodeMirror-cursor {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cm-tab { display: inline-block; text-decoration: inherit; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-rulers {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  left: 0; right: 0; top: -50px; bottom: -20px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-ruler {
 | 
				
			||||||
 | 
					  border-left: 1px solid #ccc;
 | 
				
			||||||
 | 
					  top: 0; bottom: 0;
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* DEFAULT THEME */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cm-s-default .cm-header {color: blue;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-quote {color: #090;}
 | 
				
			||||||
 | 
					.cm-negative {color: #d44;}
 | 
				
			||||||
 | 
					.cm-positive {color: #292;}
 | 
				
			||||||
 | 
					.cm-header, .cm-strong {font-weight: bold;}
 | 
				
			||||||
 | 
					.cm-em {font-style: italic;}
 | 
				
			||||||
 | 
					.cm-link {text-decoration: underline;}
 | 
				
			||||||
 | 
					.cm-strikethrough {text-decoration: line-through;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cm-s-default .cm-keyword {color: #708;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-atom {color: #219;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-number {color: #164;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-def {color: #00f;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-variable,
 | 
				
			||||||
 | 
					.cm-s-default .cm-punctuation,
 | 
				
			||||||
 | 
					.cm-s-default .cm-property,
 | 
				
			||||||
 | 
					.cm-s-default .cm-operator {}
 | 
				
			||||||
 | 
					.cm-s-default .cm-variable-2 {color: #05a;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-variable-3 {color: #085;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-comment {color: #a50;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-string {color: #a11;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-string-2 {color: #f50;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-meta {color: #555;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-qualifier {color: #555;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-builtin {color: #30a;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-bracket {color: #997;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-tag {color: #170;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-attribute {color: #00c;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-hr {color: #999;}
 | 
				
			||||||
 | 
					.cm-s-default .cm-link {color: #00c;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cm-s-default .cm-error {color: #f00;}
 | 
				
			||||||
 | 
					.cm-invalidchar {color: #f00;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-composing { border-bottom: 2px solid; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Default styles for common addons */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
 | 
				
			||||||
 | 
					div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
 | 
				
			||||||
 | 
					.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
 | 
				
			||||||
 | 
					.CodeMirror-activeline-background {background: #e8f2ff;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* STOP */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* The rest of this file contains styles related to the mechanics of
 | 
				
			||||||
 | 
					   the editor. You probably shouldn't touch them. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  background: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-scroll {
 | 
				
			||||||
 | 
					  overflow: scroll !important; /* Things will break if this is overridden */
 | 
				
			||||||
 | 
					  /* 30px is the magic margin used to hide the element's real scrollbars */
 | 
				
			||||||
 | 
					  /* See overflow: hidden in .CodeMirror */
 | 
				
			||||||
 | 
					  margin-bottom: -30px; margin-right: -30px;
 | 
				
			||||||
 | 
					  padding-bottom: 30px;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  outline: none; /* Prevent dragging from highlighting the element */
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-sizer {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  border-right: 30px solid transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* The fake, visible scrollbars. Used to force redraw during scrolling
 | 
				
			||||||
 | 
					   before actual scrolling happens, thus preventing shaking and
 | 
				
			||||||
 | 
					   flickering artifacts. */
 | 
				
			||||||
 | 
					.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  z-index: 6;
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-vscrollbar {
 | 
				
			||||||
 | 
					  right: 0; top: 0;
 | 
				
			||||||
 | 
					  overflow-x: hidden;
 | 
				
			||||||
 | 
					  overflow-y: scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-hscrollbar {
 | 
				
			||||||
 | 
					  bottom: 0; left: 0;
 | 
				
			||||||
 | 
					  overflow-y: hidden;
 | 
				
			||||||
 | 
					  overflow-x: scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-scrollbar-filler {
 | 
				
			||||||
 | 
					  right: 0; bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-gutter-filler {
 | 
				
			||||||
 | 
					  left: 0; bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-gutters {
 | 
				
			||||||
 | 
					  position: absolute; left: 0; top: 0;
 | 
				
			||||||
 | 
					  min-height: 100%;
 | 
				
			||||||
 | 
					  z-index: 3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-gutter {
 | 
				
			||||||
 | 
					  white-space: normal;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  vertical-align: top;
 | 
				
			||||||
 | 
					  margin-bottom: -30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-gutter-wrapper {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  z-index: 4;
 | 
				
			||||||
 | 
					  background: none !important;
 | 
				
			||||||
 | 
					  border: none !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-gutter-background {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0; bottom: 0;
 | 
				
			||||||
 | 
					  z-index: 4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-gutter-elt {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  cursor: default;
 | 
				
			||||||
 | 
					  z-index: 4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-gutter-wrapper {
 | 
				
			||||||
 | 
					  -webkit-user-select: none;
 | 
				
			||||||
 | 
					  -moz-user-select: none;
 | 
				
			||||||
 | 
					  user-select: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-lines {
 | 
				
			||||||
 | 
					  cursor: text;
 | 
				
			||||||
 | 
					  min-height: 1px; /* prevents collapsing before first draw */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror pre {
 | 
				
			||||||
 | 
					  /* Reset some styles that the rest of the page might have set */
 | 
				
			||||||
 | 
					  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
 | 
				
			||||||
 | 
					  border-width: 0;
 | 
				
			||||||
 | 
					  background: transparent;
 | 
				
			||||||
 | 
					  font-family: inherit;
 | 
				
			||||||
 | 
					  font-size: inherit;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  white-space: pre;
 | 
				
			||||||
 | 
					  word-wrap: normal;
 | 
				
			||||||
 | 
					  line-height: inherit;
 | 
				
			||||||
 | 
					  color: inherit;
 | 
				
			||||||
 | 
					  z-index: 2;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow: visible;
 | 
				
			||||||
 | 
					  -webkit-tap-highlight-color: transparent;
 | 
				
			||||||
 | 
					  -webkit-font-variant-ligatures: contextual;
 | 
				
			||||||
 | 
					  font-variant-ligatures: contextual;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-wrap pre {
 | 
				
			||||||
 | 
					  word-wrap: break-word;
 | 
				
			||||||
 | 
					  white-space: pre-wrap;
 | 
				
			||||||
 | 
					  word-break: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-linebackground {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  left: 0; right: 0; top: 0; bottom: 0;
 | 
				
			||||||
 | 
					  z-index: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-linewidget {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  z-index: 2;
 | 
				
			||||||
 | 
					  overflow: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-widget {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-code {
 | 
				
			||||||
 | 
					  outline: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Force content-box sizing for the elements where we expect it */
 | 
				
			||||||
 | 
					.CodeMirror-scroll,
 | 
				
			||||||
 | 
					.CodeMirror-sizer,
 | 
				
			||||||
 | 
					.CodeMirror-gutter,
 | 
				
			||||||
 | 
					.CodeMirror-gutters,
 | 
				
			||||||
 | 
					.CodeMirror-linenumber {
 | 
				
			||||||
 | 
					  -moz-box-sizing: content-box;
 | 
				
			||||||
 | 
					  box-sizing: content-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-measure {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 0;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  visibility: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-cursor {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.CodeMirror-measure pre { position: static; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.CodeMirror-cursors {
 | 
				
			||||||
 | 
					  visibility: hidden;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  z-index: 3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.CodeMirror-dragcursors {
 | 
				
			||||||
 | 
					  visibility: visible;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-focused div.CodeMirror-cursors {
 | 
				
			||||||
 | 
					  visibility: visible;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.CodeMirror-selected { background: #d9d9d9; }
 | 
				
			||||||
 | 
					.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
 | 
				
			||||||
 | 
					.CodeMirror-crosshair { cursor: crosshair; }
 | 
				
			||||||
 | 
					.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
 | 
				
			||||||
 | 
					.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cm-searching {
 | 
				
			||||||
 | 
					  background: #ffa;
 | 
				
			||||||
 | 
					  background: rgba(255, 255, 0, .4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Used to force a border model for a node */
 | 
				
			||||||
 | 
					.cm-force-border { padding-right: .1px; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media print {
 | 
				
			||||||
 | 
					  /* Hide the cursor when printing */
 | 
				
			||||||
 | 
					  .CodeMirror div.CodeMirror-cursors {
 | 
				
			||||||
 | 
					    visibility: hidden;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* See issue #2901 */
 | 
				
			||||||
 | 
					.cm-tab-wrap-hack:after { content: ''; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Help users use markselection to safely style text background */
 | 
				
			||||||
 | 
					span.CodeMirror-selectedtext { background: none; }
 | 
				
			||||||
							
								
								
									
										9231
									
								
								static/vendor/codemirror/lib/codemirror.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9231
									
								
								static/vendor/codemirror/lib/codemirror.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user