diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dde164b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +vc_zoom/README.md merge=union diff --git a/.gitignore b/.gitignore index a31a519..7f92601 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode .coverage htmlcov/ .idea/ diff --git a/.tx/config b/.tx/config index 561f206..6960ce1 100644 --- a/.tx/config +++ b/.tx/config @@ -7,6 +7,12 @@ source_file = vc_vidyo/indico_vc_vidyo/translations/messages.pot source_lang = en type = PO +[indico.vc-zoom-messages] +file_filter = vc_zoom/indico_vc_zoom/translations//LC_MESSAGES/messages.po +source_file = vc_zoom/indico_vc_zoom/translations/messages.pot +source_lang = en +type = PO + [indico.piwik-messages] file_filter = piwik/indico_piwik/translations//LC_MESSAGES/messages.po source_file = piwik/indico_piwik/translations/messages.pot diff --git a/_meta/setup.py b/_meta/setup.py index 470a6f0..b45b28e 100644 --- a/_meta/setup.py +++ b/_meta/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/__init__.py b/livesync/indico_livesync/__init__.py index 0fecb07..1d58973 100644 --- a/livesync/indico_livesync/__init__.py +++ b/livesync/indico_livesync/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/base.py b/livesync/indico_livesync/base.py index bfc738b..0b4d2ab 100644 --- a/livesync/indico_livesync/base.py +++ b/livesync/indico_livesync/base.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/blueprint.py b/livesync/indico_livesync/blueprint.py index 8d5597f..c993555 100644 --- a/livesync/indico_livesync/blueprint.py +++ b/livesync/indico_livesync/blueprint.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/cli.py b/livesync/indico_livesync/cli.py index 392e972..ca8c337 100644 --- a/livesync/indico_livesync/cli.py +++ b/livesync/indico_livesync/cli.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/controllers.py b/livesync/indico_livesync/controllers.py index 391aeb5..18d531b 100644 --- a/livesync/indico_livesync/controllers.py +++ b/livesync/indico_livesync/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/forms.py b/livesync/indico_livesync/forms.py index 9551e7d..aacec8b 100644 --- a/livesync/indico_livesync/forms.py +++ b/livesync/indico_livesync/forms.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/handler.py b/livesync/indico_livesync/handler.py index 6d6a19f..5a96273 100644 --- a/livesync/indico_livesync/handler.py +++ b/livesync/indico_livesync/handler.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/marcxml.py b/livesync/indico_livesync/marcxml.py index 9f60d5c..6c50a34 100644 --- a/livesync/indico_livesync/marcxml.py +++ b/livesync/indico_livesync/marcxml.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/models/agents.py b/livesync/indico_livesync/models/agents.py index e7d599f..554475e 100644 --- a/livesync/indico_livesync/models/agents.py +++ b/livesync/indico_livesync/models/agents.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/models/queue.py b/livesync/indico_livesync/models/queue.py index a334c2c..50b6d8e 100644 --- a/livesync/indico_livesync/models/queue.py +++ b/livesync/indico_livesync/models/queue.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/plugin.py b/livesync/indico_livesync/plugin.py index 8183bdc..394eea5 100644 --- a/livesync/indico_livesync/plugin.py +++ b/livesync/indico_livesync/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/simplify.py b/livesync/indico_livesync/simplify.py index a2052a7..e1f7cf1 100644 --- a/livesync/indico_livesync/simplify.py +++ b/livesync/indico_livesync/simplify.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/task.py b/livesync/indico_livesync/task.py index 16b6ea4..73c1ac7 100644 --- a/livesync/indico_livesync/task.py +++ b/livesync/indico_livesync/task.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/uploader.py b/livesync/indico_livesync/uploader.py index d349297..72513f2 100644 --- a/livesync/indico_livesync/uploader.py +++ b/livesync/indico_livesync/uploader.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/indico_livesync/util.py b/livesync/indico_livesync/util.py index 8fc1f93..e65c48a 100644 --- a/livesync/indico_livesync/util.py +++ b/livesync/indico_livesync/util.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/setup.py b/livesync/setup.py index 53a1d66..0471e91 100644 --- a/livesync/setup.py +++ b/livesync/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/tests/agent_test.py b/livesync/tests/agent_test.py index 66dd21b..19ade01 100644 --- a/livesync/tests/agent_test.py +++ b/livesync/tests/agent_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/tests/conftest.py b/livesync/tests/conftest.py index 3412031..0a154d2 100644 --- a/livesync/tests/conftest.py +++ b/livesync/tests/conftest.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/tests/queue_test.py b/livesync/tests/queue_test.py index 0413631..d282691 100644 --- a/livesync/tests/queue_test.py +++ b/livesync/tests/queue_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/tests/simplify_test.py b/livesync/tests/simplify_test.py index 6c3cece..c3efb97 100644 --- a/livesync/tests/simplify_test.py +++ b/livesync/tests/simplify_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/tests/uploader_test.py b/livesync/tests/uploader_test.py index 38b66fe..98618a3 100644 --- a/livesync/tests/uploader_test.py +++ b/livesync/tests/uploader_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync/tests/util_test.py b/livesync/tests/util_test.py index 09d9774..440e6d7 100644 --- a/livesync/tests/util_test.py +++ b/livesync/tests/util_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync_debug/indico_livesync_debug/backend.py b/livesync_debug/indico_livesync_debug/backend.py index b61309b..3d7cb1f 100644 --- a/livesync_debug/indico_livesync_debug/backend.py +++ b/livesync_debug/indico_livesync_debug/backend.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync_debug/indico_livesync_debug/plugin.py b/livesync_debug/indico_livesync_debug/plugin.py index 9e4737a..36068d0 100644 --- a/livesync_debug/indico_livesync_debug/plugin.py +++ b/livesync_debug/indico_livesync_debug/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/livesync_debug/setup.py b/livesync_debug/setup.py index 95310fe..4aa0c40 100644 --- a/livesync_debug/setup.py +++ b/livesync_debug/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/manage-i18n.sh b/manage-i18n.sh index 159cd01..38d815c 100755 --- a/manage-i18n.sh +++ b/manage-i18n.sh @@ -48,7 +48,7 @@ for plugin in $(find . -name setup.py -exec sh -c 'basename $(dirname $0)' {} \; pybabel update -i "./indico_${plugin}/translations/messages.pot" -l "$LOCALE" -d "./indico_${plugin}/translations" elif [[ "$ACTION" == "compile" ]]; then require_locale - pybabel compile -d "./indico_${plugin}/translations/" -l "$LOCALE" + pybabel compile -f -d "./indico_${plugin}/translations/" -l "$LOCALE" fi popd >/dev/null done diff --git a/payment_manual/indico_payment_manual/__init__.py b/payment_manual/indico_payment_manual/__init__.py index 7a6a154..0c312eb 100644 --- a/payment_manual/indico_payment_manual/__init__.py +++ b/payment_manual/indico_payment_manual/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_manual/indico_payment_manual/placeholders.py b/payment_manual/indico_payment_manual/placeholders.py index 13edbf1..210074f 100644 --- a/payment_manual/indico_payment_manual/placeholders.py +++ b/payment_manual/indico_payment_manual/placeholders.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_manual/indico_payment_manual/plugin.py b/payment_manual/indico_payment_manual/plugin.py index b24c0e7..847bbf0 100644 --- a/payment_manual/indico_payment_manual/plugin.py +++ b/payment_manual/indico_payment_manual/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_manual/setup.py b/payment_manual/setup.py index 93974dd..f169f73 100644 --- a/payment_manual/setup.py +++ b/payment_manual/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/indico_payment_paypal/__init__.py b/payment_paypal/indico_payment_paypal/__init__.py index 5326abe..36dbb34 100644 --- a/payment_paypal/indico_payment_paypal/__init__.py +++ b/payment_paypal/indico_payment_paypal/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/indico_payment_paypal/blueprint.py b/payment_paypal/indico_payment_paypal/blueprint.py index 7228820..35dd9c9 100644 --- a/payment_paypal/indico_payment_paypal/blueprint.py +++ b/payment_paypal/indico_payment_paypal/blueprint.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/indico_payment_paypal/controllers.py b/payment_paypal/indico_payment_paypal/controllers.py index 364d87a..e508315 100644 --- a/payment_paypal/indico_payment_paypal/controllers.py +++ b/payment_paypal/indico_payment_paypal/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/indico_payment_paypal/plugin.py b/payment_paypal/indico_payment_paypal/plugin.py index 0c1e521..a8160ba 100644 --- a/payment_paypal/indico_payment_paypal/plugin.py +++ b/payment_paypal/indico_payment_paypal/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/indico_payment_paypal/util.py b/payment_paypal/indico_payment_paypal/util.py index 29887f1..a076a45 100644 --- a/payment_paypal/indico_payment_paypal/util.py +++ b/payment_paypal/indico_payment_paypal/util.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/setup.py b/payment_paypal/setup.py index 16c4a50..362a0c2 100644 --- a/payment_paypal/setup.py +++ b/payment_paypal/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/tests/controllers_test.py b/payment_paypal/tests/controllers_test.py index 523d5fc..fc57d3b 100644 --- a/payment_paypal/tests/controllers_test.py +++ b/payment_paypal/tests/controllers_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/payment_paypal/tests/util_test.py b/payment_paypal/tests/util_test.py index 8cb381d..1927565 100644 --- a/payment_paypal/tests/util_test.py +++ b/payment_paypal/tests/util_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/__init__.py b/piwik/indico_piwik/__init__.py index 31bd92c..b709697 100644 --- a/piwik/indico_piwik/__init__.py +++ b/piwik/indico_piwik/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/client/index.js b/piwik/indico_piwik/client/index.js index dd37c19..7f53b04 100644 --- a/piwik/indico_piwik/client/index.js +++ b/piwik/indico_piwik/client/index.js @@ -1,5 +1,5 @@ // This file is part of the Indico plugins. -// Copyright (C) 2002 - 2020 CERN +// Copyright (C) 2002 - 2021 CERN // // The Indico plugins are free software; you can redistribute // them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/client/main.css b/piwik/indico_piwik/client/main.css index 10c48ac..a73dcd8 100644 --- a/piwik/indico_piwik/client/main.css +++ b/piwik/indico_piwik/client/main.css @@ -1,5 +1,5 @@ /* This file is part of the Indico plugins. - * Copyright (C) 2002 - 2020 CERN + * Copyright (C) 2002 - 2021 CERN * * The Indico plugins are free software; you can redistribute * them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/controllers.py b/piwik/indico_piwik/controllers.py index 53adbd8..9f88202 100644 --- a/piwik/indico_piwik/controllers.py +++ b/piwik/indico_piwik/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/forms.py b/piwik/indico_piwik/forms.py index c57bb70..adee529 100644 --- a/piwik/indico_piwik/forms.py +++ b/piwik/indico_piwik/forms.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/piwik.py b/piwik/indico_piwik/piwik.py index 23ef438..e5b1201 100644 --- a/piwik/indico_piwik/piwik.py +++ b/piwik/indico_piwik/piwik.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/plugin.py b/piwik/indico_piwik/plugin.py index a06a7d4..dc620ce 100644 --- a/piwik/indico_piwik/plugin.py +++ b/piwik/indico_piwik/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/queries/base.py b/piwik/indico_piwik/queries/base.py index 6cda137..ac2bd08 100644 --- a/piwik/indico_piwik/queries/base.py +++ b/piwik/indico_piwik/queries/base.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/queries/graphs.py b/piwik/indico_piwik/queries/graphs.py index 765ea9f..823399b 100644 --- a/piwik/indico_piwik/queries/graphs.py +++ b/piwik/indico_piwik/queries/graphs.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/queries/metrics.py b/piwik/indico_piwik/queries/metrics.py index 15238cf..f650a72 100644 --- a/piwik/indico_piwik/queries/metrics.py +++ b/piwik/indico_piwik/queries/metrics.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/queries/tracking.py b/piwik/indico_piwik/queries/tracking.py index 635ddb4..ccfc6bb 100644 --- a/piwik/indico_piwik/queries/tracking.py +++ b/piwik/indico_piwik/queries/tracking.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/queries/utils.py b/piwik/indico_piwik/queries/utils.py index ab180cd..5c8e69b 100644 --- a/piwik/indico_piwik/queries/utils.py +++ b/piwik/indico_piwik/queries/utils.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/reports.py b/piwik/indico_piwik/reports.py index 07438c3..099c630 100644 --- a/piwik/indico_piwik/reports.py +++ b/piwik/indico_piwik/reports.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/indico_piwik/views.py b/piwik/indico_piwik/views.py index a3cfee7..56f8909 100644 --- a/piwik/indico_piwik/views.py +++ b/piwik/indico_piwik/views.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/piwik/setup.py b/piwik/setup.py index 6bfe0bb..fd9ee7f 100644 --- a/piwik/setup.py +++ b/piwik/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_code/indico_previewer_code/__init__.py b/previewer_code/indico_previewer_code/__init__.py index 9b79da3..888e8c8 100644 --- a/previewer_code/indico_previewer_code/__init__.py +++ b/previewer_code/indico_previewer_code/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_code/indico_previewer_code/plugin.py b/previewer_code/indico_previewer_code/plugin.py index 858985e..4be5fd5 100644 --- a/previewer_code/indico_previewer_code/plugin.py +++ b/previewer_code/indico_previewer_code/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_code/setup.py b/previewer_code/setup.py index 34a6892..4ee0a62 100644 --- a/previewer_code/setup.py +++ b/previewer_code/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_jupyter/indico_previewer_jupyter/__init__.py b/previewer_jupyter/indico_previewer_jupyter/__init__.py index 67a0745..eadb69c 100644 --- a/previewer_jupyter/indico_previewer_jupyter/__init__.py +++ b/previewer_jupyter/indico_previewer_jupyter/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_jupyter/indico_previewer_jupyter/blueprint.py b/previewer_jupyter/indico_previewer_jupyter/blueprint.py index 9984b23..fb418f6 100644 --- a/previewer_jupyter/indico_previewer_jupyter/blueprint.py +++ b/previewer_jupyter/indico_previewer_jupyter/blueprint.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_jupyter/indico_previewer_jupyter/controllers.py b/previewer_jupyter/indico_previewer_jupyter/controllers.py index 6a313b4..8641549 100644 --- a/previewer_jupyter/indico_previewer_jupyter/controllers.py +++ b/previewer_jupyter/indico_previewer_jupyter/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py b/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py index 2cdc0d3..72b75f3 100644 --- a/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py +++ b/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_jupyter/indico_previewer_jupyter/plugin.py b/previewer_jupyter/indico_previewer_jupyter/plugin.py index 3b4deb1..f2401c6 100644 --- a/previewer_jupyter/indico_previewer_jupyter/plugin.py +++ b/previewer_jupyter/indico_previewer_jupyter/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/previewer_jupyter/setup.py b/previewer_jupyter/setup.py index c3a5f27..922e20e 100644 --- a/previewer_jupyter/setup.py +++ b/previewer_jupyter/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/__init__.py b/storage_s3/indico_storage_s3/__init__.py index 08f6885..52d317a 100644 --- a/storage_s3/indico_storage_s3/__init__.py +++ b/storage_s3/indico_storage_s3/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/blueprint.py b/storage_s3/indico_storage_s3/blueprint.py index 0960abd..92fc6fa 100644 --- a/storage_s3/indico_storage_s3/blueprint.py +++ b/storage_s3/indico_storage_s3/blueprint.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/controllers.py b/storage_s3/indico_storage_s3/controllers.py index d91e5a6..e3f82a3 100644 --- a/storage_s3/indico_storage_s3/controllers.py +++ b/storage_s3/indico_storage_s3/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/migrate.py b/storage_s3/indico_storage_s3/migrate.py index 91a7c69..2ab6378 100644 --- a/storage_s3/indico_storage_s3/migrate.py +++ b/storage_s3/indico_storage_s3/migrate.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/plugin.py b/storage_s3/indico_storage_s3/plugin.py index fd94018..8509c8d 100644 --- a/storage_s3/indico_storage_s3/plugin.py +++ b/storage_s3/indico_storage_s3/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/storage.py b/storage_s3/indico_storage_s3/storage.py index 22f9bd2..966deee 100644 --- a/storage_s3/indico_storage_s3/storage.py +++ b/storage_s3/indico_storage_s3/storage.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/indico_storage_s3/task.py b/storage_s3/indico_storage_s3/task.py index ba96b39..0c31919 100644 --- a/storage_s3/indico_storage_s3/task.py +++ b/storage_s3/indico_storage_s3/task.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/setup.py b/storage_s3/setup.py index 2ea7eef..0a87290 100644 --- a/storage_s3/setup.py +++ b/storage_s3/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/storage_s3/tests/plugin_test.py b/storage_s3/tests/plugin_test.py index 9b06427..5b4f2ff 100644 --- a/storage_s3/tests/plugin_test.py +++ b/storage_s3/tests/plugin_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/update-meta.py b/update-meta.py index 00d00bf..3fe2906 100644 --- a/update-meta.py +++ b/update-meta.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/__init__.py b/ursh/indico_ursh/__init__.py index 7253397..25499e3 100644 --- a/ursh/indico_ursh/__init__.py +++ b/ursh/indico_ursh/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py index 05d203a..869bdcc 100644 --- a/ursh/indico_ursh/blueprint.py +++ b/ursh/indico_ursh/blueprint.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js index 8c852aa..f097a12 100644 --- a/ursh/indico_ursh/client/index.js +++ b/ursh/indico_ursh/client/index.js @@ -1,5 +1,5 @@ // This file is part of the Indico plugins. -// Copyright (C) 2002 - 2020 CERN +// Copyright (C) 2002 - 2021 CERN // // The Indico plugins are free software; you can redistribute // them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py index 0f0528a..289c0de 100644 --- a/ursh/indico_ursh/controllers.py +++ b/ursh/indico_ursh/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py index 3616ffd..7291da9 100644 --- a/ursh/indico_ursh/plugin.py +++ b/ursh/indico_ursh/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py index 4013a0c..a4817a7 100644 --- a/ursh/indico_ursh/util.py +++ b/ursh/indico_ursh/util.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/indico_ursh/views.py b/ursh/indico_ursh/views.py index 4c305c4..9e47818 100644 --- a/ursh/indico_ursh/views.py +++ b/ursh/indico_ursh/views.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/ursh/setup.py b/ursh/setup.py index a5f9001..6964468 100644 --- a/ursh/setup.py +++ b/ursh/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_dummy/indico_vc_dummy/plugin.py b/vc_dummy/indico_vc_dummy/plugin.py index f03b78c..cbb1c04 100644 --- a/vc_dummy/indico_vc_dummy/plugin.py +++ b/vc_dummy/indico_vc_dummy/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_dummy/setup.py b/vc_dummy/setup.py index 550f486..662f9c7 100644 --- a/vc_dummy/setup.py +++ b/vc_dummy/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/__init__.py b/vc_vidyo/indico_vc_vidyo/__init__.py index d6149b0..5dd5cb6 100644 --- a/vc_vidyo/indico_vc_vidyo/__init__.py +++ b/vc_vidyo/indico_vc_vidyo/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/api/__init__.py b/vc_vidyo/indico_vc_vidyo/api/__init__.py index a2a668a..0d54b1d 100644 --- a/vc_vidyo/indico_vc_vidyo/api/__init__.py +++ b/vc_vidyo/indico_vc_vidyo/api/__init__.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/api/cache.py b/vc_vidyo/indico_vc_vidyo/api/cache.py index 90450f7..4c05836 100644 --- a/vc_vidyo/indico_vc_vidyo/api/cache.py +++ b/vc_vidyo/indico_vc_vidyo/api/cache.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/api/client.py b/vc_vidyo/indico_vc_vidyo/api/client.py index 51ff954..9e729fe 100644 --- a/vc_vidyo/indico_vc_vidyo/api/client.py +++ b/vc_vidyo/indico_vc_vidyo/api/client.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/blueprint.py b/vc_vidyo/indico_vc_vidyo/blueprint.py index 54452cf..fb4c2d2 100644 --- a/vc_vidyo/indico_vc_vidyo/blueprint.py +++ b/vc_vidyo/indico_vc_vidyo/blueprint.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/cli.py b/vc_vidyo/indico_vc_vidyo/cli.py index 505e719..6ea1633 100644 --- a/vc_vidyo/indico_vc_vidyo/cli.py +++ b/vc_vidyo/indico_vc_vidyo/cli.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/client/index.js b/vc_vidyo/indico_vc_vidyo/client/index.js index 3773394..0707b50 100644 --- a/vc_vidyo/indico_vc_vidyo/client/index.js +++ b/vc_vidyo/indico_vc_vidyo/client/index.js @@ -1,5 +1,5 @@ // This file is part of the Indico plugins. -// Copyright (C) 2002 - 2020 CERN +// Copyright (C) 2002 - 2021 CERN // // The Indico plugins are free software; you can redistribute // them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/controllers.py b/vc_vidyo/indico_vc_vidyo/controllers.py index 316eddf..e02f9e9 100644 --- a/vc_vidyo/indico_vc_vidyo/controllers.py +++ b/vc_vidyo/indico_vc_vidyo/controllers.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; @@ -24,6 +24,6 @@ class RHVidyoRoomOwner(RHVCSystemEventBase): result['success'] = False db.session.rollback() else: - flash(_(f"You are now the owner of the room '{self.vc_room.name}'"), 'success') + flash(_("You are now the owner of the room '{room}'").format(room=self.vc_room.name), 'success') result['success'] = True return jsonify(result) diff --git a/vc_vidyo/indico_vc_vidyo/forms.py b/vc_vidyo/indico_vc_vidyo/forms.py index 9c25345..9f6c7cb 100644 --- a/vc_vidyo/indico_vc_vidyo/forms.py +++ b/vc_vidyo/indico_vc_vidyo/forms.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/http_api.py b/vc_vidyo/indico_vc_vidyo/http_api.py index f7ad592..661cff4 100644 --- a/vc_vidyo/indico_vc_vidyo/http_api.py +++ b/vc_vidyo/indico_vc_vidyo/http_api.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/models/vidyo_extensions.py b/vc_vidyo/indico_vc_vidyo/models/vidyo_extensions.py index 5553cf6..cd06bd9 100644 --- a/vc_vidyo/indico_vc_vidyo/models/vidyo_extensions.py +++ b/vc_vidyo/indico_vc_vidyo/models/vidyo_extensions.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/plugin.py b/vc_vidyo/indico_vc_vidyo/plugin.py index 5a8cf9c..849816b 100644 --- a/vc_vidyo/indico_vc_vidyo/plugin.py +++ b/vc_vidyo/indico_vc_vidyo/plugin.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/task.py b/vc_vidyo/indico_vc_vidyo/task.py index fd743fa..21f184e 100644 --- a/vc_vidyo/indico_vc_vidyo/task.py +++ b/vc_vidyo/indico_vc_vidyo/task.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/indico_vc_vidyo/translations/fr_FR/LC_MESSAGES/messages.po b/vc_vidyo/indico_vc_vidyo/translations/fr_FR/LC_MESSAGES/messages.po index 61f9670..8de1736 100644 --- a/vc_vidyo/indico_vc_vidyo/translations/fr_FR/LC_MESSAGES/messages.po +++ b/vc_vidyo/indico_vc_vidyo/translations/fr_FR/LC_MESSAGES/messages.po @@ -1,231 +1,224 @@ # Translations template for PROJECT. -# Copyright (C) 2017 ORGANIZATION +# Copyright (C) 2021 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: +# Adrian Mönnich, 2021 # Thomas Baron , 2015,2017 msgid "" msgstr "" "Project-Id-Version: Indico\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-18 11:55+0200\n" -"PO-Revision-Date: 2017-10-30 11:04+0000\n" -"Last-Translator: Thomas Baron \n" +"POT-Creation-Date: 2021-01-05 13:23+0100\n" +"PO-Revision-Date: 2021-01-05 12:24+0000\n" +"Last-Translator: Adrian Mönnich\n" "Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" +"Generated-By: Babel 2.8.0\n" "Language: fr_FR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: indico_vc_vidyo/controllers.py:38 -msgid "You are now the owner of the room '{room.name}'" -msgstr "Vous êtes maintenant responsable de la salle '{room.name}'" +#: indico_vc_vidyo/controllers.py:29 +msgid "You are now the owner of the room '{room}'" +msgstr "Vous êtes maintenant responsable de la salle '{room}'" -#: indico_vc_vidyo/forms.py:32 +#: indico_vc_vidyo/forms.py:23 msgid "The PIN must be a number" msgstr "Le code confidentiel doit être un nombre entier" -#: indico_vc_vidyo/forms.py:37 +#: indico_vc_vidyo/forms.py:28 msgid "Show PIN" msgstr "Afficher le code confidentiel" -#: indico_vc_vidyo/forms.py:39 +#: indico_vc_vidyo/forms.py:30 msgid "Show the VC Room PIN on the event page (insecure!)" msgstr "Afficher le code confidentiel de la salle Vidyo sur la page de l'événement (peu sûr!)" -#: indico_vc_vidyo/forms.py:40 +#: indico_vc_vidyo/forms.py:31 msgid "Show Auto-join URL" msgstr "Afficher l'URL de connexion" -#: indico_vc_vidyo/forms.py:42 +#: indico_vc_vidyo/forms.py:33 msgid "Show the auto-join URL on the event page" msgstr "Afficher l'URL de connexion sur la page de l'événement" -#: indico_vc_vidyo/forms.py:43 +#: indico_vc_vidyo/forms.py:34 msgid "Show Phone Access numbers" msgstr "Afficher les numéros d'accès téléphonique" -#: indico_vc_vidyo/forms.py:45 +#: indico_vc_vidyo/forms.py:36 msgid "Show a link to the list of phone access numbers" msgstr "Afficher un lien vers la liste des numéros d'accès téléphonique" -#: indico_vc_vidyo/forms.py:58 indico_vc_vidyo/templates/info_box.html:7 +#: indico_vc_vidyo/forms.py:49 indico_vc_vidyo/templates/info_box.html:7 #: indico_vc_vidyo/templates/manage_event_info_box.html:6 msgid "Description" msgstr "Description" -#: indico_vc_vidyo/forms.py:58 +#: indico_vc_vidyo/forms.py:49 msgid "The description of the room" msgstr "La description de la salle" -#: indico_vc_vidyo/forms.py:59 indico_vc_vidyo/templates/info_box.html:14 +#: indico_vc_vidyo/forms.py:50 indico_vc_vidyo/templates/info_box.html:14 #: indico_vc_vidyo/templates/manage_event_info_box.html:10 msgid "Owner" msgstr "Responsable" -#: indico_vc_vidyo/forms.py:59 +#: indico_vc_vidyo/forms.py:50 msgid "The owner of the room" msgstr "Le responsable de la salle" -#: indico_vc_vidyo/forms.py:60 +#: indico_vc_vidyo/forms.py:51 #: indico_vc_vidyo/templates/manage_event_info_box.html:39 msgid "Moderation PIN" msgstr "Code confidentiel de modération" -#: indico_vc_vidyo/forms.py:61 +#: indico_vc_vidyo/forms.py:52 msgid "Used to moderate the VC Room. Only digits allowed." msgstr "Utilisé pour modérer la salle de VC. Seuls les chiffres sont autorisés." -#: indico_vc_vidyo/forms.py:62 indico_vc_vidyo/templates/info_box.html:18 +#: indico_vc_vidyo/forms.py:53 indico_vc_vidyo/templates/info_box.html:18 #: indico_vc_vidyo/templates/manage_event_info_box.html:32 msgid "Room PIN" msgstr "Code confidentiel de la salle" -#: indico_vc_vidyo/forms.py:63 +#: indico_vc_vidyo/forms.py:54 msgid "" "Used to protect the access to the VC Room (leave blank for open access). " "Only digits allowed." msgstr "Utilisé pour protéger l'accès à la salle de VC (laisser vide pour un accès ouvert). Seuls les chiffres sont autorisés." -#: indico_vc_vidyo/forms.py:65 +#: indico_vc_vidyo/forms.py:56 msgid "Auto mute" msgstr "Coupure automatique des périphériques d'entrée" -#: indico_vc_vidyo/forms.py:66 -msgid "On" -msgstr "Activé" - -#: indico_vc_vidyo/forms.py:66 -msgid "Off" -msgstr "Désactivé" - -#: indico_vc_vidyo/forms.py:67 +#: indico_vc_vidyo/forms.py:58 msgid "" "The VidyoDesktop clients will join the VC room muted by default (audio and " "video)" msgstr "Les clients VidyoDesktop rejoindront la salle Vidyo avec le micro et la caméra coupés par défaut" -#: indico_vc_vidyo/forms.py:82 +#: indico_vc_vidyo/forms.py:73 msgid "Unable to find this user in Indico." msgstr "Impossible de trouver cet utilisateur dans Indico." -#: indico_vc_vidyo/forms.py:84 +#: indico_vc_vidyo/forms.py:75 msgid "This user does not have a suitable account to use Vidyo." msgstr "Cet utilisateur n'a pas de compte qui lui permet d'utiliser Vidyo." -#: indico_vc_vidyo/plugin.py:49 +#: indico_vc_vidyo/plugin.py:40 msgid "Vidyo email support" msgstr "Adresse électronique de l'assistance Vidyo" -#: indico_vc_vidyo/plugin.py:50 +#: indico_vc_vidyo/plugin.py:41 msgid "Username" msgstr "Identifiant" -#: indico_vc_vidyo/plugin.py:50 +#: indico_vc_vidyo/plugin.py:41 msgid "Indico username for Vidyo" msgstr "Identifiant Indico pour Vidyo" -#: indico_vc_vidyo/plugin.py:51 +#: indico_vc_vidyo/plugin.py:42 msgid "Password" msgstr "Mot de passe" -#: indico_vc_vidyo/plugin.py:52 +#: indico_vc_vidyo/plugin.py:43 msgid "Indico password for Vidyo" msgstr "Mot de passe utilisateur pour Vidyo" -#: indico_vc_vidyo/plugin.py:53 +#: indico_vc_vidyo/plugin.py:44 msgid "Admin API WSDL URL" msgstr "URL WSDL pour l'API admin" -#: indico_vc_vidyo/plugin.py:54 +#: indico_vc_vidyo/plugin.py:45 msgid "User API WSDL URL" msgstr "URL WSDL pour l'API utilisateur" -#: indico_vc_vidyo/plugin.py:55 +#: indico_vc_vidyo/plugin.py:46 msgid "Indico tenant prefix" msgstr "Préfixe de tenant pour Indico" -#: indico_vc_vidyo/plugin.py:56 +#: indico_vc_vidyo/plugin.py:47 msgid "The tenant prefix for Indico rooms created on this server" msgstr "Le préfixe de tenant pour les salles Vidyo créées sur ce serveur Indico" -#: indico_vc_vidyo/plugin.py:57 +#: indico_vc_vidyo/plugin.py:48 msgid "Public rooms' group name" msgstr "Nom du groupe Vidyo pour les salles Vidyo" -#: indico_vc_vidyo/plugin.py:58 +#: indico_vc_vidyo/plugin.py:49 msgid "Group name for public videoconference rooms created by Indico" msgstr "Nom du groupe pour les salles publiques de visioconférence créées par Indico" -#: indico_vc_vidyo/plugin.py:59 +#: indico_vc_vidyo/plugin.py:50 msgid "Authenticators" msgstr "Services d'authentification" -#: indico_vc_vidyo/plugin.py:60 +#: indico_vc_vidyo/plugin.py:51 msgid "Identity providers to convert Indico users to Vidyo accounts" msgstr "Fournisseurs d'identité pour convertir des utilisateurs Indico en comptes Vidyo" -#: indico_vc_vidyo/plugin.py:61 +#: indico_vc_vidyo/plugin.py:52 msgid "VC room age threshold" msgstr "Limite d'âge pour les salles Vidyo" -#: indico_vc_vidyo/plugin.py:62 +#: indico_vc_vidyo/plugin.py:53 msgid "" "Number of days after an Indico event when a videoconference room is " "considered old" msgstr "Nombre de jours à partir de la fin d'un événement dans Indico après lesquels une salle de visioconférence est considérée comme agée" -#: indico_vc_vidyo/plugin.py:64 +#: indico_vc_vidyo/plugin.py:55 msgid "Max. num. VC rooms before warning" msgstr "Nombre maximum de salles Vidyo avant un message d'alerte" -#: indico_vc_vidyo/plugin.py:65 +#: indico_vc_vidyo/plugin.py:56 msgid "Maximum number of rooms until a warning is sent to the managers" msgstr "Nombre maximum de salles Vidyo créées sur ce serveur avant qu'un message d'alerte soit envoyé aux administrateurs" -#: indico_vc_vidyo/plugin.py:66 +#: indico_vc_vidyo/plugin.py:57 msgid "VidyoVoice phone number" msgstr "Numéros de téléphone VidyoVoice" -#: indico_vc_vidyo/plugin.py:67 +#: indico_vc_vidyo/plugin.py:58 msgid "Link to the list of VidyoVoice phone numbers" msgstr "Lien vers la liste des numéros de téléphones VidyoVoice" -#: indico_vc_vidyo/plugin.py:68 +#: indico_vc_vidyo/plugin.py:59 msgid "Client Chooser URL" msgstr "URL de sélection du client" -#: indico_vc_vidyo/plugin.py:69 +#: indico_vc_vidyo/plugin.py:60 msgid "" "URL for client chooser interface. The room key will be passed as a 'url' GET" " query argument" msgstr "L'URL pour l'interface de sélection du client. Le code de la salle sera passé comme argument de requête GET." -#: indico_vc_vidyo/plugin.py:71 +#: indico_vc_vidyo/plugin.py:62 msgid "Creation email footer" msgstr "Pied de page du courriel de création" -#: indico_vc_vidyo/plugin.py:72 +#: indico_vc_vidyo/plugin.py:63 msgid "Footer to append to emails sent upon creation of a VC room" msgstr "Pied de page ajouté au courriel envoyé à la création d'une nouvelle salle Vidyo" -#: indico_vc_vidyo/plugin.py:162 indico_vc_vidyo/plugin.py:202 -#: indico_vc_vidyo/plugin.py:240 indico_vc_vidyo/plugin.py:269 +#: indico_vc_vidyo/plugin.py:153 indico_vc_vidyo/plugin.py:193 +#: indico_vc_vidyo/plugin.py:231 indico_vc_vidyo/plugin.py:260 msgid "No valid Vidyo account found for this user" msgstr "Pas de compte Vidyo valide pour cet utilisateur" -#: indico_vc_vidyo/plugin.py:198 indico_vc_vidyo/plugin.py:263 +#: indico_vc_vidyo/plugin.py:189 indico_vc_vidyo/plugin.py:254 msgid "Room name already in use" msgstr "Ce nom de salle est déjà utilisé" -#: indico_vc_vidyo/plugin.py:213 +#: indico_vc_vidyo/plugin.py:204 msgid "Could not find newly created room in Vidyo" msgstr "Impossible de trouver la nouvelle salle dans Vidyo" -#: indico_vc_vidyo/plugin.py:232 indico_vc_vidyo/plugin.py:259 -#: indico_vc_vidyo/plugin.py:288 +#: indico_vc_vidyo/plugin.py:223 indico_vc_vidyo/plugin.py:250 +#: indico_vc_vidyo/plugin.py:279 msgid "This room has been deleted from Vidyo" msgstr "Cette salle a été supprimée de Vidyo" diff --git a/vc_vidyo/indico_vc_vidyo/translations/messages.pot b/vc_vidyo/indico_vc_vidyo/translations/messages.pot index 785a5cf..323ba6b 100644 --- a/vc_vidyo/indico_vc_vidyo/translations/messages.pot +++ b/vc_vidyo/indico_vc_vidyo/translations/messages.pot @@ -1,14 +1,14 @@ # Translations template for PROJECT. -# Copyright (C) 2020 ORGANIZATION +# Copyright (C) 2021 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2020. +# FIRST AUTHOR , 2021. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-08-19 20:40+0200\n" +"POT-Creation-Date: 2021-01-05 13:23+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,7 @@ msgstr "" "Generated-By: Babel 2.8.0\n" #: indico_vc_vidyo/controllers.py:29 -msgid "You are now the owner of the room '{room.name}'" +msgid "You are now the owner of the room '{room}'" msgstr "" #: indico_vc_vidyo/forms.py:23 diff --git a/vc_vidyo/indico_vc_vidyo/util.py b/vc_vidyo/indico_vc_vidyo/util.py index 330fbae..b59d352 100644 --- a/vc_vidyo/indico_vc_vidyo/util.py +++ b/vc_vidyo/indico_vc_vidyo/util.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/setup.py b/vc_vidyo/setup.py index a0bc9ee..ab79b7f 100644 --- a/vc_vidyo/setup.py +++ b/vc_vidyo/setup.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_vidyo/tests/task_test.py b/vc_vidyo/tests/task_test.py index 679ff4d..bafcefc 100644 --- a/vc_vidyo/tests/task_test.py +++ b/vc_vidyo/tests/task_test.py @@ -1,5 +1,5 @@ # This file is part of the Indico plugins. -# Copyright (C) 2002 - 2020 CERN +# Copyright (C) 2002 - 2021 CERN # # The Indico plugins are free software; you can redistribute # them and/or modify them under the terms of the MIT License; diff --git a/vc_zoom/MANIFEST.in b/vc_zoom/MANIFEST.in new file mode 100644 index 0000000..facfb9e --- /dev/null +++ b/vc_zoom/MANIFEST.in @@ -0,0 +1,6 @@ +graft indico_vc_zoom/static +graft indico_vc_zoom/migrations +graft indico_vc_zoom/templates +graft indico_vc_zoom/translations + +global-exclude *.pyc __pycache__ .keep diff --git a/vc_zoom/README.md b/vc_zoom/README.md new file mode 100644 index 0000000..d20527c --- /dev/null +++ b/vc_zoom/README.md @@ -0,0 +1,73 @@ +# Indico Plugin for Zoom + +## Features + + * Creating Zoom meetings from Indico; + * Sharing Zoom meetings between more than one Indico event; + * Creating meetings on behalf of others; + * Changes of host possible after creation; + * Protection of Zoom link (only logged in, everyone or no one) + * Webinar mode; + +## Changelog + +### 2.3b2 + +- Improve logging when a Zoom API request fails +- Fail more gracefully if no Zoom account could be found for a user +- Allow using the same name for multiple Zoom rooms +- Update the join url when changing the passcode +- Provide an alternative method of looking up the Zoom user corresponding to an Indico user +- Always show the full join link and passcode to event managers +- The meeting passcode can be restricted to registered participants +- Show "Make me host" button in the management area and in contributions/sessions as well +- Warn the user if they delete a Zoom meeting linked to multiple events if they aren't the host +- Change Zoom meeting to "recurring meeting" when cloning an event +- Show detailed error when deleting a meeting fails +- Do not allow passcodes that are too long for zoom +- Remove the "Assistant Zoom ID" logic due to problems with Zoom's API rate limits (all meetings created were counted against the assistant's rate limit instead of the host's); this means the host can no longer be changed, but Indico instead provides an option to event managers to make themselves a co-host. +- Fix an error when changing the linked object of a recurring Zoom room in Indico + +**Breaking change:** The email domains are now stored as a list of strings instead of a comma-separated list. You need to update them in the plugin settings. + +### 2.3b1 + +- Initial beta release + +## Zoom App Configuration + +### Webhook (optional) + +**URL:** `https://yourserver/api/plugin/zoom/webhook` + +(write down the "Verification Token", as you will need it in the plugin configuration below) + +Select the following "Event types": + * `Meeting has been updated` + * `Meeting has been deleted` + * `Webinar has been updated` + * `Webinar has been deleted` + + +## Plugin Configuration + +These are the most relevant configuration options: + + * **Notification email addresses** - Additional e-mails which will receive notifications + * **E-mail domains** - List of e-mail domains which can be used for the Zoom API (e.g. `cern.ch`) + * **Webhook token** (optional) - the token which Zoom requests will authenticate with (get it from Zoom Marketplace) + + +### Zoom API key/secret (JWT) + +To obtain API key and API secret, please visit [https://marketplace.zoom.us/docs/guides/auth/jwt](https://marketplace.zoom.us/docs/guides/auth/jwt). + + +## Intellectual Property + +Developed by Giovanni Mariano @ **ENEA Frascati**, based on the Vidyo Plugin by the Indico Team at **CERN**. Further +improvements and modifications added by the Indico Team. + +This package is Open Source Software licensed under the [MIT License](https://opensource.org/licenses/MIT). + +**© Copyright 2020 CERN and ENEA** diff --git a/vc_zoom/conftest.py b/vc_zoom/conftest.py new file mode 100644 index 0000000..2071ebf --- /dev/null +++ b/vc_zoom/conftest.py @@ -0,0 +1,8 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +pytest_plugins = 'indico' diff --git a/vc_zoom/copyright.yml b/vc_zoom/copyright.yml new file mode 100644 index 0000000..5d9d81e --- /dev/null +++ b/vc_zoom/copyright.yml @@ -0,0 +1,2 @@ +name: CERN and ENEA +start_year: 2020 diff --git a/vc_zoom/indico_vc_zoom/__init__.py b/vc_zoom/indico_vc_zoom/__init__.py new file mode 100644 index 0000000..cd6162c --- /dev/null +++ b/vc_zoom/indico_vc_zoom/__init__.py @@ -0,0 +1,13 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from indico.util.i18n import make_bound_gettext + + +_ = make_bound_gettext('vc_zoom') diff --git a/vc_zoom/indico_vc_zoom/api/__init__.py b/vc_zoom/indico_vc_zoom/api/__init__.py new file mode 100644 index 0000000..035dd7c --- /dev/null +++ b/vc_zoom/indico_vc_zoom/api/__init__.py @@ -0,0 +1,11 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from .client import ZoomClient, ZoomIndicoClient + + +__all__ = ['ZoomIndicoClient', 'ZoomClient'] diff --git a/vc_zoom/indico_vc_zoom/api/client.py b/vc_zoom/indico_vc_zoom/api/client.py new file mode 100644 index 0000000..ad223c3 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/api/client.py @@ -0,0 +1,228 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import absolute_import, unicode_literals + +import time + +import jwt +from pytz import utc +from requests import Session +from requests.exceptions import HTTPError + + +def format_iso_dt(d): + """Convert a datetime objects to a UTC-based string. + + :param d: The :class:`datetime.datetime` to convert to a string + :returns: The string representation of the date + """ + return d.astimezone(utc).strftime('%Y-%m-%dT%H:%M:%SZ') + + +def _handle_response(resp, expected_code=200, expects_json=True): + try: + resp.raise_for_status() + if resp.status_code != expected_code: + raise HTTPError('Unexpected status code {}'.format(resp.status_code), response=resp) + except HTTPError: + from indico_vc_zoom.plugin import ZoomPlugin + ZoomPlugin.logger.error('Error in API call to %s: %s', resp.url, resp.content) + raise + return resp.json() if expects_json else resp + + +class APIException(Exception): + pass + + +class BaseComponent(object): + def __init__(self, base_uri, config, timeout): + self.base_uri = base_uri + self.config = config + self.timeout = timeout + + @property + def token(self): + header = {'alg': 'HS256', 'typ': 'JWT'} + payload = {'iss': self.config['api_key'], 'exp': int(time.time() + 3600)} + token = jwt.encode(payload, self.config['api_secret'], algorithm='HS256', headers=header) + return token.decode('utf-8') + + @property + def session(self): + session = Session() + session.headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer {}'.format(self.token) + } + return session + + +class MeetingComponent(BaseComponent): + def list(self, user_id, **kwargs): + return self.get( + '{}/users/{}/meetings'.format(self.base_uri, user_id), params=kwargs + ) + + def create(self, user_id, **kwargs): + if kwargs.get('start_time'): + kwargs['start_time'] = format_iso_dt(kwargs['start_time']) + return self.session.post( + '{}/users/{}/meetings'.format(self.base_uri, user_id), + json=kwargs + ) + + def get(self, meeting_id, **kwargs): + return self.session.get('{}/meetings/{}'.format(self.base_uri, meeting_id), json=kwargs) + + def update(self, meeting_id, **kwargs): + if kwargs.get('start_time'): + kwargs['start_time'] = format_iso_dt(kwargs['start_time']) + return self.session.patch( + '{}/meetings/{}'.format(self.base_uri, meeting_id), json=kwargs + ) + + def delete(self, meeting_id, **kwargs): + return self.session.delete( + '{}/meetings/{}'.format(self.base_uri, meeting_id), json=kwargs + ) + + +class WebinarComponent(BaseComponent): + def list(self, user_id, **kwargs): + return self.get( + '{}/users/{}/webinars'.format(self.base_uri, user_id), params=kwargs + ) + + def create(self, user_id, **kwargs): + if kwargs.get('start_time'): + kwargs['start_time'] = format_iso_dt(kwargs['start_time']) + return self.session.post( + '{}/users/{}/webinars'.format(self.base_uri, user_id), + json=kwargs + ) + + def get(self, meeting_id, **kwargs): + return self.session.get('{}/webinars/{}'.format(self.base_uri, meeting_id), json=kwargs) + + def update(self, meeting_id, **kwargs): + if kwargs.get('start_time'): + kwargs['start_time'] = format_iso_dt(kwargs['start_time']) + return self.session.patch( + '{}/webinars/{}'.format(self.base_uri, meeting_id), json=kwargs + ) + + def delete(self, meeting_id, **kwargs): + return self.session.delete( + '{}/webinars/{}'.format(self.base_uri, meeting_id), json=kwargs + ) + + +class UserComponent(BaseComponent): + def me(self): + return self.get('me') + + def list(self, **kwargs): + return self.session.get('{}/users'.format(self.base_uri), params=kwargs) + + def create(self, **kwargs): + return self.session.post('{}/users'.format(self.base_uri), params=kwargs) + + def update(self, user_id, **kwargs): + return self.session.patch('{}/users/{}'.format(self.base_uri, user_id), params=kwargs) + + def delete(self, user_id, **kwargs): + return self.session.delete('{}/users/{}'.format(self.base_uri, user_id), params=kwargs) + + def get(self, user_id, **kwargs): + return self.session.get('{}/users/{}'.format(self.base_uri, user_id), params=kwargs) + + +class ZoomClient(object): + """Zoom REST API Python Client.""" + + BASE_URI = 'https://api.zoom.us/v2' + + _components = { + 'user': UserComponent, + 'meeting': MeetingComponent, + 'webinar': WebinarComponent + } + + def __init__(self, api_key, api_secret, timeout=15): + """Create a new Zoom client. + + :param api_key: the Zoom JWT API key + :param api_secret: the Zoom JWT API Secret + :param timeout: the time out to use for API requests + """ + # Setup the config details + config = { + 'api_key': api_key, + 'api_secret': api_secret + } + + # Instantiate the components + self.components = { + key: component(base_uri=self.BASE_URI, config=config, timeout=timeout) + for key, component in self._components.viewitems() + } + + @property + def meeting(self): + """Get the meeting component.""" + return self.components['meeting'] + + @property + def user(self): + """Get the user component.""" + return self.components['user'] + + @property + def webinar(self): + """Get the webinar component.""" + return self.components['webinar'] + + +class ZoomIndicoClient(object): + def __init__(self): + from indico_vc_zoom.plugin import ZoomPlugin + self.client = ZoomClient( + ZoomPlugin.settings.get('api_key'), + ZoomPlugin.settings.get('api_secret') + ) + + def create_meeting(self, user_id, **kwargs): + return _handle_response(self.client.meeting.create(user_id, **kwargs), 201) + + def get_meeting(self, meeting_id): + return _handle_response(self.client.meeting.get(meeting_id)) + + def update_meeting(self, meeting_id, data): + return _handle_response(self.client.meeting.update(meeting_id, **data), 204, expects_json=False) + + def delete_meeting(self, meeting_id): + return _handle_response(self.client.meeting.delete(meeting_id), 204, expects_json=False) + + def create_webinar(self, user_id, **kwargs): + return _handle_response(self.client.webinar.create(user_id, **kwargs), 201) + + def get_webinar(self, webinar_id): + return _handle_response(self.client.webinar.get(webinar_id)) + + def update_webinar(self, webinar_id, data): + return _handle_response(self.client.webinar.update(webinar_id, **data), 204, expects_json=False) + + def delete_webinar(self, webinar_id): + return _handle_response(self.client.webinar.delete(webinar_id), 204, expects_json=False) + + def get_user(self, user_id, silent=False): + resp = self.client.user.get(user_id) + if resp.status_code == 404 and silent: + return None + return _handle_response(resp) diff --git a/vc_zoom/indico_vc_zoom/blueprint.py b/vc_zoom/indico_vc_zoom/blueprint.py new file mode 100644 index 0000000..57cab6d --- /dev/null +++ b/vc_zoom/indico_vc_zoom/blueprint.py @@ -0,0 +1,26 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from indico.core.plugins import IndicoPluginBlueprint + +from indico_vc_zoom.controllers import RHRoomAlternativeHost, RHWebhook + + +blueprint = IndicoPluginBlueprint('vc_zoom', 'indico_vc_zoom') + +# Room management +# using any(zoom) instead of defaults since the event vc room locator +# includes the service and normalization skips values provided in 'defaults' +blueprint.add_url_rule( + '/event//manage/videoconference///make-me-alt-host', + 'make_me_alt_host', + RHRoomAlternativeHost, + methods=('POST',) +) +blueprint.add_url_rule('/api/plugin/zoom/webhook', 'webhook', RHWebhook, methods=('POST',)) diff --git a/vc_zoom/indico_vc_zoom/cli.py b/vc_zoom/indico_vc_zoom/cli.py new file mode 100644 index 0000000..fe99cfd --- /dev/null +++ b/vc_zoom/indico_vc_zoom/cli.py @@ -0,0 +1,39 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import print_function, unicode_literals + +import click +from terminaltables import AsciiTable + +from indico.cli.core import cli_group +from indico.modules.vc.models.vc_rooms import VCRoom, VCRoomStatus + + +@cli_group(name='zoom') +def cli(): + """Manage the Zoom plugin.""" + + +@cli.command() +@click.option('--status', type=click.Choice(['deleted', 'created'])) +def rooms(status=None): + """Lists all Zoom rooms""" + + room_query = VCRoom.query.filter_by(type='zoom') + table_data = [['ID', 'Name', 'Status', 'Zoom ID']] + + if status: + room_query = room_query.filter(VCRoom.status == VCRoomStatus.get(status)) + + for room in room_query: + table_data.append([unicode(room.id), room.name, room.status.name, unicode(room.data['zoom_id'])]) + + table = AsciiTable(table_data) + for col in (0, 3, 4): + table.justify_columns[col] = 'right' + print(table.table) diff --git a/vc_zoom/indico_vc_zoom/client/index.js b/vc_zoom/indico_vc_zoom/client/index.js new file mode 100644 index 0000000..d06eb04 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/client/index.js @@ -0,0 +1,29 @@ +// This file is part of the Indico plugins. +// Copyright (C) 2020 - 2021 CERN and ENEA +// +// The Indico plugins are free software; you can redistribute +// them and/or modify them under the terms of the MIT License; +// see the LICENSE file for more details. + +import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; + +document.addEventListener('DOMContentLoaded', async () => { + $('.vc-toolbar').dropdown({ + positioning: { + level1: {my: 'right top', at: 'right bottom', offset: '0px 0px'}, + }, + }); + + document.querySelectorAll('.vc-toolbar .action-make-host').forEach(elem => { + elem.addEventListener('click', async () => { + const killProgress = IndicoUI.Dialogs.Util.progress(); + try { + await indicoAxios.post(elem.dataset.href); + window.location.reload(); + } catch (error) { + handleAxiosError(error); + killProgress(); + } + }) + }) +}); diff --git a/vc_zoom/indico_vc_zoom/controllers.py b/vc_zoom/indico_vc_zoom/controllers.py new file mode 100644 index 0000000..86fddce --- /dev/null +++ b/vc_zoom/indico_vc_zoom/controllers.py @@ -0,0 +1,69 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from flask import flash, jsonify, request, session +from flask_pluginengine import current_plugin +from sqlalchemy.orm.attributes import flag_modified +from webargs import fields +from webargs.flaskparser import use_kwargs +from werkzeug.exceptions import Forbidden + +from indico.core.db import db +from indico.modules.vc.controllers import RHVCSystemEventBase +from indico.modules.vc.exceptions import VCRoomError +from indico.modules.vc.models.vc_rooms import VCRoom +from indico.util.i18n import _ +from indico.web.rh import RH + + +class RHRoomAlternativeHost(RHVCSystemEventBase): + def _process(self): + new_identifier = session.user.identifier + if new_identifier == self.vc_room.data['host'] or new_identifier in self.vc_room.data['alternative_hosts']: + flash(_('You were already an (alternative) host of this meeting'), 'warning') + return jsonify(success=False) + + self.vc_room.data['alternative_hosts'].append(new_identifier) + flag_modified(self.vc_room, 'data') + try: + self.plugin.update_room(self.vc_room, self.event) + except VCRoomError: + db.session.rollback() + raise + else: + flash(_("You are now an alternative host of room '{room}'").format(room=self.vc_room.name), 'success') + return jsonify(success=True) + + +class RHWebhook(RH): + CSRF_ENABLED = False + + def _check_access(self): + token = request.headers.get('Authorization') + expected_token = current_plugin.settings.get('webhook_token') + if not expected_token or not token or token != expected_token: + raise Forbidden + + @use_kwargs({ + 'event': fields.String(), + 'payload': fields.Dict() + }) + def _process(self, event, payload): + meeting_id = payload['object']['id'] + vc_room = VCRoom.query.filter(VCRoom.data.contains({'zoom_id': meeting_id})).first() + + if not vc_room: + # This usually happens when a room wasn't created via indico + current_plugin.logger.debug('Action for unhandled Zoom room: %s', meeting_id) + return + + if event in {'meeting.updated', 'webinar.updated', 'meeting.deleted', 'webinar.deleted'}: + current_plugin.refresh_room(vc_room, None) + else: + current_plugin.logger.warning('Unhandled Zoom webhook payload: %s', event) diff --git a/vc_zoom/indico_vc_zoom/forms.py b/vc_zoom/indico_vc_zoom/forms.py new file mode 100644 index 0000000..75a0289 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/forms.py @@ -0,0 +1,131 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from flask import session +from flask_pluginengine import current_plugin +from wtforms.fields.core import BooleanField, StringField +from wtforms.fields.simple import TextAreaField +from wtforms.validators import DataRequired, ValidationError + +from indico.modules.vc.forms import VCRoomAttachFormBase, VCRoomFormBase +from indico.util.user import principal_from_identifier +from indico.web.forms.base import generated_data +from indico.web.forms.fields import IndicoRadioField, PrincipalField +from indico.web.forms.util import inject_validators +from indico.web.forms.validators import HiddenUnless, IndicoRegexp +from indico.web.forms.widgets import SwitchWidget + +from indico_vc_zoom import _ +from indico_vc_zoom.util import find_enterprise_email + + +class VCRoomAttachForm(VCRoomAttachFormBase): + password_visibility = IndicoRadioField(_('Passcode visibility'), + description=_("Who should be able to know this meeting's passcode"), + orientation='horizontal', + choices=[ + ('everyone', _('Everyone')), + ('logged_in', _('Logged-in users')), + ('registered', _('Registered participants')), + ('no_one', _('No one'))]) + + +class VCRoomForm(VCRoomFormBase): + """Contains all information concerning a Zoom booking.""" + + advanced_fields = {'mute_audio', 'mute_host_video', 'mute_participant_video'} | VCRoomFormBase.advanced_fields + + skip_fields = advanced_fields | VCRoomFormBase.conditional_fields + + meeting_type = IndicoRadioField(_('Meeting Type'), + description=_('The type of Zoom meeting to be created'), + orientation='horizontal', + choices=[ + ('regular', _('Regular Meeting')), + ('webinar', _('Webinar'))]) + + host_choice = IndicoRadioField(_('Meeting Host'), [DataRequired()], + choices=[('myself', _('Myself')), ('someone_else', _('Someone else'))]) + + host_user = PrincipalField(_('User'), + [HiddenUnless('host_choice', 'someone_else'), DataRequired()]) + + password = StringField(_('Passcode'), + [DataRequired(), IndicoRegexp(r'^\d{8,10}$')], + description=_('Meeting passcode (8-10 digits)')) + + password_visibility = IndicoRadioField(_('Passcode visibility'), + description=_("Who should be able to know this meeting's passcode"), + orientation='horizontal', + choices=[ + ('everyone', _('Everyone')), + ('logged_in', _('Logged-in users')), + ('registered', _('Registered participants')), + ('no_one', _('No one'))]) + + mute_audio = BooleanField(_('Mute audio'), + widget=SwitchWidget(), + description=_('Participants will join the VC room muted by default ')) + + mute_host_video = BooleanField(_('Mute video (host)'), + widget=SwitchWidget(), + description=_('The host will join the VC room with video disabled')) + + mute_participant_video = BooleanField(_('Mute video (participants)'), + widget=SwitchWidget(), + description=_('Participants will join the VC room with video disabled')) + + waiting_room = BooleanField(_('Waiting room'), + widget=SwitchWidget(), + description=_('Participants may be kept in a waiting room by the host')) + + description = TextAreaField(_('Description'), description=_('Optional description for this room')) + + def __init__(self, *args, **kwargs): + defaults = kwargs['obj'] + if defaults.host_user is None and defaults.host is not None: + host = principal_from_identifier(defaults.host) + defaults.host_choice = 'myself' if host == session.user else 'someone_else' + defaults.host_user = None if host == session.user else host + + allow_webinars = current_plugin.settings.get('allow_webinars') + + if allow_webinars: + for field_name in {'mute_audio', 'mute_participant_video', 'waiting_room'}: + inject_validators(self, field_name, [HiddenUnless('meeting_type', 'regular')]) + + super(VCRoomForm, self).__init__(*args, **kwargs) + + if not allow_webinars: + del self.meeting_type + + def validate_host_choice(self, field): + if field.data == 'myself': + self._check_zoom_user(session.user) + + def validate_host_user(self, field): + if self.host_choice.data == 'someone_else': + self._check_zoom_user(field.data) + + def _check_zoom_user(self, user): + if find_enterprise_email(user) is None: + raise ValidationError(_('This user has no Zoom account')) + + def validate_name(self, field): + # Duplicate names are fine on Zoom + pass + + @generated_data + def host(self): + if self.host_choice is None: + return None + elif self.host_choice.data == 'myself': + return session.user.identifier + else: + return self.host_user.data.identifier if self.host_user.data else None diff --git a/vc_zoom/indico_vc_zoom/notifications.py b/vc_zoom/indico_vc_zoom/notifications.py new file mode 100644 index 0000000..0e58d85 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/notifications.py @@ -0,0 +1,29 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from indico.core.notifications import make_email, send_email +from indico.util.user import principal_from_identifier +from indico.web.flask.templating import get_template_module + + +def notify_host_start_url(vc_room): + from indico_vc_zoom.plugin import ZoomPlugin + + user = principal_from_identifier(vc_room.data['host']) + to_list = {user.email} + + template_module = get_template_module( + 'vc_zoom:emails/notify_start_url.html', + plugin=ZoomPlugin.instance, + vc_room=vc_room, + user=user + ) + + email = make_email(to_list, template=template_module, html=True) + send_email(email) diff --git a/vc_zoom/indico_vc_zoom/plugin.py b/vc_zoom/indico_vc_zoom/plugin.py new file mode 100644 index 0000000..b3f17d2 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/plugin.py @@ -0,0 +1,501 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from flask import flash, session +from markupsafe import escape +from requests.exceptions import HTTPError +from sqlalchemy.orm.attributes import flag_modified +from wtforms.fields import TextAreaField +from wtforms.fields.core import BooleanField +from wtforms.fields.simple import StringField +from wtforms.validators import DataRequired, ValidationError + +from indico.core import signals +from indico.core.auth import multipass +from indico.core.plugins import IndicoPlugin, render_plugin_template, url_for_plugin +from indico.modules.events.views import WPSimpleEventDisplay +from indico.modules.vc import VCPluginMixin, VCPluginSettingsFormBase +from indico.modules.vc.exceptions import VCRoomError +from indico.modules.vc.models.vc_rooms import VCRoom +from indico.modules.vc.views import WPVCEventPage, WPVCManageEvent +from indico.util.user import principal_from_identifier +from indico.web.forms.fields import IndicoEnumSelectField, IndicoPasswordField, TextListField +from indico.web.forms.validators import HiddenUnless +from indico.web.forms.widgets import CKEditorWidget, SwitchWidget + +from indico_vc_zoom import _ +from indico_vc_zoom.api import ZoomIndicoClient +from indico_vc_zoom.blueprint import blueprint +from indico_vc_zoom.cli import cli +from indico_vc_zoom.forms import VCRoomAttachForm, VCRoomForm +from indico_vc_zoom.notifications import notify_host_start_url +from indico_vc_zoom.util import (UserLookupMode, ZoomMeetingType, fetch_zoom_meeting, find_enterprise_email, + gen_random_password, get_alt_host_emails, get_schedule_args, get_url_data_args, + process_alternative_hosts, update_zoom_meeting) + + +class PluginSettingsForm(VCPluginSettingsFormBase): + _fieldsets = [ + ('API Credentials', ['api_key', 'api_secret', 'webhook_token']), + ('Zoom Account', ['user_lookup_mode', 'email_domains', 'authenticators', 'enterprise_domain', + 'allow_webinars']), + ('Room Settings', ['mute_audio', 'mute_host_video', 'mute_participant_video', 'join_before_host', + 'waiting_room']), + ('Notifications', ['creation_email_footer', 'send_host_url']) + ] + + api_key = StringField(_('API Key'), [DataRequired()]) + + api_secret = IndicoPasswordField(_('API Secret'), [DataRequired()], toggle=True) + + webhook_token = IndicoPasswordField(_('Webhook Token'), toggle=True, + description=_("Specify Zoom's webhook token if you want live updates")) + + user_lookup_mode = IndicoEnumSelectField(_('User lookup mode'), [DataRequired()], enum=UserLookupMode, + description=_('Specify how Indico should look up the zoom user that ' + 'corresponds to an Indico user.')) + + email_domains = TextListField(_('E-mail domains'), + [HiddenUnless('user_lookup_mode', UserLookupMode.email_domains), DataRequired()], + description=_('List of e-mail domains which can use the Zoom API. Indico attempts ' + 'to find Zoom accounts using all email addresses of a user which use ' + 'those domains.')) + + authenticators = TextListField(_('Indico identity providers'), + [HiddenUnless('user_lookup_mode', UserLookupMode.authenticators), DataRequired()], + description=_('Identity providers from which to get usernames. ' + 'Indico queries those providers using the email addresses of the user ' + 'and attempts to find Zoom accounts having an email address with the ' + 'format username@enterprise-domain.')) + + enterprise_domain = StringField(_('Enterprise domain'), + [HiddenUnless('user_lookup_mode', UserLookupMode.authenticators), DataRequired()], + description=_('The domain name used together with the usernames from the Indico ' + 'identity provider')) + + allow_webinars = BooleanField(_('Allow Webinars (Experimental)'), + widget=SwitchWidget(), + description=_('Allow webinars to be created through Indico. Use at your own risk.')) + + mute_audio = BooleanField(_('Mute audio'), + widget=SwitchWidget(), + description=_('Participants will join the VC room muted by default ')) + + mute_host_video = BooleanField(_('Mute video (host)'), + widget=SwitchWidget(), + description=_('The host will join the VC room with video disabled')) + + mute_participant_video = BooleanField(_('Mute video (participants)'), + widget=SwitchWidget(), + description=_('Participants will join the VC room with video disabled')) + + join_before_host = BooleanField(_('Join Before Host'), + widget=SwitchWidget(), + description=_('Allow participants to join the meeting before the host starts the ' + 'meeting. Only used for scheduled or recurring meetings.')) + + waiting_room = BooleanField(_('Waiting room'), + widget=SwitchWidget(), + description=_('Participants may be kept in a waiting room by the host')) + + creation_email_footer = TextAreaField(_('Creation email footer'), widget=CKEditorWidget(), + description=_('Footer to append to emails sent upon creation of a VC room')) + + send_host_url = BooleanField(_('Send host URL'), + widget=SwitchWidget(), + description=_('Whether to send an e-mail with the Host URL to the meeting host upon ' + 'creation of a meeting')) + + def validate_authenticators(self, field): + invalid = set(field.data) - set(multipass.identity_providers) + if invalid: + raise ValidationError(_('Invalid identity providers: {}').format(escape(', '.join(invalid)))) + + +class ZoomPlugin(VCPluginMixin, IndicoPlugin): + """Zoom + + Zoom Plugin for Indico.""" + + configurable = True + settings_form = PluginSettingsForm + vc_room_form = VCRoomForm + vc_room_attach_form = VCRoomAttachForm + friendly_name = 'Zoom' + default_settings = dict(VCPluginMixin.default_settings, **{ + 'api_key': '', + 'api_secret': '', + 'webhook_token': '', + 'user_lookup_mode': UserLookupMode.email_domains, + 'email_domains': [], + 'authenticators': [], + 'enterprise_domain': '', + 'allow_webinars': False, + 'mute_host_video': True, + 'mute_audio': True, + 'mute_participant_video': True, + 'join_before_host': True, + 'waiting_room': False, + 'creation_email_footer': None, + 'send_host_url': False + }) + + def init(self): + super(ZoomPlugin, self).init() + self.connect(signals.plugin.cli, self._extend_indico_cli) + self.connect(signals.event.times_changed, self._times_changed) + self.template_hook('event-vc-room-list-item-labels', self._render_vc_room_labels) + self.inject_bundle('main.js', WPSimpleEventDisplay) + self.inject_bundle('main.js', WPVCEventPage) + self.inject_bundle('main.js', WPVCManageEvent) + + @property + def logo_url(self): + return url_for_plugin(self.name + '.static', filename='images/zoom_logo.png') + + @property + def icon_url(self): + return url_for_plugin(self.name + '.static', filename='images/zoom_logo.png') + + def create_form(self, event, existing_vc_room=None, existing_event_vc_room=None): + """Override the default room form creation mechanism.""" + form = super(ZoomPlugin, self).create_form( + event, + existing_vc_room=existing_vc_room, + existing_event_vc_room=existing_event_vc_room + ) + + if existing_vc_room: + form.host_choice.render_kw = {'disabled': True} + form.host_user.render_kw = {'disabled': True} + if self.settings.get('allow_webinars'): + # if we're editing a VC room, we will not allow the meeting type to be changed + form.meeting_type.render_kw = {'disabled': True} + + if form.data['meeting_type'] == 'webinar': + # webinar hosts cannot be changed through the API + form.host_choice.render_kw = {'disabled': True} + form.host_user.render_kw = {'disabled': True} + elif not form.is_submitted(): + form.password.data = gen_random_password() + return form + + def get_extra_delete_msg(self, vc_room, event_vc_room): + host = principal_from_identifier(vc_room.data['host']) + if host == session.user or len(vc_room.events) <= 1: + return '' + return render_plugin_template('vc_zoom:extra_delete_msg.html', host=host.full_name) + + def _extend_indico_cli(self, sender, **kwargs): + return cli + + def update_data_association(self, event, vc_room, room_assoc, data): + # XXX: This feels slightly hacky. Maybe we should change the API on the core? + association_is_new = room_assoc.vc_room is None + old_link = room_assoc.link_object + + # in a new room, `meeting_type` comes in `data`, otherwise it's already in the VCRoom + is_webinar = data.get('meeting_type', vc_room.data and vc_room.data.get('meeting_type')) == 'webinar' + + super(ZoomPlugin, self).update_data_association(event, vc_room, room_assoc, data) + + if vc_room.data: + # this is not a new room + if association_is_new: + # this means we are updating an existing meeting with a new vc_room-event association + update_zoom_meeting(vc_room.data['zoom_id'], { + 'start_time': None, + 'duration': None, + 'type': ( + ZoomMeetingType.recurring_webinar_no_time + if is_webinar + else ZoomMeetingType.recurring_meeting_no_time + ) + }) + elif room_assoc.link_object != old_link: + # the booking should now be linked to something else + new_schedule_args = get_schedule_args(room_assoc.link_object) + meeting = fetch_zoom_meeting(vc_room) + current_schedule_args = {k: meeting[k] for k in {'start_time', 'duration'} if k in meeting} + + # check whether the start time / duration of the scheduled meeting differs + if new_schedule_args != current_schedule_args: + update_zoom_meeting(vc_room.data['zoom_id'], new_schedule_args) + + room_assoc.data['password_visibility'] = data.pop('password_visibility') + flag_modified(room_assoc, 'data') + + def update_data_vc_room(self, vc_room, data, is_new=False): + super(ZoomPlugin, self).update_data_vc_room(vc_room, data) + fields = {'description', 'password'} + + # we may end up not getting a meeting_type from the form + # (i.e. webinars are disabled) + data.setdefault('meeting_type', 'regular' if is_new else vc_room.data['meeting_type']) + + if data['meeting_type'] == 'webinar': + fields |= {'mute_host_video'} + if is_new: + fields |= {'host', 'meeting_type'} + else: + fields |= { + 'meeting_type', 'host', 'mute_audio', 'mute_participant_video', 'mute_host_video', 'join_before_host', + 'waiting_room' + } + + for key in fields: + if key in data: + vc_room.data[key] = data.pop(key) + + flag_modified(vc_room, 'data') + + def create_room(self, vc_room, event): + """Create a new Zoom room for an event, given a VC room. + + In order to create the Zoom room, the function will try to get + a valid e-mail address for the user in question, which can be + use with the Zoom API. + + :param vc_room: the VC room from which to create the Zoom room + :param event: the event to the Zoom room will be attached + """ + client = ZoomIndicoClient() + host = principal_from_identifier(vc_room.data['host']) + host_email = find_enterprise_email(host) + + # get the object that this booking is linked to + vc_room_assoc = vc_room.events[0] + link_obj = vc_room_assoc.link_object + is_webinar = vc_room.data.setdefault('meeting_type', 'regular') == 'webinar' + scheduling_args = get_schedule_args(link_obj) if link_obj.start_dt else {} + + try: + settings = { + 'host_video': not vc_room.data['mute_host_video'], + } + + kwargs = {} + if is_webinar: + kwargs['type'] = (ZoomMeetingType.webinar + if scheduling_args + else ZoomMeetingType.recurring_webinar_no_time) + settings['alternative_hosts'] = host_email + else: + kwargs = { + 'type': ( + ZoomMeetingType.scheduled_meeting + if scheduling_args + else ZoomMeetingType.recurring_meeting_no_time + ), + 'schedule_for': host_email + } + settings.update({ + 'mute_upon_entry': vc_room.data['mute_audio'], + 'participant_video': not vc_room.data['mute_participant_video'], + 'waiting_room': vc_room.data['waiting_room'], + 'join_before_host': self.settings.get('join_before_host'), + }) + + kwargs.update({ + 'topic': vc_room.name, + 'agenda': vc_room.data['description'], + 'password': vc_room.data['password'], + 'timezone': event.timezone, + 'settings': settings + }) + kwargs.update(scheduling_args) + if is_webinar: + meeting_obj = client.create_webinar(host_email, **kwargs) + else: + meeting_obj = client.create_meeting(host_email, **kwargs) + except HTTPError as e: + self.logger.exception('Error creating Zoom Room: %s', e.response.content) + raise VCRoomError(_('Could not create the room in Zoom. Please contact support if the error persists')) + + vc_room.data.update({ + 'zoom_id': unicode(meeting_obj['id']), + 'start_url': meeting_obj['start_url'], + 'host': host.identifier, + 'alternative_hosts': process_alternative_hosts(meeting_obj['settings'].get('alternative_hosts', '')) + }) + vc_room.data.update(get_url_data_args(meeting_obj['join_url'])) + flag_modified(vc_room, 'data') + + # e-mail Host URL to meeting host + if self.settings.get('send_host_url'): + notify_host_start_url(vc_room) + + def update_room(self, vc_room, event): + client = ZoomIndicoClient() + is_webinar = vc_room.data['meeting_type'] == 'webinar' + zoom_meeting = fetch_zoom_meeting(vc_room, client=client, is_webinar=is_webinar) + changes = {} + + if vc_room.name != zoom_meeting['topic']: + changes['topic'] = vc_room.name + + if vc_room.data['description'] != zoom_meeting.get('agenda', ''): + changes['agenda'] = vc_room.data['description'] + + if vc_room.data['password'] != zoom_meeting['password']: + changes['password'] = vc_room.data['password'] + + zoom_meeting_settings = zoom_meeting['settings'] + if vc_room.data['mute_host_video'] == zoom_meeting_settings['host_video']: + changes.setdefault('settings', {})['host_video'] = not vc_room.data['mute_host_video'] + + alternative_hosts = process_alternative_hosts(zoom_meeting_settings.get('alternative_hosts', '')) + if vc_room.data['alternative_hosts'] != alternative_hosts: + new_alt_host_emails = get_alt_host_emails(vc_room.data['alternative_hosts']) + changes.setdefault('settings', {})['alternative_hosts'] = ','.join(new_alt_host_emails) + + if not is_webinar: + if vc_room.data['mute_audio'] != zoom_meeting_settings['mute_upon_entry']: + changes.setdefault('settings', {})['mute_upon_entry'] = vc_room.data['mute_audio'] + if vc_room.data['mute_participant_video'] == zoom_meeting_settings['participant_video']: + changes.setdefault('settings', {})['participant_video'] = not vc_room.data['mute_participant_video'] + if vc_room.data['waiting_room'] != zoom_meeting_settings['waiting_room']: + changes.setdefault('settings', {})['waiting_room'] = vc_room.data['waiting_room'] + + if changes: + update_zoom_meeting(vc_room.data['zoom_id'], changes, is_webinar=is_webinar) + # always refresh meeting URL (it may have changed if password changed) + zoom_meeting = fetch_zoom_meeting(vc_room, client=client, is_webinar=is_webinar) + vc_room.data.update(get_url_data_args(zoom_meeting['join_url'])) + + def refresh_room(self, vc_room, event): + is_webinar = vc_room.data['meeting_type'] == 'webinar' + zoom_meeting = fetch_zoom_meeting(vc_room, is_webinar=is_webinar) + vc_room.name = zoom_meeting['topic'] + vc_room.data.update({ + 'description': zoom_meeting.get('agenda', ''), + 'zoom_id': zoom_meeting['id'], + 'password': zoom_meeting['password'], + 'mute_host_video': zoom_meeting['settings']['host_video'], + + # these options will be empty for webinars + 'mute_audio': zoom_meeting['settings'].get('mute_upon_entry'), + 'mute_participant_video': not zoom_meeting['settings'].get('participant_video'), + 'waiting_room': zoom_meeting['settings'].get('waiting_room'), + 'alternative_hosts': process_alternative_hosts(zoom_meeting['settings'].get('alternative_hosts')) + }) + vc_room.data.update(get_url_data_args(zoom_meeting['join_url'])) + flag_modified(vc_room, 'data') + + def delete_room(self, vc_room, event): + client = ZoomIndicoClient() + zoom_id = vc_room.data['zoom_id'] + is_webinar = vc_room.data['meeting_type'] == 'webinar' + try: + if is_webinar: + client.delete_webinar(zoom_id) + else: + client.delete_meeting(zoom_id) + except HTTPError as e: + # if there's a 404, there is no problem, since the room is supposed to be gone anyway + if e.response.status_code == 404: + flash(_("Room didn't existing in Zoom anymore"), 'warning') + elif e.response.status_code == 400: + # some sort of operational error on Zoom's side, deserves a specific error message + raise VCRoomError(_('Zoom Error: "{}"').format(e.response.json()['message'])) + else: + self.logger.error("Can't delete room") + raise VCRoomError(_('Problem deleting room')) + + def clone_room(self, old_event_vc_room, link_object): + vc_room = old_event_vc_room.vc_room + is_webinar = vc_room.data.get('meeting_type', 'regular') == 'webinar' + has_only_one_association = len({assoc.event_id for assoc in vc_room.events}) == 1 + new_assoc = super(ZoomPlugin, self).clone_room(old_event_vc_room, link_object) + + if has_only_one_association: + update_zoom_meeting(vc_room.data['zoom_id'], { + 'start_time': None, + 'duration': None, + 'type': ( + ZoomMeetingType.recurring_webinar_no_time + if is_webinar + else ZoomMeetingType.recurring_meeting_no_time + ) + }) + + return new_assoc + + def get_blueprints(self): + return blueprint + + def get_vc_room_form_defaults(self, event): + defaults = super(ZoomPlugin, self).get_vc_room_form_defaults(event) + defaults.update({ + 'meeting_type': 'regular' if self.settings.get('allow_webinars') else None, + 'mute_audio': self.settings.get('mute_audio'), + 'mute_host_video': self.settings.get('mute_host_video'), + 'mute_participant_video': self.settings.get('mute_participant_video'), + 'waiting_room': self.settings.get('waiting_room'), + 'host_choice': 'myself', + 'host_user': None, + 'password_visibility': 'logged_in' + }) + return defaults + + def get_vc_room_attach_form_defaults(self, event): + defaults = super(ZoomPlugin, self).get_vc_room_attach_form_defaults(event) + defaults['password_visibility'] = 'logged_in' + return defaults + + def can_manage_vc_room(self, user, room): + return ( + user == principal_from_identifier(room.data['host']) or + super(ZoomPlugin, self).can_manage_vc_room(user, room) + ) + + def _merge_users(self, target, source, **kwargs): + super(ZoomPlugin, self)._merge_users(target, source, **kwargs) + for room in VCRoom.query.filter( + VCRoom.type == self.service_name, VCRoom.data.contains({'host': source.identifier}) + ): + room.data['host'] = target.identifier + flag_modified(room, 'data') + for room in VCRoom.query.filter( + VCRoom.type == self.service_name, VCRoom.data.contains({'alternative_hosts': [source.identifier]}) + ): + room.data['alternative_hosts'].remove(source.identifier) + room.data['alternative_hosts'].append(target.identifier) + flag_modified(room, 'data') + + def get_notification_cc_list(self, action, vc_room, event): + return {principal_from_identifier(vc_room.data['host']).email} + + def _render_vc_room_labels(self, event, vc_room, **kwargs): + if vc_room.plugin != self: + return + return render_plugin_template('room_labels.html', vc_room=vc_room) + + def _times_changed(self, sender, obj, **kwargs): + from indico.modules.events.models.events import Event + from indico.modules.events.contributions.models.contributions import Contribution + from indico.modules.events.sessions.models.blocks import SessionBlock + + if not hasattr(obj, 'vc_room_associations'): + return + + if any(assoc.vc_room.type == 'zoom' and len(assoc.vc_room.events) == 1 for assoc in obj.vc_room_associations): + if sender == Event: + message = _('There are one or more scheduled Zoom meetings associated with this event which were not ' + 'automatically updated.') + elif sender == Contribution: + message = _('There are one or more scheduled Zoom meetings associated with the contribution "{}" which ' + ' were not automatically updated.').format(obj.title) + elif sender == SessionBlock: + message = _('There are one or more scheduled Zoom meetings associated with this session block which ' + 'were not automatically updated.') + else: + return + + flash(message, 'warning') diff --git a/vc_zoom/indico_vc_zoom/static/images/zoom_logo.png b/vc_zoom/indico_vc_zoom/static/images/zoom_logo.png new file mode 100644 index 0000000..c8347b4 Binary files /dev/null and b/vc_zoom/indico_vc_zoom/static/images/zoom_logo.png differ diff --git a/vc_zoom/indico_vc_zoom/templates/buttons.html b/vc_zoom/indico_vc_zoom/templates/buttons.html new file mode 100644 index 0000000..b0d5451 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/buttons.html @@ -0,0 +1,62 @@ +{% macro render_make_me_owner(event, vc_room, event_vc_room, extra_classes='') %} + {% if session.user.identifier != vc_room.data['host'] + and session.user.identifier not in vc_room.data['alternative_hosts'] + and event.can_manage(session.user) %} + + + {% endif %} +{% endmacro %} + +{% macro render_join_button(vc_room, event_vc_room, extra_classes='') %} + {% if event_vc_room.data.password_visibility == 'everyone' or + event_vc_room.event.can_manage(session.user) or + (session.user and event_vc_room.data.password_visibility == 'logged_in') or + (session.user and event_vc_room.data.password_visibility == 'registered' and event_vc_room.event.is_user_registered(session.user)) %} + + {% trans %}Join{% endtrans %} + + {% elif event_vc_room.data.password_visibility == 'no_one' %} + + {% trans %}Join{% endtrans %} + + {% elif event_vc_room.data.password_visibility == 'registered' %} + {% if session.user %} + {% if event_vc_room.event.type == 'conference' %} + + {% trans %}Please register{% endtrans %} + + {% else %} + + {% trans %}Registration required{% endtrans %} + + {% endif %} + {% else %} + + {% trans %}Please log in and register{% endtrans %} + + {% endif %} + {% else %} + + {% trans %}Please log in{% endtrans %} + + {% endif %} +{% endmacro %} diff --git a/vc_zoom/indico_vc_zoom/templates/emails/created.html b/vc_zoom/indico_vc_zoom/templates/emails/created.html new file mode 100644 index 0000000..1dbafeb --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/emails/created.html @@ -0,0 +1,20 @@ +{% extends 'vc/emails/created.html' %} + +{% block plugin_specific_info %} +
  • + Host: + {{ (vc_room.data.host|decodeprincipal).full_name }} +
  • +
  • + Zoom URL: + {{ vc_room.data.url }} +
  • + +{% endblock %} + +{% block custom_footer %} + {% if plugin.settings.get('creation_email_footer') %} +
    + {{ plugin.settings.get('creation_email_footer') | sanitize_html }} + {% endif %} +{% endblock %} diff --git a/vc_zoom/indico_vc_zoom/templates/emails/notify_start_url.html b/vc_zoom/indico_vc_zoom/templates/emails/notify_start_url.html new file mode 100644 index 0000000..320a3af --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/emails/notify_start_url.html @@ -0,0 +1,20 @@ +{% extends 'emails/base.html' %} + +{% block subject -%} + [{{ plugin.friendly_name }}] Host URL - {{ vc_room.name }} +{%- endblock %} + +{% block header -%} +

    + ATTENTION: + You should not share this URL with anyone, since it will allow them to become meeting hosts! +

    + + + {% block custom_footer %}{% endblock %} +{%- endblock %} diff --git a/vc_zoom/indico_vc_zoom/templates/emails/remote_deleted.html b/vc_zoom/indico_vc_zoom/templates/emails/remote_deleted.html new file mode 100644 index 0000000..31904df --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/emails/remote_deleted.html @@ -0,0 +1,16 @@ +{% extends 'emails/base.html' %} + +{% block subject -%} + [{{ plugin.friendly_name }}] Room deleted from server: {{ vc_room.name }} +{%- endblock %} + +{% block header -%} +

    + The Zoom room "{{ vc_room.name }}" has been deleted from the Zoom server since it has not been used by any recent event. +

    +

    + You won't be able to attach it to any future events. If you need to do so, please create a new room. +

    +{% block custom_footer %}{% endblock %} + +{%- endblock %} diff --git a/vc_zoom/indico_vc_zoom/templates/event_buttons.html b/vc_zoom/indico_vc_zoom/templates/event_buttons.html new file mode 100644 index 0000000..401ef93 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/event_buttons.html @@ -0,0 +1,7 @@ +{% extends 'vc/event_buttons.html' %} +{% from 'vc_zoom:buttons.html' import render_join_button, render_make_me_owner %} + +{% block buttons %} + {{ render_join_button(vc_room, event_vc_room, "i-button-small event-service-right-button join-button") }} + {{ render_make_me_owner(event, vc_room, event_vc_room, extra_classes="i-button-small") }} +{% endblock %} diff --git a/vc_zoom/indico_vc_zoom/templates/extra_delete_msg.html b/vc_zoom/indico_vc_zoom/templates/extra_delete_msg.html new file mode 100644 index 0000000..c936287 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/extra_delete_msg.html @@ -0,0 +1,7 @@ +

    + {% trans %} + The host of this Zoom meeting is {{ host }}. In case you want + to delete this Zoom meeting from all events, please make sure that they do not + use it for other events that may still need it! + {% endtrans %} +

    diff --git a/vc_zoom/indico_vc_zoom/templates/info_box.html b/vc_zoom/indico_vc_zoom/templates/info_box.html new file mode 100644 index 0000000..08ed536 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/info_box.html @@ -0,0 +1,40 @@ +{% from '_clipboard_input.html' import clipboard_input %} +{% set host = vc_room.data.host %} +{% set alt_hosts = vc_room.data.alternative_hosts %} +
    +
    {% trans %}Zoom Meeting ID{% endtrans %}
    +
    {{ vc_room.data.zoom_id }}
    + {% if vc_room.data.description %} +
    {% trans %}Description{% endtrans %}
    +
    {{ vc_room.data.description }}
    + {% endif %} + {% if host %} +
    {% trans %}Host{% endtrans %}
    +
    {{ (host|decodeprincipal).full_name }}
    + {% endif %} + {% if alt_hosts %} +
    + {% trans count=alt_hosts|length -%} + Alternative host + {%- pluralize -%} + Alternative hosts + {%- endtrans %} +
    +
    + {{ alt_hosts | map('decodeprincipal') | map(attribute='full_name') | join(', ') }} +
    + {% endif %} + {% if event_vc_room.data.password_visibility == 'everyone' or + event_vc_room.event.can_manage(session.user) or + (session.user and event_vc_room.data.password_visibility == 'logged_in') or + (session.user and event_vc_room.data.password_visibility == 'registered' and event_vc_room.event.is_user_registered(session.user)) %} +
    {% trans %}Passcode{% endtrans %}
    +
    {{ vc_room.data.password }}
    + {% endif %} + {% if event_vc_room.data.show_autojoin %} +
    {% trans %}Zoom URL{% endtrans %}
    +
    + {{ clipboard_input(vc_room.data.url, name="vc-room-url-%s"|format(event_vc_room.id)) }} +
    + {% endif %} +
    diff --git a/vc_zoom/indico_vc_zoom/templates/manage_event_info_box.html b/vc_zoom/indico_vc_zoom/templates/manage_event_info_box.html new file mode 100644 index 0000000..2754718 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/manage_event_info_box.html @@ -0,0 +1,53 @@ +{% from '_password.html' import password %} +{% from '_clipboard_input.html' import clipboard_input %} +{% set host = vc_room.data.host %} +{% set alt_hosts = vc_room.data.alternative_hosts %} +
    +
    {% trans %}Zoom Meeting ID{% endtrans %}
    +
    {{ vc_room.data.zoom_id }}
    + {% if vc_room.data.description %} +
    {% trans %}Description{% endtrans %}
    +
    {{ vc_room.data.description }}
    + {% endif %} +
    {% trans %}Host{% endtrans %}
    +
    + {{ (host|decodeprincipal).full_name }} +
    + {% if alt_hosts %} +
    + {% trans count=alt_hosts|length -%} + Alternative host + {%- pluralize -%} + Alternative hosts + {%- endtrans %} +
    +
    + {{ alt_hosts | map('decodeprincipal') | map(attribute='full_name') | join(', ') }} +
    + {% endif %} +
    {% trans %}Linked to{% endtrans %}
    +
    + {% set obj = event_vc_room.link_object %} + {% if obj is none %} + (missing {{ event_vc_room.link_type.name }}) + {% elif event_vc_room.link_type.name == 'event' %} + {% trans %}the whole event{% endtrans %} + {% elif event_vc_room.link_type.name == 'contribution' %} + {% trans %}Contribution{% endtrans %}: {{ obj.title }} + {% elif event_vc_room.link_type.name == 'block' %} + {% trans %}Session{% endtrans %}: {{ obj.full_title }} + {% endif %} +
    +
    {% trans %}Passcode{% endtrans %}
    +
    {{ vc_room.data.password }}
    +
    {% trans %}Zoom URL{% endtrans %}
    +
    + {{ clipboard_input(vc_room.data.url, name="vc-room-url") }} +
    +
    {% trans %}Created on{% endtrans %}
    +
    {{ vc_room.created_dt | format_datetime(timezone=event.tzinfo) }}
    + {% if vc_room.modified_dt %} +
    {% trans %}Modified on{% endtrans %}
    +
    {{ vc_room.modified_dt | format_datetime(timezone=event.tzinfo) }}
    + {% endif %} +
    diff --git a/vc_zoom/indico_vc_zoom/templates/management_buttons.html b/vc_zoom/indico_vc_zoom/templates/management_buttons.html new file mode 100644 index 0000000..2ec6e31 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/management_buttons.html @@ -0,0 +1,7 @@ +{% extends 'vc/management_buttons.html' %} +{% from 'vc_zoom:buttons.html' import render_join_button, render_make_me_owner %} + +{% block buttons %} + {{ render_join_button(vc_room, event_vc_room, extra_classes='icon-play') }} + {{ render_make_me_owner(event_vc_room.event, vc_room, event_vc_room) }} +{% endblock %} diff --git a/vc_zoom/indico_vc_zoom/templates/room_labels.html b/vc_zoom/indico_vc_zoom/templates/room_labels.html new file mode 100644 index 0000000..36d0865 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/room_labels.html @@ -0,0 +1,6 @@ +{% if vc_room.data.meeting_type == 'webinar' %} +
    + {% trans %}Webinar{% endtrans %} +
    +{% endif %} diff --git a/vc_zoom/indico_vc_zoom/templates/vc_room_timetable_buttons.html b/vc_zoom/indico_vc_zoom/templates/vc_room_timetable_buttons.html new file mode 100644 index 0000000..67c79c1 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/templates/vc_room_timetable_buttons.html @@ -0,0 +1,8 @@ +{% extends 'vc/vc_room_timetable_buttons.html' %} +{% from 'vc_zoom:buttons.html' import render_join_button, render_make_me_owner %} +{% set vc_room = event_vc_room.vc_room %} + +{% block buttons %} + {{ render_join_button(vc_room, event_vc_room, "i-button-small event-service-right-button join-button") }} + {{ render_make_me_owner(event, vc_room, event_vc_room, extra_classes="i-button-small") }} +{% endblock %} diff --git a/vc_zoom/indico_vc_zoom/translations/fr_FR/LC_MESSAGES/messages.po b/vc_zoom/indico_vc_zoom/translations/fr_FR/LC_MESSAGES/messages.po new file mode 100644 index 0000000..65fdf03 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/translations/fr_FR/LC_MESSAGES/messages.po @@ -0,0 +1,441 @@ +# Translations template for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +# Translators: +# Thomas Baron , 2021 +# Adrian Mönnich, 2021 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-01-05 13:20+0100\n" +"PO-Revision-Date: 2020-11-25 15:06+0000\n" +"Last-Translator: Adrian Mönnich, 2021\n" +"Language-Team: French (France) (https://www.transifex.com/indico/teams/6478/fr_FR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.8.0\n" +"Language: fr_FR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: indico_vc_zoom/controllers.py:29 +msgid "You were already an (alternative) host of this meeting" +msgstr "Vous étiez déjà un hôte (alternatif) de cette réunion" + +#: indico_vc_zoom/controllers.py:40 +msgid "You are now an alternative host of room '{room}'" +msgstr "Vous êtes maintenant un hôte alternatif de la réunion '{room}'" + +#: indico_vc_zoom/forms.py:29 indico_vc_zoom/forms.py:63 +msgid "Passcode visibility" +msgstr "Visibilité du code secret" + +#: indico_vc_zoom/forms.py:30 indico_vc_zoom/forms.py:64 +msgid "Who should be able to know this meeting's passcode" +msgstr "Qui peut connaitre le code secret de cette réunion" + +#: indico_vc_zoom/forms.py:33 indico_vc_zoom/forms.py:67 +msgid "Everyone" +msgstr "Tout le monde" + +#: indico_vc_zoom/forms.py:34 indico_vc_zoom/forms.py:68 +msgid "Logged-in users" +msgstr "Utilisateurs connectés" + +#: indico_vc_zoom/forms.py:35 indico_vc_zoom/forms.py:69 +msgid "Registered participants" +msgstr "Participants inscrits" + +#: indico_vc_zoom/forms.py:36 indico_vc_zoom/forms.py:70 +msgid "No one" +msgstr "Personne" + +#: indico_vc_zoom/forms.py:46 +msgid "Meeting Type" +msgstr "Type de réunion" + +#: indico_vc_zoom/forms.py:47 +msgid "The type of Zoom meeting to be created" +msgstr "Le type de réunion Zoom à créer" + +#: indico_vc_zoom/forms.py:50 +msgid "Regular Meeting" +msgstr "Réunion normale" + +#: indico_vc_zoom/forms.py:51 indico_vc_zoom/templates/room_labels.html:4 +msgid "Webinar" +msgstr "Wébinaire" + +#: indico_vc_zoom/forms.py:53 +msgid "Meeting Host" +msgstr "Hôte de la réunion" + +#: indico_vc_zoom/forms.py:54 +msgid "Myself" +msgstr "Moi-même" + +#: indico_vc_zoom/forms.py:54 +msgid "Someone else" +msgstr "Quelqu'un d'autre" + +#: indico_vc_zoom/forms.py:56 +msgid "User" +msgstr "Utilisateur" + +#: indico_vc_zoom/forms.py:59 indico_vc_zoom/templates/info_box.html:31 +#: indico_vc_zoom/templates/manage_event_info_box.html:41 +msgid "Passcode" +msgstr "Code secret" + +#: indico_vc_zoom/forms.py:61 +msgid "Meeting passcode (8-10 digits)" +msgstr "Code secret de la réunion (8-10 chiffres)" + +#: indico_vc_zoom/forms.py:72 indico_vc_zoom/plugin.py:86 +msgid "Mute audio" +msgstr "Couper le micro" + +#: indico_vc_zoom/forms.py:74 indico_vc_zoom/plugin.py:88 +msgid "Participants will join the VC room muted by default " +msgstr "" +"Les participants rejoindront la réunion avec leur micro coupé par défaut" + +#: indico_vc_zoom/forms.py:76 indico_vc_zoom/plugin.py:90 +msgid "Mute video (host)" +msgstr "Couper la vidéo (hôte)" + +#: indico_vc_zoom/forms.py:78 indico_vc_zoom/plugin.py:92 +msgid "The host will join the VC room with video disabled" +msgstr "L'hôte rejoindra la réunion avec sa vidéo désactivée" + +#: indico_vc_zoom/forms.py:80 indico_vc_zoom/plugin.py:94 +msgid "Mute video (participants)" +msgstr "Couper la vidéo (participants)" + +#: indico_vc_zoom/forms.py:82 indico_vc_zoom/plugin.py:96 +msgid "Participants will join the VC room with video disabled" +msgstr "Les participants rejoindront la réunion avec leur vidéo coupée" + +#: indico_vc_zoom/forms.py:84 indico_vc_zoom/plugin.py:103 +msgid "Waiting room" +msgstr "Salle d'attente" + +#: indico_vc_zoom/forms.py:86 indico_vc_zoom/plugin.py:105 +msgid "Participants may be kept in a waiting room by the host" +msgstr "Les participants rejoindront une salle d'attente" + +#: indico_vc_zoom/forms.py:88 indico_vc_zoom/templates/info_box.html:8 +#: indico_vc_zoom/templates/manage_event_info_box.html:9 +msgid "Description" +msgstr "Description" + +#: indico_vc_zoom/forms.py:88 +msgid "Optional description for this room" +msgstr "Description optionnelle de cette réunion" + +#: indico_vc_zoom/forms.py:118 +msgid "This user has no Zoom account" +msgstr "Cet utilisateur n'a pas de compte Zoom" + +#: indico_vc_zoom/plugin.py:53 +msgid "API Key" +msgstr "Clé API" + +#: indico_vc_zoom/plugin.py:55 +msgid "API Secret" +msgstr "Secret de l'API" + +#: indico_vc_zoom/plugin.py:57 +msgid "Webhook Token" +msgstr "Jeton de webhook" + +#: indico_vc_zoom/plugin.py:58 +msgid "Specify Zoom's webhook token if you want live updates" +msgstr "" +"Spécifier le jeton de webhook Zoom si vous souhaitez des mises à jour en " +"direct" + +#: indico_vc_zoom/plugin.py:60 +msgid "User lookup mode" +msgstr "Mode de recherche d'utilisateur" + +#: indico_vc_zoom/plugin.py:61 +msgid "" +"Specify how Indico should look up the zoom user that corresponds to an " +"Indico user." +msgstr "" +"Veuillez spécifier comment Indico doit rechercher l'utilisateur Zoom qui " +"correspond à un utilisateur Indico." + +#: indico_vc_zoom/plugin.py:64 +msgid "E-mail domains" +msgstr "Domaines de courriel" + +#: indico_vc_zoom/plugin.py:66 +msgid "" +"List of e-mail domains which can use the Zoom API. Indico attempts to find " +"Zoom accounts using all email addresses of a user which use those domains." +msgstr "" +"Liste des domaines de courriel qui peuvent utiliser l'API Zoom. Indico " +"essaie de trouver des comptes Zoom en utilisant toutes les adresses de " +"courriel d'un utilisateur qui utilisent ces domaines." + +#: indico_vc_zoom/plugin.py:70 +msgid "Indico identity providers" +msgstr "Fournisseurs d'identité d'Indico" + +#: indico_vc_zoom/plugin.py:72 +msgid "" +"Identity providers from which to get usernames. Indico queries those " +"providers using the email addresses of the user and attempts to find Zoom " +"accounts having an email address with the format username@enterprise-domain." +msgstr "" +"Fournisseurs d'identité de qui récupérer les noms d'utilisateurs. Indico " +"interroge ces fournisseurs en utilisant les adresses de courriel de " +"l'utilisateur, et essaie de trouver des comptes Zoom ayant une adresse de " +"courriel de la forme nom-d-utilisateur@domaine-d-entreprise." + +#: indico_vc_zoom/plugin.py:77 +msgid "Enterprise domain" +msgstr "Domaine d'entreprise" + +#: indico_vc_zoom/plugin.py:79 +msgid "" +"The domain name used together with the usernames from the Indico identity " +"provider" +msgstr "" +"Le nom de domaine utilisé avec les noms d'utilisateurs du fournisseur " +"d'identité d'indico" + +#: indico_vc_zoom/plugin.py:82 +msgid "Allow Webinars (Experimental)" +msgstr "Autoriser les wébinaires (expérimental)" + +#: indico_vc_zoom/plugin.py:84 +msgid "Allow webinars to be created through Indico. Use at your own risk." +msgstr "" +"Autoriser les wébinaires à être créés via Indico. Utilisez à vos propres " +"risques." + +#: indico_vc_zoom/plugin.py:98 +msgid "Join Before Host" +msgstr "Rejoindre avant l'hôte" + +#: indico_vc_zoom/plugin.py:100 +msgid "" +"Allow participants to join the meeting before the host starts the meeting. " +"Only used for scheduled or recurring meetings." +msgstr "" +"Autoriser les participants à rejoindre la réunion avant que l'hôte démarre " +"la réunion. Utilisé uniquement pour les réunions programmées ou récurrentes." + +#: indico_vc_zoom/plugin.py:107 +msgid "Creation email footer" +msgstr "Pied de page du courriel de création" + +#: indico_vc_zoom/plugin.py:108 +msgid "Footer to append to emails sent upon creation of a VC room" +msgstr "" +"Pied de page à ajouter aux courriels envoyés lors de la création d'une " +"réunion" + +#: indico_vc_zoom/plugin.py:110 +msgid "Send host URL" +msgstr "Envoyer l'URL de l'hôte" + +#: indico_vc_zoom/plugin.py:112 +msgid "" +"Whether to send an e-mail with the Host URL to the meeting host upon " +"creation of a meeting" +msgstr "" +"Envoyer ou non un courriel contenant l'URL d'hôte à l'hôte de la réunion à " +"la création d'une réunion" + +#: indico_vc_zoom/plugin.py:118 +msgid "Invalid identity providers: {}" +msgstr "Fournisseurs d'identité invalides: {}" + +#: indico_vc_zoom/plugin.py:319 +msgid "" +"Could not create the room in Zoom. Please contact support if the error " +"persists" +msgstr "" +"Impossible de créer la réunion dans Zoom. Veuillez contacter le support si " +"l'erreur persiste" + +#: indico_vc_zoom/plugin.py:403 +msgid "Room didn't existing in Zoom anymore" +msgstr "La réunion n'existait plus dans Zoom" + +#: indico_vc_zoom/plugin.py:406 +msgid "Zoom Error: \"{}\"" +msgstr "Erreur Zoom: \"{}\"" + +#: indico_vc_zoom/plugin.py:409 +msgid "Problem deleting room" +msgstr "Erreur lors de la suppression de la salle" + +#: indico_vc_zoom/plugin.py:490 +msgid "" +"There are one or more scheduled Zoom meetings associated with this event " +"which were not automatically updated." +msgstr "" +"Il existe une ou plusieurs réunions Zoom associées à cet événement qui n'ont" +" pas été automatiquement mises à jour." + +#: indico_vc_zoom/plugin.py:493 +msgid "" +"There are one or more scheduled Zoom meetings associated with the " +"contribution \"{}\" which were not automatically updated." +msgstr "" +"Il y a une ou plusieurs réunion Zoom associées à la contribution \"{}\" qui " +"n'ont pas été automatiquement mises à jour." + +#: indico_vc_zoom/plugin.py:496 +msgid "" +"There are one or more scheduled Zoom meetings associated with this session " +"block which were not automatically updated." +msgstr "" +"Il existe une ou plusieurs réunions Zoom associées à ce bloc de session qui " +"n'ont pas été automatiquement mises à jour." + +#: indico_vc_zoom/util.py:42 +msgid "All emails" +msgstr "Toutes les adresses de courriel" + +#: indico_vc_zoom/util.py:43 +msgid "Email domains" +msgstr "Domaines de courriel" + +#: indico_vc_zoom/util.py:44 +msgid "Authenticators" +msgstr "Authentificateurs" + +#: indico_vc_zoom/util.py:124 +msgid "This room has been deleted from Zoom" +msgstr "Cette réunion a été effacée de Zoom" + +#: indico_vc_zoom/util.py:128 +msgid "" +"Problem fetching room from Zoom. Please contact support if the error " +"persists." +msgstr "" +"Erreur en récupérant la réunion Zoom. Veuillez contacter le support si cette" +" erreur persiste." + +#: indico_vc_zoom/util.py:147 +msgid "Can't update meeting. Please contact support if the error persists." +msgstr "" +"Impossible de mettre à jour la réunion. Veuillez contacter le support si " +"l'erreur persiste." + +#: indico_vc_zoom/templates/buttons.html:9 +msgid "You will become an alternative host of this Zoom meeting" +msgstr "Vous deviendrez un hôte alternatif de cette réunion Zoom" + +#: indico_vc_zoom/templates/buttons.html:12 +msgid "Make me alternative host" +msgstr "Faites de moi un hôte alternatif" + +#: indico_vc_zoom/templates/buttons.html:26 +#: indico_vc_zoom/templates/buttons.html:32 +msgid "Join" +msgstr "Rejoindre" + +#: indico_vc_zoom/templates/buttons.html:30 +msgid "You will need a passcode to join this Zoom meeting" +msgstr "Vous aurez besoin d'un code secret pour rejoindre cette réunion Zoom" + +#: indico_vc_zoom/templates/buttons.html:38 +#: indico_vc_zoom/templates/buttons.html:44 +#: indico_vc_zoom/templates/buttons.html:50 +msgid "This Zoom Meeting can only be seen by registered participants" +msgstr "Cette réunion Zoom ne peut être vue que par les participants inscrits" + +#: indico_vc_zoom/templates/buttons.html:40 +msgid "Please register" +msgstr "Veuillez vous inscrire" + +#: indico_vc_zoom/templates/buttons.html:45 +msgid "Registration required" +msgstr "Inscription nécessaire" + +#: indico_vc_zoom/templates/buttons.html:52 +msgid "Please log in and register" +msgstr "Veuillez vous connecter et vous inscrire" + +#: indico_vc_zoom/templates/buttons.html:57 +msgid "This Zoom Meeting can only be seen by logged in users" +msgstr "" +"Cette réunion Zoom ne peut être vue que par les utilisateurs connectés" + +#: indico_vc_zoom/templates/buttons.html:59 +msgid "Please log in" +msgstr "Veuillez vous connecter" + +#: indico_vc_zoom/templates/extra_delete_msg.html:2 +#, python-format +msgid "" +"The host of this Zoom meeting is %(host)s. In case you want" +" to delete this Zoom meeting from all events, please make sure that they do " +"not use it for other events that may still need it!" +msgstr "" +"L'hôte de cette réunion Zoom est %(host)s. Au cas où vous " +"voudriez supprimer cette réunion Zoom de tous les évènements, veuillez vous " +"assurer qu'ils ne l'utilisent pas pour d'autres événements qui pourraient " +"toujours en avoir besoin!" + +#: indico_vc_zoom/templates/info_box.html:5 +#: indico_vc_zoom/templates/manage_event_info_box.html:6 +msgid "Zoom Meeting ID" +msgstr "ID de réunion Zoom" + +#: indico_vc_zoom/templates/info_box.html:12 +#: indico_vc_zoom/templates/manage_event_info_box.html:12 +msgid "Host" +msgstr "Hôte" + +#: indico_vc_zoom/templates/info_box.html:17 +#: indico_vc_zoom/templates/manage_event_info_box.html:18 +msgid "Alternative host" +msgid_plural "Alternative hosts" +msgstr[0] "Hôte alternatif" +msgstr[1] "Hôtes alternatifs" + +#: indico_vc_zoom/templates/info_box.html:35 +#: indico_vc_zoom/templates/manage_event_info_box.html:43 +msgid "Zoom URL" +msgstr "URL Zoom" + +#: indico_vc_zoom/templates/manage_event_info_box.html:28 +msgid "Linked to" +msgstr "Liée à" + +#: indico_vc_zoom/templates/manage_event_info_box.html:34 +msgid "the whole event" +msgstr "l'événement entier" + +#: indico_vc_zoom/templates/manage_event_info_box.html:36 +msgid "Contribution" +msgstr "Contribution" + +#: indico_vc_zoom/templates/manage_event_info_box.html:38 +msgid "Session" +msgstr "Session" + +#: indico_vc_zoom/templates/manage_event_info_box.html:47 +msgid "Created on" +msgstr "Créé le" + +#: indico_vc_zoom/templates/manage_event_info_box.html:50 +msgid "Modified on" +msgstr "Modifiée le" + +#: indico_vc_zoom/templates/room_labels.html:3 +msgid "This is a Zoom webinar" +msgstr "Ceci est un wébinaire Zoom" diff --git a/vc_zoom/indico_vc_zoom/translations/messages.pot b/vc_zoom/indico_vc_zoom/translations/messages.pot new file mode 100644 index 0000000..2eff619 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/translations/messages.pot @@ -0,0 +1,399 @@ +# Translations template for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-01-05 13:20+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.8.0\n" + +#: indico_vc_zoom/controllers.py:29 +msgid "You were already an (alternative) host of this meeting" +msgstr "" + +#: indico_vc_zoom/controllers.py:40 +msgid "You are now an alternative host of room '{room}'" +msgstr "" + +#: indico_vc_zoom/forms.py:29 indico_vc_zoom/forms.py:63 +msgid "Passcode visibility" +msgstr "" + +#: indico_vc_zoom/forms.py:30 indico_vc_zoom/forms.py:64 +msgid "Who should be able to know this meeting's passcode" +msgstr "" + +#: indico_vc_zoom/forms.py:33 indico_vc_zoom/forms.py:67 +msgid "Everyone" +msgstr "" + +#: indico_vc_zoom/forms.py:34 indico_vc_zoom/forms.py:68 +msgid "Logged-in users" +msgstr "" + +#: indico_vc_zoom/forms.py:35 indico_vc_zoom/forms.py:69 +msgid "Registered participants" +msgstr "" + +#: indico_vc_zoom/forms.py:36 indico_vc_zoom/forms.py:70 +msgid "No one" +msgstr "" + +#: indico_vc_zoom/forms.py:46 +msgid "Meeting Type" +msgstr "" + +#: indico_vc_zoom/forms.py:47 +msgid "The type of Zoom meeting to be created" +msgstr "" + +#: indico_vc_zoom/forms.py:50 +msgid "Regular Meeting" +msgstr "" + +#: indico_vc_zoom/forms.py:51 indico_vc_zoom/templates/room_labels.html:4 +msgid "Webinar" +msgstr "" + +#: indico_vc_zoom/forms.py:53 +msgid "Meeting Host" +msgstr "" + +#: indico_vc_zoom/forms.py:54 +msgid "Myself" +msgstr "" + +#: indico_vc_zoom/forms.py:54 +msgid "Someone else" +msgstr "" + +#: indico_vc_zoom/forms.py:56 +msgid "User" +msgstr "" + +#: indico_vc_zoom/forms.py:59 indico_vc_zoom/templates/info_box.html:31 +#: indico_vc_zoom/templates/manage_event_info_box.html:41 +msgid "Passcode" +msgstr "" + +#: indico_vc_zoom/forms.py:61 +msgid "Meeting passcode (8-10 digits)" +msgstr "" + +#: indico_vc_zoom/forms.py:72 indico_vc_zoom/plugin.py:86 +msgid "Mute audio" +msgstr "" + +#: indico_vc_zoom/forms.py:74 indico_vc_zoom/plugin.py:88 +msgid "Participants will join the VC room muted by default " +msgstr "" + +#: indico_vc_zoom/forms.py:76 indico_vc_zoom/plugin.py:90 +msgid "Mute video (host)" +msgstr "" + +#: indico_vc_zoom/forms.py:78 indico_vc_zoom/plugin.py:92 +msgid "The host will join the VC room with video disabled" +msgstr "" + +#: indico_vc_zoom/forms.py:80 indico_vc_zoom/plugin.py:94 +msgid "Mute video (participants)" +msgstr "" + +#: indico_vc_zoom/forms.py:82 indico_vc_zoom/plugin.py:96 +msgid "Participants will join the VC room with video disabled" +msgstr "" + +#: indico_vc_zoom/forms.py:84 indico_vc_zoom/plugin.py:103 +msgid "Waiting room" +msgstr "" + +#: indico_vc_zoom/forms.py:86 indico_vc_zoom/plugin.py:105 +msgid "Participants may be kept in a waiting room by the host" +msgstr "" + +#: indico_vc_zoom/forms.py:88 indico_vc_zoom/templates/info_box.html:8 +#: indico_vc_zoom/templates/manage_event_info_box.html:9 +msgid "Description" +msgstr "" + +#: indico_vc_zoom/forms.py:88 +msgid "Optional description for this room" +msgstr "" + +#: indico_vc_zoom/forms.py:118 +msgid "This user has no Zoom account" +msgstr "" + +#: indico_vc_zoom/plugin.py:53 +msgid "API Key" +msgstr "" + +#: indico_vc_zoom/plugin.py:55 +msgid "API Secret" +msgstr "" + +#: indico_vc_zoom/plugin.py:57 +msgid "Webhook Token" +msgstr "" + +#: indico_vc_zoom/plugin.py:58 +msgid "Specify Zoom's webhook token if you want live updates" +msgstr "" + +#: indico_vc_zoom/plugin.py:60 +msgid "User lookup mode" +msgstr "" + +#: indico_vc_zoom/plugin.py:61 +msgid "" +"Specify how Indico should look up the zoom user that corresponds to an " +"Indico user." +msgstr "" + +#: indico_vc_zoom/plugin.py:64 +msgid "E-mail domains" +msgstr "" + +#: indico_vc_zoom/plugin.py:66 +msgid "" +"List of e-mail domains which can use the Zoom API. Indico attempts to " +"find Zoom accounts using all email addresses of a user which use those " +"domains." +msgstr "" + +#: indico_vc_zoom/plugin.py:70 +msgid "Indico identity providers" +msgstr "" + +#: indico_vc_zoom/plugin.py:72 +msgid "" +"Identity providers from which to get usernames. Indico queries those " +"providers using the email addresses of the user and attempts to find Zoom" +" accounts having an email address with the format username@enterprise-" +"domain." +msgstr "" + +#: indico_vc_zoom/plugin.py:77 +msgid "Enterprise domain" +msgstr "" + +#: indico_vc_zoom/plugin.py:79 +msgid "" +"The domain name used together with the usernames from the Indico identity" +" provider" +msgstr "" + +#: indico_vc_zoom/plugin.py:82 +msgid "Allow Webinars (Experimental)" +msgstr "" + +#: indico_vc_zoom/plugin.py:84 +msgid "Allow webinars to be created through Indico. Use at your own risk." +msgstr "" + +#: indico_vc_zoom/plugin.py:98 +msgid "Join Before Host" +msgstr "" + +#: indico_vc_zoom/plugin.py:100 +msgid "" +"Allow participants to join the meeting before the host starts the " +"meeting. Only used for scheduled or recurring meetings." +msgstr "" + +#: indico_vc_zoom/plugin.py:107 +msgid "Creation email footer" +msgstr "" + +#: indico_vc_zoom/plugin.py:108 +msgid "Footer to append to emails sent upon creation of a VC room" +msgstr "" + +#: indico_vc_zoom/plugin.py:110 +msgid "Send host URL" +msgstr "" + +#: indico_vc_zoom/plugin.py:112 +msgid "" +"Whether to send an e-mail with the Host URL to the meeting host upon " +"creation of a meeting" +msgstr "" + +#: indico_vc_zoom/plugin.py:118 +msgid "Invalid identity providers: {}" +msgstr "" + +#: indico_vc_zoom/plugin.py:319 +msgid "" +"Could not create the room in Zoom. Please contact support if the error " +"persists" +msgstr "" + +#: indico_vc_zoom/plugin.py:403 +msgid "Room didn't existing in Zoom anymore" +msgstr "" + +#: indico_vc_zoom/plugin.py:406 +msgid "Zoom Error: \"{}\"" +msgstr "" + +#: indico_vc_zoom/plugin.py:409 +msgid "Problem deleting room" +msgstr "" + +#: indico_vc_zoom/plugin.py:490 +msgid "" +"There are one or more scheduled Zoom meetings associated with this event " +"which were not automatically updated." +msgstr "" + +#: indico_vc_zoom/plugin.py:493 +msgid "" +"There are one or more scheduled Zoom meetings associated with the " +"contribution \"{}\" which were not automatically updated." +msgstr "" + +#: indico_vc_zoom/plugin.py:496 +msgid "" +"There are one or more scheduled Zoom meetings associated with this " +"session block which were not automatically updated." +msgstr "" + +#: indico_vc_zoom/util.py:42 +msgid "All emails" +msgstr "" + +#: indico_vc_zoom/util.py:43 +msgid "Email domains" +msgstr "" + +#: indico_vc_zoom/util.py:44 +msgid "Authenticators" +msgstr "" + +#: indico_vc_zoom/util.py:124 +msgid "This room has been deleted from Zoom" +msgstr "" + +#: indico_vc_zoom/util.py:128 +msgid "" +"Problem fetching room from Zoom. Please contact support if the error " +"persists." +msgstr "" + +#: indico_vc_zoom/util.py:147 +msgid "Can't update meeting. Please contact support if the error persists." +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:9 +msgid "You will become an alternative host of this Zoom meeting" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:12 +msgid "Make me alternative host" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:26 +#: indico_vc_zoom/templates/buttons.html:32 +msgid "Join" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:30 +msgid "You will need a passcode to join this Zoom meeting" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:38 +#: indico_vc_zoom/templates/buttons.html:44 +#: indico_vc_zoom/templates/buttons.html:50 +msgid "This Zoom Meeting can only be seen by registered participants" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:40 +msgid "Please register" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:45 +msgid "Registration required" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:52 +msgid "Please log in and register" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:57 +msgid "This Zoom Meeting can only be seen by logged in users" +msgstr "" + +#: indico_vc_zoom/templates/buttons.html:59 +msgid "Please log in" +msgstr "" + +#: indico_vc_zoom/templates/extra_delete_msg.html:2 +#, python-format +msgid "" +"The host of this Zoom meeting is %(host)s. In case you " +"want to delete this Zoom meeting from all events, please make sure that " +"they do not use it for other events that may still need it!" +msgstr "" + +#: indico_vc_zoom/templates/info_box.html:5 +#: indico_vc_zoom/templates/manage_event_info_box.html:6 +msgid "Zoom Meeting ID" +msgstr "" + +#: indico_vc_zoom/templates/info_box.html:12 +#: indico_vc_zoom/templates/manage_event_info_box.html:12 +msgid "Host" +msgstr "" + +#: indico_vc_zoom/templates/info_box.html:17 +#: indico_vc_zoom/templates/manage_event_info_box.html:18 +msgid "Alternative host" +msgid_plural "Alternative hosts" +msgstr[0] "" +msgstr[1] "" + +#: indico_vc_zoom/templates/info_box.html:35 +#: indico_vc_zoom/templates/manage_event_info_box.html:43 +msgid "Zoom URL" +msgstr "" + +#: indico_vc_zoom/templates/manage_event_info_box.html:28 +msgid "Linked to" +msgstr "" + +#: indico_vc_zoom/templates/manage_event_info_box.html:34 +msgid "the whole event" +msgstr "" + +#: indico_vc_zoom/templates/manage_event_info_box.html:36 +msgid "Contribution" +msgstr "" + +#: indico_vc_zoom/templates/manage_event_info_box.html:38 +msgid "Session" +msgstr "" + +#: indico_vc_zoom/templates/manage_event_info_box.html:47 +msgid "Created on" +msgstr "" + +#: indico_vc_zoom/templates/manage_event_info_box.html:50 +msgid "Modified on" +msgstr "" + +#: indico_vc_zoom/templates/room_labels.html:3 +msgid "This is a Zoom webinar" +msgstr "" + diff --git a/vc_zoom/indico_vc_zoom/util.py b/vc_zoom/indico_vc_zoom/util.py new file mode 100644 index 0000000..978d648 --- /dev/null +++ b/vc_zoom/indico_vc_zoom/util.py @@ -0,0 +1,186 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +import random +import re +import string + +from requests.exceptions import HTTPError + +from indico.core.db import db +from indico.modules.users.models.emails import UserEmail +from indico.modules.users.models.users import User +from indico.modules.users.util import get_user_by_email +from indico.modules.vc.exceptions import VCRoomError, VCRoomNotFoundError +from indico.util.caching import memoize_request +from indico.util.date_time import now_utc +from indico.util.struct.enum import IndicoEnum, RichEnum +from indico.util.user import principal_from_identifier + +from indico_vc_zoom import _ +from indico_vc_zoom.api import ZoomIndicoClient + + +class ZoomMeetingType(int, IndicoEnum): + instant_meeting = 1 + scheduled_meeting = 2 + recurring_meeting_no_time = 3 + recurring_meeting_fixed_time = 8 + pmi_meeting = 4 + webinar = 5 + recurring_webinar_no_time = 6 + recurring_webinar_fixed_time = 9 + + +class UserLookupMode(unicode, RichEnum): + __titles__ = { + 'all_emails': _('All emails'), + 'email_domains': _('Email domains'), + 'authenticators': _('Authenticators'), + } + + @property + def title(self): + return RichEnum.title.__get__(self, type(self)) + + all_emails = 'all_emails' + email_domains = 'email_domains' + authenticators = 'authenticators' + + +def _iter_user_identifiers(user): + """Iterates over all existing user identifiers that can be used with Zoom""" + from indico_vc_zoom.plugin import ZoomPlugin + done = set() + for provider in ZoomPlugin.settings.get('authenticators'): + for __, identifier in user.iter_identifiers(check_providers=True, providers={provider}): + if identifier in done: + continue + done.add(identifier) + yield identifier + + +def iter_user_emails(user): + """Yield all emails of a user that may work with zoom. + + :param user: the `User` in question + """ + from indico_vc_zoom.plugin import ZoomPlugin + mode = ZoomPlugin.settings.get('user_lookup_mode') + if mode in (UserLookupMode.all_emails, UserLookupMode.email_domains): + email_criterion = True + if mode == UserLookupMode.email_domains: + domains = ZoomPlugin.settings.get('email_domains') + if not domains: + return + email_criterion = db.or_(UserEmail.email.endswith('@{}'.format(domain)) for domain in domains) + # get all matching e-mails, primary first + query = UserEmail.query.filter( + UserEmail.user == user, + ~User.is_blocked, + ~User.is_deleted, + email_criterion + ).join(User).order_by(UserEmail.is_primary.desc()) + for entry in query: + yield entry.email + elif mode == UserLookupMode.authenticators: + domain = ZoomPlugin.settings.get('enterprise_domain') + for username in _iter_user_identifiers(user): + yield '{}@{}'.format(username, domain) + + +@memoize_request +def find_enterprise_email(user): + """Get the email address of a user that has a zoom account.""" + client = ZoomIndicoClient() + return next((email for email in iter_user_emails(user) if client.get_user(email, silent=True)), None) + + +def gen_random_password(): + """Generate a random 8-character-long numeric string.""" + return ''.join(random.choice(string.digits) for _ in range(8)) + + +def fetch_zoom_meeting(vc_room, client=None, is_webinar=False): + """Fetch a Zoom meeting from the Zoom API. + + :param vc_room: The `VCRoom` object + :param client: a `ZoomIndicoClient` object, otherwise a fresh one will be created + :param is_webinar: whether the call concerns a webinar (used to call the correct endpoint) + """ + try: + client = client or ZoomIndicoClient() + if is_webinar: + return client.get_webinar(vc_room.data['zoom_id']) + return client.get_meeting(vc_room.data['zoom_id']) + except HTTPError as e: + if e.response.status_code in {400, 404}: + # Indico will automatically mark this room as deleted + raise VCRoomNotFoundError(_('This room has been deleted from Zoom')) + else: + from indico_vc_zoom.plugin import ZoomPlugin + ZoomPlugin.logger.exception('Error getting Zoom Room: %s', e.response.content) + raise VCRoomError(_('Problem fetching room from Zoom. Please contact support if the error persists.')) + + +def update_zoom_meeting(zoom_id, changes, is_webinar=False): + """Update a meeting which already exists in the Zoom API. + + :param zoom_id: ID of the meeting + :param changes: dictionary with new attribute values + :param is_webinar: whether the call concerns a webinar (used to call the correct endpoint) + """ + client = ZoomIndicoClient() + try: + if is_webinar: + client.update_webinar(zoom_id, changes) + else: + client.update_meeting(zoom_id, changes) + except HTTPError as e: + from indico_vc_zoom.plugin import ZoomPlugin + ZoomPlugin.logger.exception("Error updating meeting '%s': %s", zoom_id, e.response.content) + raise VCRoomError(_("Can't update meeting. Please contact support if the error persists.")) + + +def get_schedule_args(obj): + """Create a dictionary with scheduling information from an Event/Contribution/SessionBlock. + + :param obj: An `Event`, `Contribution` or `SessionBlock` + """ + duration = obj.end_dt - obj.start_dt + + if obj.start_dt < now_utc(): + return {} + + return { + 'start_time': obj.start_dt, + 'duration': duration.total_seconds() / 60, + } + + +def get_url_data_args(url): + """Create a dictionary with proper public/private "join URL" fields."" + + :param url: the original URL + """ + return { + 'url': url, + 'public_url': url.split('?')[0], + } + + +def process_alternative_hosts(emails): + """Convert a comma-concatenated list of alternative host e-mails into a list of identifiers.""" + users = [get_user_by_email(email) for email in re.findall(r'[^,;]+', emails)] + return [u.identifier for u in users if u is not None] + + +def get_alt_host_emails(identifiers): + """Convert a list of identities into a list of enterprise e-mails.""" + return [find_enterprise_email(principal_from_identifier(ident)) for ident in identifiers] diff --git a/vc_zoom/pytest.ini b/vc_zoom/pytest.ini new file mode 100644 index 0000000..b7ae65f --- /dev/null +++ b/vc_zoom/pytest.ini @@ -0,0 +1,12 @@ +[pytest] +; more verbose summary (include skip/fail/error/warning), coverage +addopts = -rsfEw --cov . --cov-report html --no-cov-on-fail +; only check for tests in suffixed files +python_files = *_test.py +; we need the vc_zoom plugin to be loaded +indico_plugins = vc_zoom +; fail if there are warnings, but ignore ones that are likely just noise +filterwarnings = + error + ignore::sqlalchemy.exc.SAWarning + ignore::UserWarning diff --git a/vc_zoom/setup.cfg b/vc_zoom/setup.cfg new file mode 100644 index 0000000..fcfe660 --- /dev/null +++ b/vc_zoom/setup.cfg @@ -0,0 +1,29 @@ +[metadata] +name = indico-plugin-vc-zoom +description = Zoom video-conferencing plugin for Indico +long_description = file: README.md +long_description_content_type = text/markdown; charset=UTF-8; variant=CommonMark +url = https://github.com/indico/indico-plugins +license = MIT +author = Giovanni Mariano (ENEA) and Indico Team (CERN) +author_email = indico-team@cern.ch +classifiers = + Environment :: Plugins + Environment :: Web Environment + License :: OSI Approved :: MIT License + Programming Language :: Python :: 2.7 + +[options] +packages = find: +zip_safe = false +include_package_data = true +python_requires = ~=2.7 + +[options.entry_points] +indico.plugins = + vc_zoom = indico_vc_zoom.plugin:ZoomPlugin + + + +[pydocstyle] +ignore = D100,D101,D102,D103,D104,D105,D107,D203,D213 diff --git a/vc_zoom/setup.py b/vc_zoom/setup.py new file mode 100644 index 0000000..a1994c4 --- /dev/null +++ b/vc_zoom/setup.py @@ -0,0 +1,23 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +from __future__ import unicode_literals + +from setuptools import setup + + +# XXX: keeping some entries in here to make bulk updates easier while +# other plugins still have this metadata in setup.py; everything else +# is in setup.cfg now +setup( + name='indico-plugin-vc-zoom', + version='2.3b1', + install_requires=[ + 'indico>=2.3.2.dev0', + 'PyJWT>=1.7.1,<2' + ], +) diff --git a/vc_zoom/tests/operation_test.py b/vc_zoom/tests/operation_test.py new file mode 100644 index 0000000..93896b0 --- /dev/null +++ b/vc_zoom/tests/operation_test.py @@ -0,0 +1,135 @@ +# This file is part of the Indico plugins. +# Copyright (C) 2020 - 2021 CERN and ENEA +# +# The Indico plugins are free software; you can redistribute +# them and/or modify them under the terms of the MIT License; +# see the LICENSE file for more details. + +import pytest + +from indico.core.plugins import plugin_engine +from indico.modules.vc.models.vc_rooms import VCRoom, VCRoomEventAssociation, VCRoomLinkType, VCRoomStatus + +from indico_vc_zoom.plugin import ZoomPlugin + + +@pytest.fixture +def zoom_plugin(app): + """Return a callable which lets you create dummy Zoom room occurrences.""" + plugin = ZoomPlugin(plugin_engine, app) + plugin.settings.set('email_domains', 'megacorp.xyz') + return plugin + + +@pytest.fixture +def create_meeting(create_user, dummy_event, db, zoom_plugin): + def _create_meeting(name='New Room'): + user_joe = create_user(1, email='don.orange@megacorp.xyz') + + vc_room = VCRoom( + type='zoom', + status=VCRoomStatus.created, + name=name, + created_by_id=0, + data={ + 'description': 'something something', + 'password': '1234', + 'host': user_joe.identifier, + 'meeting_type': 'meeting', + 'mute_host_video': False, + 'mute_audio': False, + 'mute_participant_video': False, + 'waiting_room': False + } + ) + VCRoomEventAssociation(linked_event=dummy_event, vc_room=vc_room, link_type=VCRoomLinkType.event, data={}) + db.session.flush() + zoom_plugin.create_room(vc_room, dummy_event) + return vc_room + return _create_meeting + + +@pytest.fixture +def zoom_api(create_user, mocker): + """Mock some Zoom API endpoints.""" + api_create_meeting = mocker.patch('indico_vc_zoom.plugin.ZoomIndicoClient.create_meeting') + api_create_meeting.return_value = { + 'id': '12345abc', + 'join_url': 'https://example.com/kitties', + 'start_url': 'https://example.com/puppies', + 'password': '1234', + 'host_id': 'don.orange@megacorp.xyz', + 'topic': 'New Room', + 'agenda': 'something something', + 'settings': { + 'host_video': True, + 'mute_upon_entry': False, + 'participant_video': True, + 'waiting_room': False + } + } + + api_update_meeting = mocker.patch('indico_vc_zoom.plugin.ZoomIndicoClient.update_meeting') + api_update_meeting.return_value = {} + + create_user(1, email='don.orange@megacorp.xyz') + + api_get_user = mocker.patch('indico_vc_zoom.plugin.ZoomIndicoClient.get_user') + api_get_user.return_value = { + 'id': '7890abcd', + 'email': 'don.orange@megacorp.xyz' + } + + api_get_meeting = mocker.patch('indico_vc_zoom.plugin.ZoomIndicoClient.get_meeting') + api_get_meeting.return_value = api_create_meeting.return_value + + return { + 'create_meeting': api_create_meeting, + 'get_meeting': api_get_meeting, + 'update_meeting': api_update_meeting, + 'get_user': api_get_user + } + + +def test_room_creation(create_meeting, zoom_api): + vc_room = create_meeting() + assert vc_room.data['url'] == 'https://example.com/kitties' + assert vc_room.data['host'] == 'User:1' + assert zoom_api['create_meeting'].called + + +def test_password_change(create_user, mocker, create_meeting, zoom_plugin, zoom_api): + create_user(2, email='joe.bidon@megacorp.xyz') + vc_room = create_meeting() + vc_room.data['password'] = '1337' + + # simulate changes between calls of "GET meeting" + def _get_meeting(self, meeting_id): + result = { + 'id': meeting_id, + 'join_url': 'https://example.com/llamas' if _get_meeting.called else 'https://example.com/kitties', + 'start_url': 'https://example.com/puppies', + 'password': '1337' if _get_meeting.called else '1234', + 'host_id': 'don.orange@megacorp.xyz', + 'topic': 'New Room', + 'agenda': 'something something', + 'settings': { + 'host_video': True, + 'mute_upon_entry': False, + 'participant_video': True, + 'waiting_room': False + } + } + _get_meeting.called = True + return result + _get_meeting.called = False + mocker.patch('indico_vc_zoom.plugin.ZoomIndicoClient.get_meeting', _get_meeting) + + zoom_plugin.update_room(vc_room, vc_room.events[0].event) + + zoom_api['update_meeting'].assert_called_with('12345abc', { + 'password': '1337' + }) + + assert vc_room.data['password'] == '1337' + assert vc_room.data['url'] == 'https://example.com/llamas' diff --git a/vc_zoom/webpack-bundles.json b/vc_zoom/webpack-bundles.json new file mode 100644 index 0000000..5f28835 --- /dev/null +++ b/vc_zoom/webpack-bundles.json @@ -0,0 +1,5 @@ +{ + "entry": { + "main": "./index.js" + } +}